diff --git a/.github/actions/run-apptainer/action.yml b/.github/actions/run-apptainer/action.yml new file mode 100644 index 000000000..89333c560 --- /dev/null +++ b/.github/actions/run-apptainer/action.yml @@ -0,0 +1,52 @@ +name: 'Run ICD tests via Apptainer' +description: 'Start carta_backend and run ICD tests' +inputs: + os_version: + description: 'Platfrom' + required: true + image: + description: 'Apptainer image' + required: true + port: + description: 'Port number for carta_backend' + required: true + test_stage_name: + description: 'ICD test stage' + required: true + +runs: + using: 'composite' + steps: + - run: | + SRC_DIR=$GITHUB_WORKSPACE/source + BUILD_DIR=$GITHUB_WORKSPACE/build-${{ inputs.os_version }} + TEST_STAGE="$BUILD_DIR/ICD-RxJS/ICD_test_stages/${{ inputs.test_stage_name }}.tests" + LOG_FILE="/tmp/carta_icd_${{ inputs.os_version }}_${{ inputs.test_stage_name }}.log" + apptainer exec \ + --bind $GITHUB_WORKSPACE:$GITHUB_WORKSPACE \ + --bind /images:/images \ + --pwd $BUILD_DIR \ + ${{ inputs.image }} /bin/bash -c "\ + # Start the carta_backend + ASAN_OPTIONS=suppressions=$SRC_DIR/debug/asan/myasan.supp \ + LSAN_OPTIONS=suppressions=$SRC_DIR/debug/asan/myasan-leaks.supp \ + ASAN_SYMBOLIZER_PATH=llvm-symbolizer \ + ./carta_backend /images \ + --top_level_folder /images \ + --port ${{ inputs.port }} \ + --omp_threads=4 \ + --debug_no_auth \ + --no_frontend \ + --no_database \ + --no_log \ + --verbosity=5 >> $LOG_FILE 2>&1 & \ + CARTA_BACKEND_PID=\$(pgrep -f 'carta_backend.*${{ inputs.port }}' | head -n 1) && \ + echo 'carta_backend started with PID' \$CARTA_BACKEND_PID && \ + # Run the ICD tests + cd $BUILD_DIR/ICD-RxJS && \ + pwd && \ + cat $TEST_STAGE && \ + while IFS= read -r test_file || [[ -n "\$test_file" ]]; do + CI=true npm test -- "\$test_file" + done < $TEST_STAGE" + shell: bash diff --git a/.github/actions/run-macos/action.yml b/.github/actions/run-macos/action.yml new file mode 100644 index 000000000..7e6b61720 --- /dev/null +++ b/.github/actions/run-macos/action.yml @@ -0,0 +1,36 @@ +name: 'Run ICD tests on macOS' +description: 'Start the carta_backend, run the ICD tests, and stop the carta_backend' +inputs: + test_stage_name: + description: 'ICD test stage' + required: true +runs: + using: 'composite' + steps: + - name: Start the carta-backend + run: | + SRC_DIR=$GITHUB_WORKSPACE/source + BUILD_DIR=$GITHUB_WORKSPACE/build + cd $BUILD_DIR + ASAN_OPTIONS=suppressions=$SRC_DIR/debug/asan/myasan.supp \ + LSAN_OPTIONS=suppressions=$SRC_DIR/debug/asan/myasan-leaks.supp \ + ASAN_SYMBOLIZER_PATH=llvm-symbolizer \ + ./carta_backend /images --top_level_folder /images \ + --port 5555 \ + --omp_threads=4 --debug_no_auth --no_frontend --no_database --verbosity=5 & + echo "CARTA_BACKEND_PID=$!" >> $GITHUB_ENV + shell: bash + + - name: ICD tests + run: | + ICD_DIR=$GITHUB_WORKSPACE/ICD-RxJS + cd $ICD_DIR + for test_file in $(cat ICD_test_stages/${{ inputs.test_stage_name }}.tests); do + CI=true npm test $test_file + sleep 3 && pgrep carta_backend + done + shell: bash + + - name: Stop carta-backend + run: kill ${{ env.CARTA_BACKEND_PID }} + shell: bash diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index c7e25e414..515217888 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -9,6 +9,8 @@ - [ ] changelog updated / no changelog update needed - [ ] e2e test passing / corresponding fix added / new e2e test created +- [ ] ICD test passing / corresponding fix added / new ICD test created - [ ] protobuf updated to the latest dev commit / no protobuf update needed +- [ ] protobuf version bumped / protobuf version not bumped - [ ] added reviewers and assignee -- [ ] added ZenHub estimate, milestone, and release +- [ ] GitHub Project estimate added diff --git a/.github/workflows/icd_tests.yml b/.github/workflows/icd_tests.yml new file mode 100644 index 000000000..26d1b9a69 --- /dev/null +++ b/.github/workflows/icd_tests.yml @@ -0,0 +1,1043 @@ +name: ICD tests +on: + schedule: + - cron: '0 0 * * *' # UTC time +env: + ICD_RXJS_BRANCH_NAME: dev + +jobs: + + Build: + name: Build ${{ matrix.os_version }} + runs-on: ${{ matrix.runner }} + timeout-minutes: 10 + strategy: + fail-fast: false + matrix: + include: + - os_version: macOS-11 + os: macos + runner: macOS-11 + - os_version: macOS-12 + os: macos + runner: [macOS-12, ICD] + - os_version: macOS-13 + os: macos + runner: macOS-13 + - os_version: ubuntu-20.04 + os: linux + runner: [self-hosted, Linux, Apptainer, ICD1] + image: /opt/apptainer/ubuntu-2004-dec2023.sif + port: 9001 + - os_version: ubuntu-22.04 + os: linux + runner: [self-hosted, Linux, Apptainer, ICD2] + image: /opt/apptainer/ubuntu-2204-dec2023.sif + port: 9002 + - os_version: rhel-7 + os: linux + runner: [self-hosted, Linux, Apptainer, ICD3] + image: /opt/apptainer/centos7-dec2023.sif + port: 9003 + - os_version: rhel-8 + os: linux + runner: [self-hosted, Linux, Apptainer, ICD4] + image: /opt/apptainer/almalinux8-dec2023.sif + port: 9004 + - os_version: rhel-9 + os: linux + runner: [self-hosted, Linux, Apptainer, ICD5] + image: /opt/apptainer/almalinux9-dec2023.sif + port: 9005 + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + path: source + + - name: System information (macOS) + if: matrix.os == 'macos' + shell: bash + run: | + uname -a + sw_vers + + - name: Build carta-backend (macOS) + if: matrix.os == 'macos' + shell: bash + run: | + SRC_DIR=$GITHUB_WORKSPACE/source + BUILD_DIR=$GITHUB_WORKSPACE/build + cd $SRC_DIR && git submodule update --init --recursive + rm -rf $BUILD_DIR && mkdir -p $BUILD_DIR + cd $BUILD_DIR + cmake $SRC_DIR \ + -Dtest=on \ + -DCMAKE_BUILD_TYPE=Debug \ + -DDevSuppressExternalWarnings=ON \ + -DCMAKE_CXX_FLAGS='-O0 -g -fsanitize=address -fno-omit-frame-pointer' \ + -DCMAKE_EXE_LINKER_FLAGS='-fsanitize=address' + make -j 16 + + - name: Build carta-backend (Linux) + if: matrix.os == 'linux' + shell: bash + run: | + SRC_DIR=$GITHUB_WORKSPACE/source + BUILD_DIR=$GITHUB_WORKSPACE/build-${{ matrix.os_version }} + rm -rf $BUILD_DIR && mkdir -p $BUILD_DIR + cd source ; git submodule update --init --recursive + apptainer exec --bind $GITHUB_WORKSPACE:$GITHUB_WORKSPACE --pwd $SRC_DIR ${{ matrix.image }} /bin/bash -c "\ + cd $BUILD_DIR && \ + cmake $SRC_DIR \ + -Dtest=on \ + -DCMAKE_BUILD_TYPE=Debug \ + -DDevSuppressExternalWarnings=ON \ + -DCMAKE_CXX_FLAGS='-O0 -g -fsanitize=address -fno-omit-frame-pointer' \ + -DCMAKE_EXE_LINKER_FLAGS='-fsanitize=address' && \ + make -j 16" + + - name: Check backend runs (macOS) + if: matrix.os == 'macos' + shell: bash + run: | + ./build/carta_backend --version + + - name: Check backend runs (Linux) + if: matrix.os == 'linux' + shell: bash + run: | + BUILD_DIR=$GITHUB_WORKSPACE/build-${{ matrix.os_version }} + apptainer exec --bind $GITHUB_WORKSPACE:$GITHUB_WORKSPACE --pwd $BUILD_DIR ${{ matrix.image }} /bin/bash -c "./carta_backend --version" + + Prepare-ICD-RxJS: + name: Prepare-ICD-RxJS ${{ matrix.os_version }} + runs-on: ${{ matrix.runner }} + timeout-minutes: 5 + strategy: + fail-fast: false + matrix: + include: + - os_version: macOS-11 + os: macos + runner: macOS-11 + - os_version: macOS-12 + os: macos + runner: [macOS-12, ICD] + - os_version: macOS-13 + os: macos + runner: macOS-13 + - os_version: ubuntu-20.04 + os: linux + runner: [self-hosted, Linux, Apptainer, ICD1] + image: /opt/apptainer/ubuntu-2004-dec2023.sif + port: 9001 + - os_version: ubuntu-22.04 + os: linux + runner: [self-hosted, Linux, Apptainer, ICD2] + image: /opt/apptainer/ubuntu-2204-dec2023.sif + port: 9002 + - os_version: rhel-7 + os: linux + runner: [self-hosted, Linux, Apptainer, ICD3] + image: /opt/apptainer/centos7-dec2023.sif + port: 9003 + - os_version: rhel-8 + os: linux + runner: [self-hosted, Linux, Apptainer, ICD4] + image: /opt/apptainer/almalinux8-dec2023.sif + port: 9004 + - os_version: rhel-9 + os: linux + runner: [self-hosted, Linux, Apptainer, ICD5] + image: /opt/apptainer/almalinux9-dec2023.sif + port: 9005 + needs: Build + steps: + - name: Checkout ICD-RxJS repository + uses: actions/checkout@v4 + with: + repository: CARTAvis/ICD-RxJS + ref: ${{ env.ICD_RXJS_BRANCH_NAME }} + path: ICD-RxJS + + - name: Prepare ICD-RxJS (macOS) + if: matrix.os == 'macos' + run: | + ICD_DIR=$GITHUB_WORKSPACE/ICD-RxJS + cd $ICD_DIR + git submodule init && git submodule update && npm install + cd protobuf + ./build_proto.sh + cd ../src/test + perl -p -i -e 's/3002/5555/' config.json + + - name: Prepare ICD-RxJS (Linux) + if: matrix.os == 'linux' + run: | + BUILD_DIR=$GITHUB_WORKSPACE/build-${{ matrix.os_version }} + ICD_DIR=$GITHUB_WORKSPACE/ICD-RxJS + cp -r $ICD_DIR $BUILD_DIR + cd $BUILD_DIR/ICD-RxJS + git submodule update --init --recursive + apptainer exec --bind $GITHUB_WORKSPACE:$GITHUB_WORKSPACE --pwd $BUILD_DIR ${{ matrix.image }} /bin/bash -c "\ + cd ICD-RxJS && \ + npm install && \ + cd protobuf && \ + ./build_proto.sh && \ + cd ../src/test && \ + perl -p -i -e 's/3002/${{ matrix.port }}/' config.json" + + File-Browser-ICD-Tests: + name: File-Browser ${{ matrix.os_version }} + runs-on: ${{ matrix.runner }} + timeout-minutes: 5 + strategy: + fail-fast: false + matrix: + include: + - os_version: macOS-11 + os: macos + runner: macOS-11 + - os_version: macOS-12 + os: macos + runner: [macOS-12, ICD] + - os_version: macOS-13 + os: macos + runner: macOS-13 + - os_version: ubuntu-20.04 + os: linux + runner: [self-hosted, Linux, Apptainer, ICD1] + image: /opt/apptainer/ubuntu-2004-dec2023.sif + port: 9001 + - os_version: ubuntu-22.04 + os: linux + runner: [self-hosted, Linux, Apptainer, ICD2] + image: /opt/apptainer/ubuntu-2204-dec2023.sif + port: 9002 + - os_version: rhel-7 + os: linux + runner: [self-hosted, Linux, Apptainer, ICD3] + image: /opt/apptainer/centos7-dec2023.sif + port: 9003 + - os_version: rhel-8 + os: linux + runner: [self-hosted, Linux, Apptainer, ICD4] + image: /opt/apptainer/almalinux8-dec2023.sif + port: 9004 + - os_version: rhel-9 + os: linux + runner: [self-hosted, Linux, Apptainer, ICD5] + image: /opt/apptainer/almalinux9-dec2023.sif + port: 9005 + needs: Prepare-ICD-RxJS + if: always() + steps: + # macOS steps + - name: File Browser ICD tests + if: matrix.os == 'macos' + uses: ./source/.github/actions/run-macos + with: + test_stage_name: 'file_browser' + # Linux steps + - name: File Browser ICD tests + if: matrix.os == 'linux' + uses: ./source/.github/actions/run-apptainer + with: + os_version: ${{ matrix.os_version }} + image: ${{ matrix.image }} + port: ${{ matrix.port }} + test_stage_name: 'file_browser' + + Animator-ICD-Tests: + name: Animator ${{ matrix.os_version }} + runs-on: ${{ matrix.runner }} + timeout-minutes: 5 + strategy: + fail-fast: false + matrix: + include: + - os_version: macOS-11 + os: macos + runner: macOS-11 + - os_version: macOS-12 + os: macos + runner: [macOS-12, ICD] + - os_version: macOS-13 + os: macos + runner: macOS-13 + - os_version: ubuntu-20.04 + os: linux + runner: [self-hosted, Linux, Apptainer, ICD1] + image: /opt/apptainer/ubuntu-2004-dec2023.sif + port: 9001 + - os_version: ubuntu-22.04 + os: linux + runner: [self-hosted, Linux, Apptainer, ICD2] + image: /opt/apptainer/ubuntu-2204-dec2023.sif + port: 9002 + - os_version: rhel-7 + os: linux + runner: [self-hosted, Linux, Apptainer, ICD3] + image: /opt/apptainer/centos7-dec2023.sif + port: 9003 + - os_version: rhel-8 + os: linux + runner: [self-hosted, Linux, Apptainer, ICD4] + image: /opt/apptainer/almalinux8-dec2023.sif + port: 9004 + - os_version: rhel-9 + os: linux + runner: [self-hosted, Linux, Apptainer, ICD5] + image: /opt/apptainer/almalinux9-dec2023.sif + port: 9005 + needs: [File-Browser-ICD-Tests, Prepare-ICD-RxJS] + if: always() + steps: + # macOS steps + - name: Animator ICD tests + if: matrix.os == 'macos' + uses: ./source/.github/actions/run-macos + with: + test_stage_name: 'animator' + # Linux steps + - name: Animator ICD tests + if: matrix.os == 'linux' + uses: ./source/.github/actions/run-apptainer + with: + os_version: ${{ matrix.os_version }} + image: ${{ matrix.image }} + port: ${{ matrix.port }} + test_stage_name: 'animator' + + Region-Statistics-ICD-Tests: + name: Region Statistics ${{ matrix.os_version }} + runs-on: ${{ matrix.runner }} + timeout-minutes: 5 + strategy: + fail-fast: false + matrix: + include: + - os_version: macOS-11 + os: macos + runner: macOS-11 + - os_version: macOS-12 + os: macos + runner: [macOS-12, ICD] + - os_version: macOS-13 + os: macos + runner: macOS-13 + - os_version: ubuntu-20.04 + os: linux + runner: [self-hosted, Linux, Apptainer, ICD1] + image: /opt/apptainer/ubuntu-2004-dec2023.sif + port: 9001 + - os_version: ubuntu-22.04 + os: linux + runner: [self-hosted, Linux, Apptainer, ICD2] + image: /opt/apptainer/ubuntu-2204-dec2023.sif + port: 9002 + - os_version: rhel-7 + os: linux + runner: [self-hosted, Linux, Apptainer, ICD3] + image: /opt/apptainer/centos7-dec2023.sif + port: 9003 + - os_version: rhel-8 + os: linux + runner: [self-hosted, Linux, Apptainer, ICD4] + image: /opt/apptainer/almalinux8-dec2023.sif + port: 9004 + - os_version: rhel-9 + os: linux + runner: [self-hosted, Linux, Apptainer, ICD5] + image: /opt/apptainer/almalinux9-dec2023.sif + port: 9005 + needs: [Animator-ICD-Tests, Prepare-ICD-RxJS] + if: always() + steps: + # macOS steps + - name: Region-Statistics ICD tests + if: matrix.os == 'macos' + uses: ./source/.github/actions/run-macos + with: + test_stage_name: 'region_statistics' + # Linux steps + - name: Region-Statistics ICD tests + if: matrix.os == 'linux' + uses: ./source/.github/actions/run-apptainer + with: + os_version: ${{ matrix.os_version }} + image: ${{ matrix.image }} + port: ${{ matrix.port }} + test_stage_name: 'region_statistics' + + Region-Manipulation-ICD-Tests: + name: Region Manipulation ${{ matrix.os_version }} + runs-on: ${{ matrix.runner }} + timeout-minutes: 5 + strategy: + fail-fast: false + matrix: + include: + - os_version: macOS-11 + os: macos + runner: macOS-11 + - os_version: macOS-12 + os: macos + runner: [macOS-12, ICD] + - os_version: macOS-13 + os: macos + runner: macOS-13 + - os_version: ubuntu-20.04 + os: linux + runner: [self-hosted, Linux, Apptainer, ICD1] + image: /opt/apptainer/ubuntu-2004-dec2023.sif + port: 9001 + - os_version: ubuntu-22.04 + os: linux + runner: [self-hosted, Linux, Apptainer, ICD2] + image: /opt/apptainer/ubuntu-2204-dec2023.sif + port: 9002 + - os_version: rhel-7 + os: linux + runner: [self-hosted, Linux, Apptainer, ICD3] + image: /opt/apptainer/centos7-dec2023.sif + port: 9003 + - os_version: rhel-8 + os: linux + runner: [self-hosted, Linux, Apptainer, ICD4] + image: /opt/apptainer/almalinux8-dec2023.sif + port: 9004 + - os_version: rhel-9 + os: linux + runner: [self-hosted, Linux, Apptainer, ICD5] + image: /opt/apptainer/almalinux9-dec2023.sif + port: 9005 + needs: [Region-Statistics-ICD-Tests, Prepare-ICD-RxJS] + if: always() + steps: + # macOS steps + - name: Region Manipulation ICD tests + if: matrix.os == 'macos' + uses: ./source/.github/actions/run-macos + with: + test_stage_name: 'region_manipulation' + # Linux steps + - name: Region Manipulation ICD tests + if: matrix.os == 'linux' + uses: ./source/.github/actions/run-apptainer + with: + os_version: ${{ matrix.os_version }} + image: ${{ matrix.image }} + port: ${{ matrix.port }} + test_stage_name: 'region_manipulation' + + Cube-Histogram-ICD-Tests: + name: Cube Histogram ${{ matrix.os_version }} + runs-on: ${{ matrix.runner }} + timeout-minutes: 10 + strategy: + fail-fast: false + matrix: + include: + - os_version: macOS-11 + os: macos + runner: macOS-11 + - os_version: macOS-12 + os: macos + runner: [macOS-12, ICD] + - os_version: macOS-13 + os: macos + runner: macOS-13 + - os_version: ubuntu-20.04 + os: linux + runner: [self-hosted, Linux, Apptainer, ICD1] + image: /opt/apptainer/ubuntu-2004-dec2023.sif + port: 9001 + - os_version: ubuntu-22.04 + os: linux + runner: [self-hosted, Linux, Apptainer, ICD2] + image: /opt/apptainer/ubuntu-2204-dec2023.sif + port: 9002 + - os_version: rhel-7 + os: linux + runner: [self-hosted, Linux, Apptainer, ICD3] + image: /opt/apptainer/centos7-dec2023.sif + port: 9003 + - os_version: rhel-8 + os: linux + runner: [self-hosted, Linux, Apptainer, ICD4] + image: /opt/apptainer/almalinux8-dec2023.sif + port: 9004 + - os_version: rhel-9 + os: linux + runner: [self-hosted, Linux, Apptainer, ICD5] + image: /opt/apptainer/almalinux9-dec2023.sif + port: 9005 + needs: [Region-Manipulation-ICD-Tests, Prepare-ICD-RxJS] + if: always() + steps: + # macOS steps + - name: Cube Histogram ICD tests + if: matrix.os == 'macos' + uses: ./source/.github/actions/run-macos + with: + test_stage_name: 'cube_histogram' + # Linux steps + - name: Cube Histogram ICD tests + if: matrix.os == 'linux' + uses: ./source/.github/actions/run-apptainer + with: + os_version: ${{ matrix.os_version }} + image: ${{ matrix.image }} + port: ${{ matrix.port }} + test_stage_name: 'cube_histogram' + + PV-Generator-ICD-Tests: + name: PV Generator ${{ matrix.os_version }} + runs-on: ${{ matrix.runner }} + timeout-minutes: 10 + strategy: + fail-fast: false + matrix: + include: + - os_version: macOS-11 + os: macos + runner: macOS-11 + - os_version: macOS-12 + os: macos + runner: [macOS-12, ICD] + - os_version: macOS-13 + os: macos + runner: macOS-13 + - os_version: ubuntu-20.04 + os: linux + runner: [self-hosted, Linux, Apptainer, ICD1] + image: /opt/apptainer/ubuntu-2004-dec2023.sif + port: 9001 + - os_version: ubuntu-22.04 + os: linux + runner: [self-hosted, Linux, Apptainer, ICD2] + image: /opt/apptainer/ubuntu-2204-dec2023.sif + port: 9002 + - os_version: rhel-7 + os: linux + runner: [self-hosted, Linux, Apptainer, ICD3] + image: /opt/apptainer/centos7-dec2023.sif + port: 9003 + - os_version: rhel-8 + os: linux + runner: [self-hosted, Linux, Apptainer, ICD4] + image: /opt/apptainer/almalinux8-dec2023.sif + port: 9004 + - os_version: rhel-9 + os: linux + runner: [self-hosted, Linux, Apptainer, ICD5] + image: /opt/apptainer/almalinux9-dec2023.sif + port: 9005 + needs: [Cube-Histogram-ICD-Tests, Prepare-ICD-RxJS] + if: always() + steps: + # macOS steps + - name: PV Generator ICD tests + if: matrix.os == 'macos' + uses: ./source/.github/actions/run-macos + with: + test_stage_name: 'pv_generator' + # Linux steps + - name: PV Generator ICD tests + if: matrix.os == 'linux' + uses: ./source/.github/actions/run-apptainer + with: + os_version: ${{ matrix.os_version }} + image: ${{ matrix.image }} + port: ${{ matrix.port }} + test_stage_name: 'pv_generator' + + Raster-Tiles-ICD-Tests: + name: Raster Tiles ${{ matrix.os_version }} + runs-on: ${{ matrix.runner }} + timeout-minutes: 5 + strategy: + fail-fast: false + matrix: + include: + - os_version: macOS-11 + os: macos + runner: macOS-11 + - os_version: macOS-12 + os: macos + runner: [macOS-12, ICD] + - os_version: macOS-13 + os: macos + runner: macOS-13 + - os_version: ubuntu-20.04 + os: linux + runner: [self-hosted, Linux, Apptainer, ICD1] + image: /opt/apptainer/ubuntu-2004-dec2023.sif + port: 9001 + - os_version: ubuntu-22.04 + os: linux + runner: [self-hosted, Linux, Apptainer, ICD2] + image: /opt/apptainer/ubuntu-2204-dec2023.sif + port: 9002 + - os_version: rhel-7 + os: linux + runner: [self-hosted, Linux, Apptainer, ICD3] + image: /opt/apptainer/centos7-dec2023.sif + port: 9003 + - os_version: rhel-8 + os: linux + runner: [self-hosted, Linux, Apptainer, ICD4] + image: /opt/apptainer/almalinux8-dec2023.sif + port: 9004 + - os_version: rhel-9 + os: linux + runner: [self-hosted, Linux, Apptainer, ICD5] + image: /opt/apptainer/almalinux9-dec2023.sif + port: 9005 + needs: [PV-Generator-ICD-Tests, Prepare-ICD-RxJS] + if: always() + steps: + # macOS steps + - name: Raster Tiles ICD tests + if: matrix.os == 'macos' + uses: ./source/.github/actions/run-macos + with: + test_stage_name: 'raster_tiles' + # Linux steps + - name: Raster Tiles ICD tests + if: matrix.os == 'linux' + uses: ./source/.github/actions/run-apptainer + with: + os_version: ${{ matrix.os_version }} + image: ${{ matrix.image }} + port: ${{ matrix.port }} + test_stage_name: 'raster_tiles' + + Catalog-ICD-Tests: + name: Catalog ${{ matrix.os_version }} + runs-on: ${{ matrix.runner }} + timeout-minutes: 5 + strategy: + fail-fast: false + matrix: + include: + - os_version: macOS-11 + os: macos + runner: macOS-11 + - os_version: macOS-12 + os: macos + runner: [macOS-12, ICD] + - os_version: macOS-13 + os: macos + runner: macOS-13 + - os_version: ubuntu-20.04 + os: linux + runner: [self-hosted, Linux, Apptainer, ICD1] + image: /opt/apptainer/ubuntu-2004-dec2023.sif + port: 9001 + - os_version: ubuntu-22.04 + os: linux + runner: [self-hosted, Linux, Apptainer, ICD2] + image: /opt/apptainer/ubuntu-2204-dec2023.sif + port: 9002 + - os_version: rhel-7 + os: linux + runner: [self-hosted, Linux, Apptainer, ICD3] + image: /opt/apptainer/centos7-dec2023.sif + port: 9003 + - os_version: rhel-8 + os: linux + runner: [self-hosted, Linux, Apptainer, ICD4] + image: /opt/apptainer/almalinux8-dec2023.sif + port: 9004 + - os_version: rhel-9 + os: linux + runner: [self-hosted, Linux, Apptainer, ICD5] + image: /opt/apptainer/almalinux9-dec2023.sif + port: 9005 + needs: [Raster-Tiles-ICD-Tests, Prepare-ICD-RxJS] + if: always() + steps: + # macOS steps + - name: Catalog ICD tests + if: matrix.os == 'macos' + uses: ./source/.github/actions/run-macos + with: + test_stage_name: 'catalog' + # Linux steps + - name: Catalog ICD tests + if: matrix.os == 'linux' + uses: ./source/.github/actions/run-apptainer + with: + os_version: ${{ matrix.os_version }} + image: ${{ matrix.image }} + port: ${{ matrix.port }} + test_stage_name: 'catalog' + + Moment-ICD-Tests: + name: Moment ${{ matrix.os_version }} + runs-on: ${{ matrix.runner }} + timeout-minutes: 5 + strategy: + fail-fast: false + matrix: + include: + - os_version: macOS-11 + os: macos + runner: macOS-11 + - os_version: macOS-12 + os: macos + runner: [macOS-12, ICD] + - os_version: macOS-13 + os: macos + runner: macOS-13 + - os_version: ubuntu-20.04 + os: linux + runner: [self-hosted, Linux, Apptainer, ICD1] + image: /opt/apptainer/ubuntu-2004-dec2023.sif + port: 9001 + - os_version: ubuntu-22.04 + os: linux + runner: [self-hosted, Linux, Apptainer, ICD2] + image: /opt/apptainer/ubuntu-2204-dec2023.sif + port: 9002 + - os_version: rhel-7 + os: linux + runner: [self-hosted, Linux, Apptainer, ICD3] + image: /opt/apptainer/centos7-dec2023.sif + port: 9003 + - os_version: rhel-8 + os: linux + runner: [self-hosted, Linux, Apptainer, ICD4] + image: /opt/apptainer/almalinux8-dec2023.sif + port: 9004 + - os_version: rhel-9 + os: linux + runner: [self-hosted, Linux, Apptainer, ICD5] + image: /opt/apptainer/almalinux9-dec2023.sif + port: 9005 + needs: [Catalog-ICD-Tests, Prepare-ICD-RxJS] + if: always() + steps: + # macOS steps + - name: Moment ICD tests + if: matrix.os == 'macos' + uses: ./source/.github/actions/run-macos + with: + test_stage_name: 'moment' + # Linux steps + - name: Moment ICD tests + if: matrix.os == 'linux' + uses: ./source/.github/actions/run-apptainer + with: + os_version: ${{ matrix.os_version }} + image: ${{ matrix.image }} + port: ${{ matrix.port }} + test_stage_name: 'moment' + + Match-ICD-Tests: + name: Match ${{ matrix.os_version }} + runs-on: ${{ matrix.runner }} + timeout-minutes: 5 + strategy: + fail-fast: false + matrix: + include: + - os_version: macOS-11 + os: macos + runner: macOS-11 + - os_version: macOS-12 + os: macos + runner: [macOS-12, ICD] + - os_version: macOS-13 + os: macos + runner: macOS-13 + - os_version: ubuntu-20.04 + os: linux + runner: [self-hosted, Linux, Apptainer, ICD1] + image: /opt/apptainer/ubuntu-2004-dec2023.sif + port: 9001 + - os_version: ubuntu-22.04 + os: linux + runner: [self-hosted, Linux, Apptainer, ICD2] + image: /opt/apptainer/ubuntu-2204-dec2023.sif + port: 9002 + - os_version: rhel-7 + os: linux + runner: [self-hosted, Linux, Apptainer, ICD3] + image: /opt/apptainer/centos7-dec2023.sif + port: 9003 + - os_version: rhel-8 + os: linux + runner: [self-hosted, Linux, Apptainer, ICD4] + image: /opt/apptainer/almalinux8-dec2023.sif + port: 9004 + - os_version: rhel-9 + os: linux + runner: [self-hosted, Linux, Apptainer, ICD5] + image: /opt/apptainer/almalinux9-dec2023.sif + port: 9005 + needs: [Moment-ICD-Tests, Prepare-ICD-RxJS] + if: always() + steps: + # macOS steps + - name: Match ICD tests + if: matrix.os == 'macos' + uses: ./source/.github/actions/run-macos + with: + test_stage_name: 'match' + # Linux steps + - name: Match ICD tests + if: matrix.os == 'linux' + uses: ./source/.github/actions/run-apptainer + with: + os_version: ${{ matrix.os_version }} + image: ${{ matrix.image }} + port: ${{ matrix.port }} + test_stage_name: 'match' + + Close-File-ICD-Tests: + name: Close File ${{ matrix.os_version }} + runs-on: ${{ matrix.runner }} + timeout-minutes: 5 + strategy: + fail-fast: false + matrix: + include: + - os_version: macOS-11 + os: macos + runner: macOS-11 + - os_version: macOS-12 + os: macos + runner: [macOS-12, ICD] + - os_version: macOS-13 + os: macos + runner: macOS-13 + - os_version: ubuntu-20.04 + os: linux + runner: [self-hosted, Linux, Apptainer, ICD1] + image: /opt/apptainer/ubuntu-2004-dec2023.sif + port: 9001 + - os_version: ubuntu-22.04 + os: linux + runner: [self-hosted, Linux, Apptainer, ICD2] + image: /opt/apptainer/ubuntu-2204-dec2023.sif + port: 9002 + - os_version: rhel-7 + os: linux + runner: [self-hosted, Linux, Apptainer, ICD3] + image: /opt/apptainer/centos7-dec2023.sif + port: 9003 + - os_version: rhel-8 + os: linux + runner: [self-hosted, Linux, Apptainer, ICD4] + image: /opt/apptainer/almalinux8-dec2023.sif + port: 9004 + - os_version: rhel-9 + os: linux + runner: [self-hosted, Linux, Apptainer, ICD5] + image: /opt/apptainer/almalinux9-dec2023.sif + port: 9005 + needs: [Match-ICD-Tests, Prepare-ICD-RxJS] + if: always() + steps: + # macOS steps + - name: Close File ICD tests + if: matrix.os == 'macos' + uses: ./source/.github/actions/run-macos + with: + test_stage_name: 'close_file' + # Linux steps + - name: Close File ICD tests + if: matrix.os == 'linux' + uses: ./source/.github/actions/run-apptainer + with: + os_version: ${{ matrix.os_version }} + image: ${{ matrix.image }} + port: ${{ matrix.port }} + test_stage_name: 'close_file' + + Image-Fitting-ICD-Tests: + name: Image Fitting ${{ matrix.os_version }} + runs-on: ${{ matrix.runner }} + timeout-minutes: 10 + strategy: + fail-fast: false + matrix: + include: + - os_version: macOS-11 + os: macos + runner: macOS-11 + - os_version: macOS-12 + os: macos + runner: [macOS-12, ICD] + - os_version: macOS-13 + os: macos + runner: macOS-13 + - os_version: ubuntu-20.04 + os: linux + runner: [self-hosted, Linux, Apptainer, ICD1] + image: /opt/apptainer/ubuntu-2004-dec2023.sif + port: 9001 + - os_version: ubuntu-22.04 + os: linux + runner: [self-hosted, Linux, Apptainer, ICD2] + image: /opt/apptainer/ubuntu-2204-dec2023.sif + port: 9002 + - os_version: rhel-7 + os: linux + runner: [self-hosted, Linux, Apptainer, ICD3] + image: /opt/apptainer/centos7-dec2023.sif + port: 9003 + - os_version: rhel-8 + os: linux + runner: [self-hosted, Linux, Apptainer, ICD4] + image: /opt/apptainer/almalinux8-dec2023.sif + port: 9004 + - os_version: rhel-9 + os: linux + runner: [self-hosted, Linux, Apptainer, ICD5] + image: /opt/apptainer/almalinux9-dec2023.sif + port: 9005 + needs: [Close-File-ICD-Tests, Prepare-ICD-RxJS] + if: always() + steps: + # macOS steps + - name: Image Fitting ICD tests + if: matrix.os == 'macos' + uses: ./source/.github/actions/run-macos + with: + test_stage_name: 'image_fitting' + # Linux steps + - name: Image Fitting ICD tests + if: matrix.os == 'linux' + uses: ./source/.github/actions/run-apptainer + with: + os_version: ${{ matrix.os_version }} + image: ${{ matrix.image }} + port: ${{ matrix.port }} + test_stage_name: 'image_fitting' + + Vector-Overlay-ICD-Tests: + name: Vector Overlay ${{ matrix.os_version }} + runs-on: ${{ matrix.runner }} + timeout-minutes: 5 + strategy: + fail-fast: false + matrix: + include: + - os_version: macOS-11 + os: macos + runner: macOS-11 + - os_version: macOS-12 + os: macos + runner: [macOS-12, ICD] + - os_version: macOS-13 + os: macos + runner: macOS-13 + - os_version: ubuntu-20.04 + os: linux + runner: [self-hosted, Linux, Apptainer, ICD1] + image: /opt/apptainer/ubuntu-2004-dec2023.sif + port: 9001 + - os_version: ubuntu-22.04 + os: linux + runner: [self-hosted, Linux, Apptainer, ICD2] + image: /opt/apptainer/ubuntu-2204-dec2023.sif + port: 9002 + - os_version: rhel-7 + os: linux + runner: [self-hosted, Linux, Apptainer, ICD3] + image: /opt/apptainer/centos7-dec2023.sif + port: 9003 + - os_version: rhel-8 + os: linux + runner: [self-hosted, Linux, Apptainer, ICD4] + image: /opt/apptainer/almalinux8-dec2023.sif + port: 9004 + - os_version: rhel-9 + os: linux + runner: [self-hosted, Linux, Apptainer, ICD5] + image: /opt/apptainer/almalinux9-dec2023.sif + port: 9005 + needs: [Image-Fitting-ICD-Tests, Prepare-ICD-RxJS] + if: always() + steps: + # macOS steps + - name: Vector Overlay ICD tests + if: matrix.os == 'macos' + uses: ./source/.github/actions/run-macos + with: + test_stage_name: 'vector_overlay' + # Linux steps + - name: Vector Overlay ICD tests + if: matrix.os == 'linux' + uses: ./source/.github/actions/run-apptainer + with: + os_version: ${{ matrix.os_version }} + image: ${{ matrix.image }} + port: ${{ matrix.port }} + test_stage_name: 'vector_overlay' + + Resume-ICD-Tests: + name: Resume ${{ matrix.os_version }} + runs-on: ${{ matrix.runner }} + timeout-minutes: 5 + strategy: + fail-fast: false + matrix: + include: + - os_version: macOS-11 + os: macos + runner: macOS-11 + - os_version: macOS-12 + os: macos + runner: [macOS-12, ICD] + - os_version: macOS-13 + os: macos + runner: macOS-13 + - os_version: ubuntu-20.04 + os: linux + runner: [self-hosted, Linux, Apptainer, ICD1] + image: /opt/apptainer/ubuntu-2004-dec2023.sif + port: 9001 + - os_version: ubuntu-22.04 + os: linux + runner: [self-hosted, Linux, Apptainer, ICD2] + image: /opt/apptainer/ubuntu-2204-dec2023.sif + port: 9002 + - os_version: rhel-7 + os: linux + runner: [self-hosted, Linux, Apptainer, ICD3] + image: /opt/apptainer/centos7-dec2023.sif + port: 9003 + - os_version: rhel-8 + os: linux + runner: [self-hosted, Linux, Apptainer, ICD4] + image: /opt/apptainer/almalinux8-dec2023.sif + port: 9004 + - os_version: rhel-9 + os: linux + runner: [self-hosted, Linux, Apptainer, ICD5] + image: /opt/apptainer/almalinux9-dec2023.sif + port: 9005 + needs: [Vector-Overlay-ICD-Tests, Prepare-ICD-RxJS] + if: always() + steps: + # macOS steps + - name: Resume ICD tests + if: matrix.os == 'macos' + uses: ./source/.github/actions/run-macos + with: + test_stage_name: 'resume' + # Linux steps + - name: Resume ICD tests + if: matrix.os == 'linux' + uses: ./source/.github/actions/run-apptainer + with: + os_version: ${{ matrix.os_version }} + image: ${{ matrix.image }} + port: ${{ matrix.port }} + test_stage_name: 'resume' diff --git a/.gitignore b/.gitignore index 242ab548d..fff4de4f6 100644 --- a/.gitignore +++ b/.gitignore @@ -19,3 +19,7 @@ coverage/ # Doxygen docs/html + +# Dependency download locations +third-party/uSockets/ +third-party/uWebSockets/ diff --git a/.gitmodules b/.gitmodules index 7613f0be7..7381ee460 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,9 +1,6 @@ [submodule "carta-protobuf"] path = carta-protobuf url = https://github.com/CARTAvis/carta-protobuf.git -[submodule "uWebSockets"] - path = third-party/uWebSockets - url = https://github.com/uNetworking/uWebSockets.git [submodule "cxxopts"] path = third-party/cxxopts url = https://github.com/jarro2783/cxxopts diff --git a/CHANGELOG.md b/CHANGELOG.md index 62b6bd906..858141dd9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,10 +4,39 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). -## [4.0.0-rc.0] +## [Unreleased] + +### Fixed +* Fixed crash when loading non-image HDU by URL ([#1365](https://github.com/CARTAvis/carta-backend/issues/1365)). +* Fix crash when parsing FITS header long value ([#1366](https://github.com/CARTAvis/carta-backend/issues/1366)). + +### Changed +* Move the loader cache to separate files ([#1021](https://github.com/CARTAvis/carta-backend/issues/1021)). +* Improve the code style in HTTP server ([#1260](https://github.com/CARTAvis/carta-backend/issues/1260)). +* Remove program settings equality operators ([#1001](https://github.com/CARTAvis/carta-backend/issues/1001)). +* Normalize the style of guard names in header files ([#1023](https://github.com/CARTAvis/carta-backend/issues/1023)). +* Improved file IDs for generated images ([#1224](https://github.com/CARTAvis/carta-frontend/issues/1224)). +* Store global settings in a singleton class ([#1302](https://github.com/CARTAvis/carta-backend/issues/1302)). +* Move Region code to RegionConverter for maintenance and performance ([#1347](https://github.com/CARTAvis/carta-backend/issues/1347)). +* Improve performance of region spatial profiles and PV image generation ([#1339](https://github.com/CARTAvis/carta-backend/issues/1339)). + +## [4.1.0] + +### Fixed +* Include casacore log messages in carta log ([#1169](https://github.com/CARTAvis/carta-backend/issues/1169)). +* Fixed the problem of opening old IRAM fits images ([#1312](https://github.com/CARTAvis/carta-backend/issues/1312)). +* Fixed scripting interface and symlink directory issues ([#1283](https://github.com/CARTAvis/carta-frontend/issues/1283), [#1284](https://github.com/CARTAvis/carta-frontend/issues/1284), [#1314](https://github.com/CARTAvis/carta-frontend/issues/1314)). +* Fixed incorrect std calculation when fitting images with nan values ([#1318](https://github.com/CARTAvis/carta-backend/issues/1318)). +* Fixed the hanging problem when deleting a region during the spectral profile process ([#1328](https://github.com/CARTAvis/carta-backend/issues/1328)). + +### Changed +* Updated for compatibility with latest carta-casacore using CASA 6.6.0. + +## [4.0.0] ### Changed * Support animation playback with matched images in multi-panel view ([#1860](https://github.com/CARTAvis/carta-frontend/issues/1860)). +* Update the submodule uWebSockets ([#1297](https://github.com/CARTAvis/carta-backend/issues/1297)). ### Fixed * Prevent the installation of pugixml library files ([#1261](https://github.com/CARTAvis/carta-backend/issues/1261)). diff --git a/CMakeLists.txt b/CMakeLists.txt index 055645c54..ae8a92aa3 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -109,6 +109,17 @@ endif () FIND_PACKAGE(HDF5 REQUIRED COMPONENTS CXX) FIND_PACKAGE(Protobuf REQUIRED) INCLUDE_DIRECTORIES(${PROTOBUF_INCLUDE_DIR}) + +if (Protobuf_VERSION VERSION_GREATER_EQUAL "4.25.3") + FIND_PACKAGE(absl REQUIRED) + set(PROTOBUF_LIBRARY + ${PROTOBUF_LIBRARY} + absl_hash + absl_log_internal_message + absl_log_internal_nullguard) + message(STATUS "Newer protobuf version (${Protobuf_VERSION}) includes abseil libraries") +endif () + FIND_PACKAGE(Threads) INCLUDE_DIRECTORIES(${HDF5_INCLUDE_DIR}) @@ -192,7 +203,8 @@ set(LINK_LIBS set(SOURCE_FILES ${SOURCE_FILES} - third-party/pugixml/src/pugixml.cpp + third-party/pugixml/src/pugixml.cpp + src/Cache/LoaderCache.cc src/Cache/TileCache.cc src/Cache/TilePool.cc src/DataStream/Compression.cc @@ -224,6 +236,7 @@ set(SOURCE_FILES src/ImageStats/Histogram.cc src/ImageStats/StatsCalculator.cc src/Logger/Logger.cc + src/Logger/CartaLogSink.cc src/Main/Main.cc src/Main/ProgramSettings.cc src/Main/WebBrowser.cc @@ -231,6 +244,7 @@ set(SOURCE_FILES src/Region/Ds9ImportExport.cc src/Region/LineBoxRegions.cc src/Region/Region.cc + src/Region/RegionConverter.cc src/Region/RegionHandler.cc src/Region/RegionImportExport.cc src/Session/CursorSettings.cc diff --git a/Doxyfile b/Doxyfile index b78869783..af087f9a7 100644 --- a/Doxyfile +++ b/Doxyfile @@ -1208,7 +1208,8 @@ HTML_FILE_EXTENSION = .html # of the possible markers and block names see the documentation. # This tag requires that the tag GENERATE_HTML is set to YES. -HTML_HEADER = +HTML_HEADER = docs/header.html + # The HTML_FOOTER tag can be used to specify a user-defined HTML footer for each # generated HTML page. If the tag is left blank doxygen will generate a standard diff --git a/carta-protobuf b/carta-protobuf index 28f4b3a60..202c31d25 160000 --- a/carta-protobuf +++ b/carta-protobuf @@ -1 +1 @@ -Subproject commit 28f4b3a6008ffef35084b07ceb3e76841ca3c348 +Subproject commit 202c31d25ec4bda97cbb9bd71fee589541872847 diff --git a/docs/header.html b/docs/header.html new file mode 100644 index 000000000..add8aa93d --- /dev/null +++ b/docs/header.html @@ -0,0 +1,74 @@ + + + + + + + + +$projectname: $title +$title + + + + + + + + + +$treeview +$search +$mathjax + +$extrastylesheet + + + + +
+ + + +
+ + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
$projectname $projectnumber +
+
$projectbrief
+
+
$projectbrief
+
$searchbox
$searchbox
+
+ + diff --git a/docs/static/main.md b/docs/static/main.md index 85bfb9e7d..839317b37 100644 --- a/docs/static/main.md +++ b/docs/static/main.md @@ -7,6 +7,7 @@ Documentation for other CARTA components ---------------------------------------- * [CARTA user manual](https://carta.readthedocs.io) (for users) -* %CARTA frontend (TBD) +* [CARTA frontend](https://cartavis.org/carta-frontend/) (for developers) +* [CARTA interface control document](https://carta-protobuf.readthedocs.io) (for developers) * [CARTA controller](https://carta-controller.readthedocs.io) (for system administrators) * [CARTA Python scripting wrapper](https://carta-python.readthedocs.io) (for users of the scripting interface) diff --git a/scripts/carta b/scripts/carta index 4764a3ac6..d37937e75 100755 --- a/scripts/carta +++ b/scripts/carta @@ -11,8 +11,10 @@ if [ ! -z $FIRST_IP ]; then export SERVER_IP=$FIRST_IP fi -if [ -x "$(command -v casa_data_autoupdate)" ]; then - casa_data_autoupdate +if [[ ! " $@ " =~ ( --version | -v | --help | -h ) ]]; then + if [ -x "$(command -v casa_data_autoupdate)" ]; then + casa_data_autoupdate + fi fi carta_backend "$@" diff --git a/scripts/style.py b/scripts/style.py index 643ca3d9a..2e24f7446 100755 --- a/scripts/style.py +++ b/scripts/style.py @@ -3,6 +3,7 @@ import sys import os import re +import glob import argparse import subprocess @@ -11,8 +12,7 @@ class Test: TESTS = {} EXCLUDE_FROM_ALL = False - EXTENSIONS = ("cc", "h", "tcc") - FILE_REGEX = fr".*\.({'|'.join(EXTENSIONS)})$" + EXTENSIONS = {"cc", "h", "tcc"} quiet = False out = print @@ -29,11 +29,13 @@ def get_tests(cls, testname): return (cls.TESTS[testname],) @classmethod - def cpp_files(cls, directory): + def cpp_files(cls, directory, exclude=set()): + FILE_REGEX = fr".*\.({'|'.join(cls.EXTENSIONS - exclude)})$" + for root, dirs, files in os.walk(directory): dirs[:] = [d for d in dirs] for basename in files: - if re.match(cls.FILE_REGEX, basename): + if re.match(FILE_REGEX, basename): filename = os.path.join(root, basename) yield filename @@ -48,7 +50,7 @@ def fix(cls, directories): class Header(Test): HEADER = """/* This file is part of the CARTA Image Viewer: https://github.com/CARTAvis/carta-backend - Copyright 2018-2022 Academia Sinica Institute of Astronomy and Astrophysics (ASIAA), + Copyright 2018- Academia Sinica Institute of Astronomy and Astrophysics (ASIAA), Associated Universities, Inc. (AUI) and the Inter-University Institute for Data Intensive Astronomy (IDIA) SPDX-License-Identifier: GPL-3.0-or-later */ @@ -184,6 +186,96 @@ def fix(cls, directories): return 0 +class HeaderGuards(Test): + IFNDEF_REGEX = re.compile("^#ifndef ([A-Z_0-9]*_(H|TCC)_*)$", flags=re.MULTILINE) + LAST_ENDIF_REGEX = re.compile(r"^(#endif[^\n]*?)\n$(?!.*#endif)", flags=re.MULTILINE|re.DOTALL) + + @classmethod + def make_guard_name(cls, filename): + name = re.sub('[/.$]', '_', filename).upper() + return f"CARTA_{name}_" + + @classmethod + def make_endif(cls, name): + return f"#endif // {name}" + + @classmethod + def check(cls, directories): + status = 0 + + for directory in directories: + for filename in cls.cpp_files(directory, exclude={"cc"}): + with open(filename) as f: + data = f.read() + + new_guard_name = cls.make_guard_name(filename) + + m = cls.IFNDEF_REGEX.search(data) + + if m is None: + cls.out("Can't find header guard in", filename) + status = 1 + else: + + existing_guard_name = m.group(1) + + if new_guard_name != existing_guard_name: + cls.out("Bad header guard in", filename) + status = 1 + + last_endif = cls.LAST_ENDIF_REGEX.search(data).group(1) + if last_endif != cls.make_endif(existing_guard_name): + cls.out("Mismatched header guard #endif comment in", filename) + status = 1 + + return status + + @classmethod + def fix(cls, directories): + for directory in directories: + for filename in cls.cpp_files(directory, exclude={"cc"}): + with open(filename) as f: + data = f.read() + + new_guard_name = cls.make_guard_name(filename) + new_endif = cls.make_endif(new_guard_name) + data_changed = False + + m = cls.IFNDEF_REGEX.search(data) + + if m is None: + cls.out("Can't find header guard in", filename) + cls.out("Fixing...") + + data = re.sub("(/\*.*?\*/\n)", rf"\1\n#ifndef {new_guard_name}\n#define {new_guard_name}\n", data, count=1, flags=re.DOTALL) + data = re.sub(r"\n$", rf"\n{new_endif}\n", data) + data_changed = True + + else: + existing_guard_name = m.group(1) + + if new_guard_name != existing_guard_name: + cls.out("Bad header guard in", filename) + cls.out("Fixing...") + + data = re.sub(existing_guard_name, new_guard_name, data) + data_changed = True + + last_endif = cls.LAST_ENDIF_REGEX.search(data).group(1) + if last_endif != new_endif: + cls.out("Mismatched header guard #endif comment in", filename) + cls.out("Fixing...") + + data = cls.LAST_ENDIF_REGEX.sub(rf"{new_endif}\n", data) + data_changed = True + + if data_changed: + with open(filename, "w") as f: + f.write(data) + + return 0 + + class Style(Test): EXCLUDE_FROM_ALL = True diff --git a/src/Cache/LoaderCache.cc b/src/Cache/LoaderCache.cc new file mode 100644 index 000000000..f06cd59e9 --- /dev/null +++ b/src/Cache/LoaderCache.cc @@ -0,0 +1,63 @@ +/* This file is part of the CARTA Image Viewer: https://github.com/CARTAvis/carta-backend + Copyright 2018- Academia Sinica Institute of Astronomy and Astrophysics (ASIAA), + Associated Universities, Inc. (AUI) and the Inter-University Institute for Data Intensive Astronomy (IDIA) + SPDX-License-Identifier: GPL-3.0-or-later +*/ + +#include "LoaderCache.h" + +#include "Logger/Logger.h" + +using namespace carta; + +LoaderCache::LoaderCache(int capacity) : _capacity(capacity){}; + +std::shared_ptr LoaderCache::Get(const std::string& filename, const std::string& directory) { + std::unique_lock guard(_loader_cache_mutex); + auto key = GetKey(filename, directory); + + // We have a cached loader, but the file has changed + if ((_map.find(key) != _map.end()) && _map[key] && _map[key]->ImageUpdated()) { + _map.erase(key); + _queue.remove(key); + } + + // We don't have a cached loader + if (_map.find(key) == _map.end()) { + // Create the loader -- don't block while doing this + std::shared_ptr loader_ptr; + guard.unlock(); + loader_ptr = std::shared_ptr(FileLoader::GetLoader(filename, directory)); + guard.lock(); + + // Check if the loader was added in the meantime + if (_map.find(key) == _map.end()) { + // Evict oldest loader if necessary + if (_map.size() == _capacity) { + _map.erase(_queue.back()); + _queue.pop_back(); + } + + // Insert the new loader + _map[key] = loader_ptr; + _queue.push_front(key); + } + } else { + // Touch the cache entry + _queue.remove(key); + _queue.push_front(key); + } + + return _map[key]; +} + +void LoaderCache::Remove(const std::string& filename, const std::string& directory) { + std::unique_lock guard(_loader_cache_mutex); + auto key = GetKey(filename, directory); + _map.erase(key); + _queue.remove(key); +} + +std::string LoaderCache::GetKey(const std::string& filename, const std::string& directory) { + return (directory.empty() ? filename : fmt::format("{}/{}", directory, filename)); +} diff --git a/src/Cache/LoaderCache.h b/src/Cache/LoaderCache.h new file mode 100644 index 000000000..63b8c723d --- /dev/null +++ b/src/Cache/LoaderCache.h @@ -0,0 +1,35 @@ +/* This file is part of the CARTA Image Viewer: https://github.com/CARTAvis/carta-backend + Copyright 2018- Academia Sinica Institute of Astronomy and Astrophysics (ASIAA), + Associated Universities, Inc. (AUI) and the Inter-University Institute for Data Intensive Astronomy (IDIA) + SPDX-License-Identifier: GPL-3.0-or-later +*/ + +#ifndef CARTA_SRC_CACHE_LOADERCACHE_H_ +#define CARTA_SRC_CACHE_LOADERCACHE_H_ + +#include +#include +#include + +#include "ImageData/FileLoader.h" + +namespace carta { + +// Cache of loaders for reading images from disk. +class LoaderCache { +public: + LoaderCache(int capacity); + std::shared_ptr Get(const std::string& filename, const std::string& directory = ""); + void Remove(const std::string& filename, const std::string& directory = ""); + +private: + std::string GetKey(const std::string& filename, const std::string& directory); + int _capacity; + std::unordered_map> _map; + std::list _queue; + std::mutex _loader_cache_mutex; +}; + +} // namespace carta + +#endif // CARTA_SRC_CACHE_LOADERCACHE_H_ diff --git a/src/Cache/RequirementsCache.h b/src/Cache/RequirementsCache.h index b72a09275..1fa771842 100644 --- a/src/Cache/RequirementsCache.h +++ b/src/Cache/RequirementsCache.h @@ -1,11 +1,11 @@ /* This file is part of the CARTA Image Viewer: https://github.com/CARTAvis/carta-backend - Copyright 2018-2022 Academia Sinica Institute of Astronomy and Astrophysics (ASIAA), + Copyright 2018- Academia Sinica Institute of Astronomy and Astrophysics (ASIAA), Associated Universities, Inc. (AUI) and the Inter-University Institute for Data Intensive Astronomy (IDIA) SPDX-License-Identifier: GPL-3.0-or-later */ -#ifndef CARTA_BACKEND__REQUIREMENTSCACHE_H_ -#define CARTA_BACKEND__REQUIREMENTSCACHE_H_ +#ifndef CARTA_SRC_CACHE_REQUIREMENTSCACHE_H_ +#define CARTA_SRC_CACHE_REQUIREMENTSCACHE_H_ #include "ImageStats/BasicStatsCalculator.h" #include "ImageStats/Histogram.h" @@ -223,4 +223,4 @@ struct StatsCache { } // namespace carta -#endif // CARTA_BACKEND__REQUIREMENTSCACHE_H_ +#endif // CARTA_SRC_CACHE_REQUIREMENTSCACHE_H_ diff --git a/src/Cache/TileCache.cc b/src/Cache/TileCache.cc index cf0f942e2..60b594730 100644 --- a/src/Cache/TileCache.cc +++ b/src/Cache/TileCache.cc @@ -1,5 +1,5 @@ /* This file is part of the CARTA Image Viewer: https://github.com/CARTAvis/carta-backend - Copyright 2018-2022 Academia Sinica Institute of Astronomy and Astrophysics (ASIAA), + Copyright 2018- Academia Sinica Institute of Astronomy and Astrophysics (ASIAA), Associated Universities, Inc. (AUI) and the Inter-University Institute for Data Intensive Astronomy (IDIA) SPDX-License-Identifier: GPL-3.0-or-later */ diff --git a/src/Cache/TileCache.h b/src/Cache/TileCache.h index 0f026b53c..d0f34753e 100644 --- a/src/Cache/TileCache.h +++ b/src/Cache/TileCache.h @@ -1,11 +1,11 @@ /* This file is part of the CARTA Image Viewer: https://github.com/CARTAvis/carta-backend - Copyright 2018-2022 Academia Sinica Institute of Astronomy and Astrophysics (ASIAA), + Copyright 2018- Academia Sinica Institute of Astronomy and Astrophysics (ASIAA), Associated Universities, Inc. (AUI) and the Inter-University Institute for Data Intensive Astronomy (IDIA) SPDX-License-Identifier: GPL-3.0-or-later */ -#ifndef CARTA_BACKEND__TILE_CACHE_H_ -#define CARTA_BACKEND__TILE_CACHE_H_ +#ifndef CARTA_SRC_CACHE_TILECACHE_H_ +#define CARTA_SRC_CACHE_TILECACHE_H_ #include #include @@ -115,4 +115,4 @@ class TileCache { } // namespace carta -#endif // CARTA_BACKEND__TILE_CACHE_H_ +#endif // CARTA_SRC_CACHE_TILECACHE_H_ diff --git a/src/Cache/TileCacheKey.h b/src/Cache/TileCacheKey.h index f09db272f..a904eb3af 100644 --- a/src/Cache/TileCacheKey.h +++ b/src/Cache/TileCacheKey.h @@ -1,11 +1,11 @@ /* This file is part of the CARTA Image Viewer: https://github.com/CARTAvis/carta-backend - Copyright 2018-2022 Academia Sinica Institute of Astronomy and Astrophysics (ASIAA), + Copyright 2018- Academia Sinica Institute of Astronomy and Astrophysics (ASIAA), Associated Universities, Inc. (AUI) and the Inter-University Institute for Data Intensive Astronomy (IDIA) SPDX-License-Identifier: GPL-3.0-or-later */ -#ifndef CARTA_BACKEND_TILECACHEKEY_H -#define CARTA_BACKEND_TILECACHEKEY_H +#ifndef CARTA_SRC_CACHE_TILECACHEKEY_H_ +#define CARTA_SRC_CACHE_TILECACHEKEY_H_ namespace carta { /** @brief Key for tiles used in TileCache @@ -54,4 +54,4 @@ struct hash { }; } // namespace std -#endif // CARTA_BACKEND_TILECACHEKEY_H +#endif // CARTA_SRC_CACHE_TILECACHEKEY_H_ diff --git a/src/Cache/TilePool.cc b/src/Cache/TilePool.cc index 2cdf2f68e..c16bac39b 100644 --- a/src/Cache/TilePool.cc +++ b/src/Cache/TilePool.cc @@ -1,5 +1,5 @@ /* This file is part of the CARTA Image Viewer: https://github.com/CARTAvis/carta-backend - Copyright 2018-2022 Academia Sinica Institute of Astronomy and Astrophysics (ASIAA), + Copyright 2018- Academia Sinica Institute of Astronomy and Astrophysics (ASIAA), Associated Universities, Inc. (AUI) and the Inter-University Institute for Data Intensive Astronomy (IDIA) SPDX-License-Identifier: GPL-3.0-or-later */ diff --git a/src/Cache/TilePool.h b/src/Cache/TilePool.h index 2192e085d..b1a2a33bc 100644 --- a/src/Cache/TilePool.h +++ b/src/Cache/TilePool.h @@ -1,11 +1,11 @@ /* This file is part of the CARTA Image Viewer: https://github.com/CARTAvis/carta-backend - Copyright 2018-2022 Academia Sinica Institute of Astronomy and Astrophysics (ASIAA), + Copyright 2018- Academia Sinica Institute of Astronomy and Astrophysics (ASIAA), Associated Universities, Inc. (AUI) and the Inter-University Institute for Data Intensive Astronomy (IDIA) SPDX-License-Identifier: GPL-3.0-or-later */ -#ifndef CARTA_BACKEND_TILEPOOL_H -#define CARTA_BACKEND_TILEPOOL_H +#ifndef CARTA_SRC_CACHE_TILEPOOL_H_ +#define CARTA_SRC_CACHE_TILEPOOL_H_ #include #include @@ -80,4 +80,4 @@ struct TilePool : std::enable_shared_from_this { } // namespace carta -#endif // CARTA_BACKEND_TILEPOOL_H +#endif // CARTA_SRC_CACHE_TILEPOOL_H_ diff --git a/src/DataStream/Compression.cc b/src/DataStream/Compression.cc index ef28794f4..45b2da924 100644 --- a/src/DataStream/Compression.cc +++ b/src/DataStream/Compression.cc @@ -1,5 +1,5 @@ /* This file is part of the CARTA Image Viewer: https://github.com/CARTAvis/carta-backend - Copyright 2018-2022 Academia Sinica Institute of Astronomy and Astrophysics (ASIAA), + Copyright 2018- Academia Sinica Institute of Astronomy and Astrophysics (ASIAA), Associated Universities, Inc. (AUI) and the Inter-University Institute for Data Intensive Astronomy (IDIA) SPDX-License-Identifier: GPL-3.0-or-later */ diff --git a/src/DataStream/Compression.h b/src/DataStream/Compression.h index 15984e46f..2d3c15944 100644 --- a/src/DataStream/Compression.h +++ b/src/DataStream/Compression.h @@ -1,11 +1,11 @@ /* This file is part of the CARTA Image Viewer: https://github.com/CARTAvis/carta-backend - Copyright 2018-2022 Academia Sinica Institute of Astronomy and Astrophysics (ASIAA), + Copyright 2018- Academia Sinica Institute of Astronomy and Astrophysics (ASIAA), Associated Universities, Inc. (AUI) and the Inter-University Institute for Data Intensive Astronomy (IDIA) SPDX-License-Identifier: GPL-3.0-or-later */ -#ifndef CARTA_BACKEND__COMPRESSION_H_ -#define CARTA_BACKEND__COMPRESSION_H_ +#ifndef CARTA_SRC_DATASTREAM_COMPRESSION_H_ +#define CARTA_SRC_DATASTREAM_COMPRESSION_H_ #include #include @@ -24,4 +24,4 @@ void EncodeIntegers(std::vector& array, bool strided = false); } // namespace carta -#endif // CARTA_BACKEND__COMPRESSION_H_ +#endif // CARTA_SRC_DATASTREAM_COMPRESSION_H_ diff --git a/src/DataStream/Contouring.cc b/src/DataStream/Contouring.cc index a4c72e6d6..17f262380 100644 --- a/src/DataStream/Contouring.cc +++ b/src/DataStream/Contouring.cc @@ -1,5 +1,5 @@ /* This file is part of the CARTA Image Viewer: https://github.com/CARTAvis/carta-backend - Copyright 2018-2022 Academia Sinica Institute of Astronomy and Astrophysics (ASIAA), + Copyright 2018- Academia Sinica Institute of Astronomy and Astrophysics (ASIAA), Associated Universities, Inc. (AUI) and the Inter-University Institute for Data Intensive Astronomy (IDIA) SPDX-License-Identifier: GPL-3.0-or-later */ diff --git a/src/DataStream/Contouring.h b/src/DataStream/Contouring.h index e9cf4fc31..7362f4281 100644 --- a/src/DataStream/Contouring.h +++ b/src/DataStream/Contouring.h @@ -1,11 +1,11 @@ /* This file is part of the CARTA Image Viewer: https://github.com/CARTAvis/carta-backend - Copyright 2018-2022 Academia Sinica Institute of Astronomy and Astrophysics (ASIAA), + Copyright 2018- Academia Sinica Institute of Astronomy and Astrophysics (ASIAA), Associated Universities, Inc. (AUI) and the Inter-University Institute for Data Intensive Astronomy (IDIA) SPDX-License-Identifier: GPL-3.0-or-later */ -#ifndef CARTA_BACKEND__CONTOURING_H_ -#define CARTA_BACKEND__CONTOURING_H_ +#ifndef CARTA_SRC_DATASTREAM_CONTOURING_H_ +#define CARTA_SRC_DATASTREAM_CONTOURING_H_ #include #include @@ -25,4 +25,4 @@ void TraceContours(float* image, int64_t width, int64_t height, double scale, do } // namespace carta -#endif // CARTA_BACKEND__CONTOURING_H_ +#endif // CARTA_SRC_DATASTREAM_CONTOURING_H_ diff --git a/src/DataStream/Smoothing.cc b/src/DataStream/Smoothing.cc index ff343e8a8..96dd69732 100644 --- a/src/DataStream/Smoothing.cc +++ b/src/DataStream/Smoothing.cc @@ -1,5 +1,5 @@ /* This file is part of the CARTA Image Viewer: https://github.com/CARTAvis/carta-backend - Copyright 2018-2022 Academia Sinica Institute of Astronomy and Astrophysics (ASIAA), + Copyright 2018- Academia Sinica Institute of Astronomy and Astrophysics (ASIAA), Associated Universities, Inc. (AUI) and the Inter-University Institute for Data Intensive Astronomy (IDIA) SPDX-License-Identifier: GPL-3.0-or-later */ diff --git a/src/DataStream/Smoothing.h b/src/DataStream/Smoothing.h index 9821545c8..453c3f04c 100644 --- a/src/DataStream/Smoothing.h +++ b/src/DataStream/Smoothing.h @@ -1,5 +1,5 @@ /* This file is part of the CARTA Image Viewer: https://github.com/CARTAvis/carta-backend - Copyright 2018-2022 Academia Sinica Institute of Astronomy and Astrophysics (ASIAA), + Copyright 2018- Academia Sinica Institute of Astronomy and Astrophysics (ASIAA), Associated Universities, Inc. (AUI) and the Inter-University Institute for Data Intensive Astronomy (IDIA) SPDX-License-Identifier: GPL-3.0-or-later */ @@ -8,8 +8,8 @@ // Created by angus on 2019/09/23. // -#ifndef CARTA_BACKEND__SMOOTHING_H_ -#define CARTA_BACKEND__SMOOTHING_H_ +#ifndef CARTA_SRC_DATASTREAM_SMOOTHING_H_ +#define CARTA_SRC_DATASTREAM_SMOOTHING_H_ #include #include @@ -77,4 +77,4 @@ void NearestNeighbor(const float* src_data, float* dest_data, int64_t src_width, } // namespace carta -#endif // CARTA_BACKEND__SMOOTHING_H_ +#endif // CARTA_SRC_DATASTREAM_SMOOTHING_H_ diff --git a/src/DataStream/Tile.cc b/src/DataStream/Tile.cc index 6e3453f6b..4c2a0b885 100644 --- a/src/DataStream/Tile.cc +++ b/src/DataStream/Tile.cc @@ -1,5 +1,5 @@ /* This file is part of the CARTA Image Viewer: https://github.com/CARTAvis/carta-backend - Copyright 2018-2022 Academia Sinica Institute of Astronomy and Astrophysics (ASIAA), + Copyright 2018- Academia Sinica Institute of Astronomy and Astrophysics (ASIAA), Associated Universities, Inc. (AUI) and the Inter-University Institute for Data Intensive Astronomy (IDIA) SPDX-License-Identifier: GPL-3.0-or-later */ diff --git a/src/DataStream/Tile.h b/src/DataStream/Tile.h index dfef64aa3..6dcdca1db 100644 --- a/src/DataStream/Tile.h +++ b/src/DataStream/Tile.h @@ -1,11 +1,11 @@ /* This file is part of the CARTA Image Viewer: https://github.com/CARTAvis/carta-backend - Copyright 2018-2022 Academia Sinica Institute of Astronomy and Astrophysics (ASIAA), + Copyright 2018- Academia Sinica Institute of Astronomy and Astrophysics (ASIAA), Associated Universities, Inc. (AUI) and the Inter-University Institute for Data Intensive Astronomy (IDIA) SPDX-License-Identifier: GPL-3.0-or-later */ -#ifndef CARTA_BACKEND__TILE_H_ -#define CARTA_BACKEND__TILE_H_ +#ifndef CARTA_SRC_DATASTREAM_TILE_H_ +#define CARTA_SRC_DATASTREAM_TILE_H_ #include #include @@ -52,4 +52,4 @@ struct Tile { } // namespace carta -#endif // CARTA_BACKEND__TILE_H_ +#endif // CARTA_SRC_DATASTREAM_TILE_H_ diff --git a/src/DataStream/VectorField.cc b/src/DataStream/VectorField.cc index e447130e2..953083ab0 100644 --- a/src/DataStream/VectorField.cc +++ b/src/DataStream/VectorField.cc @@ -1,5 +1,5 @@ /* This file is part of the CARTA Image Viewer: https://github.com/CARTAvis/carta-backend - Copyright 2018-2022 Academia Sinica Institute of Astronomy and Astrophysics (ASIAA), + Copyright 2018- Academia Sinica Institute of Astronomy and Astrophysics (ASIAA), Associated Universities, Inc. (AUI) and the Inter-University Institute for Data Intensive Astronomy (IDIA) SPDX-License-Identifier: GPL-3.0-or-later */ diff --git a/src/DataStream/VectorField.h b/src/DataStream/VectorField.h index e49b69789..7afa2c0e0 100644 --- a/src/DataStream/VectorField.h +++ b/src/DataStream/VectorField.h @@ -1,11 +1,11 @@ /* This file is part of the CARTA Image Viewer: https://github.com/CARTAvis/carta-backend - Copyright 2018-2022 Academia Sinica Institute of Astronomy and Astrophysics (ASIAA), + Copyright 2018- Academia Sinica Institute of Astronomy and Astrophysics (ASIAA), Associated Universities, Inc. (AUI) and the Inter-University Institute for Data Intensive Astronomy (IDIA) SPDX-License-Identifier: GPL-3.0-or-later */ -#ifndef CARTA_BACKEND__FRAME_VECTORFIELD_H_ -#define CARTA_BACKEND__FRAME_VECTORFIELD_H_ +#ifndef CARTA_SRC_DATASTREAM_VECTORFIELD_H_ +#define CARTA_SRC_DATASTREAM_VECTORFIELD_H_ #include #include @@ -138,4 +138,4 @@ CARTA::ImageBounds GetImageBounds(const carta::Tile& tile, int image_width, int } // namespace carta -#endif // CARTA_BACKEND__FRAME_VECTORFIELD_H_ +#endif // CARTA_SRC_DATASTREAM_VECTORFIELD_H_ diff --git a/src/FileList/FileExtInfoLoader.cc b/src/FileList/FileExtInfoLoader.cc index 4172aeddd..85c01f61b 100644 --- a/src/FileList/FileExtInfoLoader.cc +++ b/src/FileList/FileExtInfoLoader.cc @@ -1,5 +1,5 @@ /* This file is part of the CARTA Image Viewer: https://github.com/CARTAvis/carta-backend - Copyright 2018-2022 Academia Sinica Institute of Astronomy and Astrophysics (ASIAA), + Copyright 2018- Academia Sinica Institute of Astronomy and Astrophysics (ASIAA), Associated Universities, Inc. (AUI) and the Inter-University Institute for Data Intensive Astronomy (IDIA) SPDX-License-Identifier: GPL-3.0-or-later */ diff --git a/src/FileList/FileExtInfoLoader.h b/src/FileList/FileExtInfoLoader.h index 1ebc3255e..9ce5d1eb0 100644 --- a/src/FileList/FileExtInfoLoader.h +++ b/src/FileList/FileExtInfoLoader.h @@ -1,13 +1,13 @@ /* This file is part of the CARTA Image Viewer: https://github.com/CARTAvis/carta-backend - Copyright 2018-2022 Academia Sinica Institute of Astronomy and Astrophysics (ASIAA), + Copyright 2018- Academia Sinica Institute of Astronomy and Astrophysics (ASIAA), Associated Universities, Inc. (AUI) and the Inter-University Institute for Data Intensive Astronomy (IDIA) SPDX-License-Identifier: GPL-3.0-or-later */ //# FileExtInfoLoader.h: load FileInfoExtended fields for all supported file types -#ifndef CARTA_BACKEND__FILELIST_FILEEXTINFOLOADER_H_ -#define CARTA_BACKEND__FILELIST_FILEEXTINFOLOADER_H_ +#ifndef CARTA_SRC_FILELIST_FILEEXTINFOLOADER_H_ +#define CARTA_SRC_FILELIST_FILEEXTINFOLOADER_H_ #include #include @@ -79,4 +79,4 @@ class FileExtInfoLoader { } // namespace carta -#endif // CARTA_BACKEND__FILELIST_FILEINFOLOADER_H_ +#endif // CARTA_SRC_FILELIST_FILEEXTINFOLOADER_H_ diff --git a/src/FileList/FileInfoLoader.cc b/src/FileList/FileInfoLoader.cc index f0b1f93c7..8107fe384 100644 --- a/src/FileList/FileInfoLoader.cc +++ b/src/FileList/FileInfoLoader.cc @@ -1,5 +1,5 @@ /* This file is part of the CARTA Image Viewer: https://github.com/CARTAvis/carta-backend - Copyright 2018-2022 Academia Sinica Institute of Astronomy and Astrophysics (ASIAA), + Copyright 2018- Academia Sinica Institute of Astronomy and Astrophysics (ASIAA), Associated Universities, Inc. (AUI) and the Inter-University Institute for Data Intensive Astronomy (IDIA) SPDX-License-Identifier: GPL-3.0-or-later */ diff --git a/src/FileList/FileInfoLoader.h b/src/FileList/FileInfoLoader.h index 0559817d9..b84fc7a37 100644 --- a/src/FileList/FileInfoLoader.h +++ b/src/FileList/FileInfoLoader.h @@ -1,13 +1,13 @@ /* This file is part of the CARTA Image Viewer: https://github.com/CARTAvis/carta-backend - Copyright 2018-2022 Academia Sinica Institute of Astronomy and Astrophysics (ASIAA), + Copyright 2018- Academia Sinica Institute of Astronomy and Astrophysics (ASIAA), Associated Universities, Inc. (AUI) and the Inter-University Institute for Data Intensive Astronomy (IDIA) SPDX-License-Identifier: GPL-3.0-or-later */ //# FileInfoLoader.h: load FileInfo fields for given file -#ifndef CARTA_BACKEND__FILELIST_FILEINFOLOADER_H_ -#define CARTA_BACKEND__FILELIST_FILEINFOLOADER_H_ +#ifndef CARTA_SRC_FILELIST_FILEINFOLOADER_H_ +#define CARTA_SRC_FILELIST_FILEINFOLOADER_H_ #include @@ -32,4 +32,4 @@ class FileInfoLoader { } // namespace carta -#endif // CARTA_BACKEND__FILELIST_FILEINFOLOADER_H_ +#endif // CARTA_SRC_FILELIST_FILEINFOLOADER_H_ diff --git a/src/FileList/FileListHandler.cc b/src/FileList/FileListHandler.cc index 2a02b9ec9..62a4ac4f5 100644 --- a/src/FileList/FileListHandler.cc +++ b/src/FileList/FileListHandler.cc @@ -1,5 +1,5 @@ /* This file is part of the CARTA Image Viewer: https://github.com/CARTAvis/carta-backend - Copyright 2018-2022 Academia Sinica Institute of Astronomy and Astrophysics (ASIAA), + Copyright 2018- Academia Sinica Institute of Astronomy and Astrophysics (ASIAA), Associated Universities, Inc. (AUI) and the Inter-University Institute for Data Intensive Astronomy (IDIA) SPDX-License-Identifier: GPL-3.0-or-later */ @@ -55,6 +55,9 @@ void FileListHandler::GetRelativePath(std::string& folder) { folder = folder.substr(1); // remove leading '/' } } + if (folder.empty()) { + folder = "."; + } } void FileListHandler::GetFileList(CARTA::FileListResponse& file_list_response, const std::string& folder, ResultMsg& result_msg, @@ -72,65 +75,51 @@ void FileListHandler::GetFileList(CARTA::FileListResponse& file_list_response, c requested_folder = folder_string; } - std::string absolute_path(requested_folder), directory; - - if (requested_folder == _top_level_folder) { - // Set directory relative to top (current directory). Parent is empty string. - file_list_response.set_directory("."); - } else { - // Normalize folder relative to top, restore path - GetRelativePath(requested_folder); - casacore::Path path(_top_level_folder); - path.append(requested_folder); - - // Resolve path (., .., ~, symlinks) - try { - absolute_path = path.resolvedName(); - } catch (casacore::AipsError& err) { - try { - absolute_path = path.absoluteName(); - } catch (casacore::AipsError& err) { - file_list_response.set_success(false); - file_list_response.set_message("Cannot resolve directory path for file list."); - return; - } - } + // Normalize requested folder relative to top (top + requested = full path) + GetRelativePath(requested_folder); - // Set parent relative to top - std::string parent(path.dirName()); - GetRelativePath(parent); - file_list_response.set_parent(parent); + // Resolve path (., .., ~, symlinks) + std::string message; + auto resolved_path = GetResolvedFilename(_top_level_folder, requested_folder, "", message); - // Set directory relative to top - directory = absolute_path; - GetRelativePath(directory); - file_list_response.set_directory(directory); + // Check resolved path + if (resolved_path.empty()) { + file_list_response.set_success(false); + file_list_response.set_message("File list failed: " + message); + return; + } else if ((_top_level_folder.find(resolved_path) == 0) && (resolved_path.length() < _top_level_folder.length())) { + // path is above top folder! + file_list_response.set_success(false); + file_list_response.set_message("Forbidden path."); + return; } - if ((_top_level_folder.find(absolute_path) == 0) && (absolute_path.length() < _top_level_folder.length())) { - // absolute path is above top folder + casacore::File folder_path(resolved_path); + if (!folder_path.isDirectory()) { file_list_response.set_success(false); - file_list_response.set_message("Forbidden path."); + file_list_response.set_message("File list failed: requested path " + folder + " is not a directory."); return; } - casacore::File folder_path(absolute_path); - std::string message; + // Set response parent and directory + if (requested_folder == ".") { + // is top folder; no directory + file_list_response.set_parent(requested_folder); + } else { + // Make full path to separate directory and base names + casacore::Path full_path(_top_level_folder); + full_path.append(requested_folder); + // parent + std::string parent(full_path.dirName()); + GetRelativePath(parent); + file_list_response.set_parent(parent); + // directory + std::string directory(full_path.baseName()); + file_list_response.set_directory(requested_folder); + } + // Iterate through directory to generate file list try { - if (!folder_path.exists()) { - file_list_response.set_success(false); - file_list_response.set_message("Requested directory " + directory + " does not exist."); - return; - } - - if (!folder_path.isDirectory()) { - file_list_response.set_success(false); - file_list_response.set_message("Requested path " + directory + " is not a directory."); - return; - } - - // Iterate through directory to generate file list casacore::Directory start_dir(folder_path); casacore::DirectoryIterator dir_iter(start_dir); @@ -326,37 +315,38 @@ bool FileListHandler::FillRegionFileInfo( void FileListHandler::OnRegionFileInfoRequest( const CARTA::RegionFileInfoRequest& request, CARTA::RegionFileInfoResponse& response, ResultMsg& result_msg) { // Fill response message with file info and contents - casacore::Path top_path(_top_level_folder); - top_path.append(request.directory()); - auto filename = request.file(); - top_path.append(filename); - casacore::File cc_file(top_path); - std::string message, contents; - bool success(false); + auto directory = request.directory(); + auto file = request.file(); + std::string message; - if (!cc_file.exists()) { - message = "File " + filename + " does not exist."; - response.add_contents(contents); - } else if (!cc_file.isRegular(true)) { - message = "File " + filename + " is not a region file."; - response.add_contents(contents); - } else if (!cc_file.isReadable()) { - message = "File " + filename + " is not readable."; - response.add_contents(contents); - } else { - casacore::String full_name(cc_file.path().resolvedName()); - auto& file_info = *response.mutable_file_info(); - FillRegionFileInfo(file_info, full_name); - std::vector file_contents; - if (file_info.type() == CARTA::FileType::UNKNOWN) { - message = "File " + filename + " is not a region file."; - response.add_contents(contents); + casacore::String full_name = GetResolvedFilename(_top_level_folder, directory, file, message); + + bool success(false), add_contents(true); + if (!full_name.empty()) { + casacore::File cc_file(full_name); + if (!cc_file.isRegular(true)) { + message = "File " + file + " is not a region file."; } else { - GetRegionFileContents(full_name, file_contents); - success = true; - *response.mutable_contents() = {file_contents.begin(), file_contents.end()}; + auto& file_info = *response.mutable_file_info(); + FillRegionFileInfo(file_info, full_name); + + if (file_info.type() == CARTA::FileType::UNKNOWN) { + message = "File " + file + " is not a region file."; + } else { + std::vector file_contents; + GetRegionFileContents(full_name, file_contents); + success = true; + *response.mutable_contents() = {file_contents.begin(), file_contents.end()}; + add_contents = false; + } } } + + if (add_contents) { + std::string contents; + response.add_contents(contents); + } + response.set_success(success); response.set_message(message); } diff --git a/src/FileList/FileListHandler.h b/src/FileList/FileListHandler.h index 2729e4bac..e0a4aaf63 100644 --- a/src/FileList/FileListHandler.h +++ b/src/FileList/FileListHandler.h @@ -1,13 +1,13 @@ /* This file is part of the CARTA Image Viewer: https://github.com/CARTAvis/carta-backend - Copyright 2018-2022 Academia Sinica Institute of Astronomy and Astrophysics (ASIAA), + Copyright 2018- Academia Sinica Institute of Astronomy and Astrophysics (ASIAA), Associated Universities, Inc. (AUI) and the Inter-University Institute for Data Intensive Astronomy (IDIA) SPDX-License-Identifier: GPL-3.0-or-later */ // file list handler for all users' requests -#ifndef CARTA_BACKEND__FILELISTHANDLER_H_ -#define CARTA_BACKEND__FILELISTHANDLER_H_ +#ifndef CARTA_SRC_FILELIST_FILELISTHANDLER_H_ +#define CARTA_SRC_FILELIST_FILELISTHANDLER_H_ #include #include @@ -69,4 +69,4 @@ class FileListHandler { } // namespace carta -#endif // CARTA_BACKEND__FILELISTHANDLER_H_ +#endif // CARTA_SRC_FILELIST_FILELISTHANDLER_H_ diff --git a/src/FileList/FitsHduList.cc b/src/FileList/FitsHduList.cc index 7b8c0ddf1..52a259dd4 100644 --- a/src/FileList/FitsHduList.cc +++ b/src/FileList/FitsHduList.cc @@ -1,5 +1,5 @@ /* This file is part of the CARTA Image Viewer: https://github.com/CARTAvis/carta-backend - Copyright 2018-2022 Academia Sinica Institute of Astronomy and Astrophysics (ASIAA), + Copyright 2018- Academia Sinica Institute of Astronomy and Astrophysics (ASIAA), Associated Universities, Inc. (AUI) and the Inter-University Institute for Data Intensive Astronomy (IDIA) SPDX-License-Identifier: GPL-3.0-or-later */ diff --git a/src/FileList/FitsHduList.h b/src/FileList/FitsHduList.h index 73af080d1..5717150f7 100644 --- a/src/FileList/FitsHduList.h +++ b/src/FileList/FitsHduList.h @@ -1,11 +1,11 @@ /* This file is part of the CARTA Image Viewer: https://github.com/CARTAvis/carta-backend - Copyright 2018-2022 Academia Sinica Institute of Astronomy and Astrophysics (ASIAA), + Copyright 2018- Academia Sinica Institute of Astronomy and Astrophysics (ASIAA), Associated Universities, Inc. (AUI) and the Inter-University Institute for Data Intensive Astronomy (IDIA) SPDX-License-Identifier: GPL-3.0-or-later */ -#ifndef CARTA_BACKEND_FILELIST_FITSHDULIST_H_ -#define CARTA_BACKEND_FILELIST_FITSHDULIST_H_ +#ifndef CARTA_SRC_FILELIST_FITSHDULIST_H_ +#define CARTA_SRC_FILELIST_FITSHDULIST_H_ #include #include @@ -27,4 +27,4 @@ class FitsHduList { } // namespace carta -#endif // CARTA_BACKEND_FILELIST_FITSHDULIST_H_ +#endif // CARTA_SRC_FILELIST_FITSHDULIST_H_ diff --git a/src/Frame/Frame.cc b/src/Frame/Frame.cc index bfc73f401..bfdfb1acc 100644 --- a/src/Frame/Frame.cc +++ b/src/Frame/Frame.cc @@ -1,5 +1,5 @@ /* This file is part of the CARTA Image Viewer: https://github.com/CARTAvis/carta-backend - Copyright 2018-2022 Academia Sinica Institute of Astronomy and Astrophysics (ASIAA), + Copyright 2018- Academia Sinica Institute of Astronomy and Astrophysics (ASIAA), Associated Universities, Inc. (AUI) and the Inter-University Institute for Data Intensive Astronomy (IDIA) SPDX-License-Identifier: GPL-3.0-or-later */ @@ -182,6 +182,11 @@ int Frame::StokesAxis() { return _stokes_axis; } +bool Frame::IsCurrentZStokes(const StokesSource& stokes_source) { + return (stokes_source.z_range.from == stokes_source.z_range.to) && (stokes_source.z_range.from == CurrentZ()) && + (stokes_source.stokes == CurrentStokes()); +} + bool Frame::GetBeams(std::vector& beams) { std::string error; bool beams_ok = _loader->GetBeams(beams, error); @@ -331,12 +336,12 @@ bool Frame::SetImageChannels(int new_z, int new_stokes, std::string& message) { bool z_ok(CheckZ(new_z)); bool stokes_ok(CheckStokes(new_stokes)); if (z_ok && stokes_ok) { - _z_index = new_z; - _stokes_index = new_stokes; - // invalidate the image cache InvalidateImageCache(); + _z_index = new_z; + _stokes_index = new_stokes; + if (!(_loader->UseTileCache() && _loader->HasMip(2)) || IsComputedStokes(_stokes_index)) { // Reload the full channel cache for loaders which use it FillImageCache(); @@ -366,7 +371,6 @@ bool Frame::SetCursor(float x, float y) { bool Frame::FillImageCache() { // get image data for z, stokes - bool write_lock(true); queuing_rw_mutex_scoped cache_lock(&_cache_mutex, write_lock); @@ -1605,68 +1609,120 @@ bool Frame::GetSlicerSubImage(const StokesSlicer& stokes_slicer, casacore::SubIm bool Frame::GetRegionData(const StokesRegion& stokes_region, std::vector& data, bool report_performance) { // Get image data with a region applied Timer t; - casacore::SubImage sub_image; - bool subimage_ok = GetRegionSubImage(stokes_region, sub_image); + std::vector region_mask; - if (!subimage_ok) { - return false; - } - - casacore::IPosition subimage_shape = sub_image.shape(); - if (subimage_shape.empty()) { - return false; + if (IsCurrentZStokes(stokes_region.stokes_source)) { + try { + // Slice cached image data using LCRegion bounding box + casacore::Slicer bounding_box = stokes_region.image_region.asLCRegion().boundingBox(); + StokesSlicer stokes_slicer(stokes_region.stokes_source, bounding_box); + data.resize(bounding_box.length().product()); + + if (GetSlicerData(stokes_slicer, data.data())) { + // Next get the LCRegion as a mask (LCRegion is a Lattice) + casacore::Array tmpmask = stokes_region.image_region.asLCRegion().get(); + region_mask = tmpmask.tovector(); + } else { + data.clear(); + } + } catch (const casacore::AipsError& err) { + // ImageRegion underlying region was not LCRegion + data.clear(); + } } - try { - casacore::IPosition start(subimage_shape.size(), 0); - casacore::IPosition count(subimage_shape); - casacore::Slicer slicer(start, count); // entire subimage - bool is_computed_stokes(!stokes_region.stokes_source.IsOriginalImage()); + if (data.empty()) { + // Apply region to image to get SubImage data + casacore::SubImage sub_image; + bool subimage_ok = GetRegionSubImage(stokes_region, sub_image); - // Get image data - std::unique_lock ulock(_image_mutex); - if (_loader->IsGenerated() || is_computed_stokes) { // For the image in memory - casacore::Array tmp; - sub_image.doGetSlice(tmp, slicer); - data = tmp.tovector(); - } else { - data.resize(subimage_shape.product()); // must size correctly before sharing - casacore::Array tmp(subimage_shape, data.data(), casacore::StorageInitPolicy::SHARE); - sub_image.doGetSlice(tmp, slicer); + if (!subimage_ok) { + return false; } - // Get mask that defines region in subimage bounding box - casacore::Array tmpmask; - sub_image.doGetMaskSlice(tmpmask, slicer); - ulock.unlock(); + casacore::IPosition subimage_shape = sub_image.shape(); + if (subimage_shape.empty()) { + return false; + } - // Apply mask to data - std::vector datamask = tmpmask.tovector(); - for (size_t i = 0; i < data.size(); ++i) { - if (!datamask[i]) { - data[i] = NAN; + try { + casacore::IPosition start(subimage_shape.size(), 0); + casacore::IPosition count(subimage_shape); + casacore::Slicer slicer(start, count); // entire subimage + bool is_computed_stokes(!stokes_region.stokes_source.IsOriginalImage()); + + // Get image data and mask, with image mutex locked + std::unique_lock ulock(_image_mutex); + casacore::Array tmpdata; + if (_loader->IsGenerated() || is_computed_stokes) { // For the image in memory + sub_image.doGetSlice(tmpdata, slicer); + data = tmpdata.tovector(); + } else { + data.resize(subimage_shape.product()); // must size correctly before sharing + tmpdata = casacore::Array(subimage_shape, data.data(), casacore::StorageInitPolicy::SHARE); + sub_image.doGetSlice(tmpdata, slicer); } + + // Get mask that defines region in subimage bounding box + casacore::Array tmpmask; + sub_image.doGetMaskSlice(tmpmask, slicer); + ulock.unlock(); + region_mask = tmpmask.tovector(); + } catch (const casacore::AipsError& err) { + data.clear(); + return false; } + } - if (report_performance) { - spdlog::performance("Get region subimage data in {:.3f} ms", t.Elapsed().ms()); + // Apply mask to data + for (size_t i = 0; i < data.size(); ++i) { + if (!region_mask[i]) { + data[i] = NAN; } + } - return true; - } catch (casacore::AipsError& err) { - data.clear(); + if (report_performance) { + spdlog::performance("Get region subimage data in {:.3f} ms", t.Elapsed().ms()); } - return false; + return true; } bool Frame::GetSlicerData(const StokesSlicer& stokes_slicer, float* data) { - // Get image data with a slicer applied + // Get image data with a slicer applied; data must be correctly resized + bool data_ok(false); casacore::Array tmp(stokes_slicer.slicer.length(), data, casacore::StorageInitPolicy::SHARE); - std::unique_lock ulock(_image_mutex); - bool data_ok = _loader->GetSlice(tmp, stokes_slicer); - _loader->CloseImageIfUpdated(); - ulock.unlock(); + + if (_image_cache_valid && IsCurrentZStokes(stokes_slicer.stokes_source)) { + // Slice image cache + auto cache_shape = ImageShape(); + auto slicer_start = stokes_slicer.slicer.start(); + auto slicer_end = stokes_slicer.slicer.end(); + + // Adjust cache shape and slicer for single channel and stokes + if (_spectral_axis >= 0) { + cache_shape(_spectral_axis) = 1; + slicer_start(_spectral_axis) = 0; + slicer_end(_spectral_axis) = 0; + } + if (_stokes_axis >= 0) { + cache_shape(_stokes_axis) = 1; + slicer_start(_stokes_axis) = 0; + slicer_end(_stokes_axis) = 0; + } + casacore::Slicer cache_slicer(slicer_start, slicer_end, casacore::Slicer::endIsLast); + + queuing_rw_mutex_scoped cache_lock(&_cache_mutex, false); // read lock + casacore::Array image_cache_as_array(cache_shape, _image_cache.get(), casacore::StorageInitPolicy::SHARE); + tmp = image_cache_as_array(cache_slicer); + data_ok = true; + } else { + // Use loader to slice image + std::unique_lock ulock(_image_mutex); + data_ok = _loader->GetSlice(tmp, stokes_slicer); + _loader->CloseImageIfUpdated(); + ulock.unlock(); + } return data_ok; } @@ -1827,7 +1883,7 @@ bool Frame::FitImage(const CARTA::FittingRequest& fitting_request, CARTA::Fittin } casa::SPIIF image(_loader->GetStokesImage(output_stokes_region.stokes_source)); success = _image_fitter->GetGeneratedImages( - image, output_stokes_region.image_region, file_id, GetFileName(), model_image, residual_image, fitting_response); + image, output_stokes_region.image_region, GetFileName(), model_image, residual_image, fitting_response); } } diff --git a/src/Frame/Frame.h b/src/Frame/Frame.h index 023373786..7d56cce54 100644 --- a/src/Frame/Frame.h +++ b/src/Frame/Frame.h @@ -1,5 +1,5 @@ /* This file is part of the CARTA Image Viewer: https://github.com/CARTAvis/carta-backend - Copyright 2018-2022 Academia Sinica Institute of Astronomy and Astrophysics (ASIAA), + Copyright 2018- Academia Sinica Institute of Astronomy and Astrophysics (ASIAA), Associated Universities, Inc. (AUI) and the Inter-University Institute for Data Intensive Astronomy (IDIA) SPDX-License-Identifier: GPL-3.0-or-later */ @@ -7,8 +7,8 @@ //# Frame.h: represents an open image file. Handles slicing data and region calculations //# (profiles, histograms, stats) -#ifndef CARTA_BACKEND__FRAME_H_ -#define CARTA_BACKEND__FRAME_H_ +#ifndef CARTA_SRC_FRAME_FRAME_H_ +#define CARTA_SRC_FRAME_FRAME_H_ #include #include @@ -105,6 +105,7 @@ class Frame { size_t NumStokes(); // if no stokes axis, nstokes=1 int CurrentZ(); int CurrentStokes(); + bool IsCurrentZStokes(const StokesSource& stokes_source); int SpectralAxis(); int StokesAxis(); bool GetBeams(std::vector& beams); @@ -296,7 +297,6 @@ class Frame { ContourSettings _contour_settings; // Image data cache and mutex - // std::vector _image_cache; // image data for current z, stokes long long int _image_cache_size; std::unique_ptr _image_cache; bool _image_cache_valid; // cached image data is valid for current z and stokes @@ -337,4 +337,4 @@ class Frame { } // namespace carta -#endif // CARTA_BACKEND__FRAME_H_ +#endif // CARTA_SRC_FRAME_FRAME_H_ diff --git a/src/HttpServer/HttpServer.cc b/src/HttpServer/HttpServer.cc index 282a7fe07..44e40980a 100644 --- a/src/HttpServer/HttpServer.cc +++ b/src/HttpServer/HttpServer.cc @@ -1,5 +1,5 @@ /* This file is part of the CARTA Image Viewer: https://github.com/CARTAvis/carta-backend - Copyright 2018-2022 Academia Sinica Institute of Astronomy and Astrophysics (ASIAA), + Copyright 2018- Academia Sinica Institute of Astronomy and Astrophysics (ASIAA), Associated Universities, Inc. (AUI) and the Inter-University Institute for Data Intensive Astronomy (IDIA) SPDX-License-Identifier: GPL-3.0-or-later */ @@ -24,7 +24,13 @@ using json = nlohmann::json; namespace carta { -const std::string success_string = json({{"success", true}}).dump(); +const std::string SUCCESS_STRING = json({{"success", true}}).dump(); +const std::string LAYOUT = "layout"; +const std::string SNIPPET = "snippet"; +const std::string WORKSPACE = "workspace"; + +const std::unordered_map SCHEMA_URLS = { + {LAYOUT, CARTA_LAYOUT_SCHEMA_URL}, {SNIPPET, CARTA_SNIPPET_SCHEMA_URL}, {WORKSPACE, CARTA_WORKSPACE_SCHEMA_URL}}; uint32_t HttpServer::_scripting_request_id = 0; @@ -67,23 +73,16 @@ void HttpServer::RegisterRoutes() { app.put(fmt::format("{}/api/database/preferences", _url_prefix), [&](auto res, auto req) { HandleSetPreferences(res, req); }); app.del(fmt::format("{}/api/database/preferences", _url_prefix), [&](auto res, auto req) { HandleClearPreferences(res, req); }); - app.get(fmt::format("{}/api/database/list/layouts", _url_prefix), [&](auto res, auto req) { HandleGetObjectList("layout", res, req); }); - app.get(fmt::format("{}/api/database/layouts", _url_prefix), [&](auto res, auto req) { HandleGetObjects("layout", res, req); }); - app.get(fmt::format("{}/api/database/layout/:name", _url_prefix), [&](auto res, auto req) { HandleGetObject("layout", res, req); }); - app.put(fmt::format("{}/api/database/layout", _url_prefix), [&](auto res, auto req) { HandleSetObject("layout", res, req); }); - app.del(fmt::format("{}/api/database/layout", _url_prefix), [&](auto res, auto req) { HandleClearObject("layout", res, req); }); - - app.get(fmt::format("{}/api/database/list/snippets", _url_prefix), [&](auto res, auto req) { HandleGetObjectList("snippet", res, req); }); - app.get(fmt::format("{}/api/database/snippets", _url_prefix), [&](auto res, auto req) { HandleGetObjects("snippet", res, req); }); - app.get(fmt::format("{}/api/database/snippet/:name", _url_prefix), [&](auto res, auto req) { HandleGetObject("snippet", res, req); }); - app.put(fmt::format("{}/api/database/snippet", _url_prefix), [&](auto res, auto req) { HandleSetObject("snippet", res, req); }); - app.del(fmt::format("{}/api/database/snippet", _url_prefix), [&](auto res, auto req) { HandleClearObject("snippet", res, req); }); - - app.get(fmt::format("{}/api/database/list/workspaces", _url_prefix), [&](auto res, auto req) { HandleGetObjectList("workspace", res, req); }); - app.get(fmt::format("{}/api/database/workspaces", _url_prefix), [&](auto res, auto req) { HandleGetObjects("workspace", res, req); }); - app.get(fmt::format("{}/api/database/workspace/:name", _url_prefix), [&](auto res, auto req) { HandleGetObject("workspace", res, req); }); - app.put(fmt::format("{}/api/database/workspace", _url_prefix), [&](auto res, auto req) { HandleSetObject("workspace", res, req); }); - app.del(fmt::format("{}/api/database/workspace", _url_prefix), [&](auto res, auto req) { HandleClearObject("workspace", res, req); }); + for (const auto& elem : SCHEMA_URLS) { + const auto& object_type = elem.first; + app.get(fmt::format("{}/api/database/list/{}s", _url_prefix, object_type), + [&](auto res, auto req) { HandleGetObjectList(object_type, res, req); }); + app.get(fmt::format("{}/api/database/{}s", _url_prefix, object_type), [&](auto res, auto req) { HandleGetObjects(object_type, res, req); }); + app.get( + fmt::format("{}/api/database/{}/:name", _url_prefix, object_type), [&](auto res, auto req) { HandleGetObject(object_type, res, req); }); + app.put(fmt::format("{}/api/database/{}", _url_prefix, object_type), [&](auto res, auto req) { HandleSetObject(object_type, res, req); }); + app.del(fmt::format("{}/api/database/{}", _url_prefix, object_type), [&](auto res, auto req) { HandleClearObject(object_type, res, req); }); + } } else { app.get(fmt::format("{}/api/database/*", _url_prefix), [&](auto res, auto req) { NotImplemented(res, req); }); app.put(fmt::format("{}/api/database/*", _url_prefix), [&](auto res, auto req) { NotImplemented(res, req); }); @@ -347,7 +346,7 @@ void HttpServer::HandleSetPreferences(Res* res, Req* req) { res->writeHeader("Content-Type", "application/json"); AddNoCacheHeaders(res); if (status == HTTP_200) { - res->end(success_string); + res->end(SUCCESS_STRING); } else { res->end(); } @@ -402,7 +401,7 @@ void HttpServer::HandleClearPreferences(Res* res, Req* req) { AddNoCacheHeaders(res); res->writeHeader("Content-Type", "application/json"); if (status == HTTP_200) { - res->end(success_string); + res->end(SUCCESS_STRING); } else { res->end(); } @@ -479,7 +478,7 @@ void HttpServer::HandleSetObject(const std::string& object_type, Res* res, Req* AddNoCacheHeaders(res); res->writeHeader("Content-Type", "application/json"); if (status == HTTP_200) { - res->end(success_string); + res->end(SUCCESS_STRING); } else { res->end(); } @@ -498,7 +497,7 @@ void HttpServer::HandleClearObject(const std::string& object_type, Res* res, Req AddNoCacheHeaders(res); res->writeHeader("Content-Type", "application/json"); if (status == HTTP_200) { - res->end(success_string); + res->end(SUCCESS_STRING); } else { res->end(); } @@ -595,12 +594,11 @@ bool HttpServer::WriteObjectFile(const std::string& object_type, const std::stri fs::create_directories(object_path.parent_path()); std::ofstream file(object_path.string()); // Ensure correct schema value is written - if (object_type == "layout") { - obj["$schema"] = CARTA_LAYOUT_SCHEMA_URL; - } else if (object_type == "snippet") { - obj["$schema"] = CARTA_SNIPPET_SCHEMA_URL; - } else if (object_type == "workspace") { - obj["$schema"] = CARTA_WORKSPACE_SCHEMA_URL; + if (SCHEMA_URLS.count(object_type)) { + obj["$schema"] = SCHEMA_URLS.at(object_type); + } else { + spdlog::error("Unknown object types: {}.", object_type); + return false; } auto json_string = obj.dump(4); diff --git a/src/HttpServer/HttpServer.h b/src/HttpServer/HttpServer.h index 20c9b7a6f..f40af7d32 100644 --- a/src/HttpServer/HttpServer.h +++ b/src/HttpServer/HttpServer.h @@ -1,11 +1,11 @@ /* This file is part of the CARTA Image Viewer: https://github.com/CARTAvis/carta-backend - Copyright 2018-2022 Academia Sinica Institute of Astronomy and Astrophysics (ASIAA), + Copyright 2018- Academia Sinica Institute of Astronomy and Astrophysics (ASIAA), Associated Universities, Inc. (AUI) and the Inter-University Institute for Data Intensive Astronomy (IDIA) SPDX-License-Identifier: GPL-3.0-or-later */ -#ifndef CARTA_BACKEND_SRC_HTTPSERVER_HTTPSERVER_H_ -#define CARTA_BACKEND_SRC_HTTPSERVER_HTTPSERVER_H_ +#ifndef CARTA_SRC_HTTPSERVER_HTTPSERVER_H_ +#define CARTA_SRC_HTTPSERVER_HTTPSERVER_H_ #include #include @@ -102,4 +102,4 @@ class HttpServer { }; } // namespace carta -#endif // CARTA_BACKEND_SRC_HTTPSERVER_HTTPSERVER_H_ +#endif // CARTA_SRC_HTTPSERVER_HTTPSERVER_H_ diff --git a/src/HttpServer/MimeTypes.h b/src/HttpServer/MimeTypes.h index 794eab6f5..5837c85de 100644 --- a/src/HttpServer/MimeTypes.h +++ b/src/HttpServer/MimeTypes.h @@ -1,11 +1,11 @@ /* This file is part of the CARTA Image Viewer: https://github.com/CARTAvis/carta-backend - Copyright 2018-2022 Academia Sinica Institute of Astronomy and Astrophysics (ASIAA), + Copyright 2018- Academia Sinica Institute of Astronomy and Astrophysics (ASIAA), Associated Universities, Inc. (AUI) and the Inter-University Institute for Data Intensive Astronomy (IDIA) SPDX-License-Identifier: GPL-3.0-or-later */ -#ifndef CARTA_BACKEND_SRC_HTTPSERVER_MIMETYPES_H_ -#define CARTA_BACKEND_SRC_HTTPSERVER_MIMETYPES_H_ +#ifndef CARTA_SRC_HTTPSERVER_MIMETYPES_H_ +#define CARTA_SRC_HTTPSERVER_MIMETYPES_H_ #include #include @@ -16,4 +16,4 @@ const static std::unordered_map MimeTypes = {{".css", {".svg", "image/svg+xml"}, {".woff", "font/woff"}, {".woff2", "font/woff2"}, {".wasm", "application/wasm"}}; } // namespace carta -#endif // CARTA_BACKEND_SRC_HTTPSERVER_MIMETYPES_H_ +#endif // CARTA_SRC_HTTPSERVER_MIMETYPES_H_ diff --git a/src/ImageData/CartaFitsImage.cc b/src/ImageData/CartaFitsImage.cc index 13f29a91f..98e16a8f7 100644 --- a/src/ImageData/CartaFitsImage.cc +++ b/src/ImageData/CartaFitsImage.cc @@ -1,5 +1,5 @@ /* This file is part of the CARTA Image Viewer: https://github.com/CARTAvis/carta-backend - Copyright 2018-2022 Academia Sinica Institute of Astronomy and Astrophysics (ASIAA), + Copyright 2018- Academia Sinica Institute of Astronomy and Astrophysics (ASIAA), Associated Universities, Inc. (AUI) and the Inter-University Institute for Data Intensive Astronomy (IDIA) SPDX-License-Identifier: GPL-3.0-or-later */ @@ -475,7 +475,8 @@ void CartaFitsImage::SetFitsHeaderStrings(int nheaders, const std::string& heade } // For setting up image - _image_header_strings = no_history_strings; + casacore::Vector const no_history_strings_v(no_history_strings); + _image_header_strings = no_history_strings_v; } casacore::Vector CartaFitsImage::FitsHeaderStrings() { diff --git a/src/ImageData/CartaFitsImage.h b/src/ImageData/CartaFitsImage.h index 4a6c01fc4..845c606ff 100644 --- a/src/ImageData/CartaFitsImage.h +++ b/src/ImageData/CartaFitsImage.h @@ -1,5 +1,5 @@ /* This file is part of the CARTA Image Viewer: https://github.com/CARTAvis/carta-backend - Copyright 2018-2022 Academia Sinica Institute of Astronomy and Astrophysics (ASIAA), + Copyright 2018- Academia Sinica Institute of Astronomy and Astrophysics (ASIAA), Associated Universities, Inc. (AUI) and the Inter-University Institute for Data Intensive Astronomy (IDIA) SPDX-License-Identifier: GPL-3.0-or-later */ @@ -7,8 +7,8 @@ //# CartaFitsImage.h : FITS Image class derived from casacore::ImageInterface for images not supported by casacore, //# including compressed and Int64 -#ifndef CARTA_BACKEND_IMAGEDATA_CARTAFITSIMAGE_H_ -#define CARTA_BACKEND_IMAGEDATA_CARTAFITSIMAGE_H_ +#ifndef CARTA_SRC_IMAGEDATA_CARTAFITSIMAGE_H_ +#define CARTA_SRC_IMAGEDATA_CARTAFITSIMAGE_H_ #include #include @@ -125,4 +125,4 @@ class CartaFitsImage : public casacore::ImageInterface { #include "CartaFitsImage.tcc" -#endif // CARTA_BACKEND_IMAGEDATA_CARTAFITSIMAGE_H_ +#endif // CARTA_SRC_IMAGEDATA_CARTAFITSIMAGE_H_ diff --git a/src/ImageData/CartaFitsImage.tcc b/src/ImageData/CartaFitsImage.tcc index e40b97283..72c491b64 100644 --- a/src/ImageData/CartaFitsImage.tcc +++ b/src/ImageData/CartaFitsImage.tcc @@ -1,11 +1,11 @@ /* This file is part of the CARTA Image Viewer: https://github.com/CARTAvis/carta-backend - Copyright 2018-2022 Academia Sinica Institute of Astronomy and Astrophysics (ASIAA), + Copyright 2018- Academia Sinica Institute of Astronomy and Astrophysics (ASIAA), Associated Universities, Inc. (AUI) and the Inter-University Institute for Data Intensive Astronomy (IDIA) SPDX-License-Identifier: GPL-3.0-or-later */ -#ifndef CARTA_BACKEND_IMAGEDATA_CARTAFITSIMAGE_TCC_ -#define CARTA_BACKEND_IMAGEDATA_CARTAFITSIMAGE_TCC_ +#ifndef CARTA_SRC_IMAGEDATA_CARTAFITSIMAGE_TCC_ +#define CARTA_SRC_IMAGEDATA_CARTAFITSIMAGE_TCC_ #include "CartaFitsImage.h" @@ -168,4 +168,4 @@ bool CartaFitsImage::GetNanPixelMask(casacore::ArrayLattice& mask) { } // namespace carta -#endif // CARTA_BACKEND_IMAGEDATA_CARTAFITSIMAGE_TCC_ +#endif // CARTA_SRC_IMAGEDATA_CARTAFITSIMAGE_TCC_ diff --git a/src/ImageData/CartaHdf5Image.cc b/src/ImageData/CartaHdf5Image.cc index 77542ef81..d9ef37db3 100644 --- a/src/ImageData/CartaHdf5Image.cc +++ b/src/ImageData/CartaHdf5Image.cc @@ -1,5 +1,5 @@ /* This file is part of the CARTA Image Viewer: https://github.com/CARTAvis/carta-backend - Copyright 2018-2022 Academia Sinica Institute of Astronomy and Astrophysics (ASIAA), + Copyright 2018- Academia Sinica Institute of Astronomy and Astrophysics (ASIAA), Associated Universities, Inc. (AUI) and the Inter-University Institute for Data Intensive Astronomy (IDIA) SPDX-License-Identifier: GPL-3.0-or-later */ diff --git a/src/ImageData/CartaHdf5Image.h b/src/ImageData/CartaHdf5Image.h index c10405796..419ca1535 100644 --- a/src/ImageData/CartaHdf5Image.h +++ b/src/ImageData/CartaHdf5Image.h @@ -1,13 +1,13 @@ /* This file is part of the CARTA Image Viewer: https://github.com/CARTAvis/carta-backend - Copyright 2018-2022 Academia Sinica Institute of Astronomy and Astrophysics (ASIAA), + Copyright 2018- Academia Sinica Institute of Astronomy and Astrophysics (ASIAA), Associated Universities, Inc. (AUI) and the Inter-University Institute for Data Intensive Astronomy (IDIA) SPDX-License-Identifier: GPL-3.0-or-later */ //# CartaHdf5Image.h : HDF5 Image class derived from casacore::ImageInterface -#ifndef CARTA_BACKEND_IMAGEDATA_CARTAHDF5IMAGE_H_ -#define CARTA_BACKEND_IMAGEDATA_CARTAHDF5IMAGE_H_ +#ifndef CARTA_SRC_IMAGEDATA_CARTAHDF5IMAGE_H_ +#define CARTA_SRC_IMAGEDATA_CARTAHDF5IMAGE_H_ #include #include @@ -85,4 +85,4 @@ class CartaHdf5Image : public casacore::ImageInterface { } // namespace carta -#endif // CARTA_BACKEND_IMAGEDATA_CARTAHDF5IMAGE_H_ +#endif // CARTA_SRC_IMAGEDATA_CARTAHDF5IMAGE_H_ diff --git a/src/ImageData/CartaMiriadImage.cc b/src/ImageData/CartaMiriadImage.cc index be6692d3b..99074fede 100644 --- a/src/ImageData/CartaMiriadImage.cc +++ b/src/ImageData/CartaMiriadImage.cc @@ -1,5 +1,5 @@ /* This file is part of the CARTA Image Viewer: https://github.com/CARTAvis/carta-backend - Copyright 2018-2022 Academia Sinica Institute of Astronomy and Astrophysics (ASIAA), + Copyright 2018- Academia Sinica Institute of Astronomy and Astrophysics (ASIAA), Associated Universities, Inc. (AUI) and the Inter-University Institute for Data Intensive Astronomy (IDIA) SPDX-License-Identifier: GPL-3.0-or-later */ diff --git a/src/ImageData/CartaMiriadImage.h b/src/ImageData/CartaMiriadImage.h index e503eee2b..73ba0c78d 100644 --- a/src/ImageData/CartaMiriadImage.h +++ b/src/ImageData/CartaMiriadImage.h @@ -1,13 +1,13 @@ /* This file is part of the CARTA Image Viewer: https://github.com/CARTAvis/carta-backend - Copyright 2018-2022 Academia Sinica Institute of Astronomy and Astrophysics (ASIAA), + Copyright 2018- Academia Sinica Institute of Astronomy and Astrophysics (ASIAA), Associated Universities, Inc. (AUI) and the Inter-University Institute for Data Intensive Astronomy (IDIA) SPDX-License-Identifier: GPL-3.0-or-later */ //# CartaMiriadImage.h : MIRIAD Image class to support masks -#ifndef CARTA_BACKEND_IMAGEDATA_CARTAMIRIADIMAGE_H_ -#define CARTA_BACKEND_IMAGEDATA_CARTAMIRIADIMAGE_H_ +#ifndef CARTA_SRC_IMAGEDATA_CARTAMIRIADIMAGE_H_ +#define CARTA_SRC_IMAGEDATA_CARTAMIRIADIMAGE_H_ #include #include @@ -62,4 +62,4 @@ class CartaMiriadImage : public casacore::MIRIADImage { } // namespace carta -#endif // CARTA_BACKEND_IMAGEDATA_CARTAMIRIADIMAGE_H_ +#endif // CARTA_SRC_IMAGEDATA_CARTAMIRIADIMAGE_H_ diff --git a/src/ImageData/CasaLoader.h b/src/ImageData/CasaLoader.h index 67e2ce86e..c1f95db73 100644 --- a/src/ImageData/CasaLoader.h +++ b/src/ImageData/CasaLoader.h @@ -1,11 +1,11 @@ /* This file is part of the CARTA Image Viewer: https://github.com/CARTAvis/carta-backend - Copyright 2018-2022 Academia Sinica Institute of Astronomy and Astrophysics (ASIAA), + Copyright 2018- Academia Sinica Institute of Astronomy and Astrophysics (ASIAA), Associated Universities, Inc. (AUI) and the Inter-University Institute for Data Intensive Astronomy (IDIA) SPDX-License-Identifier: GPL-3.0-or-later */ -#ifndef CARTA_BACKEND_IMAGEDATA_CASALOADER_H_ -#define CARTA_BACKEND_IMAGEDATA_CASALOADER_H_ +#ifndef CARTA_SRC_IMAGEDATA_CASALOADER_H_ +#define CARTA_SRC_IMAGEDATA_CASALOADER_H_ #include #include @@ -129,4 +129,4 @@ casacore::TempImage* CasaLoader::ConvertImageToFloat(casacore::LatticeBas } // namespace carta -#endif // CARTA_BACKEND_IMAGEDATA_CASALOADER_H_ +#endif // CARTA_SRC_IMAGEDATA_CASALOADER_H_ diff --git a/src/ImageData/CompListLoader.h b/src/ImageData/CompListLoader.h index 9dd4dae5c..1691837bb 100644 --- a/src/ImageData/CompListLoader.h +++ b/src/ImageData/CompListLoader.h @@ -1,11 +1,11 @@ /* This file is part of the CARTA Image Viewer: https://github.com/CARTAvis/carta-backend - Copyright 2018-2022 Academia Sinica Institute of Astronomy and Astrophysics (ASIAA), + Copyright 2018- Academia Sinica Institute of Astronomy and Astrophysics (ASIAA), Associated Universities, Inc. (AUI) and the Inter-University Institute for Data Intensive Astronomy (IDIA) SPDX-License-Identifier: GPL-3.0-or-later */ -#ifndef CARTA_BACKEND_IMAGEDATA_COMPLISTLOADER_H_ -#define CARTA_BACKEND_IMAGEDATA_COMPLISTLOADER_H_ +#ifndef CARTA_SRC_IMAGEDATA_COMPLISTLOADER_H_ +#define CARTA_SRC_IMAGEDATA_COMPLISTLOADER_H_ #include #include @@ -43,4 +43,4 @@ void CompListLoader::AllocateImage(const std::string& /*hdu*/) { } // namespace carta -#endif // CARTA_BACKEND_IMAGEDATA_COMPLISTLOADER_H_ +#endif // CARTA_SRC_IMAGEDATA_COMPLISTLOADER_H_ diff --git a/src/ImageData/CompressedFits.cc b/src/ImageData/CompressedFits.cc index 63cd40d74..7e9c9dda5 100644 --- a/src/ImageData/CompressedFits.cc +++ b/src/ImageData/CompressedFits.cc @@ -1,5 +1,5 @@ /* This file is part of the CARTA Image Viewer: https://github.com/CARTAvis/carta-backend - Copyright 2018-2022 Academia Sinica Institute of Astronomy and Astrophysics (ASIAA), + Copyright 2018- Academia Sinica Institute of Astronomy and Astrophysics (ASIAA), Associated Universities, Inc. (AUI) and the Inter-University Institute for Data Intensive Astronomy (IDIA) SPDX-License-Identifier: GPL-3.0-or-later */ @@ -414,6 +414,10 @@ void CompressedFits::AddHeaderEntry( } catch (std::invalid_argument) { // Set string value only entry->set_entry_type(CARTA::EntryType::STRING); + } catch (std::out_of_range) { + long lvalue = std::stol(value); + entry->set_numeric_value(lvalue); + entry->set_entry_type(CARTA::EntryType::INT); } } } diff --git a/src/ImageData/CompressedFits.h b/src/ImageData/CompressedFits.h index 7c3103519..4ecd99d4e 100644 --- a/src/ImageData/CompressedFits.h +++ b/src/ImageData/CompressedFits.h @@ -1,11 +1,11 @@ /* This file is part of the CARTA Image Viewer: https://github.com/CARTAvis/carta-backend - Copyright 2018-2022 Academia Sinica Institute of Astronomy and Astrophysics (ASIAA), + Copyright 2018- Academia Sinica Institute of Astronomy and Astrophysics (ASIAA), Associated Universities, Inc. (AUI) and the Inter-University Institute for Data Intensive Astronomy (IDIA) SPDX-License-Identifier: GPL-3.0-or-later */ -#ifndef CARTA_BACKEND_IMAGEDATA_COMPRESSEDFITS_H_ -#define CARTA_BACKEND_IMAGEDATA_COMPRESSEDFITS_H_ +#ifndef CARTA_SRC_IMAGEDATA_COMPRESSEDFITS_H_ +#define CARTA_SRC_IMAGEDATA_COMPRESSEDFITS_H_ #include #include @@ -159,4 +159,4 @@ class CompressedFits { } // namespace carta -#endif // CARTA_BACKEND_IMAGEDATA_COMPRESSEDFITS_H_ +#endif // CARTA_SRC_IMAGEDATA_COMPRESSEDFITS_H_ diff --git a/src/ImageData/ConcatLoader.h b/src/ImageData/ConcatLoader.h index 5fe6d10b0..88185f7b9 100644 --- a/src/ImageData/ConcatLoader.h +++ b/src/ImageData/ConcatLoader.h @@ -1,11 +1,11 @@ /* This file is part of the CARTA Image Viewer: https://github.com/CARTAvis/carta-backend - Copyright 2018-2022 Academia Sinica Institute of Astronomy and Astrophysics (ASIAA), + Copyright 2018- Academia Sinica Institute of Astronomy and Astrophysics (ASIAA), Associated Universities, Inc. (AUI) and the Inter-University Institute for Data Intensive Astronomy (IDIA) SPDX-License-Identifier: GPL-3.0-or-later */ -#ifndef CARTA_BACKEND_IMAGEDATA_CONCATLOADER_H_ -#define CARTA_BACKEND_IMAGEDATA_CONCATLOADER_H_ +#ifndef CARTA_SRC_IMAGEDATA_CONCATLOADER_H_ +#define CARTA_SRC_IMAGEDATA_CONCATLOADER_H_ #include #include @@ -44,4 +44,4 @@ void ConcatLoader::AllocateImage(const std::string& /*hdu*/) { } // namespace carta -#endif // CARTA_BACKEND_IMAGEDATA_CONCATLOADER_H_ +#endif // CARTA_SRC_IMAGEDATA_CONCATLOADER_H_ diff --git a/src/ImageData/ExprLoader.h b/src/ImageData/ExprLoader.h index 7a7cd878b..a908ac09a 100644 --- a/src/ImageData/ExprLoader.h +++ b/src/ImageData/ExprLoader.h @@ -1,11 +1,11 @@ /* This file is part of the CARTA Image Viewer: https://github.com/CARTAvis/carta-backend - Copyright 2018-2022 Academia Sinica Institute of Astronomy and Astrophysics (ASIAA), + Copyright 2018- Academia Sinica Institute of Astronomy and Astrophysics (ASIAA), Associated Universities, Inc. (AUI) and the Inter-University Institute for Data Intensive Astronomy (IDIA) SPDX-License-Identifier: GPL-3.0-or-later */ -#ifndef CARTA_BACKEND_IMAGEDATA_EXPRLOADER_H_ -#define CARTA_BACKEND_IMAGEDATA_EXPRLOADER_H_ +#ifndef CARTA_SRC_IMAGEDATA_EXPRLOADER_H_ +#define CARTA_SRC_IMAGEDATA_EXPRLOADER_H_ #include #include @@ -93,4 +93,4 @@ bool ExprLoader::SaveFile(const CARTA::FileType type, const std::string& output_ } // namespace carta -#endif // CARTA_BACKEND_IMAGEDATA_EXPRLOADER_H_ +#endif // CARTA_SRC_IMAGEDATA_EXPRLOADER_H_ diff --git a/src/ImageData/FileInfo.cc b/src/ImageData/FileInfo.cc index ff49cd3e0..9ad3c3884 100644 --- a/src/ImageData/FileInfo.cc +++ b/src/ImageData/FileInfo.cc @@ -1,5 +1,5 @@ /* This file is part of the CARTA Image Viewer: https://github.com/CARTAvis/carta-backend - Copyright 2018-2022 Academia Sinica Institute of Astronomy and Astrophysics (ASIAA), + Copyright 2018- Academia Sinica Institute of Astronomy and Astrophysics (ASIAA), Associated Universities, Inc. (AUI) and the Inter-University Institute for Data Intensive Astronomy (IDIA) SPDX-License-Identifier: GPL-3.0-or-later */ diff --git a/src/ImageData/FileInfo.h b/src/ImageData/FileInfo.h index c058a5fac..e57c4a7b6 100644 --- a/src/ImageData/FileInfo.h +++ b/src/ImageData/FileInfo.h @@ -1,11 +1,11 @@ /* This file is part of the CARTA Image Viewer: https://github.com/CARTAvis/carta-backend - Copyright 2018-2022 Academia Sinica Institute of Astronomy and Astrophysics (ASIAA), + Copyright 2018- Academia Sinica Institute of Astronomy and Astrophysics (ASIAA), Associated Universities, Inc. (AUI) and the Inter-University Institute for Data Intensive Astronomy (IDIA) SPDX-License-Identifier: GPL-3.0-or-later */ -#ifndef CARTA_BACKEND_FILEINFO_H -#define CARTA_BACKEND_FILEINFO_H +#ifndef CARTA_SRC_IMAGEDATA_FILEINFO_H_ +#define CARTA_SRC_IMAGEDATA_FILEINFO_H_ #include #include @@ -138,4 +138,4 @@ static bool ConvertFitsStokesValue(const int& in_stokes_value, int& out_stokes_v } // namespace FileInfo } // namespace carta -#endif // CARTA_BACKEND_FILEINFO_H +#endif // CARTA_SRC_IMAGEDATA_FILEINFO_H_ diff --git a/src/ImageData/FileLoader.cc b/src/ImageData/FileLoader.cc index 1fe67cf50..3eacec890 100644 --- a/src/ImageData/FileLoader.cc +++ b/src/ImageData/FileLoader.cc @@ -1,5 +1,5 @@ /* This file is part of the CARTA Image Viewer: https://github.com/CARTAvis/carta-backend - Copyright 2018-2022 Academia Sinica Institute of Astronomy and Astrophysics (ASIAA), + Copyright 2018- Academia Sinica Institute of Astronomy and Astrophysics (ASIAA), Associated Universities, Inc. (AUI) and the Inter-University Institute for Data Intensive Astronomy (IDIA) SPDX-License-Identifier: GPL-3.0-or-later */ diff --git a/src/ImageData/FileLoader.h b/src/ImageData/FileLoader.h index 17e037bea..5765a5314 100644 --- a/src/ImageData/FileLoader.h +++ b/src/ImageData/FileLoader.h @@ -1,11 +1,11 @@ /* This file is part of the CARTA Image Viewer: https://github.com/CARTAvis/carta-backend - Copyright 2018-2022 Academia Sinica Institute of Astronomy and Astrophysics (ASIAA), + Copyright 2018- Academia Sinica Institute of Astronomy and Astrophysics (ASIAA), Associated Universities, Inc. (AUI) and the Inter-University Institute for Data Intensive Astronomy (IDIA) SPDX-License-Identifier: GPL-3.0-or-later */ -#ifndef CARTA_BACKEND_IMAGEDATA_FILELOADER_H_ -#define CARTA_BACKEND_IMAGEDATA_FILELOADER_H_ +#ifndef CARTA_SRC_IMAGEDATA_FILELOADER_H_ +#define CARTA_SRC_IMAGEDATA_FILELOADER_H_ #include #include @@ -207,4 +207,4 @@ class FileLoader { } // namespace carta -#endif // CARTA_BACKEND_IMAGEDATA_FILELOADER_H_ +#endif // CARTA_SRC_IMAGEDATA_FILELOADER_H_ diff --git a/src/ImageData/FitsLoader.cc b/src/ImageData/FitsLoader.cc index d8dedb44e..1848bd759 100644 --- a/src/ImageData/FitsLoader.cc +++ b/src/ImageData/FitsLoader.cc @@ -1,5 +1,5 @@ /* This file is part of the CARTA Image Viewer: https://github.com/CARTAvis/carta-backend - Copyright 2018-2022 Academia Sinica Institute of Astronomy and Astrophysics (ASIAA), + Copyright 2018- Academia Sinica Institute of Astronomy and Astrophysics (ASIAA), Associated Universities, Inc. (AUI) and the Inter-University Institute for Data Intensive Astronomy (IDIA) SPDX-License-Identifier: GPL-3.0-or-later */ @@ -62,14 +62,14 @@ void FitsLoader::AllocateImage(const std::string& hdu) { } } - // Default is casacore::FITSImage; if fails, try CartaFitsImage - bool use_casacore_fits(true); - auto num_headers = GetNumHeaders(_filename, hdu_num); - + std::string error; + auto num_headers = GetNumImageHeaders(_filename, hdu_num, error); if (num_headers == 0) { - throw(casacore::AipsError("Error reading FITS file.")); + throw(casacore::AipsError(error)); } + // Default is casacore::FITSImage; if fails, try CartaFitsImage + bool use_casacore_fits(true); if (num_headers > 2000) { // casacore::FITSImage parses HISTORY use_casacore_fits = false; @@ -135,8 +135,8 @@ void FitsLoader::AllocateImage(const std::string& hdu) { } } -int FitsLoader::GetNumHeaders(const std::string& filename, int hdu) { - // Return number of FITS headers, 0 if error. +int FitsLoader::GetNumImageHeaders(const std::string& filename, int hdu, std::string& error) { + // Return number of FITS headers if image hdu, 0 if error. int num_headers(0); // Open file read-only @@ -144,13 +144,29 @@ int FitsLoader::GetNumHeaders(const std::string& filename, int hdu) { int status(0); fits_open_file(&fptr, filename.c_str(), 0, &status); if (status) { + error = "Error reading FITS file."; return num_headers; } - // Advance to hdu (FITS hdu is 1-based) - int* hdutype(nullptr); - fits_movabs_hdu(fptr, hdu + 1, hdutype, &status); + // Advance to hdu (FITS hdu is 1-based) and check if image + int hdutype(-1); + fits_movabs_hdu(fptr, hdu + 1, &hdutype, &status); if (status) { + error = "Cannot advance to requested HDU."; + return num_headers; + } + if (hdutype != IMAGE_HDU) { + error = "HDU is not an image."; + return num_headers; + } + + // Check if image exists in HDU + std::string key("NAXIS"); + char* comment(nullptr); // unused + int naxis(0); + fits_read_key(fptr, TINT, key.c_str(), &naxis, comment, &status); + if (naxis == 0) { + error = "HDU image is empty."; return num_headers; } diff --git a/src/ImageData/FitsLoader.h b/src/ImageData/FitsLoader.h index 13ae2cc6f..a2fce9796 100644 --- a/src/ImageData/FitsLoader.h +++ b/src/ImageData/FitsLoader.h @@ -1,11 +1,11 @@ /* This file is part of the CARTA Image Viewer: https://github.com/CARTAvis/carta-backend - Copyright 2018-2022 Academia Sinica Institute of Astronomy and Astrophysics (ASIAA), + Copyright 2018- Academia Sinica Institute of Astronomy and Astrophysics (ASIAA), Associated Universities, Inc. (AUI) and the Inter-University Institute for Data Intensive Astronomy (IDIA) SPDX-License-Identifier: GPL-3.0-or-later */ -#ifndef CARTA_BACKEND_IMAGEDATA_FITSLOADER_H_ -#define CARTA_BACKEND_IMAGEDATA_FITSLOADER_H_ +#ifndef CARTA_SRC_IMAGEDATA_FITSLOADER_H_ +#define CARTA_SRC_IMAGEDATA_FITSLOADER_H_ #include "FileLoader.h" @@ -21,7 +21,7 @@ class FitsLoader : public FileLoader { casacore::uInt _hdu_num; void AllocateImage(const std::string& hdu) override; - int GetNumHeaders(const std::string& filename, int hdu); + int GetNumImageHeaders(const std::string& filename, int hdu, std::string& error); // Image beam headers/table bool Is64BitBeamsTable(const std::string& filename); @@ -32,4 +32,4 @@ class FitsLoader : public FileLoader { } // namespace carta -#endif // CARTA_BACKEND_IMAGEDATA_FITSLOADER_H_ +#endif // CARTA_SRC_IMAGEDATA_FITSLOADER_H_ diff --git a/src/ImageData/Hdf5Attributes.cc b/src/ImageData/Hdf5Attributes.cc index 4b13340b5..5d5d63c0a 100644 --- a/src/ImageData/Hdf5Attributes.cc +++ b/src/ImageData/Hdf5Attributes.cc @@ -1,5 +1,5 @@ /* This file is part of the CARTA Image Viewer: https://github.com/CARTAvis/carta-backend - Copyright 2018-2022 Academia Sinica Institute of Astronomy and Astrophysics (ASIAA), + Copyright 2018- Academia Sinica Institute of Astronomy and Astrophysics (ASIAA), Associated Universities, Inc. (AUI) and the Inter-University Institute for Data Intensive Astronomy (IDIA) SPDX-License-Identifier: GPL-3.0-or-later */ diff --git a/src/ImageData/Hdf5Attributes.h b/src/ImageData/Hdf5Attributes.h index 341e7efc8..a203f192e 100644 --- a/src/ImageData/Hdf5Attributes.h +++ b/src/ImageData/Hdf5Attributes.h @@ -1,12 +1,12 @@ /* This file is part of the CARTA Image Viewer: https://github.com/CARTAvis/carta-backend - Copyright 2018-2022 Academia Sinica Institute of Astronomy and Astrophysics (ASIAA), + Copyright 2018- Academia Sinica Institute of Astronomy and Astrophysics (ASIAA), Associated Universities, Inc. (AUI) and the Inter-University Institute for Data Intensive Astronomy (IDIA) SPDX-License-Identifier: GPL-3.0-or-later */ //# Hdf5Attributes.h: get HDF5 header attributes in casacore::Record -#ifndef CARTA_BACKEND_IMAGEDATA_HDF5ATTRIBUTES_H_ -#define CARTA_BACKEND_IMAGEDATA_HDF5ATTRIBUTES_H_ +#ifndef CARTA_SRC_IMAGEDATA_HDF5ATTRIBUTES_H_ +#define CARTA_SRC_IMAGEDATA_HDF5ATTRIBUTES_H_ #pragma once @@ -27,4 +27,4 @@ class Hdf5Attributes { } // namespace carta -#endif // CARTA_BACKEND_IMAGEDATA_HDF5ATTRIBUTES_H_ +#endif // CARTA_SRC_IMAGEDATA_HDF5ATTRIBUTES_H_ diff --git a/src/ImageData/Hdf5Loader.cc b/src/ImageData/Hdf5Loader.cc index e5b853a8e..ca9f0811a 100644 --- a/src/ImageData/Hdf5Loader.cc +++ b/src/ImageData/Hdf5Loader.cc @@ -1,5 +1,5 @@ /* This file is part of the CARTA Image Viewer: https://github.com/CARTAvis/carta-backend - Copyright 2018-2022 Academia Sinica Institute of Astronomy and Astrophysics (ASIAA), + Copyright 2018- Academia Sinica Institute of Astronomy and Astrophysics (ASIAA), Associated Universities, Inc. (AUI) and the Inter-University Institute for Data Intensive Astronomy (IDIA) SPDX-License-Identifier: GPL-3.0-or-later */ diff --git a/src/ImageData/Hdf5Loader.h b/src/ImageData/Hdf5Loader.h index 08360c8a2..14f9acebf 100644 --- a/src/ImageData/Hdf5Loader.h +++ b/src/ImageData/Hdf5Loader.h @@ -1,11 +1,11 @@ /* This file is part of the CARTA Image Viewer: https://github.com/CARTAvis/carta-backend - Copyright 2018-2022 Academia Sinica Institute of Astronomy and Astrophysics (ASIAA), + Copyright 2018- Academia Sinica Institute of Astronomy and Astrophysics (ASIAA), Associated Universities, Inc. (AUI) and the Inter-University Institute for Data Intensive Astronomy (IDIA) SPDX-License-Identifier: GPL-3.0-or-later */ -#ifndef CARTA_BACKEND_IMAGEDATA_HDF5LOADER_H_ -#define CARTA_BACKEND_IMAGEDATA_HDF5LOADER_H_ +#ifndef CARTA_SRC_IMAGEDATA_HDF5LOADER_H_ +#define CARTA_SRC_IMAGEDATA_HDF5LOADER_H_ #include #include @@ -72,4 +72,4 @@ class Hdf5Loader : public FileLoader { #include "Hdf5Loader.tcc" -#endif // CARTA_BACKEND_IMAGEDATA_HDF5LOADER_H_ +#endif // CARTA_SRC_IMAGEDATA_HDF5LOADER_H_ diff --git a/src/ImageData/Hdf5Loader.tcc b/src/ImageData/Hdf5Loader.tcc index 1b9db3664..c2cb19e4b 100644 --- a/src/ImageData/Hdf5Loader.tcc +++ b/src/ImageData/Hdf5Loader.tcc @@ -1,11 +1,11 @@ /* This file is part of the CARTA Image Viewer: https://github.com/CARTAvis/carta-backend - Copyright 2018-2022 Academia Sinica Institute of Astronomy and Astrophysics (ASIAA), + Copyright 2018- Academia Sinica Institute of Astronomy and Astrophysics (ASIAA), Associated Universities, Inc. (AUI) and the Inter-University Institute for Data Intensive Astronomy (IDIA) SPDX-License-Identifier: GPL-3.0-or-later */ -#ifndef CARTA_BACKEND_IMAGEDATA_HDF5LOADER_TCC_ -#define CARTA_BACKEND_IMAGEDATA_HDF5LOADER_TCC_ +#ifndef CARTA_SRC_IMAGEDATA_HDF5LOADER_TCC_ +#define CARTA_SRC_IMAGEDATA_HDF5LOADER_TCC_ #include "Hdf5Loader.h" @@ -66,4 +66,4 @@ std::unique_ptr Hdf5Loader::GetStatsDataTyped(FileInfo::Dat } // namespace carta -#endif // CARTA_BACKEND_IMAGEDATA_HDF5LOADER_TCC_ +#endif // CARTA_SRC_IMAGEDATA_HDF5LOADER_TCC_ diff --git a/src/ImageData/ImagePtrLoader.h b/src/ImageData/ImagePtrLoader.h index 0e8351cfb..85fa9424c 100644 --- a/src/ImageData/ImagePtrLoader.h +++ b/src/ImageData/ImagePtrLoader.h @@ -1,11 +1,11 @@ /* This file is part of the CARTA Image Viewer: https://github.com/CARTAvis/carta-backend - Copyright 2018-2022 Academia Sinica Institute of Astronomy and Astrophysics (ASIAA), + Copyright 2018- Academia Sinica Institute of Astronomy and Astrophysics (ASIAA), Associated Universities, Inc. (AUI) and the Inter-University Institute for Data Intensive Astronomy (IDIA) SPDX-License-Identifier: GPL-3.0-or-later */ -#ifndef CARTA_BACKEND_IMAGEDATA_IMAGEPTRLOADER_H_ -#define CARTA_BACKEND_IMAGEDATA_IMAGEPTRLOADER_H_ +#ifndef CARTA_SRC_IMAGEDATA_IMAGEPTRLOADER_H_ +#define CARTA_SRC_IMAGEDATA_IMAGEPTRLOADER_H_ #include "FileLoader.h" @@ -34,4 +34,4 @@ void ImagePtrLoader::AllocateImage(const std::string& /*hdu*/) {} } // namespace carta -#endif // CARTA_BACKEND_IMAGEDATA_IMAGEPTRLOADER_H_ +#endif // CARTA_SRC_IMAGEDATA_IMAGEPTRLOADER_H_ diff --git a/src/ImageData/MiriadLoader.h b/src/ImageData/MiriadLoader.h index f0dd8a704..810ed23f5 100644 --- a/src/ImageData/MiriadLoader.h +++ b/src/ImageData/MiriadLoader.h @@ -1,11 +1,11 @@ /* This file is part of the CARTA Image Viewer: https://github.com/CARTAvis/carta-backend - Copyright 2018-2022 Academia Sinica Institute of Astronomy and Astrophysics (ASIAA), + Copyright 2018- Academia Sinica Institute of Astronomy and Astrophysics (ASIAA), Associated Universities, Inc. (AUI) and the Inter-University Institute for Data Intensive Astronomy (IDIA) SPDX-License-Identifier: GPL-3.0-or-later */ -#ifndef CARTA_BACKEND_IMAGEDATA_MIRIADLOADER_H_ -#define CARTA_BACKEND_IMAGEDATA_MIRIADLOADER_H_ +#ifndef CARTA_SRC_IMAGEDATA_MIRIADLOADER_H_ +#define CARTA_SRC_IMAGEDATA_MIRIADLOADER_H_ #include @@ -71,4 +71,4 @@ void MiriadLoader::AllocateImage(const std::string& /*hdu*/) { } // namespace carta -#endif // CARTA_BACKEND_IMAGEDATA_MIRIADLOADER_H_ +#endif // CARTA_SRC_IMAGEDATA_MIRIADLOADER_H_ diff --git a/src/ImageData/PolarizationCalculator.cc b/src/ImageData/PolarizationCalculator.cc index baad86c23..71e901504 100644 --- a/src/ImageData/PolarizationCalculator.cc +++ b/src/ImageData/PolarizationCalculator.cc @@ -1,5 +1,5 @@ /* This file is part of the CARTA Image Viewer: https://github.com/CARTAvis/carta-backend - Copyright 2018-2022 Academia Sinica Institute of Astronomy and Astrophysics (ASIAA), + Copyright 2018- Academia Sinica Institute of Astronomy and Astrophysics (ASIAA), Associated Universities, Inc. (AUI) and the Inter-University Institute for Data Intensive Astronomy (IDIA) SPDX-License-Identifier: GPL-3.0-or-later */ diff --git a/src/ImageData/PolarizationCalculator.h b/src/ImageData/PolarizationCalculator.h index 239357e7e..c47fce60d 100644 --- a/src/ImageData/PolarizationCalculator.h +++ b/src/ImageData/PolarizationCalculator.h @@ -1,11 +1,11 @@ /* This file is part of the CARTA Image Viewer: https://github.com/CARTAvis/carta-backend - Copyright 2018-2022 Academia Sinica Institute of Astronomy and Astrophysics (ASIAA), + Copyright 2018- Academia Sinica Institute of Astronomy and Astrophysics (ASIAA), Associated Universities, Inc. (AUI) and the Inter-University Institute for Data Intensive Astronomy (IDIA) SPDX-License-Identifier: GPL-3.0-or-later */ -#ifndef CARTA_BACKEND__IMAGEDATA_POLARIZATIONCALCULATOR_H_ -#define CARTA_BACKEND__IMAGEDATA_POLARIZATIONCALCULATOR_H_ +#ifndef CARTA_SRC_IMAGEDATA_POLARIZATIONCALCULATOR_H_ +#define CARTA_SRC_IMAGEDATA_POLARIZATIONCALCULATOR_H_ #include #include @@ -50,4 +50,4 @@ class PolarizationCalculator { } // namespace carta -#endif // CARTA_BACKEND__IMAGEDATA_POLARIZATIONCALCULATOR_H_ +#endif // CARTA_SRC_IMAGEDATA_POLARIZATIONCALCULATOR_H_ diff --git a/src/ImageData/StokesFilesConnector.cc b/src/ImageData/StokesFilesConnector.cc index 5a0c3b280..845275999 100644 --- a/src/ImageData/StokesFilesConnector.cc +++ b/src/ImageData/StokesFilesConnector.cc @@ -1,5 +1,5 @@ /* This file is part of the CARTA Image Viewer: https://github.com/CARTAvis/carta-backend - Copyright 2018-2022 Academia Sinica Institute of Astronomy and Astrophysics (ASIAA), + Copyright 2018- Academia Sinica Institute of Astronomy and Astrophysics (ASIAA), Associated Universities, Inc. (AUI) and the Inter-University Institute for Data Intensive Astronomy (IDIA) SPDX-License-Identifier: GPL-3.0-or-later */ @@ -201,28 +201,28 @@ bool StokesFilesConnector::OpenStokesFiles(const CARTA::ConcatStokesFiles& messa for (int i = 0; i < message.stokes_files_size(); ++i) { auto stokes_file = message.stokes_files(i); auto stokes_type = message.stokes_files(i).polarization_type(); - casacore::String hdu(stokes_file.hdu()); - casacore::String full_name(GetResolvedFilename(_top_level_folder, stokes_file.directory(), stokes_file.file())); if (_loaders.count(stokes_type)) { err = "Duplicate Stokes type found!"; return false; } + casacore::String full_name(GetResolvedFilename(_top_level_folder, stokes_file.directory(), stokes_file.file(), err)); + // open an image file - if (!full_name.empty()) { - try { - if (hdu.empty()) { // use first when required - hdu = "0"; - } - _loaders[stokes_type].reset(FileLoader::GetLoader(full_name)); - _loaders[stokes_type]->OpenFile(hdu); - } catch (casacore::AipsError& ex) { - err = fmt::format("Failed to open the file: {}", ex.getMesg()); - return false; + if (full_name.empty()) { + return false; + } + + try { + casacore::String hdu(stokes_file.hdu()); + if (hdu.empty()) { // use first when required + hdu = "0"; } - } else { - err = "File name is empty or does not exist!"; + _loaders[stokes_type].reset(FileLoader::GetLoader(full_name)); + _loaders[stokes_type]->OpenFile(hdu); + } catch (casacore::AipsError& ex) { + err = fmt::format("Failed to open the file: {}", ex.getMesg()); return false; } diff --git a/src/ImageData/StokesFilesConnector.h b/src/ImageData/StokesFilesConnector.h index ebe695dd1..197960662 100644 --- a/src/ImageData/StokesFilesConnector.h +++ b/src/ImageData/StokesFilesConnector.h @@ -1,11 +1,11 @@ /* This file is part of the CARTA Image Viewer: https://github.com/CARTAvis/carta-backend - Copyright 2018-2022 Academia Sinica Institute of Astronomy and Astrophysics (ASIAA), + Copyright 2018- Academia Sinica Institute of Astronomy and Astrophysics (ASIAA), Associated Universities, Inc. (AUI) and the Inter-University Institute for Data Intensive Astronomy (IDIA) SPDX-License-Identifier: GPL-3.0-or-later */ -#ifndef CARTA_BACKEND__IMAGEDATA_STOKESFILESCONNECTOR_H_ -#define CARTA_BACKEND__IMAGEDATA_STOKESFILESCONNECTOR_H_ +#ifndef CARTA_SRC_IMAGEDATA_STOKESFILESCONNECTOR_H_ +#define CARTA_SRC_IMAGEDATA_STOKESFILESCONNECTOR_H_ #include #include @@ -36,4 +36,4 @@ class StokesFilesConnector { } // namespace carta -#endif // CARTA_BACKEND__IMAGEDATA_STOKESFILESCONNECTOR_H_ +#endif // CARTA_SRC_IMAGEDATA_STOKESFILESCONNECTOR_H_ diff --git a/src/ImageFitter/ImageFitter.cc b/src/ImageFitter/ImageFitter.cc index 6c8d46808..84e709871 100644 --- a/src/ImageFitter/ImageFitter.cc +++ b/src/ImageFitter/ImageFitter.cc @@ -1,5 +1,5 @@ /* This file is part of the CARTA Image Viewer: https://github.com/CARTAvis/carta-backend - Copyright 2018-2022 Academia Sinica Institute of Astronomy and Astrophysics (ASIAA), + Copyright 2018- Academia Sinica Institute of Astronomy and Astrophysics (ASIAA), Associated Universities, Inc. (AUI) and the Inter-University Institute for Data Intensive Astronomy (IDIA) SPDX-License-Identifier: GPL-3.0-or-later */ @@ -47,7 +47,7 @@ bool ImageFitter::FitImage(size_t width, size_t height, float* image, double bea _create_residual_data = create_residual_image; _progress_callback = progress_callback; - CalculateNanNum(); + CalculateNanNumAndStd(); SetInitialValues(initial_values, background_offset, fixed_params); // avoid SolveSystem crashes with insufficient data points @@ -113,26 +113,13 @@ bool ImageFitter::FitImage(size_t width, size_t height, float* image, double bea return success; } -bool ImageFitter::GetGeneratedImages(casa::SPIIF image, const casacore::ImageRegion& image_region, int file_id, const std::string& filename, +bool ImageFitter::GetGeneratedImages(casa::SPIIF image, const casacore::ImageRegion& image_region, const std::string& filename, GeneratedImage& model_image, GeneratedImage& residual_image, CARTA::FittingResponse& fitting_response) { - if (file_id < 0) { - fitting_response.set_message("generating images from generated PV and model/residual images is not supported"); - return false; - } - - // Todo: find another better way to assign the temp file Id - bool is_moment = file_id > 999; - int model_id = (file_id + 1) * (is_moment ? FITTING_WITH_MOMENT_ID_MULTIPLIER : FITTING_ID_MULTIPLIER) + 1; - int residual_id = model_id + 1; - if (_create_model_data) { - model_image = GeneratedImage(model_id, is_moment ? GetGeneratedMomentFilename(filename, "model") : GetFilename(filename, "model"), - GetImageData(image, image_region, _model_data)); + model_image = GeneratedImage(GetFilename(filename, "model"), GetImageData(image, image_region, _model_data)); } if (_create_residual_data) { - residual_image = - GeneratedImage(residual_id, is_moment ? GetGeneratedMomentFilename(filename, "residual") : GetFilename(filename, "residual"), - GetImageData(image, image_region, _residual_data)); + residual_image = GeneratedImage(GetFilename(filename, "residual"), GetImageData(image, image_region, _residual_data)); } return true; } @@ -141,13 +128,21 @@ void ImageFitter::StopFitting() { _fit_data.stop_fitting = true; } -void ImageFitter::CalculateNanNum() { +void ImageFitter::CalculateNanNumAndStd() { + std::vector data_notnan; + data_notnan.reserve(_fit_data.n); + _fit_data.n_notnan = _fit_data.n; for (size_t i = 0; i < _fit_data.n; i++) { if (isnan(_fit_data.data[i])) { _fit_data.n_notnan--; + } else { + data_notnan.push_back(_fit_data.data[i]); } } + + _image_std = GetMedianAbsDeviation(_fit_data.n_notnan, data_notnan.data()); + spdlog::debug("MAD = {}", _image_std); } void ImageFitter::SetInitialValues( @@ -221,9 +216,6 @@ int ImageFitter::SolveSystem(CARTA::FittingSolverType solver) { gsl_vector* y = gsl_multifit_nlinear_position(work); gsl_matrix* covar = gsl_matrix_alloc(p, p); - gsl_multifit_nlinear_init(_fit_values, &_fdf, work); // work-around for filling in f - _image_std = GetMedianAbsDeviation(_fdf.n, f->data); - gsl_vector* weights = gsl_vector_alloc(n); gsl_vector_set_all(weights, 1 / _image_std / _image_std); gsl_multifit_nlinear_winit(_fit_values, weights, &_fdf, work); @@ -417,17 +409,20 @@ casa::SPIIF ImageFitter::GetImageData(casa::SPIIF image, const casacore::ImageRe } std::string ImageFitter::GetFilename(const std::string& filename, std::string suffix) { - fs::path filepath(filename); + std::string moment_suffix; + fs::path filepath; + if (filename.rfind(".moment.") != std::string::npos) { + std::string input_filename = filename.substr(0, filename.rfind(".moment.")); + filepath = fs::path(input_filename); + moment_suffix = filename.substr(filename.rfind(".moment.")); + } else { + filepath = fs::path(filename); + } + fs::path output_filename = filepath.stem(); output_filename += "_" + suffix; output_filename += filepath.extension(); - return output_filename.string(); -} - -std::string ImageFitter::GetGeneratedMomentFilename(const std::string& filename, std::string suffix) { - std::string output_filename = filename.substr(0, filename.rfind(".moment.")); - std::string moment_suffix = filename.substr(filename.rfind(".moment.")); - return GetFilename(output_filename, suffix) + moment_suffix; + return output_filename.string() + moment_suffix; } int ImageFitter::FuncF(const gsl_vector* fit_values, void* fit_data, gsl_vector* f) { diff --git a/src/ImageFitter/ImageFitter.h b/src/ImageFitter/ImageFitter.h index 75ec90913..6a5684426 100644 --- a/src/ImageFitter/ImageFitter.h +++ b/src/ImageFitter/ImageFitter.h @@ -1,11 +1,11 @@ /* This file is part of the CARTA Image Viewer: https://github.com/CARTAvis/carta-backend - Copyright 2018-2022 Academia Sinica Institute of Astronomy and Astrophysics (ASIAA), + Copyright 2018- Academia Sinica Institute of Astronomy and Astrophysics (ASIAA), Associated Universities, Inc. (AUI) and the Inter-University Institute for Data Intensive Astronomy (IDIA) SPDX-License-Identifier: GPL-3.0-or-later */ -#ifndef CARTA_BACKEND_IMAGEFITTER_IMAGEFITTER_H_ -#define CARTA_BACKEND_IMAGEFITTER_IMAGEFITTER_H_ +#ifndef CARTA_SRC_IMAGEFITTER_IMAGEFITTER_H_ +#define CARTA_SRC_IMAGEFITTER_IMAGEFITTER_H_ #include #include @@ -97,7 +97,7 @@ class ImageFitter { * @param fitting_response The fitting response message * @return Whether the images are successfully generated */ - bool GetGeneratedImages(std::shared_ptr> image, const casacore::ImageRegion& image_region, int file_id, + bool GetGeneratedImages(std::shared_ptr> image, const casacore::ImageRegion& image_region, const std::string& filename, GeneratedImage& model_image, GeneratedImage& residual_image, CARTA::FittingResponse& fitting_response); /** @brief Stop the ongoing fitting process. */ void StopFitting(); @@ -139,9 +139,9 @@ class ImageFitter { GeneratorProgressCallback _progress_callback; /** - * @brief Calculate the number of NaN values in the image data. + * @brief Calculate the number of NaN values and standard deviation of the image data. */ - void CalculateNanNum(); + void CalculateNanNumAndStd(); /** * @brief Set initial fitting parameters for the fitting. * @param initial_values Initial fitting parameters @@ -242,4 +242,4 @@ class ImageFitter { } // namespace carta -#endif // CARTA_BACKEND_IMAGEFITTER_IMAGEFITTER_H_ +#endif // CARTA_SRC_IMAGEFITTER_IMAGEFITTER_H_ diff --git a/src/ImageGenerators/Image2DConvolver.h b/src/ImageGenerators/Image2DConvolver.h index aba6089af..4a0adf56e 100644 --- a/src/ImageGenerators/Image2DConvolver.h +++ b/src/ImageGenerators/Image2DConvolver.h @@ -1,5 +1,5 @@ /* This file is part of the CARTA Image Viewer: https://github.com/CARTAvis/carta-backend - Copyright 2018-2022 Academia Sinica Institute of Astronomy and Astrophysics (ASIAA), + Copyright 2018- Academia Sinica Institute of Astronomy and Astrophysics (ASIAA), Associated Universities, Inc. (AUI) and the Inter-University Institute for Data Intensive Astronomy (IDIA) SPDX-License-Identifier: GPL-3.0-or-later */ @@ -7,8 +7,8 @@ // // Re-write from the file: "carta-casacore/casa6/casa5/code/imageanalysis/ImageAnalysis/Image2DConvolver.h" // -#ifndef CARTA_BACKEND__MOMENT_IMAGE2DCONVOLVER_H_ -#define CARTA_BACKEND__MOMENT_IMAGE2DCONVOLVER_H_ +#ifndef CARTA_SRC_IMAGEGENERATORS_IMAGE2DCONVOLVER_H_ +#define CARTA_SRC_IMAGEGENERATORS_IMAGE2DCONVOLVER_H_ #include #include @@ -152,4 +152,4 @@ class Image2DConvolver : public casa::ImageTask { #include "Image2DConvolver.tcc" -#endif // CARTA_BACKEND__MOMENT_IMAGE2DCONVOLVER_H_ +#endif // CARTA_SRC_IMAGEGENERATORS_IMAGE2DCONVOLVER_H_ diff --git a/src/ImageGenerators/Image2DConvolver.tcc b/src/ImageGenerators/Image2DConvolver.tcc index c47342218..77ebefc2a 100644 --- a/src/ImageGenerators/Image2DConvolver.tcc +++ b/src/ImageGenerators/Image2DConvolver.tcc @@ -1,5 +1,5 @@ /* This file is part of the CARTA Image Viewer: https://github.com/CARTAvis/carta-backend - Copyright 2018-2022 Academia Sinica Institute of Astronomy and Astrophysics (ASIAA), + Copyright 2018- Academia Sinica Institute of Astronomy and Astrophysics (ASIAA), Associated Universities, Inc. (AUI) and the Inter-University Institute for Data Intensive Astronomy (IDIA) SPDX-License-Identifier: GPL-3.0-or-later */ @@ -7,8 +7,8 @@ // // Re-write from the file: "carta-casacore/casa6/casa5/code/imageanalysis/ImageAnalysis/Image2DConvolver.tcc" // -#ifndef CARTA_BACKEND__MOMENT_IMAGE2DCONVOLVER_TCC_ -#define CARTA_BACKEND__MOMENT_IMAGE2DCONVOLVER_TCC_ +#ifndef CARTA_SRC_IMAGEGENERATORS_IMAGE2DCONVOLVER_TCC_ +#define CARTA_SRC_IMAGEGENERATORS_IMAGE2DCONVOLVER_TCC_ #include "../Logger/Logger.h" #include "Util/Casacore.h" @@ -165,7 +165,8 @@ void Image2DConvolver::_convolve(SPIIT imageOut, const ImageInterface& ima if (logFactors) { pixelArea = cSys.directionCoordinate().getPixelArea().getValue("arcsec*arcsec"); if (!_targetres) { - GaussianBeam kernelBeam(kernelParms); + casacore::Vector const kernelParmsV(kernelParms); + GaussianBeam kernelBeam(kernelParmsV); factor1 = pixelArea / kernelBeam.getArea("arcsec*arcsec"); } } @@ -207,25 +208,27 @@ void Image2DConvolver::_doSingleBeam(ImageInfo& iiOut, Double& kernelVolume, String& brightnessUnitOut, GaussianBeam& beamOut, SPIIT imageOut, const ImageInterface& imageIn, const vector& originalParms, VectorKernel::KernelTypes kernelType, Bool logFactors, Double factor1, Double pixelArea) const { GaussianBeam inputBeam = imageIn.imageInfo().restoringBeam(); + casacore::Vector const kernelParmsV(kernelParms); + casacore::Vector const originalParmsV(originalParms); if (_targetres) { kernelParms = _getConvolvingBeamForTargetResolution(originalParms, inputBeam); spdlog::debug("Convolving image that has a beam of {} with a Gaussian of {} to reach a target resolution of {}", - FormatBeam(inputBeam), FormatBeam(GaussianBeam(kernelParms)), FormatBeam(GaussianBeam(originalParms))); + FormatBeam(inputBeam), FormatBeam(GaussianBeam(kernelParmsV)), FormatBeam(GaussianBeam(originalParmsV))); kernelVolume = _makeKernel(kernel, kernelType, kernelParms, imageIn); } const CoordinateSystem& cSys = imageIn.coordinates(); auto scaleFactor = _dealWithRestoringBeam( - brightnessUnitOut, beamOut, kernel, kernelVolume, kernelType, kernelParms, cSys, inputBeam, imageIn.units(), true); + brightnessUnitOut, beamOut, kernel, kernelVolume, kernelType, kernelParmsV, cSys, inputBeam, imageIn.units(), true); string message; message += "Scaling pixel values by "; if (logFactors) { if (_targetres) { - GaussianBeam kernelBeam(kernelParms); + GaussianBeam kernelBeam(kernelParmsV); factor1 = pixelArea / kernelBeam.getArea("arcsec*arcsec"); } Double factor2 = beamOut.getArea("arcsec*arcsec") / inputBeam.getArea("arcsec*arcsec"); @@ -290,9 +293,10 @@ void Image2DConvolver::_doMultipleBeams(ImageInfo& iiOut, Double& kernelVolum casacore::Int channel = -1; casacore::Int polarization = -1; + casacore::Vector const kernelParmsV(kernelParms); if (_targetres) { iiOut.removeRestoringBeam(); - iiOut.setRestoringBeam(casacore::GaussianBeam(kernelParms)); + iiOut.setRestoringBeam(casacore::GaussianBeam(kernelParmsV)); } casacore::uInt count = (nChan > 0 && nPol > 0) ? nChan * nPol : nChan > 0 ? nChan : nPol; @@ -347,14 +351,15 @@ void Image2DConvolver::_doMultipleBeams(ImageInfo& iiOut, Double& kernelVolum message += " "; - if (near(inputBeam, GaussianBeam(originalParms), 1e-5, casacore::Quantity(1e-2, "arcsec"))) { + casacore::Vector const originalParmsV(originalParms); + if (near(inputBeam, GaussianBeam(originalParmsV), 1e-5, casacore::Quantity(1e-2, "arcsec"))) { doConvolve = false; message += fmt::format("Input beam is already near target resolution so this plane will not be convolved."); } else { kernelParms = _getConvolvingBeamForTargetResolution(originalParms, inputBeam); kernelVolume = _makeKernel(kernel, kernelType, kernelParms, imageIn); message += fmt::format(": Convolving image which has a beam of {} with a Gaussian of {} to reach a target resolution of {}", - FormatBeam(inputBeam), FormatBeam(GaussianBeam(kernelParms)), FormatBeam(GaussianBeam(originalParms))); + FormatBeam(inputBeam), FormatBeam(GaussianBeam(kernelParmsV)), FormatBeam(GaussianBeam(originalParmsV))); } spdlog::debug(message); @@ -363,12 +368,12 @@ void Image2DConvolver::_doMultipleBeams(ImageInfo& iiOut, Double& kernelVolum casacore::TempImage subImageOut(subImage.shape(), subImage.coordinates()); if (doConvolve) { auto scaleFactor = _dealWithRestoringBeam( - brightnessUnitOut, beamOut, kernel, kernelVolume, kernelType, kernelParms, subCsys, inputBeam, imageIn.units(), i == 0); + brightnessUnitOut, beamOut, kernel, kernelVolume, kernelType, kernelParmsV, subCsys, inputBeam, imageIn.units(), i == 0); { string message("Scaling pixel values by "); if (logFactors) { if (_targetres) { - casacore::GaussianBeam kernelBeam(kernelParms); + casacore::GaussianBeam kernelBeam(kernelParmsV); factor1 = pixelArea / kernelBeam.getArea("arcsec*arcsec"); } auto factor2 = beamOut.getArea("arcsec*arcsec") / inputBeam.getArea("arcsec*arcsec"); @@ -438,7 +443,8 @@ template Double Image2DConvolver::_makeKernel(casacore::Array& kernelArray, casacore::VectorKernel::KernelTypes kernelType, const std::vector& parameters, const casacore::ImageInterface& imageIn) const { // Check number of parameters - _checkKernelParameters(kernelType, parameters); + casacore::Vector const parametersV(parameters); + _checkKernelParameters(kernelType, parametersV); // Convert kernel widths to pixels from world. Demands major and minor both in pixels or both in world, else exception casacore::Vector dParameters; @@ -744,4 +750,4 @@ void Image2DConvolver::StopCalculation() { _stop = true; } -#endif // CARTA_BACKEND__MOMENT_IMAGE2DCONVOLVER_H_ +#endif // CARTA_SRC_IMAGEGENERATORS_IMAGE2DCONVOLVER_TCC_ diff --git a/src/ImageGenerators/ImageGenerator.h b/src/ImageGenerators/ImageGenerator.h index 152b09701..eeeaafa7e 100644 --- a/src/ImageGenerators/ImageGenerator.h +++ b/src/ImageGenerators/ImageGenerator.h @@ -1,33 +1,26 @@ /* This file is part of the CARTA Image Viewer: https://github.com/CARTAvis/carta-backend - Copyright 2018-2022 Academia Sinica Institute of Astronomy and Astrophysics (ASIAA), + Copyright 2018- Academia Sinica Institute of Astronomy and Astrophysics (ASIAA), Associated Universities, Inc. (AUI) and the Inter-University Institute for Data Intensive Astronomy (IDIA) SPDX-License-Identifier: GPL-3.0-or-later */ -#ifndef CARTA_BACKEND_IMAGEGENERATORS_IMAGEGENERATOR_H_ -#define CARTA_BACKEND_IMAGEGENERATORS_IMAGEGENERATOR_H_ +#ifndef CARTA_SRC_IMAGEGENERATORS_IMAGEGENERATOR_H_ +#define CARTA_SRC_IMAGEGENERATORS_IMAGEGENERATOR_H_ #include #include -#define MOMENT_ID_MULTIPLIER 1000 -#define PV_ID_MULTIPLIER -1000 -#define FITTING_ID_MULTIPLIER -1000 -#define FITTING_WITH_MOMENT_ID_MULTIPLIER -10 - using GeneratorProgressCallback = std::function; namespace carta { struct GeneratedImage { - int file_id; std::string name; std::shared_ptr> image; GeneratedImage() {} - GeneratedImage(int file_id_, std::string name_, std::shared_ptr> image_) { - file_id = file_id_; + GeneratedImage(std::string name_, std::shared_ptr> image_) { name = name_; image = image_; } @@ -35,4 +28,4 @@ struct GeneratedImage { } // namespace carta -#endif // CARTA_BACKEND_IMAGEGENERATORS_IMAGEGENERATOR_H_ +#endif // CARTA_SRC_IMAGEGENERATORS_IMAGEGENERATOR_H_ diff --git a/src/ImageGenerators/ImageMoments.h b/src/ImageGenerators/ImageMoments.h index d163cb81a..fbcdd8aa5 100644 --- a/src/ImageGenerators/ImageMoments.h +++ b/src/ImageGenerators/ImageMoments.h @@ -1,5 +1,5 @@ /* This file is part of the CARTA Image Viewer: https://github.com/CARTAvis/carta-backend - Copyright 2018-2022 Academia Sinica Institute of Astronomy and Astrophysics (ASIAA), + Copyright 2018- Academia Sinica Institute of Astronomy and Astrophysics (ASIAA), Associated Universities, Inc. (AUI) and the Inter-University Institute for Data Intensive Astronomy (IDIA) SPDX-License-Identifier: GPL-3.0-or-later */ @@ -7,8 +7,8 @@ // // Re-write from the file: "carta-casacore/casa6/casa5/code/imageanalysis/ImageAnalysis/ImageMoments.h" // -#ifndef CARTA_BACKEND__MOMENT_IMAGEMOMENTS_H_ -#define CARTA_BACKEND__MOMENT_IMAGEMOMENTS_H_ +#ifndef CARTA_SRC_IMAGEGENERATORS_IMAGEMOMENTS_H_ +#define CARTA_SRC_IMAGEGENERATORS_IMAGEMOMENTS_H_ #include #include @@ -128,4 +128,4 @@ class ImageMoments : public casa::MomentsBase { #include "ImageMoments.tcc" -#endif // CARTA_BACKEND__MOMENT_IMAGEMOMENTS_H_ +#endif // CARTA_SRC_IMAGEGENERATORS_IMAGEMOMENTS_H_ diff --git a/src/ImageGenerators/ImageMoments.tcc b/src/ImageGenerators/ImageMoments.tcc index 98f17470e..8bf02bcaa 100644 --- a/src/ImageGenerators/ImageMoments.tcc +++ b/src/ImageGenerators/ImageMoments.tcc @@ -1,5 +1,5 @@ /* This file is part of the CARTA Image Viewer: https://github.com/CARTAvis/carta-backend - Copyright 2018-2022 Academia Sinica Institute of Astronomy and Astrophysics (ASIAA), + Copyright 2018- Academia Sinica Institute of Astronomy and Astrophysics (ASIAA), Associated Universities, Inc. (AUI) and the Inter-University Institute for Data Intensive Astronomy (IDIA) SPDX-License-Identifier: GPL-3.0-or-later */ @@ -7,8 +7,8 @@ // // Re-write from the file: "carta-casacore/casa6/casa5/code/imageanalysis/ImageAnalysis/ImageMoments.tcc" // -#ifndef CARTA_BACKEND__MOMENT_IMAGEMOMENTS_TCC_ -#define CARTA_BACKEND__MOMENT_IMAGEMOMENTS_TCC_ +#ifndef CARTA_SRC_IMAGEGENERATORS_IMAGEMOMENTS_TCC_ +#define CARTA_SRC_IMAGEGENERATORS_IMAGEMOMENTS_TCC_ #include "../Logger/Logger.h" #include "Util/Casacore.h" @@ -28,8 +28,7 @@ ImageMoments::ImageMoments(const casacore::ImageInterface& image, casacore template casacore::Bool ImageMoments::SetNewImage(const casacore::ImageInterface& image) { - T* dummy = nullptr; - casacore::DataType imageType = casacore::whatType(dummy); + casacore::DataType imageType = casacore::whatType(); ThrowIf(imageType != casacore::TpFloat && imageType != casacore::TpDouble, "Moments can only be evaluated for Float or Double valued images"); @@ -728,4 +727,4 @@ casacore::IPosition ImageMoments::ChunkShape(casacore::uInt axis, const casac return chunk_shape; } -#endif // CARTA_BACKEND__MOMENT_IMAGEMOMENTS_TCC_ +#endif // CARTA_SRC_IMAGEGENERATORS_IMAGEMOMENTS_TCC_ diff --git a/src/ImageGenerators/MomentGenerator.cc b/src/ImageGenerators/MomentGenerator.cc index 1e1b36c8c..bb062d1b5 100644 --- a/src/ImageGenerators/MomentGenerator.cc +++ b/src/ImageGenerators/MomentGenerator.cc @@ -1,5 +1,5 @@ /* This file is part of the CARTA Image Viewer: https://github.com/CARTAvis/carta-backend - Copyright 2018-2022 Academia Sinica Institute of Astronomy and Astrophysics (ASIAA), + Copyright 2018- Academia Sinica Institute of Astronomy and Astrophysics (ASIAA), Associated Universities, Inc. (AUI) and the Inter-University Institute for Data Intensive Astronomy (IDIA) SPDX-License-Identifier: GPL-3.0-or-later */ @@ -75,17 +75,13 @@ bool MomentGenerator::CalculateMoments(int file_id, const casacore::ImageRegion& out_file_name += std::to_string(name_index); } - // Set a temp moment file Id. - // With each name index, advance by number of moment types to avoid duplicates. - int moment_file_id = (file_id + 1) * MOMENT_ID_MULTIPLIER + (name_index * _moment_map.size()) + moment_type; - // Fill results std::shared_ptr> moment_image = dynamic_pointer_cast>(result_images[i]); // Add moment requests info to an image header as the HISTORY key moment_image->appendLog(_logger); - collapse_results.push_back(GeneratedImage(moment_file_id, out_file_name, moment_image)); + collapse_results.push_back(GeneratedImage(out_file_name, moment_image)); } _success = true; } catch (const AipsError& x) { diff --git a/src/ImageGenerators/MomentGenerator.h b/src/ImageGenerators/MomentGenerator.h index 2d5c05a28..eda5c466c 100644 --- a/src/ImageGenerators/MomentGenerator.h +++ b/src/ImageGenerators/MomentGenerator.h @@ -1,11 +1,11 @@ /* This file is part of the CARTA Image Viewer: https://github.com/CARTAvis/carta-backend - Copyright 2018-2022 Academia Sinica Institute of Astronomy and Astrophysics (ASIAA), + Copyright 2018- Academia Sinica Institute of Astronomy and Astrophysics (ASIAA), Associated Universities, Inc. (AUI) and the Inter-University Institute for Data Intensive Astronomy (IDIA) SPDX-License-Identifier: GPL-3.0-or-later */ -#ifndef CARTA_BACKEND_MOMENT_MOMENTGENERATOR_H_ -#define CARTA_BACKEND_MOMENT_MOMENTGENERATOR_H_ +#ifndef CARTA_SRC_IMAGEGENERATORS_MOMENTGENERATOR_H_ +#define CARTA_SRC_IMAGEGENERATORS_MOMENTGENERATOR_H_ #include #include @@ -89,4 +89,4 @@ class MomentGenerator : public casa::ImageMomentsProgressMonitor { } // namespace carta -#endif // CARTA_BACKEND_MOMENT_MOMENTGENERATOR_H_ +#endif // CARTA_SRC_IMAGEGENERATORS_MOMENTGENERATOR_H_ diff --git a/src/ImageGenerators/PvGenerator.cc b/src/ImageGenerators/PvGenerator.cc index 6f8c71bb1..c0de88ed9 100644 --- a/src/ImageGenerators/PvGenerator.cc +++ b/src/ImageGenerators/PvGenerator.cc @@ -1,5 +1,5 @@ /* This file is part of the CARTA Image Viewer: https://github.com/CARTAvis/carta-backend - Copyright 2018-2022 Academia Sinica Institute of Astronomy and Astrophysics (ASIAA), + Copyright 2018- Academia Sinica Institute of Astronomy and Astrophysics (ASIAA), Associated Universities, Inc. (AUI) and the Inter-University Institute for Data Intensive Astronomy (IDIA) SPDX-License-Identifier: GPL-3.0-or-later */ @@ -15,11 +15,10 @@ using namespace carta; -PvGenerator::PvGenerator() : _file_id(0), _name("") {} +PvGenerator::PvGenerator() : _name("") {} -void PvGenerator::SetFileIdName(int file_id, int index, const std::string& filename, bool is_preview) { +void PvGenerator::SetFileName(int index, const std::string& filename, bool is_preview) { // Optional (not for preview) file id, and name for PV image - _file_id = ((file_id + 1) * PV_ID_MULTIPLIER) - index; SetPvImageName(filename, index, is_preview); } @@ -49,7 +48,7 @@ bool PvGenerator::GetPvImage(const std::shared_ptr& frame, const casacore image->flush(); // Set returned image - pv_image = GeneratedImage(_file_id, _name, image); + pv_image = GeneratedImage(_name, image); return true; } diff --git a/src/ImageGenerators/PvGenerator.h b/src/ImageGenerators/PvGenerator.h index 6751326e6..d746cdc93 100644 --- a/src/ImageGenerators/PvGenerator.h +++ b/src/ImageGenerators/PvGenerator.h @@ -1,11 +1,11 @@ /* This file is part of the CARTA Image Viewer: https://github.com/CARTAvis/carta-backend - Copyright 2018-2022 Academia Sinica Institute of Astronomy and Astrophysics (ASIAA), + Copyright 2018- Academia Sinica Institute of Astronomy and Astrophysics (ASIAA), Associated Universities, Inc. (AUI) and the Inter-University Institute for Data Intensive Astronomy (IDIA) SPDX-License-Identifier: GPL-3.0-or-later */ -#ifndef CARTA_BACKEND_IMAGEGENERATORS_PVGENERATOR_H_ -#define CARTA_BACKEND_IMAGEGENERATORS_PVGENERATOR_H_ +#ifndef CARTA_SRC_IMAGEGENERATORS_PVGENERATOR_H_ +#define CARTA_SRC_IMAGEGENERATORS_PVGENERATOR_H_ #include #include @@ -24,7 +24,7 @@ class PvGenerator { ~PvGenerator() = default; // For PV generator (not preview) - void SetFileIdName(int file_id, int index, const std::string& filename, bool is_preview = false); + void SetFileName(int index, const std::string& filename, bool is_preview = false); // Create generated PV image from input data. If reverse, [spectral, offset] instead of normal [offset, spectral]. // Returns generated image and success, with message if failure. @@ -49,4 +49,4 @@ class PvGenerator { } // namespace carta -#endif // CARTA_BACKEND_IMAGEGENERATORS_PVGENERATOR_H_ +#endif // CARTA_SRC_IMAGEGENERATORS_PVGENERATOR_H_ diff --git a/src/ImageGenerators/PvPreviewCube.cc b/src/ImageGenerators/PvPreviewCube.cc index 61429ff82..394716d93 100644 --- a/src/ImageGenerators/PvPreviewCube.cc +++ b/src/ImageGenerators/PvPreviewCube.cc @@ -1,5 +1,5 @@ /* This file is part of the CARTA Image Viewer: https://github.com/CARTAvis/carta-backend - Copyright 2018-2022 Academia Sinica Institute of Astronomy and Astrophysics (ASIAA), + Copyright 2018- Academia Sinica Institute of Astronomy and Astrophysics (ASIAA), Associated Universities, Inc. (AUI) and the Inter-University Institute for Data Intensive Astronomy (IDIA) SPDX-License-Identifier: GPL-3.0-or-later */ @@ -12,6 +12,7 @@ #include #include "DataStream/Smoothing.h" +#include "Logger/Logger.h" #include "Timer/Timer.h" #define LOAD_DATA_PROGRESS_INTERVAL 1000 @@ -162,8 +163,8 @@ bool PvPreviewCube::GetRegionProfile(const casacore::Slicer& region_bounding_box auto box_start = region_bounding_box.start(); auto box_length = region_bounding_box.length(); - // Initialize profile - size_t nchan = box_length(_preview_image->coordinates().spectralAxisNumber()); + // Initialize profile to channels in preview image + size_t nchan = _preview_image->shape()(_preview_image->coordinates().spectralAxisNumber()); profile.resize(nchan, NAN); std::vector npix_per_chan(nchan, 0.0); auto data_shape = _cube_data.shape(); diff --git a/src/ImageGenerators/PvPreviewCube.h b/src/ImageGenerators/PvPreviewCube.h index 6fdafbbe5..32622317d 100644 --- a/src/ImageGenerators/PvPreviewCube.h +++ b/src/ImageGenerators/PvPreviewCube.h @@ -1,19 +1,19 @@ /* This file is part of the CARTA Image Viewer: https://github.com/CARTAvis/carta-backend - Copyright 2018-2022 Academia Sinica Institute of Astronomy and Astrophysics (ASIAA), + Copyright 2018- Academia Sinica Institute of Astronomy and Astrophysics (ASIAA), Associated Universities, Inc. (AUI) and the Inter-University Institute for Data Intensive Astronomy (IDIA) SPDX-License-Identifier: GPL-3.0-or-later */ -#ifndef CARTA_BACKEND_IMAGEGENERATORS_PVPREVIEWCUBE_H_ -#define CARTA_BACKEND_IMAGEGENERATORS_PVPREVIEWCUBE_H_ +#ifndef CARTA_SRC_IMAGEGENERATORS_PVPREVIEWCUBE_H_ +#define CARTA_SRC_IMAGEGENERATORS_PVPREVIEWCUBE_H_ + +#include #include "ImageGenerators/ImageGenerator.h" #include "Region/Region.h" #include "Util/File.h" #include "Util/Image.h" -#include - namespace carta { struct PreviewCubeParameters { @@ -115,4 +115,4 @@ class PvPreviewCube { } // namespace carta -#endif // CARTA_BACKEND_IMAGEGENERATORS_PVPREVIEWCUBE_H_ +#endif // CARTA_SRC_IMAGEGENERATORS_PVPREVIEWCUBE_H_ diff --git a/src/ImageGenerators/PvPreviewCut.cc b/src/ImageGenerators/PvPreviewCut.cc index e031b5974..f9bb12f1b 100644 --- a/src/ImageGenerators/PvPreviewCut.cc +++ b/src/ImageGenerators/PvPreviewCut.cc @@ -1,5 +1,5 @@ /* This file is part of the CARTA Image Viewer: https://github.com/CARTAvis/carta-backend - Copyright 2018-2022 Academia Sinica Institute of Astronomy and Astrophysics (ASIAA), + Copyright 2018- Academia Sinica Institute of Astronomy and Astrophysics (ASIAA), Associated Universities, Inc. (AUI) and the Inter-University Institute for Data Intensive Astronomy (IDIA) SPDX-License-Identifier: GPL-3.0-or-later */ diff --git a/src/ImageGenerators/PvPreviewCut.h b/src/ImageGenerators/PvPreviewCut.h index 3f87d249b..6e4fc667a 100644 --- a/src/ImageGenerators/PvPreviewCut.h +++ b/src/ImageGenerators/PvPreviewCut.h @@ -1,11 +1,13 @@ /* This file is part of the CARTA Image Viewer: https://github.com/CARTAvis/carta-backend - Copyright 2018-2022 Academia Sinica Institute of Astronomy and Astrophysics (ASIAA), + Copyright 2018- Academia Sinica Institute of Astronomy and Astrophysics (ASIAA), Associated Universities, Inc. (AUI) and the Inter-University Institute for Data Intensive Astronomy (IDIA) SPDX-License-Identifier: GPL-3.0-or-later */ -#ifndef CARTA_BACKEND_IMAGEGENERATORS_PVPREVIEWCUT_H_ -#define CARTA_BACKEND_IMAGEGENERATORS_PVPREVIEWCUT_H_ +#ifndef CARTA_SRC_IMAGEGENERATORS_PVPREVIEWCUT_H_ +#define CARTA_SRC_IMAGEGENERATORS_PVPREVIEWCUT_H_ + +#include #include "Region/Region.h" #include "Util/File.h" @@ -88,4 +90,4 @@ class PvPreviewCut { } // namespace carta -#endif // CARTA_BACKEND_IMAGEGENERATORS_PVPREVIEWCUT_H_ +#endif // CARTA_SRC_IMAGEGENERATORS_PVPREVIEWCUT_H_ diff --git a/src/ImageStats/BasicStatsCalculator.h b/src/ImageStats/BasicStatsCalculator.h index 6316c33ea..7797784e6 100644 --- a/src/ImageStats/BasicStatsCalculator.h +++ b/src/ImageStats/BasicStatsCalculator.h @@ -1,11 +1,11 @@ /* This file is part of the CARTA Image Viewer: https://github.com/CARTAvis/carta-backend - Copyright 2018-2022 Academia Sinica Institute of Astronomy and Astrophysics (ASIAA), + Copyright 2018- Academia Sinica Institute of Astronomy and Astrophysics (ASIAA), Associated Universities, Inc. (AUI) and the Inter-University Institute for Data Intensive Astronomy (IDIA) SPDX-License-Identifier: GPL-3.0-or-later */ -#ifndef CARTA_BACKEND_IMAGESTATS_BASICSTATSCALCULATOR_H_ -#define CARTA_BACKEND_IMAGESTATS_BASICSTATSCALCULATOR_H_ +#ifndef CARTA_SRC_IMAGESTATS_BASICSTATSCALCULATOR_H_ +#define CARTA_SRC_IMAGESTATS_BASICSTATSCALCULATOR_H_ #include #include @@ -81,4 +81,4 @@ class BasicStatsCalculator { #include "BasicStatsCalculator.tcc" -#endif // CARTA_BACKEND_IMAGESTATS_BASICSTATSCALCULATOR_H_ +#endif // CARTA_SRC_IMAGESTATS_BASICSTATSCALCULATOR_H_ diff --git a/src/ImageStats/BasicStatsCalculator.tcc b/src/ImageStats/BasicStatsCalculator.tcc index 40c44a22a..34b043599 100644 --- a/src/ImageStats/BasicStatsCalculator.tcc +++ b/src/ImageStats/BasicStatsCalculator.tcc @@ -1,11 +1,11 @@ /* This file is part of the CARTA Image Viewer: https://github.com/CARTAvis/carta-backend - Copyright 2018-2022 Academia Sinica Institute of Astronomy and Astrophysics (ASIAA), + Copyright 2018- Academia Sinica Institute of Astronomy and Astrophysics (ASIAA), Associated Universities, Inc. (AUI) and the Inter-University Institute for Data Intensive Astronomy (IDIA) SPDX-License-Identifier: GPL-3.0-or-later */ -#ifndef CARTA_BACKEND_IMAGESTATS_BASICSTATSCALCULATOR_TCC_ -#define CARTA_BACKEND_IMAGESTATS_BASICSTATSCALCULATOR_TCC_ +#ifndef CARTA_SRC_IMAGESTATS_BASICSTATSCALCULATOR_TCC_ +#define CARTA_SRC_IMAGESTATS_BASICSTATSCALCULATOR_TCC_ #include "Logger/Logger.h" @@ -102,4 +102,4 @@ BasicStats BasicStatsCalculator::GetStats() const { } // namespace carta -#endif // CARTA_BACKEND_IMAGESTATS_BASICSTATSCALCULATOR_TCC_ +#endif // CARTA_SRC_IMAGESTATS_BASICSTATSCALCULATOR_TCC_ diff --git a/src/ImageStats/Histogram.cc b/src/ImageStats/Histogram.cc index 9e62c0df6..f9fa7f6ee 100644 --- a/src/ImageStats/Histogram.cc +++ b/src/ImageStats/Histogram.cc @@ -1,5 +1,5 @@ /* This file is part of the CARTA Image Viewer: https://github.com/CARTAvis/carta-backend - Copyright 2018-2022 Academia Sinica Institute of Astronomy and Astrophysics (ASIAA), + Copyright 2018- Academia Sinica Institute of Astronomy and Astrophysics (ASIAA), Associated Universities, Inc. (AUI) and the Inter-University Institute for Data Intensive Astronomy (IDIA) SPDX-License-Identifier: GPL-3.0-or-later */ diff --git a/src/ImageStats/Histogram.h b/src/ImageStats/Histogram.h index d8473f06c..f0fb9d11a 100644 --- a/src/ImageStats/Histogram.h +++ b/src/ImageStats/Histogram.h @@ -1,11 +1,11 @@ /* This file is part of the CARTA Image Viewer: https://github.com/CARTAvis/carta-backend - Copyright 2018-2022 Academia Sinica Institute of Astronomy and Astrophysics (ASIAA), + Copyright 2018- Academia Sinica Institute of Astronomy and Astrophysics (ASIAA), Associated Universities, Inc. (AUI) and the Inter-University Institute for Data Intensive Astronomy (IDIA) SPDX-License-Identifier: GPL-3.0-or-later */ -#ifndef CARTA_BACKEND_IMAGESTATS_HISTOGRAM_H_ -#define CARTA_BACKEND_IMAGESTATS_HISTOGRAM_H_ +#ifndef CARTA_SRC_IMAGESTATS_HISTOGRAM_H_ +#define CARTA_SRC_IMAGESTATS_HISTOGRAM_H_ #include "BasicStatsCalculator.h" @@ -61,4 +61,4 @@ class Histogram { } // namespace carta -#endif // CARTA_BACKEND_IMAGESTATS_HISTOGRAM_H_ +#endif // CARTA_SRC_IMAGESTATS_HISTOGRAM_H_ diff --git a/src/ImageStats/StatsCalculator.cc b/src/ImageStats/StatsCalculator.cc index a93ac1293..310a508fe 100644 --- a/src/ImageStats/StatsCalculator.cc +++ b/src/ImageStats/StatsCalculator.cc @@ -1,5 +1,5 @@ /* This file is part of the CARTA Image Viewer: https://github.com/CARTAvis/carta-backend - Copyright 2018-2022 Academia Sinica Institute of Astronomy and Astrophysics (ASIAA), + Copyright 2018- Academia Sinica Institute of Astronomy and Astrophysics (ASIAA), Associated Universities, Inc. (AUI) and the Inter-University Institute for Data Intensive Astronomy (IDIA) SPDX-License-Identifier: GPL-3.0-or-later */ diff --git a/src/ImageStats/StatsCalculator.h b/src/ImageStats/StatsCalculator.h index c86aa7d3c..72271be4f 100644 --- a/src/ImageStats/StatsCalculator.h +++ b/src/ImageStats/StatsCalculator.h @@ -1,13 +1,13 @@ /* This file is part of the CARTA Image Viewer: https://github.com/CARTAvis/carta-backend - Copyright 2018-2022 Academia Sinica Institute of Astronomy and Astrophysics (ASIAA), + Copyright 2018- Academia Sinica Institute of Astronomy and Astrophysics (ASIAA), Associated Universities, Inc. (AUI) and the Inter-University Institute for Data Intensive Astronomy (IDIA) SPDX-License-Identifier: GPL-3.0-or-later */ //# StatsCalculator.h: functions for calculating statistics and histograms -#ifndef CARTA_BACKEND_IMAGESTATS_STATSCALCULATOR_H_ -#define CARTA_BACKEND_IMAGESTATS_STATSCALCULATOR_H_ +#ifndef CARTA_SRC_IMAGESTATS_STATSCALCULATOR_H_ +#define CARTA_SRC_IMAGESTATS_STATSCALCULATOR_H_ #include @@ -28,4 +28,4 @@ bool CalcStatsValues(std::map>& stats_valu } // namespace carta -#endif // CARTA_BACKEND_IMAGESTATS_STATSCALCULATOR_H_ +#endif // CARTA_SRC_IMAGESTATS_STATSCALCULATOR_H_ diff --git a/src/Logger/CartaLogSink.cc b/src/Logger/CartaLogSink.cc new file mode 100644 index 000000000..8c3acfb99 --- /dev/null +++ b/src/Logger/CartaLogSink.cc @@ -0,0 +1,44 @@ +/* This file is part of the CARTA Image Viewer: https://github.com/CARTAvis/carta-backend + Copyright 2018- Academia Sinica Institute of Astronomy and Astrophysics (ASIAA), + Associated Universities, Inc. (AUI) and the Inter-University Institute for Data Intensive Astronomy (IDIA) + SPDX-License-Identifier: GPL-3.0-or-later +*/ + +#include "CartaLogSink.h" +#include "Logger.h" + +#include +#include + +namespace carta { + +CartaLogSink::CartaLogSink(casacore::LogMessage::Priority filter) : casacore::LogSinkInterface(casacore::LogFilter(filter)) {} + +bool CartaLogSink::postLocally(const casacore::LogMessage& message) { + if (filter().pass(message)) { + auto priority = message.priority(); + auto log_message = "[casacore] " + message.message(); + if (message.priority() <= casacore::LogMessage::DEBUG1) { + spdlog::debug(log_message); + } else if (priority <= casacore::LogMessage::NORMAL) { + spdlog::info(log_message); + } else if (priority == casacore::LogMessage::WARN) { + spdlog::warn(log_message); + } else { + spdlog::error(log_message); + } + return true; + } + + return false; +} + +casacore::String CartaLogSink::localId() { + return casacore::String("CartaLogSink"); +} + +casacore::String CartaLogSink::id() const { + return casacore::String("CartaLogSink"); +} + +} // namespace carta diff --git a/src/Logger/CartaLogSink.h b/src/Logger/CartaLogSink.h new file mode 100644 index 000000000..c7e9a6a23 --- /dev/null +++ b/src/Logger/CartaLogSink.h @@ -0,0 +1,35 @@ +/* This file is part of the CARTA Image Viewer: https://github.com/CARTAvis/carta-backend + Copyright 2018- Academia Sinica Institute of Astronomy and Astrophysics (ASIAA), + Associated Universities, Inc. (AUI) and the Inter-University Institute for Data Intensive Astronomy (IDIA) + SPDX-License-Identifier: GPL-3.0-or-later +*/ + +#ifndef CARTA_SRC_LOGGER_CARTALOGSINK_H_ +#define CARTA_SRC_LOGGER_CARTALOGSINK_H_ + +#include + +namespace carta { + +class CartaLogSink : public casacore::LogSinkInterface { +public: + CartaLogSink() = default; + explicit CartaLogSink(casacore::LogMessage::Priority filter); + ~CartaLogSink() = default; + + // Write message to the spdlog if it passes the filter. + virtual bool postLocally(const casacore::LogMessage& message); + + // OVERRIDE? Write any pending output. + // virtual void flush (bool global=True) override; + + // Returns the id for this class. + static casacore::String localId(); + + // Returns the id of the LogSink in use. + virtual casacore::String id() const; +}; + +} // namespace carta + +#endif // CARTA_SRC_LOGGER_CARTALOGSINK_H_ diff --git a/src/Logger/Logger.cc b/src/Logger/Logger.cc index 57d6dd517..f2ea51392 100644 --- a/src/Logger/Logger.cc +++ b/src/Logger/Logger.cc @@ -1,11 +1,13 @@ /* This file is part of the CARTA Image Viewer: https://github.com/CARTAvis/carta-backend - Copyright 2018-2022 Academia Sinica Institute of Astronomy and Astrophysics (ASIAA), + Copyright 2018- Academia Sinica Institute of Astronomy and Astrophysics (ASIAA), Associated Universities, Inc. (AUI) and the Inter-University Institute for Data Intensive Astronomy (IDIA) SPDX-License-Identifier: GPL-3.0-or-later */ #include "Logger.h" +#include "Main/ProgramSettings.h" + #include namespace carta { @@ -13,8 +15,14 @@ namespace logger { static bool log_protocol_messages(false); -void InitLogger(bool no_log_file, int verbosity, bool log_performance, bool log_protocol_messages_, fs::path user_directory) { - log_protocol_messages = log_protocol_messages_; +void InitLogger() { + // Copy parameters from the global settings + auto& settings = ProgramSettings::GetInstance(); + log_protocol_messages = settings.log_protocol_messages; + auto no_log = settings.no_log; + auto user_directory = settings.user_directory; + auto verbosity = settings.verbosity; + auto log_performance = settings.log_performance; // Set the stdout/stderr console auto console_sink = std::make_shared(); @@ -26,11 +34,11 @@ void InitLogger(bool no_log_file, int verbosity, bool log_performance, bool log_ // Set a log file with its full name, maximum size and the number of rotated files std::string log_fullname; - if (!no_log_file) { + if (!no_log) { log_fullname = (user_directory / "log/carta.log").string(); auto stdout_log_file_sink = std::make_shared(log_fullname, LOG_FILE_SIZE, ROTATED_LOG_FILES); stdout_log_file_sink->set_formatter( - std::make_unique(CARTA_LOGGER_PATTERN, spdlog::pattern_time_type::utc)); + std::make_unique(CARTA_FILE_LOGGER_PATTERN, spdlog::pattern_time_type::utc)); console_sinks.push_back(stdout_log_file_sink); } @@ -72,7 +80,7 @@ void InitLogger(bool no_log_file, int verbosity, bool log_performance, bool log_ // Set as the default logger spdlog::set_default_logger(default_logger); - if (!no_log_file) { + if (!no_log) { spdlog::info("Writing to the log file: {}", log_fullname); } @@ -87,7 +95,7 @@ void InitLogger(bool no_log_file, int verbosity, bool log_performance, bool log_ // Set a log file with its full name, maximum size and the number of rotated files std::string perf_log_fullname; - if (!no_log_file) { + if (!no_log) { perf_log_fullname = (user_directory / "log/performance.log").string(); auto perf_log_file_sink = std::make_shared(perf_log_fullname, LOG_FILE_SIZE, ROTATED_LOG_FILES); diff --git a/src/Logger/Logger.h b/src/Logger/Logger.h index ffd4ec1bb..63125d3df 100644 --- a/src/Logger/Logger.h +++ b/src/Logger/Logger.h @@ -1,11 +1,11 @@ /* This file is part of the CARTA Image Viewer: https://github.com/CARTAvis/carta-backend - Copyright 2018-2022 Academia Sinica Institute of Astronomy and Astrophysics (ASIAA), + Copyright 2018- Academia Sinica Institute of Astronomy and Astrophysics (ASIAA), Associated Universities, Inc. (AUI) and the Inter-University Institute for Data Intensive Astronomy (IDIA) SPDX-License-Identifier: GPL-3.0-or-later */ -#ifndef CARTA_BACKEND_LOGGER_LOGGER_H_ -#define CARTA_BACKEND_LOGGER_LOGGER_H_ +#ifndef CARTA_SRC_LOGGER_LOGGER_H_ +#define CARTA_SRC_LOGGER_LOGGER_H_ #include @@ -19,12 +19,14 @@ #include "Util/FileSystem.h" +// Time format Z is zero UTC offset (ISO 8601) #define LOG_FILE_SIZE 1024 * 1024 * 5 // (Bytes) #define ROTATED_LOG_FILES 5 #define CARTA_LOGGER_TAG "CARTA" -#define CARTA_LOGGER_PATTERN "[%Y-%m-%d %H:%M:%S.%e] [%^%l%$] %v" +#define CARTA_LOGGER_PATTERN "[%Y-%m-%d %H:%M:%S.%eZ] [%n] [%^%l%$] %v" +#define CARTA_FILE_LOGGER_PATTERN "[%Y-%m-%d %H:%M:%S.%eZ] [%^%l%$] %v" #define PERF_TAG "performance" -#define PERF_PATTERN "[%Y-%m-%d %H:%M:%S.%e] [%^%l%$] [%n] %v" +#define PERF_PATTERN "[%Y-%m-%d %H:%M:%S.%eZ] [%^%l%$] [%n] %v" // customize the log function for performance namespace spdlog { @@ -48,7 +50,7 @@ class carta_sink : public ansicolor_sink { carta_sink() : ansicolor_sink(stdout, color_mode::automatic), mutex_(details::console_mutex::mutex()), - formatter_(details::make_unique(pattern_time_type::utc)) { + formatter_(details::make_unique(CARTA_LOGGER_PATTERN, pattern_time_type::utc)) { target_file_ = stdout; colors_[level::trace] = to_string_(white); colors_[level::debug] = to_string_(cyan); @@ -108,11 +110,11 @@ class carta_sink : public ansicolor_sink { namespace carta { namespace logger { -void InitLogger(bool no_log_file, int verbosity, bool log_performance, bool log_protocol_messages_, fs::path user_directory); +void InitLogger(); void LogReceivedEventType(const CARTA::EventType& event_type); void LogSentEventType(const CARTA::EventType& event_type); void FlushLogFile(); } // namespace logger } // namespace carta -#endif // CARTA_BACKEND_LOGGER_LOGGER_H_ +#endif // CARTA_SRC_LOGGER_LOGGER_H_ diff --git a/src/Main/Main.cc b/src/Main/Main.cc index 60991f064..4ac705ed3 100644 --- a/src/Main/Main.cc +++ b/src/Main/Main.cc @@ -1,5 +1,5 @@ /* This file is part of the CARTA Image Viewer: https://github.com/CARTAvis/carta-backend - Copyright 2018-2022 Academia Sinica Institute of Astronomy and Astrophysics (ASIAA), + Copyright 2018- Academia Sinica Institute of Astronomy and Astrophysics (ASIAA), Associated Universities, Inc. (AUI) and the Inter-University Institute for Data Intensive Astronomy (IDIA) SPDX-License-Identifier: GPL-3.0-or-later */ @@ -11,6 +11,7 @@ #include "FileList/FileListHandler.h" #include "HttpServer/HttpServer.h" +#include "Logger/CartaLogSink.h" #include "Logger/Logger.h" #include "ProgramSettings.h" #include "Session/SessionManager.h" @@ -20,6 +21,9 @@ #include "Util/Token.h" #include "WebBrowser.h" +#include +#include + // Entry point. Parses command line arguments and starts server listening int main(int argc, char* argv[]) { std::shared_ptr file_list_handler; @@ -42,16 +46,38 @@ int main(int argc, char* argv[]) { sigaction(SIGINT, &sig_handler, nullptr); // Main - carta::ProgramSettings settings(argc, argv); + auto settings = ProgramSettings::Initialise(argc, argv); if (settings.help || settings.version) { exit(0); } - carta::logger::InitLogger( - settings.no_log, settings.verbosity, settings.log_performance, settings.log_protocol_messages, settings.user_directory); + carta::logger::InitLogger(); settings.FlushMessages(); // flush log messages produced during Program Settings setup + // Send casacore log messages (global and local) to sink. + // CartaLogSink sends messages to spdlog, NullLogSink discards messages. + casacore::LogSinkInterface* carta_log_sink(nullptr); + switch (settings.verbosity) { + case 0: + carta_log_sink = new casacore::NullLogSink(); + break; + case 1: + case 2: + carta_log_sink = new CartaLogSink(casacore::LogMessage::SEVERE); + break; + case 3: + carta_log_sink = new CartaLogSink(casacore::LogMessage::WARN); + break; + case 4: + case 5: + default: + carta_log_sink = new CartaLogSink(casacore::LogMessage::NORMAL); + } + casacore::LogSink log_sink(carta_log_sink->filter(), std::shared_ptr(carta_log_sink)); + casacore::LogSink::globalSink(carta_log_sink); + casacore::LogIO casacore_log(log_sink); + if (settings.wait_time >= 0) { Session::SetExitTimeout(settings.wait_time); } diff --git a/src/Main/ProgramSettings.cc b/src/Main/ProgramSettings.cc index 4ea949a5c..71f8a1cf4 100644 --- a/src/Main/ProgramSettings.cc +++ b/src/Main/ProgramSettings.cc @@ -1,5 +1,5 @@ /* This file is part of the CARTA Image Viewer: https://github.com/CARTAvis/carta-backend - Copyright 2018-2022 Academia Sinica Institute of Astronomy and Astrophysics (ASIAA), + Copyright 2018- Academia Sinica Institute of Astronomy and Astrophysics (ASIAA), Associated Universities, Inc. (AUI) and the Inter-University Institute for Data Intensive Astronomy (IDIA) SPDX-License-Identifier: GPL-3.0-or-later */ @@ -422,12 +422,4 @@ void ProgramSettings::AddDeprecationWarning(const std::string& option, std::stri warning_msgs.push_back(fmt::format("Option {} found in {} is deprecated. {}", option, where, message)); } -bool ProgramSettings::operator!=(const ProgramSettings& rhs) const { - return GetTuple() != rhs.GetTuple(); -} - -bool ProgramSettings::operator==(const ProgramSettings& rhs) const { - return GetTuple() == rhs.GetTuple(); -} - } // namespace carta diff --git a/src/Main/ProgramSettings.h b/src/Main/ProgramSettings.h index 31771300c..313ef1c0d 100644 --- a/src/Main/ProgramSettings.h +++ b/src/Main/ProgramSettings.h @@ -1,11 +1,11 @@ /* This file is part of the CARTA Image Viewer: https://github.com/CARTAvis/carta-backend - Copyright 2018-2022 Academia Sinica Institute of Astronomy and Astrophysics (ASIAA), + Copyright 2018- Academia Sinica Institute of Astronomy and Astrophysics (ASIAA), Associated Universities, Inc. (AUI) and the Inter-University Institute for Data Intensive Astronomy (IDIA) SPDX-License-Identifier: GPL-3.0-or-later */ -#ifndef CARTA_BACKEND_SRC_MAIN_PROGRAMSETTINGS_H_ -#define CARTA_BACKEND_SRC_MAIN_PROGRAMSETTINGS_H_ +#ifndef CARTA_SRC_MAIN_PROGRAMSETTINGS_H_ +#define CARTA_SRC_MAIN_PROGRAMSETTINGS_H_ #include #include @@ -31,6 +31,15 @@ namespace carta { struct ProgramSettings { + static ProgramSettings& GetInstance() { + static ProgramSettings settings; + return settings; + } + + static ProgramSettings& Initialise(int argc, char** argv) { + return GetInstance() = std::move(carta::ProgramSettings(argc, argv)); + } + bool version = false; bool help = false; std::vector port; @@ -122,15 +131,6 @@ struct ProgramSettings { void SetSettingsFromJSON(const nlohmann::json& j); void PushFilePaths(); - // TODO: this is outdated. It's used by the equality operator, which is used by a test. - auto GetTuple() const { - return std::tie(help, version, port, omp_thread_count, top_level_folder, starting_folder, host, files, frontend_folder, no_http, - no_browser, no_log, log_performance, log_protocol_messages, debug_no_auth, controller_deployment, verbosity, wait_time, - init_wait_time, idle_session_wait_time); - } - bool operator!=(const ProgramSettings& rhs) const; - bool operator==(const ProgramSettings& rhs) const; - std::vector warning_msgs; std::vector debug_msgs; void FlushMessages() { @@ -141,4 +141,4 @@ struct ProgramSettings { } }; } // namespace carta -#endif // CARTA_BACKEND_SRC_MAIN_PROGRAMSETTINGS_H_ +#endif // CARTA_SRC_MAIN_PROGRAMSETTINGS_H_ diff --git a/src/Main/WebBrowser.cc b/src/Main/WebBrowser.cc index 55c555f8f..06ff09a15 100644 --- a/src/Main/WebBrowser.cc +++ b/src/Main/WebBrowser.cc @@ -1,5 +1,5 @@ /* This file is part of the CARTA Image Viewer: https://github.com/CARTAvis/carta-backend - Copyright 2018-2022 Academia Sinica Institute of Astronomy and Astrophysics (ASIAA), + Copyright 2018- Academia Sinica Institute of Astronomy and Astrophysics (ASIAA), Associated Universities, Inc. (AUI) and the Inter-University Institute for Data Intensive Astronomy (IDIA) SPDX-License-Identifier: GPL-3.0-or-later */ diff --git a/src/Main/WebBrowser.h b/src/Main/WebBrowser.h index 0bc35a719..566886c96 100644 --- a/src/Main/WebBrowser.h +++ b/src/Main/WebBrowser.h @@ -1,11 +1,11 @@ /* This file is part of the CARTA Image Viewer: https://github.com/CARTAvis/carta-backend - Copyright 2018-2022 Academia Sinica Institute of Astronomy and Astrophysics (ASIAA), + Copyright 2018- Academia Sinica Institute of Astronomy and Astrophysics (ASIAA), Associated Universities, Inc. (AUI) and the Inter-University Institute for Data Intensive Astronomy (IDIA) SPDX-License-Identifier: GPL-3.0-or-later */ -#ifndef CARTA_BACKEND_SRC_SESSIONMANAGER_WEBBROWSER_H_ -#define CARTA_BACKEND_SRC_SESSIONMANAGER_WEBBROWSER_H_ +#ifndef CARTA_SRC_MAIN_WEBBROWSER_H_ +#define CARTA_SRC_MAIN_WEBBROWSER_H_ #include #include @@ -36,4 +36,4 @@ class WebBrowser { } // namespace carta -#endif // CARTA_BACKEND_SRC_SESSIONMANAGER_WEBBROWSER_H_ +#endif // CARTA_SRC_MAIN_WEBBROWSER_H_ diff --git a/src/Region/CrtfImportExport.cc b/src/Region/CrtfImportExport.cc index 4a883fa5a..ba3957628 100644 --- a/src/Region/CrtfImportExport.cc +++ b/src/Region/CrtfImportExport.cc @@ -1,5 +1,5 @@ /* This file is part of the CARTA Image Viewer: https://github.com/CARTAvis/carta-backend - Copyright 2018-2022 Academia Sinica Institute of Astronomy and Astrophysics (ASIAA), + Copyright 2018- Academia Sinica Institute of Astronomy and Astrophysics (ASIAA), Associated Universities, Inc. (AUI) and the Inter-University Institute for Data Intensive Astronomy (IDIA) SPDX-License-Identifier: GPL-3.0-or-later */ @@ -451,7 +451,6 @@ void CrtfImportExport::ProcessFileLines(std::vector& lines) { std::vector parameters; std::unordered_map properties; ParseRegionParameters(line, parameters, properties); - // Coordinate frame for world coordinates conversion RegionState region_state; CARTA::RegionStyle region_style; @@ -499,7 +498,13 @@ void CrtfImportExport::ProcessFileLines(std::vector& lines) { if (region == "text") { // Set text label for text region if (parameters.size() == 4) { // text, x, y, "text" - region_style.mutable_annotation_style()->set_text_label0(parameters[3]); + std::string label(parameters[3]); + if (!label.empty() && + ((label.front() == '"' && label.back() == '"') || (label.front() == '\'' && label.back() == '\''))) { + label.pop_back(); + label = label.substr(1); + } + region_style.mutable_annotation_style()->set_text_label0(label); } } else { // Set text position for textbox region diff --git a/src/Region/CrtfImportExport.h b/src/Region/CrtfImportExport.h index b3135541f..ec89b2b9b 100644 --- a/src/Region/CrtfImportExport.h +++ b/src/Region/CrtfImportExport.h @@ -1,13 +1,13 @@ /* This file is part of the CARTA Image Viewer: https://github.com/CARTAvis/carta-backend - Copyright 2018-2022 Academia Sinica Institute of Astronomy and Astrophysics (ASIAA), + Copyright 2018- Academia Sinica Institute of Astronomy and Astrophysics (ASIAA), Associated Universities, Inc. (AUI) and the Inter-University Institute for Data Intensive Astronomy (IDIA) SPDX-License-Identifier: GPL-3.0-or-later */ //# CrtfImportExport.h: handle CRTF region file import and export -#ifndef CARTA_BACKEND_REGION_CRTFIMPORTEXPORT_H_ -#define CARTA_BACKEND_REGION_CRTFIMPORTEXPORT_H_ +#ifndef CARTA_SRC_REGION_CRTFIMPORTEXPORT_H_ +#define CARTA_SRC_REGION_CRTFIMPORTEXPORT_H_ #include #include @@ -101,4 +101,4 @@ class CrtfImportExport : public RegionImportExport { } // namespace carta -#endif // CARTA_BACKEND_REGION_CRTFIMPORTEXPORT_H_ +#endif // CARTA_SRC_REGION_CRTFIMPORTEXPORT_H_ diff --git a/src/Region/Ds9ImportExport.cc b/src/Region/Ds9ImportExport.cc index 47a39c103..89bc2c6d8 100644 --- a/src/Region/Ds9ImportExport.cc +++ b/src/Region/Ds9ImportExport.cc @@ -1,5 +1,5 @@ /* This file is part of the CARTA Image Viewer: https://github.com/CARTAvis/carta-backend - Copyright 2018-2022 Academia Sinica Institute of Astronomy and Astrophysics (ASIAA), + Copyright 2018- Academia Sinica Institute of Astronomy and Astrophysics (ASIAA), Associated Universities, Inc. (AUI) and the Inter-University Institute for Data Intensive Astronomy (IDIA) SPDX-License-Identifier: GPL-3.0-or-later */ diff --git a/src/Region/Ds9ImportExport.h b/src/Region/Ds9ImportExport.h index cea3146e2..3a6771b54 100644 --- a/src/Region/Ds9ImportExport.h +++ b/src/Region/Ds9ImportExport.h @@ -1,13 +1,13 @@ /* This file is part of the CARTA Image Viewer: https://github.com/CARTAvis/carta-backend - Copyright 2018-2022 Academia Sinica Institute of Astronomy and Astrophysics (ASIAA), + Copyright 2018- Academia Sinica Institute of Astronomy and Astrophysics (ASIAA), Associated Universities, Inc. (AUI) and the Inter-University Institute for Data Intensive Astronomy (IDIA) SPDX-License-Identifier: GPL-3.0-or-later */ //# Ds9ImportExport.h: handle DS9 region file import and export -#ifndef CARTA_BACKEND_REGION_DS9IMPORTEXPORT_H_ -#define CARTA_BACKEND_REGION_DS9IMPORTEXPORT_H_ +#ifndef CARTA_SRC_REGION_DS9IMPORTEXPORT_H_ +#define CARTA_SRC_REGION_DS9IMPORTEXPORT_H_ #include #include @@ -105,4 +105,4 @@ class Ds9ImportExport : public RegionImportExport { } // namespace carta -#endif // CARTA_BACKEND_REGION_DS9IMPORTEXPORT_H_ +#endif // CARTA_SRC_REGION_DS9IMPORTEXPORT_H_ diff --git a/src/Region/LineBoxRegions.cc b/src/Region/LineBoxRegions.cc index 112fa2441..f0e893483 100644 --- a/src/Region/LineBoxRegions.cc +++ b/src/Region/LineBoxRegions.cc @@ -1,5 +1,5 @@ /* This file is part of the CARTA Image Viewer: https://github.com/CARTAvis/carta-backend - Copyright 2018-2022 Academia Sinica Institute of Astronomy and Astrophysics (ASIAA), + Copyright 2018- Academia Sinica Institute of Astronomy and Astrophysics (ASIAA), Associated Universities, Inc. (AUI) and the Inter-University Institute for Data Intensive Astronomy (IDIA) SPDX-License-Identifier: GPL-3.0-or-later */ @@ -11,6 +11,7 @@ #include #include "Logger/Logger.h" +#include "Util/Message.h" namespace carta { @@ -232,10 +233,11 @@ double LineBoxRegions::GetPointSeparation( std::shared_ptr coord_sys, const std::vector& point1, const std::vector& point2) { // Returns angular separation in arcsec. Both points must be inside image or returns zero (use GetWorldLength instead, not as accurate). double separation(0.0); + casacore::Vector const point1_v(point1), point2_v(point2); std::lock_guard guard(_mvdir_mutex); try { - casacore::MVDirection mvdir1 = coord_sys->directionCoordinate().toWorld(point1); - casacore::MVDirection mvdir2 = coord_sys->directionCoordinate().toWorld(point2); + casacore::MVDirection mvdir1 = coord_sys->directionCoordinate().toWorld(point1_v); + casacore::MVDirection mvdir2 = coord_sys->directionCoordinate().toWorld(point2_v); separation = mvdir1.separation(mvdir2, "arcsec").getValue(); } catch (casacore::AipsError& err) { // invalid pixel coordinates - outside image diff --git a/src/Region/LineBoxRegions.h b/src/Region/LineBoxRegions.h index 873f4d7ad..c5f7edd9b 100644 --- a/src/Region/LineBoxRegions.h +++ b/src/Region/LineBoxRegions.h @@ -1,13 +1,13 @@ /* This file is part of the CARTA Image Viewer: https://github.com/CARTAvis/carta-backend - Copyright 2018-2022 Academia Sinica Institute of Astronomy and Astrophysics (ASIAA), + Copyright 2018- Academia Sinica Institute of Astronomy and Astrophysics (ASIAA), Associated Universities, Inc. (AUI) and the Inter-University Institute for Data Intensive Astronomy (IDIA) SPDX-License-Identifier: GPL-3.0-or-later */ // LineBoxRegions.h: class to approximate line with width as series of box regions -#ifndef CARTA_BACKEND_REGION_LINEBOXREGIONS_H_ -#define CARTA_BACKEND_REGION_LINEBOXREGIONS_H_ +#ifndef CARTA_SRC_REGION_LINEBOXREGIONS_H_ +#define CARTA_SRC_REGION_LINEBOXREGIONS_H_ #include @@ -55,4 +55,4 @@ class LineBoxRegions { } // namespace carta -#endif // CARTA_BACKEND_REGION_LINEBOXREGIONS_H_ +#endif // CARTA_SRC_REGION_LINEBOXREGIONS_H_ diff --git a/src/Region/Region.cc b/src/Region/Region.cc index d855c1c75..daf47ffd3 100644 --- a/src/Region/Region.cc +++ b/src/Region/Region.cc @@ -1,5 +1,5 @@ /* This file is part of the CARTA Image Viewer: https://github.com/CARTAvis/carta-backend - Copyright 2018-2022 Academia Sinica Institute of Astronomy and Astrophysics (ASIAA), + Copyright 2018- Academia Sinica Institute of Astronomy and Astrophysics (ASIAA), Associated Universities, Inc. (AUI) and the Inter-University Institute for Data Intensive Astronomy (IDIA) SPDX-License-Identifier: GPL-3.0-or-later */ @@ -8,28 +8,15 @@ #include "Region.h" -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include #include -#include -#include +#include -#include "../Logger/Logger.h" -#include "Util/Image.h" +#include "RegionConverter.h" using namespace carta; Region::Region(const RegionState& state, std::shared_ptr csys) - : _coord_sys(csys), _valid(false), _region_changed(false), _reference_region_set(false), _region_state(state) { + : _coord_sys(csys), _valid(false), _region_changed(false), _lcregion_set(false), _region_state(state) { _valid = CheckPoints(state.control_points, state.type); } @@ -60,12 +47,10 @@ bool Region::UpdateRegion(const RegionState& new_state) { void Region::ResetRegionCache() { // Invalid when region changes - _reference_region_set = false; - std::lock_guard guard(_region_mutex); - _wcs_control_points.clear(); - _reference_region.reset(); - _applied_regions.clear(); - _polygon_regions.clear(); + std::lock_guard guard(_lcregion_mutex); + _lcregion.reset(); + _lcregion_set = false; + _region_converter.reset(); } // ************************************************************************* @@ -128,6 +113,7 @@ bool Region::PointsFinite(const std::vector& points) { return points_finite; } +// ************************************************************************* // Region connection state (disconnected when region closed) bool Region::IsConnected() { @@ -139,644 +125,94 @@ void Region::WaitForTaskCancellation() { // to interrupt the running jobs in the std::unique_lock lock(GetActiveTaskMutex()); } -// ****************************************************************************************** -// Apply region to reference image in world coordinates (WCRegion) and save wcs control points - -bool Region::ReferenceRegionValid() { - return _reference_region_set && bool(_reference_region); -} - -void Region::SetReferenceRegion() { - // Create WCRegion (world coordinate region) in the reference image according to type using wcs control points - // Sets _reference_region (maybe to nullptr) or _reference_annotation - casacore::WCRegion* region(nullptr); - auto region_state = GetRegionState(); - std::vector pixel_points(region_state.control_points); - std::vector world_points; // point holder; one CARTA point is two world points (x, y) - casacore::IPosition pixel_axes(2, 0, 1); - casacore::Vector abs_rel; - auto type(region_state.type); - try { - switch (type) { - case CARTA::POINT: - case CARTA::ANNPOINT: { - if (ConvertCartaPointToWorld(pixel_points[0], _wcs_control_points)) { - // WCBox blc and trc are same point - std::lock_guard guard(_region_mutex); - region = new casacore::WCBox(_wcs_control_points, _wcs_control_points, pixel_axes, *_coord_sys, abs_rel); - } - break; - } - case CARTA::RECTANGLE: // 4 corners - case CARTA::POLYGON: // vertices - case CARTA::ANNRECTANGLE: // 4 corners - case CARTA::ANNPOLYGON: // vertices - case CARTA::ANNTEXT: { // 4 corners of text box - if (type == CARTA::RECTANGLE || type == CARTA::ANNRECTANGLE || type == CARTA::ANNTEXT) { - if (!RectanglePointsToWorld(pixel_points, _wcs_control_points)) { - _wcs_control_points.clear(); - } - } else { - for (auto& point : pixel_points) { - if (ConvertCartaPointToWorld(point, world_points)) { - _wcs_control_points.push_back(world_points[0]); - _wcs_control_points.push_back(world_points[1]); - } else { - _wcs_control_points.clear(); - break; - } - } - } - - if (!_wcs_control_points.empty()) { - // separate x and y in control points, convert from Vector to Quantum - std::vector x, y; - for (size_t i = 0; i < _wcs_control_points.size(); i += 2) { - x.push_back(_wcs_control_points[i].getValue()); - y.push_back(_wcs_control_points[i + 1].getValue()); - } - casacore::Vector vx(x), vy(y); - casacore::Quantum> qx, qy; - - casacore::Vector world_units = _coord_sys->worldAxisUnits(); - - qx = vx; // set values - qx.setUnit(world_units(0)); // set unit - qy = vy; // set values - qy.setUnit(world_units(1)); // set unit - - std::lock_guard guard(_region_mutex); - region = new casacore::WCPolygon(qx, qy, pixel_axes, *_coord_sys); - } - break; - } - case CARTA::ELLIPSE: - case CARTA::ANNELLIPSE: // [(cx, cy), (bmaj, bmin)] - case CARTA::ANNCOMPASS: { // [(cx, cy), (length, length)} - float ellipse_rotation; - if (EllipsePointsToWorld(pixel_points, _wcs_control_points, ellipse_rotation)) { - // control points are in order: xcenter, ycenter, major axis, minor axis - casacore::Quantity theta(ellipse_rotation, "deg"); - theta.convert("rad"); - std::lock_guard guard(_region_mutex); - region = new casacore::WCEllipsoid(_wcs_control_points[0], _wcs_control_points[1], _wcs_control_points[2], - _wcs_control_points[3], theta, 0, 1, *_coord_sys); - } - break; - } - default: - break; - } - } catch (casacore::AipsError& err) { // region failed - spdlog::error("{} region failed: {}", RegionName(type), err.getMesg()); - } - - std::shared_ptr shared_region = std::shared_ptr(region); - std::atomic_store(&_reference_region, shared_region); - - // Flag indicates that attempt was made, to avoid repeated attempts - _reference_region_set = true; -} - -bool Region::RectanglePointsToWorld(std::vector& pixel_points, std::vector& wcs_points) { - // Convert CARTA rectangle points (cx, cy), (width, height) to world coordinates - if (pixel_points.size() != 2) { - return false; - } - - // Get 4 corner points in pixel coordinates - float rotation(GetRegionState().rotation); - casacore::Vector x, y; - RectanglePointsToCorners(pixel_points, rotation, x, y); - - // Convert corners to wcs in one call for efficiency - size_t num_points(x.size()), num_axes(_coord_sys->nPixelAxes()); - casacore::Matrix pixel_coords(num_axes, num_points); - casacore::Matrix world_coords(num_axes, num_points); - pixel_coords = 0.0; - pixel_coords.row(0) = x; - pixel_coords.row(1) = y; - casacore::Vector failures; - if (!_coord_sys->toWorldMany(world_coords, pixel_coords, failures)) { - return false; - } - - // Save x and y values as Quantities - casacore::Vector world_units = _coord_sys->worldAxisUnits(); - casacore::Vector x_wcs = world_coords.row(0); - casacore::Vector y_wcs = world_coords.row(1); - // reference points: corners (x0, y0, x1, y1, x2, y2, x3, y3) in world coordinates - wcs_points.resize(num_points * 2); - for (int i = 0; i < num_points; ++i) { - wcs_points[i * 2] = casacore::Quantity(x_wcs(i), world_units(0)); - wcs_points[(i * 2) + 1] = casacore::Quantity(y_wcs(i), world_units(1)); - } - return true; -} - -void Region::RectanglePointsToCorners( - std::vector& pixel_points, float rotation, casacore::Vector& x, casacore::Vector& y) { - // Convert rectangle control points to 4 corner points - float center_x(pixel_points[0].x()), center_y(pixel_points[0].y()); - float width(pixel_points[1].x()), height(pixel_points[1].y()); - x.resize(4); - y.resize(4); - - if (rotation == 0.0) { - float x_min(center_x - width / 2.0f), x_max(center_x + width / 2.0f); - float y_min(center_y - height / 2.0f), y_max(center_y + height / 2.0f); - // Bottom left - x(0) = x_min; - y(0) = y_min; - // Bottom right - x(1) = x_max; - y(1) = y_min; - // Top right - x(2) = x_max; - y(2) = y_max; - // Top left - x(3) = x_min; - y(3) = y_max; - } else { - // Apply rotation matrix to get width and height vectors in rotated basis - float cos_x = cos(rotation * M_PI / 180.0f); - float sin_x = sin(rotation * M_PI / 180.0f); - float width_vector_x = cos_x * width; - float width_vector_y = sin_x * width; - float height_vector_x = -sin_x * height; - float height_vector_y = cos_x * height; - - // Bottom left - x(0) = center_x + (-width_vector_x - height_vector_x) / 2.0f; - y(0) = center_y + (-width_vector_y - height_vector_y) / 2.0f; - // Bottom right - x(1) = center_x + (width_vector_x - height_vector_x) / 2.0f; - y(1) = center_y + (width_vector_y - height_vector_y) / 2.0f; - // Top right - x(2) = center_x + (width_vector_x + height_vector_x) / 2.0f; - y(2) = center_y + (width_vector_y + height_vector_y) / 2.0f; - // Top left - x(3) = center_x + (-width_vector_x + height_vector_x) / 2.0f; - y(3) = center_y + (-width_vector_y + height_vector_y) / 2.0f; - } -} - -bool Region::EllipsePointsToWorld( - std::vector& pixel_points, std::vector& wcs_points, float& ellipse_rotation) { - // Convert CARTA ellipse points (cx, cy), (bmaj, bmin) to world coordinates - if (pixel_points.size() != 2) { - return false; - } - - std::vector center_world; - if (!ConvertCartaPointToWorld(pixel_points[0], center_world)) { - return false; - } - - // Store center point quantities - wcs_points = center_world; - - // Convert bmaj, bmin from pixel length to world length - float bmaj(pixel_points[1].x()), bmin(pixel_points[1].y()); - casacore::Quantity bmaj_world = _coord_sys->toWorldLength(bmaj, 0); - casacore::Quantity bmin_world = _coord_sys->toWorldLength(bmin, 1); - ellipse_rotation = GetRegionState().rotation; - - // Check if bmaj/bmin units conform (false for pV image: arcsec and Hz) - if (!bmaj_world.isConform(bmin_world.getUnit())) { - return false; - } - - // bmaj > bmin (world coords) required for WCEllipsoid; adjust rotation angle - if (bmaj_world > bmin_world) { - // carta rotation is from y-axis, ellipse rotation is from x-axis - ellipse_rotation += 90.0; - } else { - // swapping takes care of 90 deg adjustment - std::swap(bmaj_world, bmin_world); - } - - wcs_points.push_back(bmaj_world); - wcs_points.push_back(bmin_world); - return true; +std::shared_mutex& Region::GetActiveTaskMutex() { + return _active_task_mutex; } // ************************************************************************* -// Apply region to any image +// Apply region to image and return LCRegion, mask or Record -std::shared_ptr Region::GetImageRegion(int file_id, std::shared_ptr output_csys, - const casacore::IPosition& output_shape, const StokesSource& stokes_source, bool report_error) { - // Apply region to non-reference image as converted polygon vertices - // Will return nullptr if outside image or is not a closed LCRegion (line or polyline) - std::shared_ptr lc_region; - if (IsAnnotation()) { - return lc_region; - } +std::shared_ptr Region::GetImageRegion(int file_id, std::shared_ptr csys, + const casacore::IPosition& image_shape, const StokesSource& stokes_source, bool report_error) { + // Return lattice-coordinate region applied to image and/or computed stokes. + // Returns nullptr if is annotation, is not a closed region (line/polyline), or outside image. + std::shared_ptr lcregion; - std::lock_guard guard(_region_approx_mutex); - // The cache of lattice coordinate region is only for the original image (not computed stokes image). In order to avoid the ambiguity - if (stokes_source.IsOriginalImage()) { - lc_region = GetCachedLCRegion(file_id); + if (IsAnnotation() || IsLineType()) { + return lcregion; } - if (!lc_region) { - auto region_state = GetRegionState(); - if (file_id == region_state.reference_file_id) { - // Convert reference WCRegion to LCRegion and cache it - lc_region = GetConvertedLCRegion(file_id, output_csys, output_shape, stokes_source, report_error); - } else { - bool use_polygon = UseApproximatePolygon(output_csys); // check region distortion - - if (!use_polygon) { - // No distortion, do direct region conversion if possible (unless outside image or rotbox) - lc_region = GetConvertedLCRegion(file_id, output_csys, output_shape, stokes_source, report_error); - } - - if (lc_region) { - spdlog::debug("Using direct region conversion for {}", RegionName(region_state.type)); - } else { - // Use polygon approximation of reference region to translate to another image - spdlog::debug("Using polygon approximation for matched {} region", RegionName(region_state.type)); - lc_region = GetAppliedPolygonRegion(file_id, output_csys, output_shape); + // Check cache + lcregion = GetCachedLCRegion(file_id, stokes_source); - // Cache converted polygon - // Only for the original image (not computed stokes image). In order to avoid the ambiguity - if (lc_region && stokes_source.IsOriginalImage()) { - _polygon_regions[file_id] = lc_region; + if (!lcregion) { + if (IsInReferenceImage(file_id)) { + if (!_lcregion_set) { + // Create LCRegion from TableRecord + casacore::TableRecord region_record; + if (GetRegionState().IsRotbox()) { + // ControlPointsRecord is unrotated box for export, apply rotation for LCRegion + region_record = GetRotboxRecordForLCRegion(image_shape); + } else { + region_record = GetControlPointsRecord(image_shape); } - } - } - } - - return lc_region; -} - -bool Region::UseApproximatePolygon(std::shared_ptr output_csys) { - // Determine whether to convert region directly, or approximate it as a polygon in the output image. - bool use_polygon(true); - auto region_state = GetRegionState(); - CARTA::RegionType region_type = region_state.type; - - // Check ellipse and rectangle distortion; always true for other regions (polygon and point) - if ((region_type == CARTA::RegionType::ELLIPSE) || (region_type == CARTA::RegionType::RECTANGLE)) { - CARTA::Point center_point = region_state.control_points[0]; - double x_length(region_state.control_points[1].x()), y_length(region_state.control_points[1].y()); - - // Ratio of vector lengths in reference image region - double ref_length_ratio; - if (region_type == CARTA::RegionType::ELLIPSE) { - ref_length_ratio = x_length / y_length; - } else { - ref_length_ratio = y_length / x_length; - } - - std::vector points; - if (region_type == CARTA::RegionType::ELLIPSE) { - // Make polygon with 4 points - points = GetApproximateEllipsePoints(4); - } else { - // Get midpoints of 4 sides of rectangle - points = GetRectangleMidpoints(); - } - - // add center point; points = [p0, p1, p2, p3, center] - points.push_back(center_point); - - // convert points to output image pixels - casacore::Vector x, y; - if (ConvertPointsToImagePixels(points, output_csys, x, y)) { - // vector0 is (center, p0), vector1 is (center, p1) - auto v0_delta_x = x[0] - x[4]; - auto v0_delta_y = y[0] - y[4]; - auto v1_delta_x = x[1] - x[4]; - auto v1_delta_y = y[1] - y[4]; - - // Ratio of vector lengths in converted region - auto v0_length = sqrt((v0_delta_x * v0_delta_x) + (v0_delta_y * v0_delta_y)); - auto v1_length = sqrt((v1_delta_x * v1_delta_x) + (v1_delta_y * v1_delta_y)); - double converted_length_ratio = v1_length / v0_length; - // Compare reference to converted length ratio - double length_ratio_difference = fabs(ref_length_ratio - converted_length_ratio); - // spdlog::debug("{} distortion check: length ratio difference={:.3e}", RegionName(region_type), length_ratio_difference); - - if (length_ratio_difference < 1e-4) { - // Passed ratio check; check dot product of converted region - double converted_dot_product = (v0_delta_x * v1_delta_x) + (v0_delta_y * v1_delta_y); - // spdlog::debug("{} distortion check: dot product={:.3e}", RegionName(region_type), converted_dot_product); - - if (fabs(converted_dot_product) < 1e-2) { - // passed distortion tests, do not use polygon approximation - use_polygon = false; + try { + lcregion.reset(casacore::LCRegion::fromRecord(region_record, "")); + } catch (const casacore::AipsError& err) { + // Region is outside image } - } - } - } - - return use_polygon; -} - -std::vector Region::GetRectangleMidpoints() { - // Return midpoints of 4 sides of rectangle - // Find corners with rotation: blc, brc, trc, tlc - auto region_state = GetRegionState(); - casacore::Vector x, y; - RectanglePointsToCorners(region_state.control_points, region_state.rotation, x, y); - - std::vector midpoints; - // start with right side brc, trc - midpoints.push_back(Message::Point((x[1] + x[2]) / 2.0, (y[1] + y[2]) / 2.0)); - midpoints.push_back(Message::Point((x[2] + x[3]) / 2.0, (y[2] + y[3]) / 2.0)); - midpoints.push_back(Message::Point((x[3] + x[0]) / 2.0, (y[3] + y[0]) / 2.0)); - midpoints.push_back(Message::Point((x[0] + x[1]) / 2.0, (y[0] + y[1]) / 2.0)); + _lcregion_set = true; - return midpoints; -} - -std::shared_ptr Region::GetCachedPolygonRegion(int file_id) { - // Return cached polygon region applied to image with file_id - if (_polygon_regions.count(file_id)) { - std::lock_guard guard(_region_mutex); - return _polygon_regions.at(file_id); - } - - return std::shared_ptr(); -} - -std::shared_ptr Region::GetAppliedPolygonRegion( - int file_id, std::shared_ptr output_csys, const casacore::IPosition& output_shape) { - // Approximate region as polygon pixel vertices, and convert to given csys - std::shared_ptr lc_region; - - auto region_state = GetRegionState(); - bool is_point(region_state.type == CARTA::RegionType::POINT); - size_t nvertices(is_point ? 1 : DEFAULT_VERTEX_COUNT); - - // Set reference region as polygon vertices - auto polygon_points = GetReferencePolygonPoints(nvertices); - if (polygon_points.empty()) { - return lc_region; - } - - // Set x and y for matched polygon region - casacore::Vector x, y; - - if (polygon_points.size() == 1) { - // Point and ellipse have one vector for all points - if (!ConvertPointsToImagePixels(polygon_points[0], output_csys, x, y)) { - spdlog::error("Error approximating {} as polygon in matched image.", RegionName(region_state.type)); - return lc_region; - } - - if (!is_point) { - RemoveHorizontalPolygonPoints(x, y); - } - } else { - // Rectangle and polygon have one vector for each side of rectangle or segment of original polygon - for (auto& segment : polygon_points) { - casacore::Vector segment_x, segment_y; - if (!ConvertPointsToImagePixels(segment, output_csys, segment_x, segment_y)) { - spdlog::error("Error approximating {} as polygon in matched image.", RegionName(region_state.type)); - return lc_region; - } - - // For each polygon segment, if horizontal then remove points to fix LCPolygon mask - RemoveHorizontalPolygonPoints(segment_x, segment_y); - - auto old_size = x.size(); - x.resize(old_size + segment_x.size(), true); - y.resize(old_size + segment_y.size(), true); - - // Append selected segment points - for (auto i = 0; i < segment_x.size(); ++i) { - x[old_size + i] = segment_x[i]; - y[old_size + i] = segment_y[i]; - } - } - } - - try { - if (is_point) { - // Point is not a polygon (needs at least 3 points), use LCBox instead - // Form blc, trc - size_t ndim(output_shape.size()); - casacore::Vector blc(ndim, 0.0), trc(ndim); - blc(0) = x(0); - blc(1) = y(0); - trc(0) = x(0); - trc(1) = y(0); - - for (size_t i = 2; i < ndim; ++i) { - trc(i) = output_shape(i) - 1; + // Cache LCRegion + if (lcregion && stokes_source.IsOriginalImage()) { + std::lock_guard guard(_lcregion_mutex); + _lcregion = lcregion; + } } - - lc_region.reset(new casacore::LCBox(blc, trc, output_shape)); } else { - // Need 2-dim shape - casacore::IPosition keep_axes(2, 0, 1); - casacore::IPosition region_shape(output_shape.keepAxes(keep_axes)); - lc_region.reset(new casacore::LCPolygon(x, y, region_shape)); + if (!_region_converter) { + _region_converter.reset(new RegionConverter(GetRegionState(), _coord_sys)); + } + return _region_converter->GetImageRegion(file_id, csys, image_shape, stokes_source, report_error); } - } catch (const casacore::AipsError& err) { - spdlog::error("Cannot apply {} to file {}: {}", RegionName(region_state.type), file_id, err.getMesg()); } - return lc_region; + return lcregion; } -std::vector> Region::GetReferencePolygonPoints(int num_vertices) { - // Approximates reference region as polygon with input number of vertices. - // Sets _polygon_control_points in reference image pixel coordinates. - // Returns points as long as region type is supported and a closed region. - auto region_state = GetRegionState(); - switch (region_state.type) { - case CARTA::POINT: { - std::vector> points; - points.push_back(region_state.control_points); - return points; - } - case CARTA::RECTANGLE: - case CARTA::POLYGON: { - return GetApproximatePolygonPoints(num_vertices); - } - case CARTA::ELLIPSE: { - std::vector> points; - points.push_back(GetApproximateEllipsePoints(num_vertices)); - return points; - } - default: - return {}; +std::shared_ptr Region::GetCachedLCRegion(int file_id, const StokesSource& stokes_source) { + // Return cached LCRegion applied to image + std::shared_ptr lcregion; + if (!stokes_source.IsOriginalImage()) { + return lcregion; } -} - -std::vector> Region::GetApproximatePolygonPoints(int num_vertices) { - // Approximate RECTANGLE or POLYGON region as polygon with num_vertices. - // Returns vector of points for each segment - std::vector> polygon_points; - auto region_state = GetRegionState(); - std::vector region_points; - CARTA::RegionType region_type(region_state.type); - - if (region_type == CARTA::RegionType::RECTANGLE) { - // convert control points to corners to create 4-point polygon - casacore::Vector x, y; - RectanglePointsToCorners(region_state.control_points, region_state.rotation, x, y); - - for (size_t i = 0; i < x.size(); ++i) { - region_points.push_back(Message::Point(x(i), y(i))); - } - } else if (region_type == CARTA::RegionType::POLYGON) { - region_points = region_state.control_points; + if (IsInReferenceImage(file_id)) { + // Return _lcregion even if unassigned + std::lock_guard guard(_lcregion_mutex); + return _lcregion; } else { - spdlog::error("Error approximating {} as polygon: region type not supported", RegionName(region_state.type)); - return polygon_points; - } - - // Close polygon - CARTA::Point first_point(region_points[0]); - region_points.push_back(first_point); - - double total_length = GetTotalSegmentLength(region_points); - double target_segment_length = total_length / num_vertices; - - // Divide each region polygon segment into target number of segments with target length - for (size_t i = 1; i < region_points.size(); ++i) { - // Handle segment from point[i-1] to point[i] - std::vector segment_points; - - auto delta_x = region_points[i].x() - region_points[i - 1].x(); - auto delta_y = region_points[i].y() - region_points[i - 1].y(); - auto segment_length = sqrt((delta_x * delta_x) + (delta_y * delta_y)); - auto dir_x = delta_x / segment_length; - auto dir_y = delta_y / segment_length; - auto target_nsegment = round(segment_length / target_segment_length); - auto target_length = segment_length / target_nsegment; - - auto first_segment_point(region_points[i - 1]); - segment_points.push_back(first_segment_point); - - auto first_x(first_segment_point.x()); - auto first_y(first_segment_point.y()); - - for (size_t j = 1; j < target_nsegment; ++j) { - auto length_from_first = j * target_length; - auto x_offset = dir_x * length_from_first; - auto y_offset = dir_y * length_from_first; - segment_points.push_back(Message::Point(first_x + x_offset, first_y + y_offset)); - } - - polygon_points.push_back(segment_points); - } - - return polygon_points; -} - -std::vector Region::GetApproximateEllipsePoints(int num_vertices) { - // Approximate ELLIPSE region as polygon with num_vertices, return points - std::vector polygon_points; - auto region_state = GetRegionState(); - - auto cx = region_state.control_points[0].x(); - auto cy = region_state.control_points[0].y(); - auto bmaj = region_state.control_points[1].x(); - auto bmin = region_state.control_points[1].y(); - - auto delta_theta = 2.0 * M_PI / num_vertices; - auto rotation = region_state.rotation * M_PI / 180.0; - auto cos_rotation = cos(rotation); - auto sin_rotation = sin(rotation); - - for (int i = 0; i < num_vertices; ++i) { - auto theta = i * delta_theta; - auto rot_bmin = bmin * cos(theta); - auto rot_bmaj = bmaj * sin(theta); - - auto x_offset = (cos_rotation * rot_bmin) - (sin_rotation * rot_bmaj); - auto y_offset = (sin_rotation * rot_bmin) + (cos_rotation * rot_bmaj); - - polygon_points.push_back(Message::Point(cx + x_offset, cy + y_offset)); - } - - return polygon_points; -} - -double Region::GetTotalSegmentLength(std::vector& points) { - // Accumulate length of each point-to-point segment; returns total length. - double total_length(0.0); - - for (size_t i = 1; i < points.size(); ++i) { - auto delta_x = points[i].x() - points[i - 1].x(); - auto delta_y = points[i].y() - points[i - 1].y(); - total_length += sqrt((delta_x * delta_x) + (delta_y * delta_y)); - } - - return total_length; -} - -bool Region::ConvertPointsToImagePixels(const std::vector& points, std::shared_ptr output_csys, - casacore::Vector& x, casacore::Vector& y) { - // Convert pixel coords in reference image (points) to pixel coords in output image - // Coordinates returned in x and y vectors - bool converted(true); - - try { - // Convert each pixel point to output csys pixel point - size_t npoints(points.size()); - x.resize(npoints); - y.resize(npoints); - for (auto i = 0; i < npoints; ++i) { - // Convert pixel to world in reference csys - std::vector world_point; // [x, y] - if (ConvertCartaPointToWorld(points[i], world_point)) { - // Convert reference world to output csys pixel - casacore::Vector pixel_point; // [x, y] - if (ConvertWorldToPixel(world_point, output_csys, pixel_point)) { - x(i) = pixel_point(0); - y(i) = pixel_point(1); - } else { // world to pixel failed - spdlog::error("Error converting region to output image pixel coords."); - converted = false; - break; - } - } else { // pixel to world failed - spdlog::error("Error converting region to reference image world coords."); - converted = false; - break; - } + if (!_region_converter) { + _region_converter.reset(new RegionConverter(GetRegionState(), _coord_sys)); } - } catch (const casacore::AipsError& err) { - spdlog::error("Error converting region to output image: {}", err.getMesg()); - converted = false; + return _region_converter->GetCachedLCRegion(file_id); } - return converted; + // Not cached + return lcregion; } casacore::ArrayLattice Region::GetImageRegionMask(int file_id) { - // Return pixel mask for this region; requires that lcregion for this file id has been set. - // Otherwise mask is empty array. + // Return pixel mask for region applied to image. + // Requires that LCRegion for this file id has been set and cached (via GetImageRegion), + // else returns empty mask. casacore::ArrayLattice mask; - if (IsAnnotation()) { - return mask; - } - - std::shared_ptr lcregion; - if ((file_id == GetRegionState().reference_file_id) && _applied_regions.count(file_id)) { - if (_applied_regions.at(file_id)) { - std::lock_guard guard(_region_mutex); - lcregion = _applied_regions.at(file_id); - } - } else if (_polygon_regions.count(file_id)) { - if (_polygon_regions.at(file_id)) { - std::lock_guard guard(_region_mutex); - lcregion = _polygon_regions.at(file_id); - } - } + auto stokes_source = StokesSource(); + auto lcregion = GetCachedLCRegion(file_id, stokes_source); if (lcregion) { - std::lock_guard guard(_region_mutex); - // Region can either be an extension region or a fixed region, depending on whether image is matched or not + // LCRegion is an extension region or a fixed region, depending on whether image is reference or matched. auto extended_region = dynamic_cast(lcregion.get()); if (extended_region) { auto& fixed_region = static_cast(extended_region->region()); @@ -792,188 +228,75 @@ casacore::ArrayLattice Region::GetImageRegionMask(int file_id) { return mask; } -// *************************************************************** -// Apply region to any image and return LCRegion Record for export - casacore::TableRecord Region::GetImageRegionRecord( - int file_id, std::shared_ptr output_csys, const casacore::IPosition& output_shape) { - // Return Record describing Region applied to output coord sys and image_shape in pixel coordinates + int file_id, std::shared_ptr csys, const casacore::IPosition& image_shape) { + // Return Record describing any region type applied to image, in pixel coordinates. + // Can be a line or annotation region. Rotated box is a polygon describing corners *without rotation*, for export. casacore::TableRecord record; - if (IsLineType()) { - record = GetLineRecord(file_id, output_csys, output_shape); + if (IsInReferenceImage(file_id)) { + record = GetControlPointsRecord(image_shape); } else { - // Get converted LCRegion (only for enclosed regions) - // Check applied regions cache - std::shared_ptr lc_region = GetCachedLCRegion(file_id); - - if (!lc_region) { - // Convert reference region to output image - lc_region = GetConvertedLCRegion(file_id, output_csys, output_shape); - } - - if (lc_region) { - // Get LCRegion definition in Record - record = lc_region->toRecord("region"); - if (record.isDefined("region")) { - record = record.asRecord("region"); - } + if (!_region_converter) { + _region_converter.reset(new RegionConverter(GetRegionState(), _coord_sys)); } + record = _region_converter->GetImageRegionRecord(file_id, csys, image_shape); } - if (record.empty()) { - // LCRegion failed, is outside the image or a rotated rectangle. - // Manually convert control points and put in Record. - record = GetRegionPointsRecord(file_id, output_csys, output_shape); + if (!record.isDefined("isRegion")) { + // Record created from control points instead of LCRegion::toRecord + CompleteRegionRecord(record, image_shape); } - return record; } -std::shared_ptr Region::GetCachedLCRegion(int file_id) { - // Return cached region applied to image with file_id - if (_applied_regions.count(file_id)) { - std::lock_guard ulock(_region_mutex); - return _applied_regions.at(file_id); - } - - return std::shared_ptr(); -} - -std::shared_ptr Region::GetConvertedLCRegion(int file_id, std::shared_ptr output_csys, - const casacore::IPosition& output_shape, const StokesSource& stokes_source, bool report_error) { - // Convert 2D reference WCRegion to LCRegion in output coord_sys and shape - std::shared_ptr lc_region; - bool is_reference_image(file_id == GetRegionState().reference_file_id); - - if (!is_reference_image && IsRotbox()) { - // Cannot convert rotbox region, it is a polygon type. - return lc_region; - } - - try { - // Convert reference WCRegion to LCRegion in image with output csys and shape - // Create reference WCRegion if not set - if (!_reference_region_set) { - SetReferenceRegion(); - } - - // Convert to LCRegion in output csys and shape - std::lock_guard guard(_region_mutex); - if (ReferenceRegionValid()) { - std::shared_ptr reference_region = std::atomic_load(&_reference_region); - lc_region.reset(reference_region->toLCRegion(*output_csys.get(), output_shape)); - } else if (is_reference_image) { - // Create LCRegion with control points for reference image - casacore::TableRecord region_record = GetControlPointsRecord(output_shape); - lc_region.reset(casacore::LCRegion::fromRecord(region_record, "")); - } - } catch (const casacore::AipsError& err) { - if (report_error) { - spdlog::error("Error converting {} to file {}: {}", RegionName(GetRegionState().type), file_id, err.getMesg()); - } - } - - // Cache the lattice coordinate region only for the original image (not computed stokes image). In order to avoid the ambiguity - if (lc_region && stokes_source.IsOriginalImage()) { - // Make a copy and cache LCRegion in map - std::lock_guard guard(_region_mutex); - _applied_regions[file_id] = lc_region; - } - - return lc_region; -} +// *************************************************************** -casacore::TableRecord Region::GetRegionPointsRecord( - int file_id, std::shared_ptr output_csys, const casacore::IPosition& output_shape) { - // Convert control points to output coord sys if needed, and return completed record. +casacore::TableRecord Region::GetControlPointsRecord(const casacore::IPosition& image_shape) { + // Return region Record in pixel coords in format of LCRegion::toRecord() from control points. + // Rotated box is returned as unrotated LCBox, rotation retrieved from RegionState. + // For rotated box for analytics, use GetRotboxRecordForLCRegion() as polygon. casacore::TableRecord record; - auto region_state = GetRegionState(); - - if (file_id == region_state.reference_file_id) { - record = GetControlPointsRecord(output_shape); - } else { - if (_wcs_control_points.empty()) { - SetReferenceRegion(); - } - - switch (region_state.type) { - case CARTA::RegionType::POINT: - record = GetPointRecord(output_csys, output_shape); - break; - case CARTA::RegionType::RECTANGLE: - case CARTA::RegionType::POLYGON: { - // Rectangle is LCPolygon with 4 corners; - // Rotbox requires special handling to get (unrotated) rectangle corners instead of rotated polygon vertices - record = (IsRotbox() ? GetRotboxRecord(output_csys) : GetPolygonRecord(output_csys)); - break; - } - case CARTA::RegionType::ELLIPSE: - record = GetEllipseRecord(output_csys); - break; - default: - break; - } - - // Add common record fields - CompleteLCRegionRecord(record, output_shape); - } - return record; -} - -casacore::TableRecord Region::GetControlPointsRecord(const casacore::IPosition& shape) { - // Return region Record in pixel coords in format of LCRegion::toRecord() for reference image (no conversion) - // Shape needed for LCBox Record for point region - casacore::TableRecord record; auto region_state = GetRegionState(); auto region_type = region_state.type; switch (region_type) { case CARTA::RegionType::POINT: case CARTA::RegionType::ANNPOINT: { - // Box with blc=trc - auto ndim = shape.size(); + // Box with blc=trc, same ndim as image and all chan/stokes + auto ndim = image_shape.size(); casacore::Vector blc(ndim, 0.0), trc(ndim, 0.0); - blc(0) = region_state.control_points[0].x(); blc(1) = region_state.control_points[0].y(); trc(0) = region_state.control_points[0].x(); trc(1) = region_state.control_points[0].y(); + for (size_t i = 2; i < ndim; ++i) { + trc(i) = image_shape(i) - 1.0f; + } + record.define("name", "LCBox"); record.define("blc", blc); record.define("trc", trc); break; } case CARTA::RegionType::RECTANGLE: - case CARTA::RegionType::ANNRECTANGLE: { + case CARTA::RegionType::ANNRECTANGLE: + case CARTA::RegionType::ANNTEXT: { // Rectangle is LCPolygon with 4 corners; calculate from center and width/height - casacore::Vector x(5), y(5); - float center_x = region_state.control_points[0].x(); - float center_y = region_state.control_points[0].y(); - float width = region_state.control_points[1].x(); - float height = region_state.control_points[1].y(); - float x_min(center_x - width / 2.0f), x_max(center_x + width / 2.0f); - float y_min(center_y - height / 2.0f), y_max(center_y + height / 2.0f); - // Bottom left - x(0) = x_min; - y(0) = y_min; - // Bottom right - x(1) = x_max; - y(1) = y_min; - // Top right - x(2) = x_max; - y(2) = y_max; - // Top left - x(3) = x_min; - y(3) = y_max; - // LCPolygon::toRecord includes last point as first point to close region - x(4) = x(0); - y(4) = y(0); + casacore::Vector x, y; + bool apply_rotation(false); + if (GetRegionState().GetRectangleCorners(x, y, apply_rotation)) { + // LCPolygon::toRecord includes last point as first point to close region + x.resize(5, true); + y.resize(5, true); + x(4) = x(0); + y(4) = y(0); - record.define("name", "LCPolygon"); - record.define("x", x); - record.define("y", y); + record.define("name", "LCPolygon"); + record.define("x", x); + record.define("y", y); + } break; } case CARTA::RegionType::LINE: @@ -1000,316 +323,86 @@ casacore::TableRecord Region::GetControlPointsRecord(const casacore::IPosition& y(npoints) = region_state.control_points[0].y(); record.define("name", "LCPolygon"); - } else if (region_type == CARTA::RegionType::LINE || region_type == CARTA::RegionType::ANNLINE) { - // CARTA USE ONLY, name not implemented in casacore - record.define("name", "Line"); - } else if (region_type == CARTA::RegionType::ANNVECTOR) { - // CARTA USE ONLY, name not implemented in casacore - record.define("name", "Vector"); - } else if (region_type == CARTA::RegionType::ANNRULER) { - // CARTA USE ONLY, name not implemented in casacore - record.define("name", "Ruler"); } else { - // CARTA USE ONLY, name not implemented in casacore - record.define("name", "Polyline"); + // CARTA USE ONLY, line region names not in casacore because not an LCRegion + record.define("name", _region_state.GetLineRegionName()); } record.define("x", x); record.define("y", y); break; } case CARTA::RegionType::ELLIPSE: - case CARTA::RegionType::ANNELLIPSE: { + case CARTA::RegionType::ANNELLIPSE: + case CARTA::RegionType::ANNCOMPASS: { casacore::Vector center(2), radii(2); center(0) = region_state.control_points[0].x(); center(1) = region_state.control_points[0].y(); - radii(0) = region_state.control_points[1].x(); - radii(1) = region_state.control_points[1].y(); + auto major = region_state.control_points[1].x(); + auto minor = region_state.control_points[1].y(); + auto ellipse_rotation = region_state.rotation; + + // Enforce major > minor (as in WCEllipsoid) for ellipse. + bool is_compass(region_type == CARTA::RegionType::ANNCOMPASS); + if (major > minor || is_compass) { + radii(0) = major; + radii(1) = minor; + // carta rotation is from y-axis, ellipse rotation is from x-axis + ellipse_rotation += 90.0; + } else { + // swapping takes care of 90 deg adjustment + radii(0) = minor; + radii(1) = major; + } - record.define("name", "LCEllipsoid"); + if (region_type == CARTA::RegionType::ANNCOMPASS) { + record.define("name", "compass"); + } else { + record.define("name", "LCEllipsoid"); + } record.define("center", center); record.define("radii", radii); // LCEllipsoid measured from major (x) axis - casacore::Quantity theta = casacore::Quantity(region_state.rotation + 90.0, "deg"); + casacore::Quantity theta = casacore::Quantity(ellipse_rotation, "deg"); theta.convert("rad"); record.define("theta", theta.getValue()); break; } - default: + default: // Annulus not implemented break; } - - CompleteLCRegionRecord(record, shape); - return record; -} - -void Region::CompleteLCRegionRecord(casacore::TableRecord& record, const casacore::IPosition& shape) { - // Add common Record fields for record defining region - if (!record.empty()) { - record.define("isRegion", casacore::RegionType::LC); - record.define("comment", ""); - record.define("oneRel", false); - - casacore::Vector region_shape; - if (GetRegionState().type == CARTA::RegionType::POINT) { - region_shape = shape.asVector(); // LCBox uses entire image shape - } else { - region_shape.resize(2); - region_shape(0) = shape(0); - region_shape(1) = shape(1); - } - record.define("shape", region_shape); - } -} - -casacore::TableRecord Region::GetPointRecord( - std::shared_ptr output_csys, const casacore::IPosition& output_shape) { - // Return point applied to output_csys in format of LCBox::toRecord() - casacore::TableRecord record; - try { - // wcs control points is single point (x, y) - casacore::Vector pixel_point; - if (ConvertWorldToPixel(_wcs_control_points, output_csys, pixel_point)) { - auto ndim = output_shape.size(); - casacore::Vector blc(ndim), trc(ndim); - blc = 0.0; - blc(0) = pixel_point(0); - blc(1) = pixel_point(1); - trc(0) = pixel_point(0); - trc(1) = pixel_point(1); - - for (size_t i = 2; i < ndim; ++i) { - trc(i) = output_shape(i) - 1; - } - - record.define("name", "LCBox"); - record.define("blc", blc); - record.define("trc", trc); - } else { - spdlog::error("Error converting point to image."); - } - } catch (const casacore::AipsError& err) { - spdlog::error("Error converting point to image: {}", err.getMesg()); - } - + CompleteRegionRecord(record, image_shape); return record; } -casacore::TableRecord Region::GetPolygonRecord(std::shared_ptr output_csys) { - // Return region applied to output_csys in format of LCPolygon::toRecord() - // This is for POLYGON or RECTANGLE (points are four corners of box) +casacore::TableRecord Region::GetRotboxRecordForLCRegion(const casacore::IPosition& image_shape) { + // Convert rotated box corners to polygon and create LCPolygon-type record casacore::TableRecord record; - auto type = GetRegionState().type; - - try { - size_t npoints(_wcs_control_points.size() / 2); - casacore::Vector x(npoints), y(npoints); // Record fields - - // Convert each wcs control point to pixel coords in output csys - for (size_t i = 0; i < _wcs_control_points.size(); i += 2) { - std::vector world_point(2); - world_point[0] = _wcs_control_points[i]; - world_point[1] = _wcs_control_points[i + 1]; - casacore::Vector pixel_point; - if (ConvertWorldToPixel(world_point, output_csys, pixel_point)) { - // Add to x and y Vectors - int index(i / 2); - x(index) = pixel_point(0); - y(index) = pixel_point(1); - } else { - spdlog::error("Error converting {} to image pixels.", RegionName(type)); - return record; - } - } - - if (type == CARTA::RegionType::POLYGON) { - // LCPolygon::toRecord adds first point as last point to close region - x.resize(npoints + 1, true); - x(npoints) = x(0); - y.resize(npoints + 1, true); - y(npoints) = y(0); - } + casacore::Vector x, y; + if (GetRegionState().GetRectangleCorners(x, y)) { + // LCPolygon::toRecord includes last point as first point to close region + x.resize(5, true); + y.resize(5, true); + x(4) = x(0); + y(4) = y(0); - // Add fields for this region type record.define("name", "LCPolygon"); record.define("x", x); record.define("y", y); - } catch (const casacore::AipsError& err) { - spdlog::error("Error converting () to image: {}", RegionName(type), err.getMesg()); } - - return record; -} - -casacore::TableRecord Region::GetRotboxRecord(std::shared_ptr output_csys) { - // Determine corners of unrotated box (control points) applied to output_csys. - // Return region applied to output_csys in format of LCPolygon::toRecord() - casacore::TableRecord record; - - try { - // Get 4 corner points in pixel coordinates - std::vector pixel_points(GetRegionState().control_points); - float center_x(pixel_points[0].x()), center_y(pixel_points[0].y()); - float width(pixel_points[1].x()), height(pixel_points[1].y()); - - int num_points(4); - casacore::Vector x(num_points), y(num_points); - float x_min(center_x - width / 2.0f), x_max(center_x + width / 2.0f); - float y_min(center_y - height / 2.0f), y_max(center_y + height / 2.0f); - // Bottom left - x(0) = x_min; - y(0) = y_min; - // Bottom right - x(1) = x_max; - y(1) = y_min; - // Top right - x(2) = x_max; - y(2) = y_max; - // Top left - x(3) = x_min; - y(3) = y_max; - - // Convert corners to reference world coords - int num_axes(_coord_sys->nPixelAxes()); - casacore::Matrix pixel_coords(num_axes, num_points); - casacore::Matrix world_coords(num_axes, num_points); - pixel_coords = 0.0; - pixel_coords.row(0) = x; - pixel_coords.row(1) = y; - casacore::Vector failures; - if (!_coord_sys->toWorldMany(world_coords, pixel_coords, failures)) { - spdlog::error("Error converting rectangle pixel coordinates to world."); - return record; - } - casacore::Vector x_wcs = world_coords.row(0); - casacore::Vector y_wcs = world_coords.row(1); - - // Units for Quantities - casacore::Vector world_units = output_csys->worldAxisUnits(); - casacore::Vector corner_x(num_points), corner_y(num_points); - for (size_t i = 0; i < num_points; i++) { - // Convert x and y reference world coords to Quantity - std::vector world_point; - world_point.push_back(casacore::Quantity(x_wcs(i), world_units(0))); - world_point.push_back(casacore::Quantity(y_wcs(i), world_units(1))); - - // Convert reference world point to output pixel point - casacore::Vector pixel_point; - if (ConvertWorldToPixel(world_point, output_csys, pixel_point)) { - // Add to x and y Vectors - corner_x(i) = pixel_point(0); - corner_y(i) = pixel_point(1); - } else { - spdlog::error("Error converting rectangle coordinates to image."); - return record; - } - } - - // Add fields for this region type - record.define("name", "LCPolygon"); - record.define("x", corner_x); - record.define("y", corner_y); - } catch (const casacore::AipsError& err) { - spdlog::error("Error converting rectangle to image: {}", err.getMesg()); - } - - return record; -} - -casacore::TableRecord Region::GetEllipseRecord(std::shared_ptr output_csys) { - // Return region applied to output_csys in format of LCEllipsoid::toRecord() - casacore::TableRecord record; - - // Values to set in Record - casacore::Vector center(2), radii(2); - - // Center point - std::vector world_point(2); - world_point[0] = _wcs_control_points[0]; - world_point[1] = _wcs_control_points[1]; - casacore::Vector pixel_point; - try { - if (ConvertWorldToPixel(world_point, output_csys, pixel_point)) { - center(0) = pixel_point(0); - center(1) = pixel_point(1); - - // Convert radii to output world units, then to pixels - casacore::Vector increments(output_csys->increment()); - casacore::Vector world_units = output_csys->worldAxisUnits(); - casacore::Quantity bmaj = _wcs_control_points[2]; - bmaj.convert(world_units(0)); - casacore::Quantity bmin = _wcs_control_points[3]; - bmin.convert(world_units(1)); - radii(0) = fabs(bmaj.getValue() / increments(0)); - radii(1) = fabs(bmin.getValue() / increments(1)); - - // Add fields for this region type - record.define("name", "LCEllipsoid"); - record.define("center", center); - record.define("radii", radii); - - // LCEllipsoid measured from major (x) axis - // TODO: adjust angle for output csys - casacore::Quantity theta = casacore::Quantity(GetRegionState().rotation + 90.0, "deg"); - theta.convert("rad"); - record.define("theta", theta.getValue()); - } else { - spdlog::error("Incompatible coordinate systems for ellipse conversion."); - } - } catch (const casacore::AipsError& err) { - spdlog::error("Error converting ellipse to image: {}", err.getMesg()); - } - - return record; -} - -casacore::TableRecord Region::GetLineRecord( - int file_id, std::shared_ptr image_csys, const casacore::IPosition& image_shape) { - // Return line region (not closed LCRegion) in pixel coords - // for region applied to input image_csys with input image_shape - casacore::TableRecord record; - auto region_state = GetRegionState(); - - if (file_id == region_state.reference_file_id) { - record = GetControlPointsRecord(image_shape); - } else { - std::vector line_points = region_state.control_points; - auto region_type = region_state.type; - casacore::Vector x, y; - - if (ConvertPointsToImagePixels(line_points, image_csys, x, y)) { - // CARTA USE ONLY, names not implemented in casacore - if (region_type == CARTA::RegionType::LINE || region_type == CARTA::RegionType::ANNLINE) { - record.define("name", "Line"); - } else if (region_type == CARTA::RegionType::POLYLINE || region_type == CARTA::RegionType::ANNPOLYLINE) { - record.define("name", "Polyline"); - } else if (region_type == CARTA::RegionType::ANNVECTOR) { - record.define("name", "Vector"); - } else if (region_type == CARTA::RegionType::ANNRULER) { - record.define("name", "Ruler"); - } else { - return record; - } - - record.define("x", x); - record.define("y", y); - } - } - CompleteRegionRecord(record, image_shape); - return record; } void Region::CompleteRegionRecord(casacore::TableRecord& record, const casacore::IPosition& image_shape) { + // Add common Record fields for record defining region if (!record.empty()) { - // Complete Record with common fields - record.define("isRegion", 1); + record.define("isRegion", casacore::RegionType::LC); record.define("comment", ""); - record.define("oneRel", false); + record.define("oneRel", false); // control points are 0-based - casacore::Vector record_shape; - if (GetRegionState().type == CARTA::RegionType::POINT) { + casacore::Vector record_shape; + if (_region_state.IsPoint()) { // LCBox uses entire image shape record_shape = image_shape.asVector(); } else { @@ -1321,133 +414,3 @@ void Region::CompleteRegionRecord(casacore::TableRecord& record, const casacore: record.define("shape", record_shape); } } - -// *************************************************************** -// Conversion utilities - -bool Region::ConvertCartaPointToWorld(const CARTA::Point& point, std::vector& world_point) { - // Converts a CARTA point(x, y) in pixel coordinates to a Quantity vector [x, y] in world coordinates - // Returns whether conversion was successful - - // Vectors must be same number of axes as in coord system for conversion: - int naxes(_coord_sys->nPixelAxes()); - casacore::Vector pixel_values(naxes), world_values(naxes); - pixel_values = 0.0; // set "extra" axes to 0, not needed - pixel_values(0) = point.x(); - pixel_values(1) = point.y(); - - // convert pixel vector to world vector - if (!_coord_sys->toWorld(world_values, pixel_values)) { - return false; - } - - // Set Quantities from world values and units - casacore::Vector world_units = _coord_sys->worldAxisUnits(); - - world_point.resize(2); - world_point[0] = casacore::Quantity(world_values(0), world_units(0)); - world_point[1] = casacore::Quantity(world_values(1), world_units(1)); - return true; -} - -bool Region::ConvertWorldToPixel(std::vector& world_point, std::shared_ptr output_csys, - casacore::Vector& pixel_point) { - // Convert input reference world coord to output world coord, then to pixel coord - // Exception should be caught in calling function for creating error message - bool success(false); - - if (_coord_sys->hasDirectionCoordinate() && output_csys->hasDirectionCoordinate()) { - // Input and output direction reference frames - casacore::MDirection::Types reference_dir_type = _coord_sys->directionCoordinate().directionType(); - casacore::MDirection::Types output_dir_type = output_csys->directionCoordinate().directionType(); - - // Convert world point from reference to output coord sys - casacore::MDirection world_direction(world_point[0], world_point[1], reference_dir_type); - if (reference_dir_type != output_dir_type) { - world_direction = casacore::MDirection::Convert(world_direction, output_dir_type)(); - } - - // Convert output world point to pixel point - output_csys->directionCoordinate().toPixel(pixel_point, world_direction); - success = true; - } else if (_coord_sys->hasLinearCoordinate() && output_csys->hasLinearCoordinate()) { - // Get linear axes indices - auto indices = output_csys->linearAxesNumbers(); - if (indices.size() != 2) { - return false; - } - // Input and output linear frames - casacore::Vector output_units = output_csys->worldAxisUnits(); - casacore::Vector world_point_value(output_csys->nWorldAxes(), 0); - world_point_value(indices(0)) = world_point[0].get(output_units(indices(0))).getValue(); - world_point_value(indices(1)) = world_point[1].get(output_units(indices(1))).getValue(); - - // Convert world point to output pixel point - casacore::Vector tmp_pixel_point; - output_csys->toPixel(tmp_pixel_point, world_point_value); - - // Only fill the pixel coordinate results - pixel_point.resize(2); - pixel_point(0) = tmp_pixel_point(indices(0)); - pixel_point(1) = tmp_pixel_point(indices(1)); - success = true; - } - - return success; -} - -std::shared_mutex& Region::GetActiveTaskMutex() { - return _active_task_mutex; -} - -void Region::RemoveHorizontalPolygonPoints(casacore::Vector& x, casacore::Vector& y) { - // When polygon points have close y-points (horizontal segment), the x-range is masked only to the next point. - // Remove points not near integral pixel. - std::vector keep_x, keep_y; - size_t npoints(x.size()); - - for (int i = 0; i < npoints - 2; ++i) { - if (i == 0) { - // always include first point of segment - keep_x.push_back(x[i]); - keep_y.push_back(y[i]); - continue; - } - - float this_y = y[i]; - float next_y = y[i + 1]; - if (!ValuesNear(this_y, next_y)) { - // Line connecting points not ~horizontal - keep point - keep_x.push_back(x[i]); - keep_y.push_back(y[i]); - continue; - } - - // Line connecting points ~horizontal - keep point nearest integral pixel - int pixel_y = static_cast(this_y); - - if (!ValuesNear(this_y, float(pixel_y))) { - // Skip point not near pixel - continue; - } - - if ((static_cast(next_y) == pixel_y) && ((this_y - pixel_y) > (next_y - pixel_y))) { - // Skip point if next point nearer to pixel - continue; - } - - keep_x.push_back(x[i]); - keep_y.push_back(y[i]); - } - - if (keep_x.size() < npoints) { - // Set to new vector with points removed - x = casacore::Vector(keep_x); - y = casacore::Vector(keep_y); - } -} - -bool Region::ValuesNear(float val1, float val2) { - // near and nearAbs in casacore Math - return val1 == 0 || val2 == 0 ? casacore::nearAbs(val1, val2) : casacore::near(val1, val2); -} diff --git a/src/Region/Region.h b/src/Region/Region.h index bfb4229c2..a0ede9615 100644 --- a/src/Region/Region.h +++ b/src/Region/Region.h @@ -1,13 +1,13 @@ /* This file is part of the CARTA Image Viewer: https://github.com/CARTAvis/carta-backend - Copyright 2018-2022 Academia Sinica Institute of Astronomy and Astrophysics (ASIAA), + Copyright 2018- Academia Sinica Institute of Astronomy and Astrophysics (ASIAA), Associated Universities, Inc. (AUI) and the Inter-University Institute for Data Intensive Astronomy (IDIA) SPDX-License-Identifier: GPL-3.0-or-later */ -//# Region.h: class for managing 2D region parameters +//# Region.h: class for managing 2D region parameters in reference image -#ifndef CARTA_BACKEND_REGION_REGION_H_ -#define CARTA_BACKEND_REGION_REGION_H_ +#ifndef CARTA_SRC_REGION_REGION_H_ +#define CARTA_SRC_REGION_REGION_H_ #include #include @@ -15,121 +15,53 @@ #include #include -#include -#include #include #include #include -#include "Util/Message.h" +#include "RegionConverter.h" +#include "RegionState.h" #include "Util/Stokes.h" -#define DEFAULT_VERTEX_COUNT 1000 - namespace carta { -inline std::string RegionName(CARTA::RegionType type) { - std::unordered_map region_names = {{CARTA::POINT, "point"}, {CARTA::LINE, "line"}, - {CARTA::POLYLINE, "polyline"}, {CARTA::RECTANGLE, "rectangle"}, {CARTA::ELLIPSE, "ellipse"}, {CARTA::ANNULUS, "annulus"}, - {CARTA::POLYGON, "polygon"}, {CARTA::ANNPOINT, "ann point"}, {CARTA::ANNLINE, "ann line"}, {CARTA::ANNPOLYLINE, "ann polyline"}, - {CARTA::ANNRECTANGLE, "ann rectangle"}, {CARTA::ANNELLIPSE, "ann ellipse"}, {CARTA::ANNPOLYGON, "ann polygon"}, - {CARTA::ANNVECTOR, "vector"}, {CARTA::ANNRULER, "ruler"}, {CARTA::ANNTEXT, "text"}, {CARTA::ANNCOMPASS, "compass"}}; - return region_names[type]; -} - -struct RegionState { - // struct used for region parameters - int reference_file_id; - CARTA::RegionType type; - std::vector control_points; - float rotation; - - RegionState() : reference_file_id(-1), type(CARTA::POINT), rotation(0) {} - RegionState(int ref_file_id_, CARTA::RegionType type_, const std::vector& control_points_, float rotation_) - : reference_file_id(ref_file_id_), type(type_), control_points(control_points_), rotation(rotation_) {} - - void operator=(const RegionState& other) { - reference_file_id = other.reference_file_id; - type = other.type; - control_points = other.control_points; - rotation = other.rotation; - } - bool operator==(const RegionState& rhs) { - return (reference_file_id == rhs.reference_file_id) && (type == rhs.type) && !RegionChanged(rhs); - } - bool operator!=(const RegionState& rhs) { - return (reference_file_id != rhs.reference_file_id) || (type != rhs.type) || RegionChanged(rhs); - } - - bool RegionDefined() { - return !control_points.empty(); - } - - bool RegionChanged(const RegionState& rhs) { - // Ignores annotation params (for interrupting region calculations) - return (rotation != rhs.rotation) || PointsChanged(rhs); - } - bool PointsChanged(const RegionState& rhs) { - // Points must be same size, order, and value to be unchanged - if (control_points.size() != rhs.control_points.size()) { - return true; - } - for (int i = 0; i < control_points.size(); ++i) { - float x(control_points[i].x()), y(control_points[i].y()); - float rhs_x(rhs.control_points[i].x()), rhs_y(rhs.control_points[i].y()); - if ((x != rhs_x) || (y != rhs_y)) { - return true; - } - } - return false; - } -}; - class Region { public: Region(const RegionState& state, std::shared_ptr csys); - inline bool IsValid() { // control points validated + inline bool IsValid() { return _valid; }; - // set new region parameters + inline bool RegionChanged() { + return _region_changed; + } + + // Set new region parameters bool UpdateRegion(const RegionState& state); - // state accessors + // RegionState accessors, including region type information inline RegionState GetRegionState() { std::lock_guard guard(_region_state_mutex); RegionState region_state = _region_state; return region_state; } - inline int GetReferenceFileId() { - return GetRegionState().reference_file_id; - } - - inline bool RegionChanged() { // reference image, type, points, or rotation changed - return _region_changed; - } - inline bool IsPoint() { - return GetRegionState().type == CARTA::POINT; + return GetRegionState().IsPoint(); } inline bool IsLineType() { // Not enclosed region defined by 2 or more points - std::vector line_types{ - CARTA::LINE, CARTA::POLYLINE, CARTA::ANNLINE, CARTA::ANNPOLYLINE, CARTA::ANNVECTOR, CARTA::ANNRULER}; - auto type = GetRegionState().type; - return std::find(line_types.begin(), line_types.end(), type) != line_types.end(); + return GetRegionState().IsLineType(); } - inline bool IsRotbox() { - RegionState rs = GetRegionState(); - return ((rs.type == CARTA::RECTANGLE || rs.type == CARTA::ANNRECTANGLE) && (rs.rotation != 0.0)); + inline bool IsInReferenceImage(int file_id) { + return file_id == GetRegionState().reference_file_id; } inline bool IsAnnotation() { - return GetRegionState().type > CARTA::POLYGON; + return GetRegionState().IsAnnotation(); } inline std::shared_ptr CoordinateSystem() { @@ -139,111 +71,56 @@ class Region { // Communication bool IsConnected(); void WaitForTaskCancellation(); + std::shared_mutex& GetActiveTaskMutex(); - // Converted region as approximate LCPolygon and its mask - std::shared_ptr GetImageRegion(int file_id, std::shared_ptr image_csys, - const casacore::IPosition& image_shape, const StokesSource& stokes_source = StokesSource(), bool report_error = true); + // LCRegion and mask for region applied to image. Must be a closed region (not line) and not annotation. + std::shared_ptr GetImageRegion(int file_id, std::shared_ptr csys, + const casacore::IPosition& shape, const StokesSource& stokes_source = StokesSource(), bool report_error = true); casacore::ArrayLattice GetImageRegionMask(int file_id); - // Converted region in Record for export + // Record for region applied to image, for export. Not for converting to LCRegion for analytics. casacore::TableRecord GetImageRegionRecord( - int file_id, std::shared_ptr output_csys, const casacore::IPosition& output_shape); - - std::shared_mutex& GetActiveTaskMutex(); + int file_id, std::shared_ptr csys, const casacore::IPosition& shape); private: - bool SetPoints(const std::vector& points); - - // check points: number required for region type, and values are finite + // Check points: number required for region type, and values are finite bool CheckPoints(const std::vector& points, CARTA::RegionType type); bool PointsFinite(const std::vector& points); - // Reset cache when region changes + // Cached LCRegion void ResetRegionCache(); + std::shared_ptr GetCachedLCRegion(int file_id, const StokesSource& stokes_source); - // Check if reference region is set successfully - bool ReferenceRegionValid(); - - // Apply region to reference image, set WCRegion and wcs control points. - void SetReferenceRegion(); - bool RectanglePointsToWorld(std::vector& pixel_points, std::vector& wcs_points); - void RectanglePointsToCorners(std::vector& pixel_points, float rotation, casacore::Vector& x, - casacore::Vector& y); - bool EllipsePointsToWorld(std::vector& pixel_points, std::vector& wcs_points, float& rotation); - - // Reference region as approximate polygon converted to image coordinates; used for data streams - bool UseApproximatePolygon(std::shared_ptr output_csys); - std::vector GetRectangleMidpoints(); - std::shared_ptr GetCachedPolygonRegion(int file_id); - std::shared_ptr GetAppliedPolygonRegion( - int file_id, std::shared_ptr output_csys, const casacore::IPosition& output_shape); - std::vector> GetReferencePolygonPoints(int num_vertices); - std::vector> GetApproximatePolygonPoints(int num_vertices); - std::vector GetApproximateEllipsePoints(int num_vertices); - double GetTotalSegmentLength(std::vector& points); - bool ConvertPointsToImagePixels(const std::vector& points, std::shared_ptr output_csys, - casacore::Vector& x, casacore::Vector& y); - void RemoveHorizontalPolygonPoints(casacore::Vector& x, casacore::Vector& y); - bool ValuesNear(float val1, float val2); - - // Region applied to any image; used for export - std::shared_ptr GetCachedLCRegion(int file_id); - std::shared_ptr GetConvertedLCRegion(int file_id, std::shared_ptr output_csys, - const casacore::IPosition& output_shape, const StokesSource& stokes_source = StokesSource(), bool report_error = true); - - // Control points converted to pixel coords in output image, returned in LCRegion Record format for export - casacore::TableRecord GetRegionPointsRecord( - int file_id, std::shared_ptr output_csys, const casacore::IPosition& output_shape); + // Record in pixel coordinates from control points, for reference image casacore::TableRecord GetControlPointsRecord(const casacore::IPosition& shape); - void CompleteLCRegionRecord(casacore::TableRecord& record, const casacore::IPosition& shape); - casacore::TableRecord GetPointRecord(std::shared_ptr output_csys, const casacore::IPosition& output_shape); - casacore::TableRecord GetLineRecord( - int file_id, std::shared_ptr image_csys, const casacore::IPosition& image_shape); - casacore::TableRecord GetPolygonRecord(std::shared_ptr output_csys); - casacore::TableRecord GetRotboxRecord(std::shared_ptr output_csys); - casacore::TableRecord GetEllipseRecord(std::shared_ptr output_csys); + casacore::TableRecord GetRotboxRecordForLCRegion(const casacore::IPosition& shape); void CompleteRegionRecord(casacore::TableRecord& record, const casacore::IPosition& image_shape); - // Utilities to convert control points - // Input: CARTA::Point. Returns: point (x, y) in reference world coords - bool ConvertCartaPointToWorld(const CARTA::Point& point, std::vector& world_point); - // Input: point (x,y) in reference world coords. Returns: point (x,y) in output pixel coords - bool ConvertWorldToPixel(std::vector& world_point, std::shared_ptr output_csys, - casacore::Vector& pixel_point); - - // region parameters struct - RegionState _region_state; - - // coord sys and shape of reference image + // Coordinate system of reference image std::shared_ptr _coord_sys; - // Reference region cache - std::mutex _region_mutex; // creation of casacore regions is not threadsafe - std::mutex _region_approx_mutex; + // Region parameters + RegionState _region_state; std::mutex _region_state_mutex; - // Use a shared lock for long time calculations, use an exclusive lock for the object destruction - mutable std::shared_mutex _active_task_mutex; - - // Region cached as original type - std::shared_ptr _reference_region; // 2D region applied to reference image - std::vector _wcs_control_points; // for manual region conversion + // Region flags + bool _valid; // RegionState set properly + bool _region_changed; // control points or rotation changed - // Converted regions - // Reference region converted to image; key is file_id - std::unordered_map> _applied_regions; - // Polygon approximation region converted to image; key is file_id - std::unordered_map> _polygon_regions; + // Region applied to reference image + std::shared_ptr _lcregion; + std::mutex _lcregion_mutex; + bool _lcregion_set; // may be nullptr if outside image - // region flags - bool _valid; // RegionState set properly - bool _region_changed; // control points or rotation changed - bool _reference_region_set; // indicates attempt was made; may be null wcregion outside image + // Converter to handle region applied to matched image + std::unique_ptr _region_converter; - // Communication + // Communication: + // Use a shared lock for long time calculations, use an exclusive lock for the object destruction + mutable std::shared_mutex _active_task_mutex; volatile bool _connected = true; }; } // namespace carta -#endif // CARTA_BACKEND_REGION_REGION_H_ +#endif // CARTA_SRC_REGION_REGION_H_ diff --git a/src/Region/RegionConverter.cc b/src/Region/RegionConverter.cc new file mode 100644 index 000000000..2e3771851 --- /dev/null +++ b/src/Region/RegionConverter.cc @@ -0,0 +1,1012 @@ +/* This file is part of the CARTA Image Viewer: https://github.com/CARTAvis/carta-backend + Copyright 2018- Academia Sinica Institute of Astronomy and Astrophysics (ASIAA), + Associated Universities, Inc. (AUI) and the Inter-University Institute for Data Intensive Astronomy (IDIA) + SPDX-License-Identifier: GPL-3.0-or-later +*/ + +//# RegionConverter.cc: implementation of class for converting a region + +#include "RegionConverter.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "Logger/Logger.h" +#include "Util/Image.h" +#include "Util/Message.h" + +using namespace carta; + +RegionConverter::RegionConverter(const RegionState& state, std::shared_ptr csys) + : _region_state(state), _reference_coord_sys(csys), _reference_region_set(false) {} + +// **************************************************************************** +// Apply region to reference image in world coordinates (WCRegion) and save wcs control points + +bool RegionConverter::ReferenceRegionValid() { + return _reference_region_set && bool(_reference_region); +} + +void RegionConverter::SetReferenceWCRegion() { + // Create WCRegion (world coordinate region) in the reference image according to type using wcs control points. + // Sets _reference_region (including to nullptr if fails) and _reference_region_set (attempted). + // Supports closed (not line-type) annotation regions, for conversion to matched image LCRegion then Record for export. + // Rotated box is rotated, do not use for Record! + casacore::WCRegion* region(nullptr); + std::vector world_points; // for converting one point at a time + casacore::IPosition pixel_axes(2, 0, 1); // first and second axes only + casacore::Vector abs_rel; + auto type(_region_state.type); + + try { + switch (type) { + case CARTA::POINT: + case CARTA::ANNPOINT: { + // Convert one point for wcs control points + if (CartaPointToWorld(_region_state.control_points[0], _wcs_control_points)) { + // WCBox blc and trc are same point + casacore::Vector const wcs_points(_wcs_control_points); + region = new casacore::WCBox(wcs_points, wcs_points, pixel_axes, *_reference_coord_sys, abs_rel); + } + break; + } + case CARTA::RECTANGLE: // 4 corners + case CARTA::POLYGON: // vertices + case CARTA::ANNRECTANGLE: // 4 corners + case CARTA::ANNPOLYGON: // vertices + case CARTA::ANNTEXT: { // 4 corners of text box + // Use corners/vertices for wcs control points: x0, y0, x1, y1, etc. + if (type == CARTA::RECTANGLE || type == CARTA::ANNRECTANGLE || type == CARTA::ANNTEXT) { + if (!RectangleControlPointsToWorld(_wcs_control_points)) { + _wcs_control_points.clear(); + } + } else { + for (auto& point : _region_state.control_points) { + if (CartaPointToWorld(point, world_points)) { + _wcs_control_points.push_back(world_points[0]); + _wcs_control_points.push_back(world_points[1]); + } else { + _wcs_control_points.clear(); + break; + } + } + } + + if (!_wcs_control_points.empty()) { + // Convert from Vector (wcs control points) to Quantum for WCPolygon + casacore::Quantum> qx, qy; + // Separate x and y + std::vector x, y; + for (size_t i = 0; i < _wcs_control_points.size(); i += 2) { + x.push_back(_wcs_control_points[i].getValue()); + y.push_back(_wcs_control_points[i + 1].getValue()); + } + casacore::Vector x_v(x), y_v(y); + casacore::Vector world_units = _reference_coord_sys->worldAxisUnits(); + qx = x_v; // set values + qx.setUnit(world_units(0)); // set unit + qy = y_v; // set values + qy.setUnit(world_units(1)); // set unit + + region = new casacore::WCPolygon(qx, qy, pixel_axes, *_reference_coord_sys); + } + break; + } + case CARTA::ELLIPSE: // [(cx, cy), (bmaj, bmin)] + case CARTA::ANNELLIPSE: // [(cx, cy), (bmaj, bmin)] + case CARTA::ANNCOMPASS: { // [(cx, cy), (length, length)} + float ellipse_rotation; + if (EllipseControlPointsToWorld(_wcs_control_points, ellipse_rotation)) { + // wcs control points order: xcenter, ycenter, major axis, minor axis + casacore::Quantity theta(ellipse_rotation, "deg"); + theta.convert("rad"); + region = new casacore::WCEllipsoid(_wcs_control_points[0], _wcs_control_points[1], _wcs_control_points[2], + _wcs_control_points[3], theta, 0, 1, *_reference_coord_sys); + } + break; + } + default: // no WCRegion for line-type regions + break; + } + } catch (casacore::AipsError& err) { // region failed + spdlog::error("region type {} failed: {}", type, err.getMesg()); + } + + std::shared_ptr shared_region = std::shared_ptr(region); + std::atomic_store(&_reference_region, shared_region); + + // Flag indicates that attempt was made, to avoid repeated attempts + _reference_region_set = true; +} + +bool RegionConverter::RectangleControlPointsToWorld(std::vector& world_corners) { + // Convert CARTA rectangle points (cx, cy), (width, height) to corners in world coordinates (reference image) + // Get 4 corner points in pixel coordinates from control points, applying rotation + casacore::Vector x, y; + if (!_region_state.GetRectangleCorners(x, y)) { + return false; + } + + // Convert corners to wcs in one call for efficiency, rather than one point at a time + size_t num_points(x.size()), num_axes(_reference_coord_sys->nPixelAxes()); + casacore::Matrix pixel_coords(num_axes, num_points); + casacore::Matrix world_coords(num_axes, num_points); + pixel_coords = 0.0; + pixel_coords.row(0) = x; + pixel_coords.row(1) = y; + casacore::Vector failures; + if (!_reference_coord_sys->toWorldMany(world_coords, pixel_coords, failures)) { + return false; + } + + // Save x and y values as Quantities + casacore::Vector world_units = _reference_coord_sys->worldAxisUnits(); + casacore::Vector x_wcs = world_coords.row(0); + casacore::Vector y_wcs = world_coords.row(1); + // reference points: corners (x0, y0, x1, y1, x2, y2, x3, y3) in world coordinates + world_corners.resize(num_points * 2); + for (int i = 0; i < num_points; ++i) { + world_corners[i * 2] = casacore::Quantity(x_wcs(i), world_units(0)); + world_corners[(i * 2) + 1] = casacore::Quantity(y_wcs(i), world_units(1)); + } + return true; +} + +bool RegionConverter::EllipseControlPointsToWorld(std::vector& wcs_points, float& ellipse_rotation) { + // Convert CARTA ellipse points (cx, cy), (bmaj, bmin) to world coordinates, adjust rotation + auto pixel_points = _region_state.control_points; + ellipse_rotation = _region_state.rotation; + + // Convert center and store in wcs points + std::vector center_world; + if (!CartaPointToWorld(pixel_points[0], center_world)) { + return false; + } + wcs_points = center_world; + + // Convert bmaj, bmin from pixel length to world length + float bmaj(pixel_points[1].x()), bmin(pixel_points[1].y()); + casacore::Quantity bmaj_world = _reference_coord_sys->toWorldLength(bmaj, 0); + casacore::Quantity bmin_world = _reference_coord_sys->toWorldLength(bmin, 1); + + // Check if bmaj/bmin units conform (false for PV image, in arcsec and Hz) + if (!bmaj_world.isConform(bmin_world.getUnit())) { + return false; + } + + // bmaj > bmin (world coords) required for WCEllipsoid; adjust rotation angle + if (bmaj_world > bmin_world) { + // carta rotation is from y-axis, ellipse rotation is from x-axis + ellipse_rotation += 90.0; + } else { + // swapping takes care of 90 deg adjustment + std::swap(bmaj_world, bmin_world); + } + + wcs_points.push_back(bmaj_world); + wcs_points.push_back(bmin_world); + return true; +} + +bool RegionConverter::CartaPointToWorld(const CARTA::Point& point, std::vector& world_point) { + // Converts a CARTA point(x, y) in pixel coordinates to a Quantity vector [x, y] in world coordinates. + // Returns whether conversion was successful + + // Vectors must be same number of axes as in coord system for conversion: + int naxes(_reference_coord_sys->nPixelAxes()); + casacore::Vector pixel_values(naxes), world_values(naxes); + pixel_values = 0.0; // set "extra" axes to 0, not needed + pixel_values(0) = point.x(); + pixel_values(1) = point.y(); + + // convert pixel vector to world vector + if (!_reference_coord_sys->toWorld(world_values, pixel_values)) { + return false; + } + + // Set Quantities from world values and units + casacore::Vector world_units = _reference_coord_sys->worldAxisUnits(); + world_point.clear(); + world_point.push_back(casacore::Quantity(world_values(0), world_units(0))); + world_point.push_back(casacore::Quantity(world_values(1), world_units(1))); + return true; +} + +// ************************************************************************* +// Convert region to any image + +std::shared_ptr RegionConverter::GetCachedLCRegion(int file_id, bool use_approx_polygon) { + // Return cached region applied to image with file_id, if cached. + std::lock_guard guard(_region_mutex); + if (_converted_regions.find(file_id) != _converted_regions.end()) { + return _converted_regions.at(file_id); + } else if (use_approx_polygon && _polygon_regions.find(file_id) != _polygon_regions.end()) { + // Return cached polygon-approximated region applied to image with file_id + return _polygon_regions.at(file_id); + } + + return std::shared_ptr(); +} + +std::shared_ptr RegionConverter::GetImageRegion(int file_id, std::shared_ptr output_csys, + const casacore::IPosition& output_shape, const StokesSource& stokes_source, bool report_error) { + // Apply region to non-reference image, possibly as an approximate polygon to avoid distortion. + std::shared_ptr lc_region; + + // Analytic, closed regions only + if (_region_state.IsLineType() || _region_state.IsAnnotation()) { + return lc_region; + } + + if (stokes_source.IsOriginalImage()) { + // The cache of converted LCRegions is only for the original image (not computed stokes image). In order to avoid the ambiguity + lc_region = GetCachedLCRegion(file_id); + } + + if (!lc_region) { + // Create converted LCRegion and cache it + bool cache_polygon(false); + bool use_polygon = UseApproximatePolygon(output_csys); + if (_region_state.IsRotbox()) { + // Rotbox is always converted from a polygon, either box corners only (use_polygon==false) or many points (use_polygon==true) + lc_region = GetAppliedPolygonRegion(file_id, output_csys, output_shape, use_polygon); + cache_polygon = true; + } else if (use_polygon) { // distortion + // Approximate region as polygon points then convert the points. + spdlog::debug("Using polygon approximation to avoid distortion in matched region"); + lc_region = GetAppliedPolygonRegion(file_id, output_csys, output_shape, use_polygon); + cache_polygon = true; + } else { // no distortion + // Do direct region conversion from reference WCRegion (no distortion detected) + lc_region = GetConvertedLCRegion(file_id, output_csys, output_shape, stokes_source, report_error); + if (lc_region) { + // Region conversion succeeded + spdlog::debug("Using direct region conversion for matched image"); + } else { + // Region conversion failed (e.g. outside image) but can convert points to world + spdlog::debug("Using polygon approximation for failed conversion of matched region"); + lc_region = GetAppliedPolygonRegion(file_id, output_csys, output_shape, use_polygon); + cache_polygon = true; + } + } + + if (cache_polygon && lc_region && stokes_source.IsOriginalImage()) { + // Cache converted polygon, only for the original image (not computed stokes). + std::lock_guard guard(_region_mutex); + _polygon_regions[file_id] = lc_region; + } + } + + return lc_region; +} + +std::shared_ptr RegionConverter::GetConvertedLCRegion(int file_id, + std::shared_ptr output_csys, const casacore::IPosition& output_shape, const StokesSource& stokes_source, + bool report_error) { + // Convert reference WCRegion to LCRegion in output coord_sys and shape, and cache converted region. + // Check cache before calling this else will needlessly create a new LCRegion and cache it. + std::shared_ptr lc_region; + + try { + // Convert reference WCRegion to LCRegion using output csys and shape + if (!_reference_region_set) { + SetReferenceWCRegion(); + } + + if (ReferenceRegionValid()) { + std::shared_ptr reference_region = std::atomic_load(&_reference_region); + lc_region.reset(reference_region->toLCRegion(*output_csys.get(), output_shape)); + } + } catch (const casacore::AipsError& err) { + if (report_error) { + spdlog::error("Error converting region type {} to file {}: {}", _region_state.type, file_id, err.getMesg()); + } + } + + if (lc_region && stokes_source.IsOriginalImage()) { + // Cache the lattice coordinate region only for the original image (not computed stokes image). + std::lock_guard guard(_region_mutex); + _converted_regions[file_id] = lc_region; + } + + return lc_region; +} + +// ************************************************************************* +// Region as polygon to avoid distortion in matched image + +bool RegionConverter::UseApproximatePolygon(std::shared_ptr output_csys) { + // Determine whether to convert region directly, or approximate it as a polygon in the output image. + // Closed region types: rectangle, ellipse, polygon. + // Check ellipse and rectangle distortion; always use polygon for polygon regions. + CARTA::RegionType region_type = _region_state.type; + if ((region_type != CARTA::RegionType::ELLIPSE) && (region_type != CARTA::RegionType::RECTANGLE)) { + return true; + } + + // Ratio of vector lengths in reference image region + double ref_length_ratio; + double x_length(_region_state.control_points[1].x()), y_length(_region_state.control_points[1].y()); + if (region_type == CARTA::RegionType::ELLIPSE) { + ref_length_ratio = x_length / y_length; + } else { + ref_length_ratio = y_length / x_length; + } + + // Make vector of endpoints and center, to check lengths against reference image lengths + std::vector points; // [p0, p1, p2, p3, center] + if (region_type == CARTA::RegionType::ELLIPSE) { + // Make "polygon" with only 4 points + points = GetApproximateEllipsePoints(4); + } else { + // Get midpoints of 4 sides of rectangle + points = GetRectangleMidpoints(); + } + points.push_back(_region_state.control_points[0]); + + // Convert reference pixel points to output pixel points, then check vector length ratio and dot product + casacore::Vector x, y; + if (PointsToImagePixels(points, output_csys, x, y)) { + // vector0 is (center, p0), vector1 is (center, p1) + auto v0_delta_x = x[0] - x[4]; + auto v0_delta_y = y[0] - y[4]; + auto v1_delta_x = x[1] - x[4]; + auto v1_delta_y = y[1] - y[4]; + + // Compare reference length ratio to converted length ratio + // Ratio of vector lengths in converted region + auto v0_length = sqrt((v0_delta_x * v0_delta_x) + (v0_delta_y * v0_delta_y)); + auto v1_length = sqrt((v1_delta_x * v1_delta_x) + (v1_delta_y * v1_delta_y)); + double converted_length_ratio = v1_length / v0_length; + double length_ratio_difference = fabs(ref_length_ratio - converted_length_ratio); + // spdlog::debug("{} distortion check: length ratio difference={:.3e}", length_ratio_difference); + if (length_ratio_difference > 1e-4) { + // Failed ratio check, use polygon + return true; + } + + // Passed ratio check; check dot product of converted region + double converted_dot_product = (v0_delta_x * v1_delta_x) + (v0_delta_y * v1_delta_y); + // spdlog::debug("{} distortion check: dot product={:.3e}", converted_dot_product); + if (fabs(converted_dot_product) > 1e-2) { + // failed dot product test, use polygon + return true; + } + } else { + spdlog::error("Error converting region points to matched image."); + return true; + } + + return false; +} + +std::vector RegionConverter::GetRectangleMidpoints() { + // Return midpoints of 4 sides of rectangle + // Find corners with rotation: blc, brc, trc, tlc + std::vector midpoints; + casacore::Vector x, y; + if (_region_state.GetRectangleCorners(x, y)) { + // start with right side brc, trc + midpoints.push_back(Message::Point((x[1] + x[2]) / 2.0, (y[1] + y[2]) / 2.0)); + midpoints.push_back(Message::Point((x[2] + x[3]) / 2.0, (y[2] + y[3]) / 2.0)); + midpoints.push_back(Message::Point((x[3] + x[0]) / 2.0, (y[3] + y[0]) / 2.0)); + midpoints.push_back(Message::Point((x[0] + x[1]) / 2.0, (y[0] + y[1]) / 2.0)); + } + return midpoints; +} + +std::shared_ptr RegionConverter::GetAppliedPolygonRegion( + int file_id, std::shared_ptr output_csys, const casacore::IPosition& output_shape, bool has_distortion) { + // Approximate region as polygon pixel vertices, and convert to given csys. + // If has distortion, use DEFAULT_VERTEX_COUNT vertices (else is rotbox and just use corners) + std::shared_ptr lc_region; + + bool is_point(_region_state.IsPoint()); + size_t nvertices(is_point ? 1 : DEFAULT_VERTEX_COUNT); + + // Set reference region as points along polygon segments + auto polygon_points = GetReferencePolygonPoints(nvertices, has_distortion); + if (polygon_points.empty()) { + return lc_region; + } + + // Convert polygon points to x and y pixel coords in matched image + casacore::Vector x, y; + if (polygon_points.size() == 1) { + // Point, ellipse, and rotbox with no distortion have one vector for all points + if (!PointsToImagePixels(polygon_points[0], output_csys, x, y)) { + spdlog::error("Error approximating region as polygon in matched image."); + return lc_region; + } + if (!is_point && has_distortion) { + // if ~horizontal then remove intermediate points to fix kinks + RemoveHorizontalPolygonPoints(x, y); + } + } else { + // Rectangle and polygon have one vector for each segment of original rectangle/polygon + for (auto& segment : polygon_points) { + casacore::Vector segment_x, segment_y; + if (!PointsToImagePixels(segment, output_csys, segment_x, segment_y)) { + spdlog::error("Error approximating region as polygon in matched image."); + return lc_region; + } + + if (has_distortion) { + // if ~horizontal then remove intermediate points to fix "kinks" in mask + RemoveHorizontalPolygonPoints(segment_x, segment_y); + } + + // Resize x and y to append selected segment points + auto old_size = x.size(); + x.resize(old_size + segment_x.size(), true); + y.resize(old_size + segment_y.size(), true); + for (auto i = 0; i < segment_x.size(); ++i) { + x[old_size + i] = segment_x[i]; + y[old_size + i] = segment_y[i]; + } + } + } + + // Use converted pixel points to create LCRegion (LCBox for point, else LCPolygon) + try { + if (is_point) { + // Point is not a polygon (needs at least 3 points), use LCBox instead with blc, trc = point + size_t ndim(output_shape.size()); + casacore::Vector blc(ndim, 0.0), trc(ndim); + blc(0) = x(0); + blc(1) = y(0); + trc(0) = x(0); + trc(1) = y(0); + + for (size_t i = 2; i < ndim; ++i) { + trc(i) = output_shape(i) - 1; + } + + lc_region.reset(new casacore::LCBox(blc, trc, output_shape)); + } else { + // Need 2D shape + casacore::IPosition keep_axes(2, 0, 1); + casacore::IPosition region_shape(output_shape.keepAxes(keep_axes)); + lc_region.reset(new casacore::LCPolygon(x, y, region_shape)); + } + } catch (const casacore::AipsError& err) { + spdlog::error("Cannot apply region type {} to file {}: {}", _region_state.type, file_id, err.getMesg()); + } + + return lc_region; +} + +std::vector> RegionConverter::GetReferencePolygonPoints(int num_vertices, bool has_distortion) { + // Approximate reference region as polygon with input number of vertices. + // Returns points for supported region types. + std::vector> points; + + switch (_region_state.type) { + case CARTA::POINT: { + points.push_back(_region_state.control_points); + } + case CARTA::RECTANGLE: + case CARTA::POLYGON: { + return GetApproximatePolygonPoints(num_vertices, has_distortion); + } + case CARTA::ELLIPSE: { + points.push_back(GetApproximateEllipsePoints(num_vertices)); + } + default: + return points; + } + return points; +} + +std::vector> RegionConverter::GetApproximatePolygonPoints(int num_vertices, bool has_distortion) { + // Approximate RECTANGLE or POLYGON region as polygon with num_vertices if has distortion. Else convert rotbox to polygon. + // Returns vector of points for each segment of polygon/rectangle, or empty vector for other region types. + std::vector> polygon_points; + std::vector region_vertices; + CARTA::RegionType region_type(_region_state.type); + + // Rectangle corners or polygon points as polygon vertices for segments. + if (region_type == CARTA::RegionType::RECTANGLE) { + casacore::Vector x, y; + _region_state.GetRectangleCorners(x, y); + + for (size_t i = 0; i < x.size(); ++i) { + region_vertices.push_back(Message::Point(x(i), y(i))); + } + } else if (region_type == CARTA::RegionType::POLYGON) { + region_vertices = _region_state.control_points; + } else { + spdlog::error("Error approximating region as polygon: type {} not supported", _region_state.type); + return polygon_points; + } + + // Close polygon + CARTA::Point first_point(region_vertices[0]); + region_vertices.push_back(first_point); + + if (has_distortion) { + // Divide each polygon/rectangle segment into target number of segments with target length + double total_length = GetTotalSegmentLength(region_vertices); + double min_length = 0.1; + double target_segment_length = total_length / num_vertices; + target_segment_length = std::max(target_segment_length, min_length); + + for (size_t i = 1; i < region_vertices.size(); ++i) { + // Handle segment from point[i-1] to point[i] + std::vector segment_points; + + auto delta_x = region_vertices[i].x() - region_vertices[i - 1].x(); + auto delta_y = region_vertices[i].y() - region_vertices[i - 1].y(); + auto segment_length = sqrt((delta_x * delta_x) + (delta_y * delta_y)); + auto dir_x = delta_x / segment_length; + auto dir_y = delta_y / segment_length; + auto target_nsegment = round(segment_length / target_segment_length); + auto target_length = segment_length / target_nsegment; + + auto first_segment_point(region_vertices[i - 1]); + segment_points.push_back(first_segment_point); + + auto first_x(first_segment_point.x()); + auto first_y(first_segment_point.y()); + + for (size_t j = 1; j < target_nsegment; ++j) { + auto length_from_first = j * target_length; + auto x_offset = dir_x * length_from_first; + auto y_offset = dir_y * length_from_first; + segment_points.push_back(Message::Point(first_x + x_offset, first_y + y_offset)); + } + + polygon_points.push_back(segment_points); + } + } else { + // No need to add extra points, no distortion + polygon_points.push_back(region_vertices); + } + + return polygon_points; +} + +std::vector RegionConverter::GetApproximateEllipsePoints(int num_vertices) { + // Approximate ELLIPSE region as polygon with num_vertices, return points + std::vector polygon_points; + + auto cx = _region_state.control_points[0].x(); + auto cy = _region_state.control_points[0].y(); + auto bmaj = _region_state.control_points[1].x(); + auto bmin = _region_state.control_points[1].y(); + + auto delta_theta = 2.0 * M_PI / num_vertices; + auto rotation = _region_state.rotation * M_PI / 180.0; + auto cos_rotation = cos(rotation); + auto sin_rotation = sin(rotation); + + for (int i = 0; i < num_vertices; ++i) { + auto theta = i * delta_theta; + auto rot_bmin = bmin * cos(theta); + auto rot_bmaj = bmaj * sin(theta); + + auto x_offset = (cos_rotation * rot_bmin) - (sin_rotation * rot_bmaj); + auto y_offset = (sin_rotation * rot_bmin) + (cos_rotation * rot_bmaj); + + polygon_points.push_back(Message::Point(cx + x_offset, cy + y_offset)); + } + + return polygon_points; +} + +double RegionConverter::GetTotalSegmentLength(std::vector& points) { + // Accumulate length of each point-to-point segment; returns total length. + double total_length(0.0); + + for (size_t i = 1; i < points.size(); ++i) { + auto delta_x = points[i].x() - points[i - 1].x(); + auto delta_y = points[i].y() - points[i - 1].y(); + total_length += sqrt((delta_x * delta_x) + (delta_y * delta_y)); + } + + return total_length; +} + +void RegionConverter::RemoveHorizontalPolygonPoints(casacore::Vector& x, casacore::Vector& y) { + // When polygon points have close y-points (horizontal segment), the x-range is masked only to the next point. + // Remove points not near integral pixel. + std::vector keep_x, keep_y; + size_t npoints(x.size()); + + for (int i = 0; i < npoints - 2; ++i) { + if (i == 0) { + // always include first point of segment + keep_x.push_back(x[i]); + keep_y.push_back(y[i]); + continue; + } + + float this_y = y[i]; + float next_y = y[i + 1]; + if (!ValuesNear(this_y, next_y)) { + // Line connecting points not ~horizontal - keep point + keep_x.push_back(x[i]); + keep_y.push_back(y[i]); + } + } + + if (keep_x.size() < npoints) { + // Set to new vector with points removed + x = casacore::Vector(keep_x); + y = casacore::Vector(keep_y); + } +} + +bool RegionConverter::ValuesNear(float val1, float val2) { + // near and nearAbs in casacore Math + return val1 == 0 || val2 == 0 ? casacore::nearAbs(val1, val2) : casacore::near(val1, val2); +} + +// *************************************************************** +// Apply region to any image and return LCRegion Record for export + +casacore::TableRecord RegionConverter::GetImageRegionRecord( + int file_id, std::shared_ptr output_csys, const casacore::IPosition& output_shape) { + // Return Record describing Region applied to output image in pixel coordinates + casacore::TableRecord record; + + // No LCRegion for lines. LCRegion for rotbox is a polygon; Record should be for unrotated box with rotation field. + if (!_region_state.IsLineType() && !_region_state.IsRotbox()) { + // Get record from converted LCRegion, for enclosed regions only. + // Check converted regions cache (but not polygon regions cache) + std::shared_ptr lc_region = GetCachedLCRegion(file_id, false); + + // Convert reference region to output image + if (!lc_region) { + lc_region = GetConvertedLCRegion(file_id, output_csys, output_shape); + } + + // Get LCRegion definition as Record + if (lc_region) { + record = lc_region->toRecord("region"); + if (record.isDefined("region")) { + record = record.asRecord("region"); + } + } + } + + if (record.empty()) { + // LCRegion failed: is outside the image or is a line or rotated rectangle. + // Manually convert control points and put in Record. + record = GetRegionPointsRecord(output_csys, output_shape); + } + return record; +} + +casacore::TableRecord RegionConverter::GetRegionPointsRecord( + std::shared_ptr output_csys, const casacore::IPosition& output_shape) { + // Convert control points to output coord sys if needed, and return completed record. + // Used when LCRegion::toRecord() fails, usually outside image. + casacore::TableRecord record; + if (!_reference_region_set) { + SetReferenceWCRegion(); // set wcs control points + } + + switch (_region_state.type) { + case CARTA::RegionType::POINT: + case CARTA::RegionType::ANNPOINT: { + record = GetPointRecord(output_csys, output_shape); + break; + } + case CARTA::RegionType::LINE: + case CARTA::RegionType::POLYLINE: + case CARTA::RegionType::ANNLINE: + case CARTA::RegionType::ANNPOLYLINE: + case CARTA::RegionType::ANNVECTOR: + case CARTA::RegionType::ANNRULER: { + record = GetLineRecord(output_csys); + break; + } + case CARTA::RegionType::RECTANGLE: + case CARTA::RegionType::POLYGON: + case CARTA::RegionType::ANNRECTANGLE: + case CARTA::RegionType::ANNPOLYGON: + case CARTA::RegionType::ANNTEXT: { + // Rectangle types are LCPolygon with 4 (unrotated) corners. + record = (_region_state.IsRotbox() ? GetRotboxRecord(output_csys) : GetPolygonRecord(output_csys)); + break; + } + case CARTA::RegionType::ELLIPSE: + case CARTA::RegionType::ANNELLIPSE: + case CARTA::RegionType::ANNCOMPASS: { + record = GetEllipseRecord(output_csys); + break; + } + default: + break; + } + return record; +} + +casacore::TableRecord RegionConverter::GetPointRecord( + std::shared_ptr output_csys, const casacore::IPosition& output_shape) { + // Convert wcs points to output image in format of LCBox::toRecord() + casacore::TableRecord record; + try { + // wcs control points is single point (x, y) + casacore::Vector pixel_point; + if (WorldPointToImagePixels(_wcs_control_points, output_csys, pixel_point)) { + auto ndim = output_shape.size(); + casacore::Vector blc(ndim), trc(ndim); + blc = 0.0; + blc(0) = pixel_point(0); + blc(1) = pixel_point(1); + trc(0) = pixel_point(0); + trc(1) = pixel_point(1); + + for (size_t i = 2; i < ndim; ++i) { + trc(i) = output_shape(i) - 1; + } + + record.define("name", "LCBox"); + record.define("blc", blc); + record.define("trc", trc); + } else { + spdlog::error("Error converting point to image."); + } + } catch (const casacore::AipsError& err) { + spdlog::error("Error converting point to image: {}", err.getMesg()); + } + + return record; +} + +casacore::TableRecord RegionConverter::GetLineRecord(std::shared_ptr image_csys) { + // Convert control points for line-type region to output image pixels in format of LCPolygon::toRecord() + casacore::TableRecord record; + casacore::Vector x, y; + if (PointsToImagePixels(_region_state.control_points, image_csys, x, y)) { + record.define("name", _region_state.GetLineRegionName()); + record.define("x", x); + record.define("y", y); + } + return record; +} + +casacore::TableRecord RegionConverter::GetPolygonRecord(std::shared_ptr output_csys) { + // Convert wcs points to output image in format of LCPolygon::toRecord() + // This is for POLYGON or RECTANGLE (points are four corners of box) + casacore::TableRecord record; + auto type = _region_state.type; + + try { + size_t npoints(_wcs_control_points.size() / 2); + casacore::Vector x(npoints), y(npoints); // Record fields + + // Convert each wcs control point to pixel coords in output csys + for (size_t i = 0; i < _wcs_control_points.size(); i += 2) { + std::vector world_point(2); + world_point[0] = _wcs_control_points[i]; + world_point[1] = _wcs_control_points[i + 1]; + casacore::Vector pixel_point; + if (WorldPointToImagePixels(world_point, output_csys, pixel_point)) { + // Add to x and y Vectors + int index(i / 2); + x(index) = pixel_point(0); + y(index) = pixel_point(1); + } else { + spdlog::error("Error converting region type {} to image pixels.", type); + return record; + } + } + + if (type == CARTA::RegionType::POLYGON) { + // LCPolygon::toRecord adds first point as last point to close region + x.resize(npoints + 1, true); + x(npoints) = x(0); + y.resize(npoints + 1, true); + y(npoints) = y(0); + } + + // Add fields for this region type + record.define("name", "LCPolygon"); + record.define("x", x); + record.define("y", y); + } catch (const casacore::AipsError& err) { + spdlog::error("Error converting region type () to image: {}", type, err.getMesg()); + } + + return record; +} + +casacore::TableRecord RegionConverter::GetRotboxRecord(std::shared_ptr output_csys) { + // Convert control points for rotated box-type region (ignore rotation) to output image pixels + // in format of LCPolygon::toRecord(). + casacore::TableRecord record; + try { + // Get 4 corner points (unrotated) in pixel coordinates + casacore::Vector x, y; + bool apply_rotation(false); + if (_region_state.GetRectangleCorners(x, y, apply_rotation)) { + // Convert corners to reference world coords. + // Cannot use wcs control points because rotation was applied. + int num_axes(_reference_coord_sys->nPixelAxes()); + auto num_points(x.size()); + casacore::Matrix pixel_coords(num_axes, num_points); + casacore::Matrix world_coords(num_axes, num_points); + pixel_coords = 0.0; + pixel_coords.row(0) = x; + pixel_coords.row(1) = y; + casacore::Vector failures; + if (!_reference_coord_sys->toWorldMany(world_coords, pixel_coords, failures)) { + spdlog::error("Error converting rectangle pixel coordinates to world."); + return record; + } + + // Convert reference world coord points to output pixel points + casacore::Vector ref_x_world = world_coords.row(0); + casacore::Vector ref_y_world = world_coords.row(1); + casacore::Vector ref_world_units = _reference_coord_sys->worldAxisUnits(); + casacore::Vector out_x_pix(num_points), out_y_pix(num_points); + for (size_t i = 0; i < num_points; i++) { + // Reference world point as Quantity + std::vector ref_world_point; + ref_world_point.push_back(casacore::Quantity(ref_x_world(i), ref_world_units(0))); + ref_world_point.push_back(casacore::Quantity(ref_y_world(i), ref_world_units(1))); + // Convert to output pixel point + casacore::Vector out_pixel_point; + if (WorldPointToImagePixels(ref_world_point, output_csys, out_pixel_point)) { + out_x_pix(i) = out_pixel_point(0); + out_y_pix(i) = out_pixel_point(1); + } else { + spdlog::error("Error converting rectangle coordinates to image."); + return record; + } + } + + // Add fields for this region type + record.define("name", "LCPolygon"); + record.define("x", out_x_pix); + record.define("y", out_y_pix); + } + } catch (const casacore::AipsError& err) { + spdlog::error("Error converting rectangle to image: {}", err.getMesg()); + } + return record; +} + +casacore::TableRecord RegionConverter::GetEllipseRecord(std::shared_ptr output_csys) { + // Convert wcs points to output image in format of LCEllipsoid::toRecord() + casacore::TableRecord record; + casacore::Vector center(2), radii(2); // for record + + // Center point + std::vector ref_world_point(2); + ref_world_point[0] = _wcs_control_points[0]; + ref_world_point[1] = _wcs_control_points[1]; + casacore::Vector out_pixel_point; + + try { + if (WorldPointToImagePixels(ref_world_point, output_csys, out_pixel_point)) { + center(0) = out_pixel_point(0); + center(1) = out_pixel_point(1); + + // Convert radii to output world units, then to pixels using increment + casacore::Quantity bmaj = _wcs_control_points[2]; + casacore::Quantity bmin = _wcs_control_points[3]; + casacore::Vector out_increments(output_csys->increment()); + casacore::Vector out_units(output_csys->worldAxisUnits()); + bmaj.convert(out_units(0)); + bmin.convert(out_units(1)); + radii(0) = fabs(bmaj.getValue() / out_increments(0)); + radii(1) = fabs(bmin.getValue() / out_increments(1)); + + // Add fields for this region type + record.define("name", "LCEllipsoid"); + record.define("center", center); + record.define("radii", radii); + + // LCEllipsoid measured from major (x) axis + casacore::Quantity theta = casacore::Quantity(_region_state.rotation + 90.0, "deg"); + theta.convert("rad"); + record.define("theta", theta.getValue()); + } else { + spdlog::error("Incompatible coordinate systems for ellipse conversion."); + } + } catch (const casacore::AipsError& err) { + spdlog::error("Error converting ellipse to image: {}", err.getMesg()); + } + + return record; +} + +// ************************************************************************* +// Utilities for pixel/world conversion + +bool RegionConverter::PointsToImagePixels(const std::vector& points, std::shared_ptr output_csys, + casacore::Vector& x, casacore::Vector& y) { + // Convert pixel coords in reference image (points) to pixel coords in output image coordinate system (x and y). + // ref pixels -> ref world -> output world -> output pixels + bool converted(true); + try { + // Convert each pixel point to output csys pixel + size_t npoints(points.size()); + x.resize(npoints); + y.resize(npoints); + + for (auto i = 0; i < npoints; ++i) { + // Convert pixel to world (reference image) [x, y] + std::vector world_point; + if (CartaPointToWorld(points[i], world_point)) { + // Convert world to pixel (output image) [x, y] + casacore::Vector pixel_point; + if (WorldPointToImagePixels(world_point, output_csys, pixel_point)) { + x(i) = pixel_point(0); + y(i) = pixel_point(1); + } else { // world to pixel failed + spdlog::error("Error converting region to output image pixel coords."); + converted = false; + break; + } + } else { // pixel to world failed + spdlog::error("Error converting region to reference image world coords."); + converted = false; + break; + } + } + } catch (const casacore::AipsError& err) { + spdlog::error("Error converting region to output image: {}", err.getMesg()); + converted = false; + } + + return converted; +} + +bool RegionConverter::WorldPointToImagePixels(std::vector& world_point, + std::shared_ptr output_csys, casacore::Vector& pixel_point) { + // Convert reference world-coord point to output pixel-coord point: ref world -> output world -> output pixels. + // Both images must have direction coordinates or linear coordinates. + // Returns pixel points with success or throws exception (catch in calling function). + bool success(false); + if (_reference_coord_sys->hasDirectionCoordinate() && output_csys->hasDirectionCoordinate()) { + // Input and output direction reference frames + casacore::MDirection::Types reference_dir_type = _reference_coord_sys->directionCoordinate().directionType(); + casacore::MDirection::Types output_dir_type = output_csys->directionCoordinate().directionType(); + + // Convert world point from reference to output coord sys + casacore::MDirection world_direction(world_point[0], world_point[1], reference_dir_type); + if (reference_dir_type != output_dir_type) { + world_direction = casacore::MDirection::Convert(world_direction, output_dir_type)(); + } + + // Convert output world point to pixel point + output_csys->directionCoordinate().toPixel(pixel_point, world_direction); + success = true; + } else if (_reference_coord_sys->hasLinearCoordinate() && output_csys->hasLinearCoordinate()) { + // Get linear axes indices + auto indices = output_csys->linearAxesNumbers(); + if (indices.size() != 2) { + return false; + } + // Input and output linear frames + casacore::Vector output_units = output_csys->worldAxisUnits(); + casacore::Vector world_point_value(output_csys->nWorldAxes(), 0); + world_point_value(indices(0)) = world_point[0].get(output_units(indices(0))).getValue(); + world_point_value(indices(1)) = world_point[1].get(output_units(indices(1))).getValue(); + + // Convert world point to output pixel point + casacore::Vector tmp_pixel_point; + output_csys->toPixel(tmp_pixel_point, world_point_value); + + // Only fill the pixel coordinate results + pixel_point.resize(2); + pixel_point(0) = tmp_pixel_point(indices(0)); + pixel_point(1) = tmp_pixel_point(indices(1)); + success = true; + } + return success; +} diff --git a/src/Region/RegionConverter.h b/src/Region/RegionConverter.h new file mode 100644 index 000000000..1d13d89ce --- /dev/null +++ b/src/Region/RegionConverter.h @@ -0,0 +1,103 @@ +/* This file is part of the CARTA Image Viewer: https://github.com/CARTAvis/carta-backend + Copyright 2018- Academia Sinica Institute of Astronomy and Astrophysics (ASIAA), + Associated Universities, Inc. (AUI) and the Inter-University Institute for Data Intensive Astronomy (IDIA) + SPDX-License-Identifier: GPL-3.0-or-later +*/ + +//# RegionConverter.h: class for managing region conversion to matched image + +#ifndef CARTA_SRC_REGION_REGIONCONVERTER_H_ +#define CARTA_SRC_REGION_REGIONCONVERTER_H_ + +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include "RegionState.h" +#include "Util/Stokes.h" + +#define DEFAULT_VERTEX_COUNT 1000 + +namespace carta { + +class RegionConverter { +public: + RegionConverter(const RegionState& state, std::shared_ptr csys); + + // LCRegion and mask for region applied to image. Must be a closed region (not line) and not annotation. + std::shared_ptr GetCachedLCRegion(int file_id, bool use_approx_polygon = true); + std::shared_ptr GetImageRegion(int file_id, std::shared_ptr csys, + const casacore::IPosition& shape, const StokesSource& stokes_source = StokesSource(), bool report_error = true); + casacore::TableRecord GetImageRegionRecord( + int file_id, std::shared_ptr csys, const casacore::IPosition& shape); + +private: + // Reference region in world coordinates (reference coordinate system) + void SetReferenceWCRegion(); + bool ReferenceRegionValid(); + bool RectangleControlPointsToWorld(std::vector& wcs_points); + bool EllipseControlPointsToWorld(std::vector& wcs_points, float& ellipse_rotation); + bool CartaPointToWorld(const CARTA::Point& point, std::vector& world_point); + + // Region converted directly to image (not polygon approximation) + std::shared_ptr GetConvertedLCRegion(int file_id, std::shared_ptr output_csys, + const casacore::IPosition& output_shape, const StokesSource& stokes_source = StokesSource(), bool report_error = true); + + // Reference region converted to image as approximate polygon, cached with file_id + bool UseApproximatePolygon(std::shared_ptr output_csys); + std::vector GetRectangleMidpoints(); + std::shared_ptr GetAppliedPolygonRegion( + int file_id, std::shared_ptr output_csys, const casacore::IPosition& output_shape, bool has_distortion); + std::vector> GetReferencePolygonPoints(int num_vertices, bool has_distortion); + std::vector> GetApproximatePolygonPoints(int num_vertices, bool has_distortion); + std::vector GetApproximateEllipsePoints(int num_vertices); + double GetTotalSegmentLength(std::vector& points); + void RemoveHorizontalPolygonPoints(casacore::Vector& x, casacore::Vector& y); + bool ValuesNear(float val1, float val2); + + // Record for region converted to pixel coords in output image, returned in LCRegion Record format + casacore::TableRecord GetRegionPointsRecord( + std::shared_ptr output_csys, const casacore::IPosition& output_shape); + void CompleteLCRegionRecord(casacore::TableRecord& record, const casacore::IPosition& shape); + casacore::TableRecord GetPointRecord(std::shared_ptr output_csys, const casacore::IPosition& output_shape); + casacore::TableRecord GetLineRecord(std::shared_ptr image_csys); + casacore::TableRecord GetPolygonRecord(std::shared_ptr output_csys); + casacore::TableRecord GetRotboxRecord(std::shared_ptr output_csys); + casacore::TableRecord GetEllipseRecord(std::shared_ptr output_csys); + + // Utilities for pixel/world conversion + bool PointsToImagePixels(const std::vector& points, std::shared_ptr output_csys, + casacore::Vector& x, casacore::Vector& y); + // World point as (x,y) quantities to output pixel point as (x,y) vector + bool WorldPointToImagePixels(std::vector& world_point, std::shared_ptr output_csys, + casacore::Vector& pixel_point); + + // Reference image region parameters + RegionState _region_state; + std::shared_ptr _reference_coord_sys; + + // Reference region: control points converted to world coords, + // cached as WCRegion, and flag that WCRegion was attempted + // (may be null if outside coordinate system) + std::vector _wcs_control_points; + std::shared_ptr _reference_region; + bool _reference_region_set; + + // Converted regions: reference region applied to image, + // or as polygon approximation applied to image. + // Map key is file_id + std::mutex _region_mutex; + std::unordered_map> _converted_regions; + std::unordered_map> _polygon_regions; +}; + +} // namespace carta + +#endif // CARTA_SRC_REGION_REGIONCONVERTER_H_ diff --git a/src/Region/RegionHandler.cc b/src/Region/RegionHandler.cc index 082518761..6087aa2f3 100644 --- a/src/Region/RegionHandler.cc +++ b/src/Region/RegionHandler.cc @@ -1,5 +1,5 @@ /* This file is part of the CARTA Image Viewer: https://github.com/CARTAvis/carta-backend - Copyright 2018-2022 Academia Sinica Institute of Astronomy and Astrophysics (ASIAA), + Copyright 2018- Academia Sinica Institute of Astronomy and Astrophysics (ASIAA), Associated Universities, Inc. (AUI) and the Inter-University Institute for Data Intensive Astronomy (IDIA) SPDX-License-Identifier: GPL-3.0-or-later */ @@ -96,28 +96,26 @@ bool RegionHandler::SetRegion(int& region_id, RegionState& region_state, std::sh return valid_region; } -bool RegionHandler::RegionChanged(int region_id) { - // Used to trigger sending profiles etc., so not for annotation regions - if (!RegionSet(region_id, true)) { - return false; - } - return GetRegion(region_id)->RegionChanged(); -} - void RegionHandler::RemoveRegion(int region_id) { // Call destructor and erase from map if (!RegionSet(region_id)) { return; } - std::unique_lock region_lock(_region_mutex); + // Disconnect region(s) if (region_id == ALL_REGIONS) { for (auto& region : _regions) { region.second->WaitForTaskCancellation(); } - _regions.clear(); } else { _regions.at(region_id)->WaitForTaskCancellation(); + } + + // Erase region(s) + std::unique_lock region_lock(_region_mutex); + if (region_id == ALL_REGIONS) { + _regions.clear(); + } else { _regions.erase(region_id); } region_lock.unlock(); @@ -252,10 +250,9 @@ void RegionHandler::ExportRegion(int file_id, std::shared_ptr frame, CART } } - bool pixel_coord(coord_type == CARTA::CoordinateType::PIXEL); + bool export_pixel_coord(coord_type == CARTA::CoordinateType::PIXEL); auto output_csys = frame->CoordinateSystem(); - - if (!pixel_coord && !output_csys->hasDirectionCoordinate()) { + if (!export_pixel_coord && !output_csys->hasDirectionCoordinate()) { // Export fails, cannot convert to world coordinates export_ack.set_success(false); export_ack.set_message("Cannot export regions in world coordinates for linear coordinate system."); @@ -269,7 +266,7 @@ void RegionHandler::ExportRegion(int file_id, std::shared_ptr frame, CART exporter = std::unique_ptr(new CrtfImportExport(output_csys, output_shape, frame->StokesAxis())); break; case CARTA::FileType::DS9_REG: - exporter = std::unique_ptr(new Ds9ImportExport(output_csys, output_shape, pixel_coord)); + exporter = std::unique_ptr(new Ds9ImportExport(output_csys, output_shape, export_pixel_coord)); break; default: break; @@ -285,7 +282,7 @@ void RegionHandler::ExportRegion(int file_id, std::shared_ptr frame, CART auto region = GetRegion(region_id); auto region_state = region->GetRegionState(); - if ((region_state.reference_file_id == file_id) && pixel_coord) { + if ((region_state.reference_file_id == file_id) && export_pixel_coord) { // Use RegionState control points with reference file id for pixel export region_added = exporter->AddExportRegion(region_state, region_style); } else { @@ -293,7 +290,7 @@ void RegionHandler::ExportRegion(int file_id, std::shared_ptr frame, CART // Use Record containing pixel coords of region converted to output image casacore::TableRecord region_record = region->GetImageRegionRecord(file_id, output_csys, output_shape); if (!region_record.empty()) { - region_added = exporter->AddExportRegion(region_state, region_style, region_record, pixel_coord); + region_added = exporter->AddExportRegion(region_state, region_style, region_record, export_pixel_coord); } } catch (const casacore::AipsError& err) { spdlog::error("Export region record failed: {}", err.getMesg()); @@ -1251,7 +1248,6 @@ bool RegionHandler::CalculatePvPreviewImage(int frame_id, int preview_id, bool q // Get box region LCRegion and mask bool cancel(false); - casacore::Slicer box_bounding_box; auto box_lc_region = ApplyRegionToFile(box_region_id, frame_id); if (!box_lc_region) { @@ -1274,10 +1270,11 @@ bool RegionHandler::CalculatePvPreviewImage(int frame_id, int preview_id, bool q // Progress for loading data here if needed due to prior cancel if (preview_cube->GetRegionProfile(bounding_box, box_mask, progress_callback, profile, max_num_pixels, message)) { // spdlog::debug("PV preview profile {} of {} max num pixels={}", iregion, num_regions, max_num_pixels); + casacore::Vector const profile_v(profile); if (reverse) { - preview_data_matrix.column(iregion) = profile; + preview_data_matrix.column(iregion) = profile_v; } else { - preview_data_matrix.row(iregion) = profile; + preview_data_matrix.row(iregion) = profile_v; } } } @@ -1299,7 +1296,7 @@ bool RegionHandler::CalculatePvPreviewImage(int frame_id, int preview_id, bool q int start_channel(0); // spectral range applied in preview image int stokes(preview_cube->GetStokes()); PvGenerator pv_generator; - pv_generator.SetFileIdName(frame_id, preview_id, preview_cube->GetSourceFileName(), true); + pv_generator.SetFileName(preview_id, preview_cube->GetSourceFileName(), true); if (pv_generator.GetPvImage(preview_frame, no_preview_data, data_shape, increment, start_channel, stokes, reverse, pv_image, error)) { int width = data_shape(0); @@ -1379,9 +1376,9 @@ bool RegionHandler::CalculatePvImage(int file_id, int region_id, int width, Axis std::shared_lock frame_lock(frame->GetActiveTaskMutex()); auto source_filename = frame->GetFileName(); - // Create GeneratedImage with id and name in PvGenerator + // Create GeneratedImage in PvGenerator PvGenerator pv_generator; - pv_generator.SetFileIdName(file_id, name_index, source_filename); + pv_generator.SetFileName(name_index, source_filename); pv_success = pv_generator.GetPvImage(frame, pv_data, pv_shape, offset_increment, start_chan, stokes_index, reverse, pv_image, message); cancelled &= _stop_pv[file_id]; @@ -2277,6 +2274,7 @@ bool RegionHandler::FillPointSpatialProfileData(int file_id, int region_id, std: bool RegionHandler::FillLineSpatialProfileData(int file_id, int region_id, std::function cb) { // Line spatial profiles. Use callback to return each profile individually. + Timer t; if (!RegionFileIdsValid(region_id, file_id, true)) { return false; } @@ -2333,8 +2331,10 @@ bool RegionHandler::FillLineSpatialProfileData(int file_id, int region_id, std:: profile, coordinate, mip, axis_type, crpix, crval, cdelt, unit); cb(profile_message); }); + spdlog::performance("Fill line spatial profile in {:.3f} ms", t.Elapsed().ms()); } + spdlog::performance("Line spatial data in {:.3f} ms", t.Elapsed().ms()); return profile_ok; } @@ -2373,13 +2373,15 @@ bool RegionHandler::GetLineSpatialData(int file_id, int region_id, const std::st } bool RegionHandler::IsPointRegion(int region_id) { + // Analytic region, not annotation if (RegionSet(region_id, true)) { - return GetRegion(region_id)->IsPoint(); + return GetRegion(region_id)->IsPoint() && !GetRegion(region_id)->IsAnnotation(); } return false; } bool RegionHandler::IsLineRegion(int region_id) { + // Analytic region, not annotation if (RegionSet(region_id, true)) { return GetRegion(region_id)->IsLineType() && !GetRegion(region_id)->IsAnnotation(); } @@ -2387,6 +2389,7 @@ bool RegionHandler::IsLineRegion(int region_id) { } bool RegionHandler::IsClosedRegion(int region_id) { + // Analytic region, not annotation if (RegionSet(region_id, true)) { auto type = GetRegion(region_id)->GetRegionState().type; return (type == CARTA::RegionType::RECTANGLE) || (type == CARTA::RegionType::ELLIPSE) || (type == CARTA::RegionType::POLYGON); @@ -2464,25 +2467,37 @@ bool RegionHandler::GetLineProfiles(int file_id, int region_id, int width, const if (line_box_regions.GetLineBoxRegions(line_region_state, line_coord_sys, width, increment, box_regions, message)) { auto t_start = std::chrono::high_resolution_clock::now(); auto num_profiles = box_regions.size(); + size_t iprofile; + // Use completed profiles (not iprofile) for progress. + // iprofile not in order so progress is uneven. + size_t completed_profiles(0); + + // Return this to column 0 when uncomment (moved for format check): + // #pragma omp parallel for private(iprofile) shared(progress, t_start, completed_profiles) + for (iprofile = 0; iprofile < num_profiles; ++iprofile) { + if (cancelled) { + continue; + } - for (size_t iprofile = 0; iprofile < num_profiles; ++iprofile) { // Frame/region closing, or line changed if (CancelLineProfiles(region_id, file_id, line_region_state)) { cancelled = true; - return false; } // PV generator: check if user canceled if (per_z && _stop_pv[file_id]) { spdlog::debug("Stopping line profiles: PV generator cancelled"); cancelled = true; - return false; } // Line spatial profile: check if requirements removed if (!per_z && !HasSpatialRequirements(region_id, file_id, coordinate, width)) { cancelled = true; - return false; + } + + if (cancelled) { + profiles.resize(); + continue; } // Get mean profile for requested file_id and log number of pixels in region @@ -2506,7 +2521,7 @@ bool RegionHandler::GetLineProfiles(int file_id, int region_id, int width, const profiles.row(iprofile) = region_profile; } - progress = float(iprofile + 1) / float(num_profiles); + progress = float(++completed_profiles) / float(num_profiles); if (per_z) { // Update progress if time interval elapsed @@ -2521,7 +2536,7 @@ bool RegionHandler::GetLineProfiles(int file_id, int region_id, int width, const } } - return (progress >= 1.0) && !allEQ(profiles, NAN); + return (!cancelled) && (progress >= 1.0) && !allEQ(profiles, NAN); } bool RegionHandler::CancelLineProfiles(int region_id, int file_id, RegionState& region_state) { @@ -2556,9 +2571,9 @@ casacore::Vector RegionHandler::GetTemporaryRegionProfile(int region_idx, } std::lock_guard guard(_line_profile_mutex); + // Set temporary region int region_id(TEMP_REGION_ID); SetRegion(region_id, region_state, reference_csys); - if (!RegionSet(region_id, true)) { return profile; } @@ -2594,24 +2609,32 @@ casacore::Vector RegionHandler::GetTemporaryRegionProfile(int region_idx, // Get mean spectral profile and max NumPixels for small region. auto npix_per_chan = results[CARTA::StatsType::NumPixels]; num_pixels = *max_element(npix_per_chan.begin(), npix_per_chan.end()); - profile = results[CARTA::StatsType::Mean]; + profile = casacore::Vector(results[CARTA::StatsType::Mean]); // TODO: use double for profile } }); } else { // Use BasicStats to get num_pixels and mean for current channel and stokes - // Get region + // Get region as LCRegion StokesRegion stokes_region; std::shared_ptr lc_region; std::shared_lock frame_lock(_frames.at(file_id)->GetActiveTaskMutex()); if (ApplyRegionToFile(region_id, file_id, z_range, stokes_index, lc_region, stokes_region)) { - // Get region data (report_performance = false, too much output) + // Get region data by applying LCRegion to image std::vector region_data; if (_frames.at(file_id)->GetRegionData(stokes_region, region_data, false)) { - // Get BasicStats - BasicStats basic_stats; - CalcBasicStats(basic_stats, region_data.data(), region_data.size()); - num_pixels = basic_stats.num_pixels; - profile[0] = basic_stats.mean; + // Very small region, just calc needed stats here + num_pixels = 0; + double sum(0.0); + for (size_t i = 0; i < region_data.size(); ++i) { + float val(region_data[i]); + if (std::isfinite(val)) { + num_pixels++; + sum += (double)val; + } + } + if (num_pixels > 0) { + profile[0] = sum / num_pixels; + } } } frame_lock.unlock(); diff --git a/src/Region/RegionHandler.h b/src/Region/RegionHandler.h index 18b95f4e1..529a01d4f 100644 --- a/src/Region/RegionHandler.h +++ b/src/Region/RegionHandler.h @@ -1,13 +1,13 @@ /* This file is part of the CARTA Image Viewer: https://github.com/CARTAvis/carta-backend - Copyright 2018-2022 Academia Sinica Institute of Astronomy and Astrophysics (ASIAA), + Copyright 2018- Academia Sinica Institute of Astronomy and Astrophysics (ASIAA), Associated Universities, Inc. (AUI) and the Inter-University Institute for Data Intensive Astronomy (IDIA) SPDX-License-Identifier: GPL-3.0-or-later */ // RegionHandler.h: class for handling requirements and data streams for regions -#ifndef CARTA_BACKEND_REGION_REGIONHANDLER_H_ -#define CARTA_BACKEND_REGION_REGIONHANDLER_H_ +#ifndef CARTA_SRC_REGION_REGIONHANDLER_H_ +#define CARTA_SRC_REGION_REGIONHANDLER_H_ #include @@ -35,7 +35,6 @@ class RegionHandler { // Regions bool SetRegion(int& region_id, RegionState& region_state, std::shared_ptr csys); - bool RegionChanged(int region_id); void RemoveRegion(int region_id); std::shared_ptr GetRegion(int region_id); @@ -219,4 +218,4 @@ class RegionHandler { } // namespace carta -#endif // CARTA_BACKEND_REGION_REGIONHANDLER_H_ +#endif // CARTA_SRC_REGION_REGIONHANDLER_H_ diff --git a/src/Region/RegionImportExport.cc b/src/Region/RegionImportExport.cc index 2ae70dd26..cc6244341 100644 --- a/src/Region/RegionImportExport.cc +++ b/src/Region/RegionImportExport.cc @@ -1,5 +1,5 @@ /* This file is part of the CARTA Image Viewer: https://github.com/CARTAvis/carta-backend - Copyright 2018-2022 Academia Sinica Institute of Astronomy and Astrophysics (ASIAA), + Copyright 2018- Academia Sinica Institute of Astronomy and Astrophysics (ASIAA), Associated Universities, Inc. (AUI) and the Inter-University Institute for Data Intensive Astronomy (IDIA) SPDX-License-Identifier: GPL-3.0-or-later */ @@ -380,13 +380,30 @@ bool RegionImportExport::ConvertRecordToRectangle( // Convert casacore Record to box Quantity control points. // Rectangles are exported to Record as LCPolygon with 4 points: blc, brc, trc, tlc. // The input Record for a rotbox must be the corners of an unrotated box (rotation in the region state) - casacore::Vector x = region_record.asArrayFloat("x"); - casacore::Vector y = region_record.asArrayFloat("y"); + casacore::Vector x, y; + + if (region_record.dataType("x") == casacore::TpArrayFloat) { + casacore::Vector xf, yf; + xf = region_record.asArrayFloat("x"); + yf = region_record.asArrayFloat("y"); + + // Convert to Double + auto xf_size(xf.size()); + x.resize(xf_size); + y.resize(xf_size); + for (auto i = 0; i < xf_size; ++i) { + x(i) = xf(i); + y(i) = yf(i); + } + } else { + x = region_record.asArrayDouble("x"); + y = region_record.asArrayDouble("y"); + } // Make zero-based if (region_record.asBool("oneRel")) { - x -= (float)1.0; - y -= (float)1.0; + x -= 1.0; + y -= 1.0; } double cx, cy, width, height; @@ -445,7 +462,7 @@ bool RegionImportExport::ConvertRecordToEllipse(const RegionState& region_state, // RegionState needed to check if bmaj/bmin swapped for LCEllipsoid casacore::Vector center = region_record.asArrayFloat("center"); casacore::Vector radii = region_record.asArrayFloat("radii"); - casacore::Float theta = region_record.asFloat("theta"); // radians + casacore::Double theta = region_record.asDouble("theta"); // radians rotation = casacore::Quantity(theta, "rad"); rotation.convert("deg"); // CASA rotang, from x-axis diff --git a/src/Region/RegionImportExport.h b/src/Region/RegionImportExport.h index e9541e7f3..8fe466ef4 100644 --- a/src/Region/RegionImportExport.h +++ b/src/Region/RegionImportExport.h @@ -1,13 +1,13 @@ /* This file is part of the CARTA Image Viewer: https://github.com/CARTAvis/carta-backend - Copyright 2018-2022 Academia Sinica Institute of Astronomy and Astrophysics (ASIAA), + Copyright 2018- Academia Sinica Institute of Astronomy and Astrophysics (ASIAA), Associated Universities, Inc. (AUI) and the Inter-University Institute for Data Intensive Astronomy (IDIA) SPDX-License-Identifier: GPL-3.0-or-later */ //# RegionImportExport.h: handle region import/export in CRTF and DS9 formats -#ifndef CARTA_BACKEND_REGION_REGIONIMPORTEXPORT_H_ -#define CARTA_BACKEND_REGION_REGIONIMPORTEXPORT_H_ +#ifndef CARTA_SRC_REGION_REGIONIMPORTEXPORT_H_ +#define CARTA_SRC_REGION_REGIONIMPORTEXPORT_H_ #include #include @@ -116,4 +116,4 @@ class RegionImportExport { } // namespace carta -#endif // CARTA_BACKEND_REGION_REGIONIMPORTEXPORT_H_ +#endif // CARTA_SRC_REGION_REGIONIMPORTEXPORT_H_ diff --git a/src/Region/RegionState.h b/src/Region/RegionState.h new file mode 100644 index 000000000..30495e039 --- /dev/null +++ b/src/Region/RegionState.h @@ -0,0 +1,149 @@ +/* This file is part of the CARTA Image Viewer: https://github.com/CARTAvis/carta-backend + Copyright 2018- Academia Sinica Institute of Astronomy and Astrophysics (ASIAA), + Associated Universities, Inc. (AUI) and the Inter-University Institute for Data Intensive Astronomy (IDIA) + SPDX-License-Identifier: GPL-3.0-or-later +*/ + +//# RegionState.h: struct for holding region parameters + +#ifndef CARTA_SRC_REGION_REGIONSTATE_H_ +#define CARTA_SRC_REGION_REGIONSTATE_H_ + +#include + +namespace carta { + +struct RegionState { + // struct used for region parameters + int reference_file_id; + CARTA::RegionType type; + std::vector control_points; + float rotation; + + RegionState() : reference_file_id(-1), type(CARTA::POINT), rotation(0) {} + RegionState(int ref_file_id_, CARTA::RegionType type_, const std::vector& control_points_, float rotation_) + : reference_file_id(ref_file_id_), type(type_), control_points(control_points_), rotation(rotation_) {} + + bool operator==(const RegionState& rhs) { + return (reference_file_id == rhs.reference_file_id) && (type == rhs.type) && !RegionChanged(rhs); + } + + bool operator!=(const RegionState& rhs) { + return (reference_file_id != rhs.reference_file_id) || (type != rhs.type) || RegionChanged(rhs); + } + + bool RegionDefined() { + return !control_points.empty(); + } + + bool RegionChanged(const RegionState& rhs) { + // Ignores annotation params (for interrupting region calculations) + return (rotation != rhs.rotation) || PointsChanged(rhs); + } + bool PointsChanged(const RegionState& rhs) { + // Points must be same size, order, and value to be unchanged + if (control_points.size() != rhs.control_points.size()) { + return true; + } + for (int i = 0; i < control_points.size(); ++i) { + float x(control_points[i].x()), y(control_points[i].y()); + float rhs_x(rhs.control_points[i].x()), rhs_y(rhs.control_points[i].y()); + if ((x != rhs_x) || (y != rhs_y)) { + return true; + } + } + return false; + } + + bool IsPoint() { + // Includes annotation types. + return type == CARTA::POINT || type == CARTA::ANNPOINT; + } + + bool IsLineType() { + // Not enclosed region, defined by points. Includes annotation types. + std::vector line_types{ + CARTA::LINE, CARTA::POLYLINE, CARTA::ANNLINE, CARTA::ANNPOLYLINE, CARTA::ANNVECTOR, CARTA::ANNRULER}; + return std::find(line_types.begin(), line_types.end(), type) != line_types.end(); + } + + bool IsBox() { + // Rectangle-type regions. Includes annotation types. + std::vector rectangle_types{CARTA::RECTANGLE, CARTA::ANNRECTANGLE, CARTA::ANNTEXT}; + return std::find(rectangle_types.begin(), rectangle_types.end(), type) != rectangle_types.end(); + } + + bool IsRotbox() { + // Rectangle-type regions with rotation. Includes annotation types. + return IsBox() && (rotation != 0.0); + } + + bool IsAnnotation() { + return type > CARTA::POLYGON; + } + + bool GetRectangleCorners(casacore::Vector& x, casacore::Vector& y, bool apply_rotation = true) { + // Convert rectangle points [[cx, cy], [width, height]] to corner points. Optionally apply rotation. + if (type != CARTA::RECTANGLE && type != CARTA::ANNRECTANGLE && type != CARTA::ANNTEXT) { + return false; + } + float center_x(control_points[0].x()), center_y(control_points[0].y()); + float width(control_points[1].x()), height(control_points[1].y()); + x.resize(4); + y.resize(4); + + if (rotation == 0.0 || !apply_rotation) { + float x_min(center_x - width / 2.0f), x_max(center_x + width / 2.0f); + float y_min(center_y - height / 2.0f), y_max(center_y + height / 2.0f); + // Bottom left + x(0) = x_min; + y(0) = y_min; + // Bottom right + x(1) = x_max; + y(1) = y_min; + // Top right + x(2) = x_max; + y(2) = y_max; + // Top left + x(3) = x_min; + y(3) = y_max; + } else { + // Apply rotation matrix to get width and height vectors in rotated basis + float cos_x = cos(rotation * M_PI / 180.0f); + float sin_x = sin(rotation * M_PI / 180.0f); + float width_vector_x = cos_x * width; + float width_vector_y = sin_x * width; + float height_vector_x = -sin_x * height; + float height_vector_y = cos_x * height; + + // Bottom left + x(0) = center_x + (-width_vector_x - height_vector_x) / 2.0f; + y(0) = center_y + (-width_vector_y - height_vector_y) / 2.0f; + // Bottom right + x(1) = center_x + (width_vector_x - height_vector_x) / 2.0f; + y(1) = center_y + (width_vector_y - height_vector_y) / 2.0f; + // Top right + x(2) = center_x + (width_vector_x + height_vector_x) / 2.0f; + y(2) = center_y + (width_vector_y + height_vector_y) / 2.0f; + // Top left + x(3) = center_x + (-width_vector_x + height_vector_x) / 2.0f; + y(3) = center_y + (-width_vector_y + height_vector_y) / 2.0f; + } + return true; + } + + std::string GetLineRegionName() { + // Names not defined in casacore, for region record + std::string name; + if (IsLineType()) { + std::unordered_map line_region_names = {{CARTA::LINE, "line"}, {CARTA::POLYLINE, "polyline"}, + {CARTA::ANNLINE, "line"}, {CARTA::ANNPOLYLINE, "polyline"}, {CARTA::ANNVECTOR, "vector"}, {CARTA::ANNRULER, "ruler"}}; + name = line_region_names.at(type); + } + return name; + } +}; + +} // namespace carta + +#endif // CARTA_SRC_REGION_REGIONSTATE_H_ diff --git a/src/Session/AnimationObject.h b/src/Session/AnimationObject.h index 93e3386b0..6e64b0e54 100644 --- a/src/Session/AnimationObject.h +++ b/src/Session/AnimationObject.h @@ -1,11 +1,11 @@ /* This file is part of the CARTA Image Viewer: https://github.com/CARTAvis/carta-backend - Copyright 2018-2022 Academia Sinica Institute of Astronomy and Astrophysics (ASIAA), + Copyright 2018- Academia Sinica Institute of Astronomy and Astrophysics (ASIAA), Associated Universities, Inc. (AUI) and the Inter-University Institute for Data Intensive Astronomy (IDIA) SPDX-License-Identifier: GPL-3.0-or-later */ -#ifndef CARTA_BACKEND__ANIMATIONOBJECT_H_ -#define CARTA_BACKEND__ANIMATIONOBJECT_H_ +#ifndef CARTA_SRC_SESSION_ANIMATIONOBJECT_H_ +#define CARTA_SRC_SESSION_ANIMATIONOBJECT_H_ #include #include @@ -113,4 +113,4 @@ class AnimationObject { } // namespace carta -#endif // CARTA_BACKEND__ANIMATIONOBJECT_H_ +#endif // CARTA_SRC_SESSION_ANIMATIONOBJECT_H_ diff --git a/src/Session/CursorSettings.cc b/src/Session/CursorSettings.cc index db1b77bac..8b97046b8 100644 --- a/src/Session/CursorSettings.cc +++ b/src/Session/CursorSettings.cc @@ -1,5 +1,5 @@ /* This file is part of the CARTA Image Viewer: https://github.com/CARTAvis/carta-backend - Copyright 2018-2022 Academia Sinica Institute of Astronomy and Astrophysics (ASIAA), + Copyright 2018- Academia Sinica Institute of Astronomy and Astrophysics (ASIAA), Associated Universities, Inc. (AUI) and the Inter-University Institute for Data Intensive Astronomy (IDIA) SPDX-License-Identifier: GPL-3.0-or-later */ diff --git a/src/Session/CursorSettings.h b/src/Session/CursorSettings.h index e2cd7ad38..40f6d8c3f 100644 --- a/src/Session/CursorSettings.h +++ b/src/Session/CursorSettings.h @@ -1,11 +1,11 @@ /* This file is part of the CARTA Image Viewer: https://github.com/CARTAvis/carta-backend - Copyright 2018-2022 Academia Sinica Institute of Astronomy and Astrophysics (ASIAA), + Copyright 2018- Academia Sinica Institute of Astronomy and Astrophysics (ASIAA), Associated Universities, Inc. (AUI) and the Inter-University Institute for Data Intensive Astronomy (IDIA) SPDX-License-Identifier: GPL-3.0-or-later */ -#ifndef CARTA_BACKEND__CURSORSETTINGS_H_ -#define CARTA_BACKEND__CURSORSETTINGS_H_ +#ifndef CARTA_SRC_SESSION_CURSORSETTINGS_H_ +#define CARTA_SRC_SESSION_CURSORSETTINGS_H_ #include #include @@ -41,4 +41,4 @@ class CursorSettings { } // namespace carta -#endif // CARTA_BACKEND__CURSORSETTINGS_H_ +#endif // CARTA_SRC_SESSION_CURSORSETTINGS_H_ diff --git a/src/Session/OnMessageTask.cc b/src/Session/OnMessageTask.cc index b1976a6f0..d3a49b4b6 100644 --- a/src/Session/OnMessageTask.cc +++ b/src/Session/OnMessageTask.cc @@ -1,5 +1,5 @@ /* This file is part of the CARTA Image Viewer: https://github.com/CARTAvis/carta-backend - Copyright 2018-2022 Academia Sinica Institute of Astronomy and Astrophysics (ASIAA), + Copyright 2018- Academia Sinica Institute of Astronomy and Astrophysics (ASIAA), Associated Universities, Inc. (AUI) and the Inter-University Institute for Data Intensive Astronomy (IDIA) SPDX-License-Identifier: GPL-3.0-or-later */ diff --git a/src/Session/OnMessageTask.h b/src/Session/OnMessageTask.h index 2ba318518..6315576a3 100644 --- a/src/Session/OnMessageTask.h +++ b/src/Session/OnMessageTask.h @@ -1,13 +1,13 @@ /* This file is part of the CARTA Image Viewer: https://github.com/CARTAvis/carta-backend - Copyright 2018-2022 Academia Sinica Institute of Astronomy and Astrophysics (ASIAA), + Copyright 2018- Academia Sinica Institute of Astronomy and Astrophysics (ASIAA), Associated Universities, Inc. (AUI) and the Inter-University Institute for Data Intensive Astronomy (IDIA) SPDX-License-Identifier: GPL-3.0-or-later */ //# OnMessageTask.h: dequeues messages and calls appropriate Session handlers -#ifndef CARTA_BACKEND__ONMESSAGETASK_H_ -#define CARTA_BACKEND__ONMESSAGETASK_H_ +#ifndef CARTA_SRC_SESSION_ONMESSAGETASK_H_ +#define CARTA_SRC_SESSION_ONMESSAGETASK_H_ #include #include @@ -120,4 +120,4 @@ class PvPreviewUpdateTask : public OnMessageTask { #include "OnMessageTask.tcc" -#endif // CARTA_BACKEND__ONMESSAGETASK_H_ +#endif // CARTA_SRC_SESSION_ONMESSAGETASK_H_ diff --git a/src/Session/OnMessageTask.tcc b/src/Session/OnMessageTask.tcc index cb0417e35..3a73db282 100644 --- a/src/Session/OnMessageTask.tcc +++ b/src/Session/OnMessageTask.tcc @@ -1,11 +1,11 @@ /* This file is part of the CARTA Image Viewer: https://github.com/CARTAvis/carta-backend - Copyright 2018-2022 Academia Sinica Institute of Astronomy and Astrophysics (ASIAA), + Copyright 2018- Academia Sinica Institute of Astronomy and Astrophysics (ASIAA), Associated Universities, Inc. (AUI) and the Inter-University Institute for Data Intensive Astronomy (IDIA) SPDX-License-Identifier: GPL-3.0-or-later */ -#ifndef CARTA_BACKEND__ONMESSAGETASK_TCC_ -#define CARTA_BACKEND__ONMESSAGETASK_TCC_ +#ifndef CARTA_SRC_SESSION_ONMESSAGETASK_TCC_ +#define CARTA_SRC_SESSION_ONMESSAGETASK_TCC_ namespace carta { @@ -53,4 +53,4 @@ public: } // namespace carta -#endif // CARTA_BACKEND__ONMESSAGETASK_TCC_ +#endif // CARTA_SRC_SESSION_ONMESSAGETASK_TCC_ diff --git a/src/Session/Session.cc b/src/Session/Session.cc index 724253426..9bd81f974 100644 --- a/src/Session/Session.cc +++ b/src/Session/Session.cc @@ -1,5 +1,5 @@ /* This file is part of the CARTA Image Viewer: https://github.com/CARTAvis/carta-backend - Copyright 2018-2022 Academia Sinica Institute of Astronomy and Astrophysics (ASIAA), + Copyright 2018- Academia Sinica Institute of Astronomy and Astrophysics (ASIAA), Associated Universities, Inc. (AUI) and the Inter-University Institute for Data Intensive Astronomy (IDIA) SPDX-License-Identifier: GPL-3.0-or-later */ @@ -41,58 +41,6 @@ using namespace carta; -LoaderCache::LoaderCache(int capacity) : _capacity(capacity){}; - -std::shared_ptr LoaderCache::Get(const std::string& filename, const std::string& directory) { - std::unique_lock guard(_loader_cache_mutex); - auto key = GetKey(filename, directory); - - // We have a cached loader, but the file has changed - if ((_map.find(key) != _map.end()) && _map[key] && _map[key]->ImageUpdated()) { - _map.erase(key); - _queue.remove(key); - } - - // We don't have a cached loader - if (_map.find(key) == _map.end()) { - // Create the loader -- don't block while doing this - std::shared_ptr loader_ptr; - guard.unlock(); - loader_ptr = std::shared_ptr(FileLoader::GetLoader(filename, directory)); - guard.lock(); - - // Check if the loader was added in the meantime - if (_map.find(key) == _map.end()) { - // Evict oldest loader if necessary - if (_map.size() == _capacity) { - _map.erase(_queue.back()); - _queue.pop_back(); - } - - // Insert the new loader - _map[key] = loader_ptr; - _queue.push_front(key); - } - } else { - // Touch the cache entry - _queue.remove(key); - _queue.push_front(key); - } - - return _map[key]; -} - -void LoaderCache::Remove(const std::string& filename, const std::string& directory) { - std::unique_lock guard(_loader_cache_mutex); - auto key = GetKey(filename, directory); - _map.erase(key); - _queue.remove(key); -} - -std::string LoaderCache::GetKey(const std::string& filename, const std::string& directory) { - return (directory.empty() ? filename : fmt::format("{}/{}", directory, filename)); -} - volatile int Session::_num_sessions = 0; int Session::_exit_after_num_seconds = 5; bool Session::_exit_when_all_sessions_closed = false; @@ -100,17 +48,12 @@ bool Session::_controller_deployment = false; std::thread* Session::_animation_thread = nullptr; Session::Session(uWS::WebSocket* ws, uWS::Loop* loop, uint32_t id, std::string address, - std::string top_level_folder, std::string starting_folder, std::shared_ptr file_list_handler, bool read_only_mode, - bool enable_scripting) + std::shared_ptr file_list_handler) : _socket(ws), _loop(loop), _id(id), _address(address), - _top_level_folder(top_level_folder), - _starting_folder(starting_folder), - _table_controller(std::make_unique(_top_level_folder, _starting_folder)), - _read_only_mode(read_only_mode), - _enable_scripting(enable_scripting), + _table_controller(std::make_unique()), _region_handler(nullptr), _file_list_handler(file_list_handler), _sync_id(0), @@ -118,6 +61,10 @@ Session::Session(uWS::WebSocket* ws, uWS::Loop* loop _animation_active(false), _cursor_settings(this), _loaders(LOADER_CACHE_SIZE) { + auto& settings = ProgramSettings::GetInstance(); + _top_level_folder = settings.top_level_folder; + _read_only_mode = settings.read_only_mode; + _enable_scripting = settings.enable_scripting; _histogram_progress = 1.0; _ref_count = 0; _animation_object = nullptr; @@ -177,6 +124,11 @@ Session::~Session() { logger::FlushLogFile(); } +void Session::SetExitTimeout(int secs) { + _exit_after_num_seconds = secs; + _exit_when_all_sessions_closed = true; +} + void Session::SetInitExitTimeout(int secs) { __exit_backend_timer = secs; struct sigaction sig_handler; @@ -362,9 +314,8 @@ bool Session::FillFileInfo( // Resolve filename and fill file info submessage bool file_info_ok(false); - fullname = GetResolvedFilename(_top_level_folder, folder, filename); + fullname = GetResolvedFilename(_top_level_folder, folder, filename, message); if (fullname.empty()) { - message = fmt::format("File {} does not exist.", filename); return file_info_ok; } @@ -523,18 +474,17 @@ bool Session::OnOpenFile(const CARTA::OpenFile& message, uint32_t request_id, bo if (lel_expr) { // filename field is LEL expression - auto dir_path = GetResolvedFilename(_top_level_folder, directory, ""); - auto loader = _loaders.Get(filename, dir_path); - - try { - loader->OpenFile(hdu); - - auto image = loader->GetImage(); - success = OnOpenFile(file_id, filename, image, &ack); - } catch (const casacore::AipsError& err) { - _loaders.Remove(filename, dir_path); - success = false; - err_message = err.getMesg(); + auto dir_path = GetResolvedFilename(_top_level_folder, directory, "", err_message); + if (!dir_path.empty()) { + auto loader = _loaders.Get(filename, dir_path); + try { + loader->OpenFile(hdu); + auto image = loader->GetImage(); + success = OnOpenFile(file_id, filename, image, &ack); + } catch (const casacore::AipsError& err) { + _loaders.Remove(filename, dir_path); + err_message = err.getMesg(); + } } } else { ack.set_file_id(file_id); @@ -578,7 +528,8 @@ bool Session::OnOpenFile(const CARTA::OpenFile& message, uint32_t request_id, bo DeleteFrame(file_id); } std::unique_lock lock(_frame_mutex); // open/close lock - _frames[file_id] = move(frame); + _frames[file_id] = std::move(frame); + _last_file_id = file_id; lock.unlock(); // copy file info, extended file info @@ -649,7 +600,8 @@ bool Session::OnOpenFile( DeleteFrame(file_id); } std::unique_lock lock(_frame_mutex); // open/close lock - _frames[file_id] = move(frame); + _frames[file_id] = std::move(frame); + _last_file_id = file_id; lock.unlock(); // Set file info, extended file info @@ -908,10 +860,10 @@ void Session::OnImportRegion(const CARTA::ImportRegion& message, uint32_t reques std::string region_file; // name or contents if (import_file) { // check that file can be opened - region_file = GetResolvedFilename(_top_level_folder, directory, filename); - casacore::File ccfile(region_file); - if (!ccfile.exists() || !ccfile.isReadable()) { - auto import_ack = Message::ImportRegionAck(false, "Import region failed: cannot open file."); + std::string error; + region_file = GetResolvedFilename(_top_level_folder, directory, filename, error); + if (region_file.empty()) { + auto import_ack = Message::ImportRegionAck(false, "Import region failed: " + error); SendFileEvent(file_id, CARTA::EventType::IMPORT_REGION_ACK, request_id, import_ack); return; } @@ -1297,10 +1249,11 @@ void Session::OnMomentRequest(const CARTA::MomentRequest& moment_request, uint32 } // Open moments images from the cache, open files acknowledgements will be sent to the frontend + int next_file_id = GetNextFileId(); for (int i = 0; i < collapse_results.size(); ++i) { auto& collapse_result = collapse_results[i]; auto* open_file_ack = moment_response.add_open_file_acks(); - OnOpenFile(collapse_result.file_id, collapse_result.name, collapse_result.image, open_file_ack); + OnOpenFile(next_file_id++, collapse_result.name, collapse_result.image, open_file_ack); } // Send moment response message @@ -1429,8 +1382,9 @@ void Session::OnPvRequest(const CARTA::PvRequest& pv_request, uint32_t request_i if (_region_handler->CalculatePvImage(pv_request, frame, progress_callback, pv_response, pv_image)) { // Fill response OpenFileAck + int next_file_id = GetNextFileId(); auto* open_file_ack = pv_response.mutable_open_file_ack(); - OnOpenFile(pv_image.file_id, pv_image.name, pv_image.image, open_file_ack); + OnOpenFile(next_file_id, pv_image.name, pv_image.image, open_file_ack); } } spdlog::performance("Generate pv response in {:.3f} ms", t.Elapsed().ms()); @@ -1492,13 +1446,14 @@ void Session::OnFittingRequest(const CARTA::FittingRequest& fitting_request, uin } if (success) { + int next_file_id = GetNextFileId(); if (fitting_request.create_model_image()) { auto* model_image_open_file_ack = fitting_response.mutable_model_image(); - OnOpenFile(model_image.file_id, model_image.name, model_image.image, model_image_open_file_ack); + OnOpenFile(next_file_id, model_image.name, model_image.image, model_image_open_file_ack); } if (fitting_request.create_residual_image()) { auto* residual_image_open_file_ack = fitting_response.mutable_residual_image(); - OnOpenFile(residual_image.file_id, residual_image.name, residual_image.image, residual_image_open_file_ack); + OnOpenFile(++next_file_id, residual_image.name, residual_image.image, residual_image_open_file_ack); } } @@ -2440,8 +2395,12 @@ std::chrono::high_resolution_clock::time_point Session::GetLastMessageTimestamp( } void Session::CloseCachedImage(const std::string& directory, const std::string& file) { - std::string fullname = GetResolvedFilename(_top_level_folder, directory, file); - for (auto& frame : _frames) { - frame.second->CloseCachedImage(fullname); + std::string message; + std::string fullname = GetResolvedFilename(_top_level_folder, directory, file, message); + + if (!fullname.empty()) { + for (auto& frame : _frames) { + frame.second->CloseCachedImage(fullname); + } } } diff --git a/src/Session/Session.h b/src/Session/Session.h index 796863c3a..b6455ccee 100644 --- a/src/Session/Session.h +++ b/src/Session/Session.h @@ -1,13 +1,13 @@ /* This file is part of the CARTA Image Viewer: https://github.com/CARTAvis/carta-backend - Copyright 2018-2022 Academia Sinica Institute of Astronomy and Astrophysics (ASIAA), + Copyright 2018- Academia Sinica Institute of Astronomy and Astrophysics (ASIAA), Associated Universities, Inc. (AUI) and the Inter-University Institute for Data Intensive Astronomy (IDIA) SPDX-License-Identifier: GPL-3.0-or-later */ // # Session.h: representation of a client connected to a server; processes requests from frontend -#ifndef CARTA_BACKEND__SESSION_H_ -#define CARTA_BACKEND__SESSION_H_ +#ifndef CARTA_SRC_SESSION_SESSION_H_ +#define CARTA_SRC_SESSION_SESSION_H_ #include #include @@ -24,17 +24,18 @@ #include #include "AnimationObject.h" +#include "Cache/LoaderCache.h" #include "CursorSettings.h" #include "FileList/FileListHandler.h" #include "Frame/Frame.h" #include "ImageData/StokesFilesConnector.h" +#include "Main/ProgramSettings.h" #include "Region/RegionHandler.h" #include "SessionContext.h" +#include "Table/TableController.h" #include "ThreadingManager/Concurrency.h" #include "Util/Message.h" -#include "Table/TableController.h" - #define HISTOGRAM_CANCEL -1.0 #define UPDATE_HISTOGRAM_PROGRESS_PER_SECONDS 2.0 #define LOADER_CACHE_SIZE 25 @@ -49,26 +50,10 @@ struct PerSocketData { string address; }; -// Cache of loaders for reading images from disk. -class LoaderCache { -public: - LoaderCache(int capacity); - std::shared_ptr Get(const std::string& filename, const std::string& directory = ""); - void Remove(const std::string& filename, const std::string& directory = ""); - -private: - std::string GetKey(const std::string& filename, const std::string& directory); - int _capacity; - std::unordered_map> _map; - std::list _queue; - std::mutex _loader_cache_mutex; -}; - class Session { public: - Session(uWS::WebSocket* ws, uWS::Loop* loop, uint32_t id, std::string address, std::string top_level_folder, - std::string starting_folder, std::shared_ptr file_list_handler, bool read_only_mode = false, - bool enable_scripting = false); + Session(uWS::WebSocket* ws, uWS::Loop* loop, uint32_t id, std::string address, + std::shared_ptr file_list_handler); ~Session(); // CARTA ICD @@ -195,10 +180,8 @@ class Session { return _animation_object && !_animation_object->_stop_called; } int CalculateAnimationFlowWindow(); - static void SetExitTimeout(int secs) { - _exit_after_num_seconds = secs; - _exit_when_all_sessions_closed = true; - } + + static void SetExitTimeout(int secs); static void SetInitExitTimeout(int secs); static void SetControllerDeploymentFlag(bool controller_deployment) { @@ -257,6 +240,11 @@ class Session { bool FillExtendedFileInfo(CARTA::FileInfoExtended& extended_info, std::shared_ptr> image, const std::string& filename, std::string& message, std::shared_ptr& image_loader); + // Next unused file id for generated images + inline int GetNextFileId() { + return _last_file_id + 1; + } + // Delete Frame(s) void DeleteFrame(int file_id); @@ -287,10 +275,6 @@ class Session { uint32_t _id; std::string _address; - std::string _top_level_folder; - std::string _starting_folder; - bool _read_only_mode; - bool _enable_scripting; // File browser std::shared_ptr _file_list_handler; @@ -300,6 +284,7 @@ class Session { // Frame; key is file_id; shared with RegionHandler for data streams std::unordered_map> _frames; + int _last_file_id; std::mutex _frame_mutex; const std::unique_ptr _table_controller; @@ -348,8 +333,13 @@ class Session { // Timestamp for the last protobuf message std::chrono::high_resolution_clock::time_point _last_message_timestamp; + + // Parameters which are copied from the global settings + std::string _top_level_folder; + bool _read_only_mode; + bool _enable_scripting; }; } // namespace carta -#endif // CARTA_BACKEND__SESSION_H_ +#endif // CARTA_SRC_SESSION_SESSION_H_ diff --git a/src/Session/SessionContext.h b/src/Session/SessionContext.h index b4fc4b5a6..841233ee5 100644 --- a/src/Session/SessionContext.h +++ b/src/Session/SessionContext.h @@ -1,11 +1,11 @@ /* This file is part of the CARTA Image Viewer: https://github.com/CARTAvis/carta-backend - Copyright 2018-2022 Academia Sinica Institute of Astronomy and Astrophysics (ASIAA), + Copyright 2018- Academia Sinica Institute of Astronomy and Astrophysics (ASIAA), Associated Universities, Inc. (AUI) and the Inter-University Institute for Data Intensive Astronomy (IDIA) SPDX-License-Identifier: GPL-3.0-or-later */ -#ifndef __SESSION_CONTEXT_H__ -#define __SESSION_CONTEXT_H__ +#ifndef CARTA_SRC_SESSION_SESSIONCONTEXT_H_ +#define CARTA_SRC_SESSION_SESSIONCONTEXT_H_ namespace carta { @@ -30,4 +30,4 @@ class SessionContext { } // namespace carta -#endif // __SESSION_CONTEXT_H__ +#endif // CARTA_SRC_SESSION_SESSIONCONTEXT_H_ diff --git a/src/Session/SessionManager.cc b/src/Session/SessionManager.cc index 4cfac86f7..acf0804fa 100644 --- a/src/Session/SessionManager.cc +++ b/src/Session/SessionManager.cc @@ -1,5 +1,5 @@ /* This file is part of the CARTA Image Viewer: https://github.com/CARTAvis/carta-backend - Copyright 2018-2022 Academia Sinica Institute of Astronomy and Astrophysics (ASIAA), + Copyright 2018- Academia Sinica Institute of Astronomy and Astrophysics (ASIAA), Associated Universities, Inc. (AUI) and the Inter-University Institute for Data Intensive Astronomy (IDIA) SPDX-License-Identifier: GPL-3.0-or-later */ @@ -87,8 +87,7 @@ void SessionManager::OnConnect(WSType* ws) { // create a Session std::unique_lock ulock(_sessions_mutex); - _sessions[session_id] = new Session(ws, loop, session_id, address, _settings.top_level_folder, _settings.starting_folder, - _file_list_handler, _settings.read_only_mode, _settings.enable_scripting); + _sessions[session_id] = new Session(ws, loop, session_id, address, _file_list_handler); _sessions[session_id]->IncreaseRefCount(); diff --git a/src/Session/SessionManager.h b/src/Session/SessionManager.h index 38cee8fab..f206849d9 100644 --- a/src/Session/SessionManager.h +++ b/src/Session/SessionManager.h @@ -1,11 +1,11 @@ /* This file is part of the CARTA Image Viewer: https://github.com/CARTAvis/carta-backend - Copyright 2018-2022 Academia Sinica Institute of Astronomy and Astrophysics (ASIAA), + Copyright 2018- Academia Sinica Institute of Astronomy and Astrophysics (ASIAA), Associated Universities, Inc. (AUI) and the Inter-University Institute for Data Intensive Astronomy (IDIA) SPDX-License-Identifier: GPL-3.0-or-later */ -#ifndef CARTA_BACKEND_SRC_SESSIONMANAGER_SESSIONMANAGER_H_ -#define CARTA_BACKEND_SRC_SESSIONMANAGER_SESSIONMANAGER_H_ +#ifndef CARTA_SRC_SESSION_SESSIONMANAGER_H_ +#define CARTA_SRC_SESSION_SESSIONMANAGER_H_ #include #include @@ -52,4 +52,4 @@ class SessionManager { std::string IPAsText(std::string_view binary); }; } // namespace carta -#endif // CARTA_BACKEND_SRC_SESSIONMANAGER_SESSIONMANAGER_H_ +#endif // CARTA_SRC_SESSION_SESSIONMANAGER_H_ diff --git a/src/Table/Columns.cc b/src/Table/Columns.cc index 3fea15208..0e464df89 100644 --- a/src/Table/Columns.cc +++ b/src/Table/Columns.cc @@ -1,5 +1,5 @@ /* This file is part of the CARTA Image Viewer: https://github.com/CARTAvis/carta-backend - Copyright 2018-2022 Academia Sinica Institute of Astronomy and Astrophysics (ASIAA), + Copyright 2018- Academia Sinica Institute of Astronomy and Astrophysics (ASIAA), Associated Universities, Inc. (AUI) and the Inter-University Institute for Data Intensive Astronomy (IDIA) SPDX-License-Identifier: GPL-3.0-or-later */ diff --git a/src/Table/Columns.h b/src/Table/Columns.h index 73352ad5f..f180c03ab 100644 --- a/src/Table/Columns.h +++ b/src/Table/Columns.h @@ -1,11 +1,11 @@ /* This file is part of the CARTA Image Viewer: https://github.com/CARTAvis/carta-backend - Copyright 2018-2022 Academia Sinica Institute of Astronomy and Astrophysics (ASIAA), + Copyright 2018- Academia Sinica Institute of Astronomy and Astrophysics (ASIAA), Associated Universities, Inc. (AUI) and the Inter-University Institute for Data Intensive Astronomy (IDIA) SPDX-License-Identifier: GPL-3.0-or-later */ -#ifndef VOTABLE_TEST__COLUMNS_H_ -#define VOTABLE_TEST__COLUMNS_H_ +#ifndef CARTA_SRC_TABLE_COLUMNS_H_ +#define CARTA_SRC_TABLE_COLUMNS_H_ #include #include @@ -93,4 +93,4 @@ class DataColumn : public Column { }; } // namespace carta -#endif // VOTABLE_TEST__COLUMNS_H_ +#endif // CARTA_SRC_TABLE_COLUMNS_H_ diff --git a/src/Table/DataColumn.tcc b/src/Table/DataColumn.tcc index 69d22c5a8..7a69ff3fa 100644 --- a/src/Table/DataColumn.tcc +++ b/src/Table/DataColumn.tcc @@ -1,11 +1,11 @@ /* This file is part of the CARTA Image Viewer: https://github.com/CARTAvis/carta-backend - Copyright 2018-2022 Academia Sinica Institute of Astronomy and Astrophysics (ASIAA), + Copyright 2018- Academia Sinica Institute of Astronomy and Astrophysics (ASIAA), Associated Universities, Inc. (AUI) and the Inter-University Institute for Data Intensive Astronomy (IDIA) SPDX-License-Identifier: GPL-3.0-or-later */ -#ifndef VOTABLE_TEST__DATACOLUMN_TCC_ -#define VOTABLE_TEST__DATACOLUMN_TCC_ +#ifndef CARTA_SRC_TABLE_DATACOLUMN_TCC_ +#define CARTA_SRC_TABLE_DATACOLUMN_TCC_ #include "Columns.h" @@ -281,4 +281,4 @@ void DataColumn::FillColumnData( } // namespace carta -#endif // VOTABLE_TEST__DATACOLUMN_TCC_ +#endif // CARTA_SRC_TABLE_DATACOLUMN_TCC_ diff --git a/src/Table/Table.cc b/src/Table/Table.cc index f3f8379a9..5d3af92b1 100644 --- a/src/Table/Table.cc +++ b/src/Table/Table.cc @@ -1,5 +1,5 @@ /* This file is part of the CARTA Image Viewer: https://github.com/CARTAvis/carta-backend - Copyright 2018-2022 Academia Sinica Institute of Astronomy and Astrophysics (ASIAA), + Copyright 2018- Academia Sinica Institute of Astronomy and Astrophysics (ASIAA), Associated Universities, Inc. (AUI) and the Inter-University Institute for Data Intensive Astronomy (IDIA) SPDX-License-Identifier: GPL-3.0-or-later */ diff --git a/src/Table/Table.h b/src/Table/Table.h index c64615cab..758035a22 100644 --- a/src/Table/Table.h +++ b/src/Table/Table.h @@ -1,11 +1,11 @@ /* This file is part of the CARTA Image Viewer: https://github.com/CARTAvis/carta-backend - Copyright 2018-2022 Academia Sinica Institute of Astronomy and Astrophysics (ASIAA), + Copyright 2018- Academia Sinica Institute of Astronomy and Astrophysics (ASIAA), Associated Universities, Inc. (AUI) and the Inter-University Institute for Data Intensive Astronomy (IDIA) SPDX-License-Identifier: GPL-3.0-or-later */ -#ifndef VOTABLE_TEST__TABLE_H_ -#define VOTABLE_TEST__TABLE_H_ +#ifndef CARTA_SRC_TABLE_TABLE_H_ +#define CARTA_SRC_TABLE_TABLE_H_ #include #include @@ -69,4 +69,4 @@ class Table { static std::string GetHeader(const std::string& filename); }; } // namespace carta -#endif // VOTABLE_TEST__TABLE_H_ +#endif // CARTA_SRC_TABLE_TABLE_H_ diff --git a/src/Table/TableController.cc b/src/Table/TableController.cc index db37d9e28..0268d1d6f 100644 --- a/src/Table/TableController.cc +++ b/src/Table/TableController.cc @@ -1,5 +1,5 @@ /* This file is part of the CARTA Image Viewer: https://github.com/CARTAvis/carta-backend - Copyright 2018-2022 Academia Sinica Institute of Astronomy and Astrophysics (ASIAA), + Copyright 2018- Academia Sinica Institute of Astronomy and Astrophysics (ASIAA), Associated Universities, Inc. (AUI) and the Inter-University Institute for Data Intensive Astronomy (IDIA) SPDX-License-Identifier: GPL-3.0-or-later */ @@ -9,6 +9,7 @@ #include #include "Logger/Logger.h" +#include "Main/ProgramSettings.h" #include "Timer/ListProgressReporter.h" #include "Util/File.h" @@ -18,8 +19,11 @@ using namespace carta; -TableController::TableController(const std::string& top_level_folder, const std::string& starting_folder) - : _top_level_folder(top_level_folder), _starting_folder(starting_folder) {} +TableController::TableController() { + auto& settings = ProgramSettings::GetInstance(); + _top_level_folder = settings.top_level_folder; + _starting_folder = settings.starting_folder; +} void TableController::OnOpenFileRequest(const CARTA::OpenCatalogFile& open_file_request, CARTA::OpenCatalogFileAck& open_file_response) { int file_id = open_file_request.file_id(); diff --git a/src/Table/TableController.h b/src/Table/TableController.h index 6b676cb78..a2685b4b9 100644 --- a/src/Table/TableController.h +++ b/src/Table/TableController.h @@ -1,11 +1,11 @@ /* This file is part of the CARTA Image Viewer: https://github.com/CARTAvis/carta-backend - Copyright 2018-2022 Academia Sinica Institute of Astronomy and Astrophysics (ASIAA), + Copyright 2018- Academia Sinica Institute of Astronomy and Astrophysics (ASIAA), Associated Universities, Inc. (AUI) and the Inter-University Institute for Data Intensive Astronomy (IDIA) SPDX-License-Identifier: GPL-3.0-or-later */ -#ifndef CARTA_BACKEND_TABLE_TABLECONTROLLER_H_ -#define CARTA_BACKEND_TABLE_TABLECONTROLLER_H_ +#ifndef CARTA_SRC_TABLE_TABLECONTROLLER_H_ +#define CARTA_SRC_TABLE_TABLECONTROLLER_H_ #include #include @@ -16,6 +16,7 @@ #include #include +#include "Main/ProgramSettings.h" #include "Table.h" #include "Util/FileSystem.h" @@ -32,7 +33,7 @@ struct TableViewCache { class TableController { public: - TableController(const std::string& top_level_folder, const std::string& starting_folder); + TableController(); void OnFileListRequest(const CARTA::CatalogListRequest& file_list_request, CARTA::CatalogListResponse& file_list_response); void OnFileInfoRequest(const CARTA::CatalogFileInfoRequest& file_info_request, CARTA::CatalogFileInfoResponse& file_info_response); void OnOpenFileRequest(const CARTA::OpenCatalogFile& open_file_request, CARTA::OpenCatalogFileAck& open_file_response); @@ -53,8 +54,6 @@ class TableController { static bool FilterParamsChanged(const std::vector& filter_configs, std::string sort_column, CARTA::SortingType sorting_type, const TableViewCache& cached_config); fs::path GetPath(std::string directory, std::string name = ""); - std::string _top_level_folder; - std::string _starting_folder; std::unordered_map _tables; std::unordered_map _view_cache; @@ -62,6 +61,8 @@ class TableController { volatile bool _stop_getting_file_list; volatile bool _first_report_made; std::function _progress_callback; + std::string _top_level_folder; + std::string _starting_folder; }; } // namespace carta -#endif // CARTA_BACKEND_TABLE_TABLECONTROLLER_H_ +#endif // CARTA_SRC_TABLE_TABLECONTROLLER_H_ diff --git a/src/Table/TableView.cc b/src/Table/TableView.cc index 1ebeb0804..ae7f05f86 100644 --- a/src/Table/TableView.cc +++ b/src/Table/TableView.cc @@ -1,5 +1,5 @@ /* This file is part of the CARTA Image Viewer: https://github.com/CARTAvis/carta-backend - Copyright 2018-2022 Academia Sinica Institute of Astronomy and Astrophysics (ASIAA), + Copyright 2018- Academia Sinica Institute of Astronomy and Astrophysics (ASIAA), Associated Universities, Inc. (AUI) and the Inter-University Institute for Data Intensive Astronomy (IDIA) SPDX-License-Identifier: GPL-3.0-or-later */ diff --git a/src/Table/TableView.h b/src/Table/TableView.h index 4e55d81fe..d87b56b6e 100644 --- a/src/Table/TableView.h +++ b/src/Table/TableView.h @@ -1,11 +1,11 @@ /* This file is part of the CARTA Image Viewer: https://github.com/CARTAvis/carta-backend - Copyright 2018-2022 Academia Sinica Institute of Astronomy and Astrophysics (ASIAA), + Copyright 2018- Academia Sinica Institute of Astronomy and Astrophysics (ASIAA), Associated Universities, Inc. (AUI) and the Inter-University Institute for Data Intensive Astronomy (IDIA) SPDX-License-Identifier: GPL-3.0-or-later */ -#ifndef VOTABLE_TEST__TABLEVIEW_H_ -#define VOTABLE_TEST__TABLEVIEW_H_ +#ifndef CARTA_SRC_TABLE_TABLEVIEW_H_ +#define CARTA_SRC_TABLE_TABLEVIEW_H_ #include @@ -51,4 +51,4 @@ class TableView { #include "TableView.tcc" -#endif // VOTABLE_TEST__TABLEVIEW_H_ +#endif // CARTA_SRC_TABLE_TABLEVIEW_H_ diff --git a/src/Table/TableView.tcc b/src/Table/TableView.tcc index 54132f8cd..10b2d2b15 100644 --- a/src/Table/TableView.tcc +++ b/src/Table/TableView.tcc @@ -1,11 +1,11 @@ /* This file is part of the CARTA Image Viewer: https://github.com/CARTAvis/carta-backend - Copyright 2018-2022 Academia Sinica Institute of Astronomy and Astrophysics (ASIAA), + Copyright 2018- Academia Sinica Institute of Astronomy and Astrophysics (ASIAA), Associated Universities, Inc. (AUI) and the Inter-University Institute for Data Intensive Astronomy (IDIA) SPDX-License-Identifier: GPL-3.0-or-later */ -#ifndef VOTABLE_TEST__TABLEVIEW_TCC_ -#define VOTABLE_TEST__TABLEVIEW_TCC_ +#ifndef CARTA_SRC_TABLE_TABLEVIEW_TCC_ +#define CARTA_SRC_TABLE_TABLEVIEW_TCC_ #include "TableView.h" @@ -22,4 +22,4 @@ std::vector TableView::Values(const Column* column, int64_t start, int64_t en } // namespace carta -#endif // VOTABLE_TEST__TABLEVIEW_TCC_ +#endif // CARTA_SRC_TABLE_TABLEVIEW_TCC_ diff --git a/src/ThreadingManager/Concurrency.h b/src/ThreadingManager/Concurrency.h index 6916ecaaf..27aa5f664 100644 --- a/src/ThreadingManager/Concurrency.h +++ b/src/ThreadingManager/Concurrency.h @@ -1,10 +1,11 @@ /* This file is part of the CARTA Image Viewer: https://github.com/CARTAvis/carta-backend - Copyright 2018-2022 Academia Sinica Institute of Astronomy and Astrophysics (ASIAA), + Copyright 2018- Academia Sinica Institute of Astronomy and Astrophysics (ASIAA), Associated Universities, Inc. (AUI) and the Inter-University Institute for Data Intensive Astronomy (IDIA) SPDX-License-Identifier: GPL-3.0-or-later */ -#pragma once +#ifndef CARTA_SRC_THREADINGMANAGER_CONCURRENCY_H_ +#define CARTA_SRC_THREADINGMANAGER_CONCURRENCY_H_ #include #include @@ -186,3 +187,5 @@ class queuing_rw_mutex_scoped { }; } // namespace carta + +#endif // CARTA_SRC_THREADINGMANAGER_CONCURRENCY_H_ diff --git a/src/ThreadingManager/ThreadingManager.cc b/src/ThreadingManager/ThreadingManager.cc index 64d6bdf44..292f39b54 100644 --- a/src/ThreadingManager/ThreadingManager.cc +++ b/src/ThreadingManager/ThreadingManager.cc @@ -1,5 +1,5 @@ /* This file is part of the CARTA Image Viewer: https://github.com/CARTAvis/carta-backend - Copyright 2018-2022 Academia Sinica Institute of Astronomy and Astrophysics (ASIAA), + Copyright 2018- Academia Sinica Institute of Astronomy and Astrophysics (ASIAA), Associated Universities, Inc. (AUI) and the Inter-University Institute for Data Intensive Astronomy (IDIA) SPDX-License-Identifier: GPL-3.0-or-later */ diff --git a/src/ThreadingManager/ThreadingManager.h b/src/ThreadingManager/ThreadingManager.h index 13a1c0a87..a3750e54a 100644 --- a/src/ThreadingManager/ThreadingManager.h +++ b/src/ThreadingManager/ThreadingManager.h @@ -1,11 +1,11 @@ /* This file is part of the CARTA Image Viewer: https://github.com/CARTAvis/carta-backend - Copyright 2018-2022 Academia Sinica Institute of Astronomy and Astrophysics (ASIAA), + Copyright 2018- Academia Sinica Institute of Astronomy and Astrophysics (ASIAA), Associated Universities, Inc. (AUI) and the Inter-University Institute for Data Intensive Astronomy (IDIA) SPDX-License-Identifier: GPL-3.0-or-later */ -#ifndef __THREADING_H__ -#define __THREADING_H__ +#ifndef CARTA_SRC_THREADINGMANAGER_THREADINGMANAGER_H_ +#define CARTA_SRC_THREADINGMANAGER_THREADINGMANAGER_H_ #include #include "Session/OnMessageTask.h" @@ -41,4 +41,4 @@ class ThreadManager { } // namespace carta -#endif // __THREADING_H__ +#endif // CARTA_SRC_THREADINGMANAGER_THREADINGMANAGER_H_ diff --git a/src/Timer/ListProgressReporter.cc b/src/Timer/ListProgressReporter.cc index 32f4e7a41..73720adfc 100644 --- a/src/Timer/ListProgressReporter.cc +++ b/src/Timer/ListProgressReporter.cc @@ -1,5 +1,5 @@ /* This file is part of the CARTA Image Viewer: https://github.com/CARTAvis/carta-backend - Copyright 2018-2022 Academia Sinica Institute of Astronomy and Astrophysics (ASIAA), + Copyright 2018- Academia Sinica Institute of Astronomy and Astrophysics (ASIAA), Associated Universities, Inc. (AUI) and the Inter-University Institute for Data Intensive Astronomy (IDIA) SPDX-License-Identifier: GPL-3.0-or-later */ diff --git a/src/Timer/ListProgressReporter.h b/src/Timer/ListProgressReporter.h index a5ccaae35..f0dabae7f 100644 --- a/src/Timer/ListProgressReporter.h +++ b/src/Timer/ListProgressReporter.h @@ -1,11 +1,11 @@ /* This file is part of the CARTA Image Viewer: https://github.com/CARTAvis/carta-backend - Copyright 2018-2022 Academia Sinica Institute of Astronomy and Astrophysics (ASIAA), + Copyright 2018- Academia Sinica Institute of Astronomy and Astrophysics (ASIAA), Associated Universities, Inc. (AUI) and the Inter-University Institute for Data Intensive Astronomy (IDIA) SPDX-License-Identifier: GPL-3.0-or-later */ -#ifndef CARTA_BACKEND_TIMER_LISTPROGRESSREPORTER_H_ -#define CARTA_BACKEND_TIMER_LISTPROGRESSREPORTER_H_ +#ifndef CARTA_SRC_TIMER_LISTPROGRESSREPORTER_H_ +#define CARTA_SRC_TIMER_LISTPROGRESSREPORTER_H_ #include #include @@ -33,4 +33,4 @@ class ListProgressReporter { } // namespace carta -#endif // CARTA_BACKEND_TIMER_LISTPROGRESSREPORTER_H_ +#endif // CARTA_SRC_TIMER_LISTPROGRESSREPORTER_H_ diff --git a/src/Timer/Timer.cc b/src/Timer/Timer.cc index bb8525431..5dc08dc02 100644 --- a/src/Timer/Timer.cc +++ b/src/Timer/Timer.cc @@ -1,5 +1,5 @@ /* This file is part of the CARTA Image Viewer: https://github.com/CARTAvis/carta-backend - Copyright 2018-2022 Academia Sinica Institute of Astronomy and Astrophysics (ASIAA), + Copyright 2018- Academia Sinica Institute of Astronomy and Astrophysics (ASIAA), Associated Universities, Inc. (AUI) and the Inter-University Institute for Data Intensive Astronomy (IDIA) SPDX-License-Identifier: GPL-3.0-or-later */ diff --git a/src/Timer/Timer.h b/src/Timer/Timer.h index 2c8314f0f..b486f5b0b 100644 --- a/src/Timer/Timer.h +++ b/src/Timer/Timer.h @@ -1,11 +1,11 @@ /* This file is part of the CARTA Image Viewer: https://github.com/CARTAvis/carta-backend - Copyright 2018-2022 Academia Sinica Institute of Astronomy and Astrophysics (ASIAA), + Copyright 2018- Academia Sinica Institute of Astronomy and Astrophysics (ASIAA), Associated Universities, Inc. (AUI) and the Inter-University Institute for Data Intensive Astronomy (IDIA) SPDX-License-Identifier: GPL-3.0-or-later */ -#ifndef CARTA_BACKEND_TIMER_TIMER_H_ -#define CARTA_BACKEND_TIMER_TIMER_H_ +#ifndef CARTA_SRC_TIMER_TIMER_H_ +#define CARTA_SRC_TIMER_TIMER_H_ #include #include @@ -37,4 +37,4 @@ class Timer { } // namespace carta -#endif // CARTA_BACKEND_TIMER_TIMER_H_ +#endif // CARTA_SRC_TIMER_TIMER_H_ diff --git a/src/Util/App.cc b/src/Util/App.cc index 6717a7c4e..1dc7e7baa 100644 --- a/src/Util/App.cc +++ b/src/Util/App.cc @@ -1,5 +1,5 @@ /* This file is part of the CARTA Image Viewer: https://github.com/CARTAvis/carta-backend - Copyright 2018-2022 Academia Sinica Institute of Astronomy and Astrophysics (ASIAA), + Copyright 2018- Academia Sinica Institute of Astronomy and Astrophysics (ASIAA), Associated Universities, Inc. (AUI) and the Inter-University Institute for Data Intensive Astronomy (IDIA) SPDX-License-Identifier: GPL-3.0-or-later */ diff --git a/src/Util/App.h b/src/Util/App.h index fafd8333d..6190f98d1 100644 --- a/src/Util/App.h +++ b/src/Util/App.h @@ -1,19 +1,19 @@ /* This file is part of the CARTA Image Viewer: https://github.com/CARTAvis/carta-backend - Copyright 2018-2022 Academia Sinica Institute of Astronomy and Astrophysics (ASIAA), + Copyright 2018- Academia Sinica Institute of Astronomy and Astrophysics (ASIAA), Associated Universities, Inc. (AUI) and the Inter-University Institute for Data Intensive Astronomy (IDIA) SPDX-License-Identifier: GPL-3.0-or-later */ -#ifndef CARTA_BACKEND__UTIL_APP_H_ -#define CARTA_BACKEND__UTIL_APP_H_ +#ifndef CARTA_SRC_UTIL_APP_H_ +#define CARTA_SRC_UTIL_APP_H_ #include // version -#define VERSION_ID "4.0.0-rc.0" +#define VERSION_ID "5.0.0-dev" bool FindExecutablePath(std::string& path); std::string GetReleaseInformation(); std::string OutputOfCommand(const char* command); -#endif // CARTA_BACKEND__UTIL_APP_H_ +#endif // CARTA_SRC_UTIL_APP_H_ diff --git a/src/Util/Casacore.cc b/src/Util/Casacore.cc index c9eb8b180..4699f7fdc 100644 --- a/src/Util/Casacore.cc +++ b/src/Util/Casacore.cc @@ -1,5 +1,5 @@ /* This file is part of the CARTA Image Viewer: https://github.com/CARTAvis/carta-backend - Copyright 2018-2022 Academia Sinica Institute of Astronomy and Astrophysics (ASIAA), + Copyright 2018- Academia Sinica Institute of Astronomy and Astrophysics (ASIAA), Associated Universities, Inc. (AUI) and the Inter-University Institute for Data Intensive Astronomy (IDIA) SPDX-License-Identifier: GPL-3.0-or-later */ @@ -98,19 +98,37 @@ bool IsSubdirectory(string folder, string top_folder) { return false; } -casacore::String GetResolvedFilename(const string& root_dir, const string& directory, const string& file) { +casacore::String GetResolvedFilename(const string& root_dir, const string& directory, const string& file, string& message) { // Given directory (relative to root folder) and file, return resolved path and filename // (absolute pathname with symlinks resolved) casacore::String resolved_filename; - casacore::Path root_path(root_dir); - root_path.append(directory); - root_path.append(file); - casacore::File cc_file(root_path); - if (cc_file.exists()) { - try { - resolved_filename = cc_file.path().resolvedName(); - } catch (casacore::AipsError& err) { - // return empty string + casacore::Path path(root_dir); + path.append(directory); + casacore::File cc_directory(path); + + if (!cc_directory.exists()) { + message = "Directory " + directory + " does not exist."; + } else if (!cc_directory.isReadable()) { + message = "Directory " + directory + " is not readable."; + } else { + path.append(file); + casacore::File cc_file(path); + + if (!cc_file.exists()) { + message = "File " + file + " does not exist."; + } else if (!cc_file.isReadable()) { + message = "File " + file + " is not readable."; + } else { + try { + resolved_filename = cc_file.path().resolvedName(); + } catch (const casacore::AipsError& err) { + try { + resolved_filename = cc_file.path().absoluteName(); + } catch (const casacore::AipsError& err) { + // return empty string + message = "Cannot resolve file path."; + } + } } } return resolved_filename; @@ -186,6 +204,7 @@ void NormalizeUnit(casacore::String& unit) { unit.gsub("beam-1 Jy", "Jy/beam"); unit.gsub("beam^-1 Jy", "Jy/beam"); unit.gsub("Pixel", "pixel"); + unit.gsub("DEGREE", "deg"); unit.gsub("\"", ""); // Convert unit without prefix diff --git a/src/Util/Casacore.h b/src/Util/Casacore.h index bf6cf0fd5..259850dd9 100644 --- a/src/Util/Casacore.h +++ b/src/Util/Casacore.h @@ -1,19 +1,22 @@ /* This file is part of the CARTA Image Viewer: https://github.com/CARTAvis/carta-backend - Copyright 2018-2022 Academia Sinica Institute of Astronomy and Astrophysics (ASIAA), + Copyright 2018- Academia Sinica Institute of Astronomy and Astrophysics (ASIAA), Associated Universities, Inc. (AUI) and the Inter-University Institute for Data Intensive Astronomy (IDIA) SPDX-License-Identifier: GPL-3.0-or-later */ -#ifndef CARTA_BACKEND__UTIL_CASACORE_H_ -#define CARTA_BACKEND__UTIL_CASACORE_H_ +#ifndef CARTA_SRC_UTIL_CASACORE_H_ +#define CARTA_SRC_UTIL_CASACORE_H_ #include #include #include bool CheckFolderPaths(std::string& top_level_string, std::string& starting_string); + bool IsSubdirectory(std::string folder, std::string top_folder); -casacore::String GetResolvedFilename(const std::string& root_dir, const std::string& directory, const std::string& file); + +casacore::String GetResolvedFilename( + const std::string& root_dir, const std::string& directory, const std::string& file, std::string& message); // Determine image type from filename inline casacore::ImageOpener::ImageTypes CasacoreImageType(const std::string& filename) { @@ -32,4 +35,4 @@ void NormalizeUnit(casacore::String& unit); // Parse AIPS beam header using regex_match bool ParseHistoryBeamHeader(std::string& header, std::string& bmaj, std::string& bmin, std::string& bpa); -#endif // CARTA_BACKEND__UTIL_CASACORE_H_ +#endif // CARTA_SRC_UTIL_CASACORE_H_ diff --git a/src/Util/File.cc b/src/Util/File.cc index c3a5d9869..2dd10b154 100644 --- a/src/Util/File.cc +++ b/src/Util/File.cc @@ -1,5 +1,5 @@ /* This file is part of the CARTA Image Viewer: https://github.com/CARTAvis/carta-backend - Copyright 2018-2022 Academia Sinica Institute of Astronomy and Astrophysics (ASIAA), + Copyright 2018- Academia Sinica Institute of Astronomy and Astrophysics (ASIAA), Associated Universities, Inc. (AUI) and the Inter-University Institute for Data Intensive Astronomy (IDIA) SPDX-License-Identifier: GPL-3.0-or-later */ diff --git a/src/Util/File.h b/src/Util/File.h index 06d2ab10c..19863ac8a 100644 --- a/src/Util/File.h +++ b/src/Util/File.h @@ -1,11 +1,11 @@ /* This file is part of the CARTA Image Viewer: https://github.com/CARTAvis/carta-backend - Copyright 2018-2022 Academia Sinica Institute of Astronomy and Astrophysics (ASIAA), + Copyright 2018- Academia Sinica Institute of Astronomy and Astrophysics (ASIAA), Associated Universities, Inc. (AUI) and the Inter-University Institute for Data Intensive Astronomy (IDIA) SPDX-License-Identifier: GPL-3.0-or-later */ -#ifndef CARTA_BACKEND__UTIL_FILE_H_ -#define CARTA_BACKEND__UTIL_FILE_H_ +#ifndef CARTA_SRC_UTIL_FILE_H_ +#define CARTA_SRC_UTIL_FILE_H_ #include @@ -36,4 +36,4 @@ bool IsGzMagicNumber(uint32_t magic_number); int GetNumItems(const std::string& path); fs::path SearchPath(std::string filename); -#endif // CARTA_BACKEND__UTIL_FILE_H_ +#endif // CARTA_SRC_UTIL_FILE_H_ diff --git a/src/Util/FileSystem.h b/src/Util/FileSystem.h index e7b180ba5..3f2ae9957 100644 --- a/src/Util/FileSystem.h +++ b/src/Util/FileSystem.h @@ -1,13 +1,13 @@ /* This file is part of the CARTA Image Viewer: https://github.com/CARTAvis/carta-backend - Copyright 2018-2022 Academia Sinica Institute of Astronomy and Astrophysics (ASIAA), + Copyright 2018- Academia Sinica Institute of Astronomy and Astrophysics (ASIAA), Associated Universities, Inc. (AUI) and the Inter-University Institute for Data Intensive Astronomy (IDIA) SPDX-License-Identifier: GPL-3.0-or-later */ -#ifndef CARTA_BACKEND__UTIL_FILESYSTEM_H_ -#define CARTA_BACKEND__UTIL_FILESYSTEM_H_ +#ifndef CARTA_SRC_UTIL_FILESYSTEM_H_ +#define CARTA_SRC_UTIL_FILESYSTEM_H_ #include namespace fs = std::filesystem; -#endif // CARTA_BACKEND__UTIL_FILESYSTEM_H_ +#endif // CARTA_SRC_UTIL_FILESYSTEM_H_ diff --git a/src/Util/Image.h b/src/Util/Image.h index f48347111..2c1bc1782 100644 --- a/src/Util/Image.h +++ b/src/Util/Image.h @@ -1,11 +1,11 @@ /* This file is part of the CARTA Image Viewer: https://github.com/CARTAvis/carta-backend - Copyright 2018-2022 Academia Sinica Institute of Astronomy and Astrophysics (ASIAA), + Copyright 2018- Academia Sinica Institute of Astronomy and Astrophysics (ASIAA), Associated Universities, Inc. (AUI) and the Inter-University Institute for Data Intensive Astronomy (IDIA) SPDX-License-Identifier: GPL-3.0-or-later */ -#ifndef CARTA_BACKEND__UTIL_IMAGE_H_ -#define CARTA_BACKEND__UTIL_IMAGE_H_ +#ifndef CARTA_SRC_UTIL_IMAGE_H_ +#define CARTA_SRC_UTIL_IMAGE_H_ #include #include @@ -123,4 +123,4 @@ struct PointXy { } }; -#endif // CARTA_BACKEND__UTIL_IMAGE_H_ +#endif // CARTA_SRC_UTIL_IMAGE_H_ diff --git a/src/Util/Message.cc b/src/Util/Message.cc index 973379bd3..8fb9afa4b 100644 --- a/src/Util/Message.cc +++ b/src/Util/Message.cc @@ -1,5 +1,5 @@ /* This file is part of the CARTA Image Viewer: https://github.com/CARTAvis/carta-backend - Copyright 2018-2022 Academia Sinica Institute of Astronomy and Astrophysics (ASIAA), + Copyright 2018- Academia Sinica Institute of Astronomy and Astrophysics (ASIAA), Associated Universities, Inc. (AUI) and the Inter-University Institute for Data Intensive Astronomy (IDIA) SPDX-License-Identifier: GPL-3.0-or-later */ diff --git a/src/Util/Message.h b/src/Util/Message.h index 85c673716..ab27c2e7d 100644 --- a/src/Util/Message.h +++ b/src/Util/Message.h @@ -1,11 +1,11 @@ /* This file is part of the CARTA Image Viewer: https://github.com/CARTAvis/carta-backend - Copyright 2018-2022 Academia Sinica Institute of Astronomy and Astrophysics (ASIAA), + Copyright 2018- Academia Sinica Institute of Astronomy and Astrophysics (ASIAA), Associated Universities, Inc. (AUI) and the Inter-University Institute for Data Intensive Astronomy (IDIA) SPDX-License-Identifier: GPL-3.0-or-later */ -#ifndef CARTA_BACKEND__UTIL_MESSAGE_H_ -#define CARTA_BACKEND__UTIL_MESSAGE_H_ +#ifndef CARTA_SRC_UTIL_MESSAGE_H_ +#define CARTA_SRC_UTIL_MESSAGE_H_ #include #include @@ -165,4 +165,4 @@ void FillStatistics(CARTA::RegionStatsData& stats_data, const std::vector T Message::DecodeMessage(std::vector& message) { @@ -16,4 +16,4 @@ T Message::DecodeMessage(std::vector& message) { return decoded_message; } -#endif // CARTA_BACKEND__UTIL_MESSAGE_TCC_ +#endif // CARTA_SRC_UTIL_MESSAGE_TCC_ diff --git a/src/Util/Stokes.cc b/src/Util/Stokes.cc index 1e42b4aac..78820b801 100644 --- a/src/Util/Stokes.cc +++ b/src/Util/Stokes.cc @@ -1,5 +1,5 @@ /* This file is part of the CARTA Image Viewer: https://github.com/CARTAvis/carta-backend - Copyright 2018-2022 Academia Sinica Institute of Astronomy and Astrophysics (ASIAA), + Copyright 2018- Academia Sinica Institute of Astronomy and Astrophysics (ASIAA), Associated Universities, Inc. (AUI) and the Inter-University Institute for Data Intensive Astronomy (IDIA) SPDX-License-Identifier: GPL-3.0-or-later */ diff --git a/src/Util/Stokes.h b/src/Util/Stokes.h index 565c25cfe..87000dc00 100644 --- a/src/Util/Stokes.h +++ b/src/Util/Stokes.h @@ -1,11 +1,11 @@ /* This file is part of the CARTA Image Viewer: https://github.com/CARTAvis/carta-backend - Copyright 2018-2022 Academia Sinica Institute of Astronomy and Astrophysics (ASIAA), + Copyright 2018- Academia Sinica Institute of Astronomy and Astrophysics (ASIAA), Associated Universities, Inc. (AUI) and the Inter-University Institute for Data Intensive Astronomy (IDIA) SPDX-License-Identifier: GPL-3.0-or-later */ -#ifndef CARTA_BACKEND__UTIL_STOKES_H_ -#define CARTA_BACKEND__UTIL_STOKES_H_ +#ifndef CARTA_SRC_UTIL_STOKES_H_ +#define CARTA_SRC_UTIL_STOKES_H_ #include @@ -94,4 +94,4 @@ struct StokesSource { } }; -#endif // CARTA_BACKEND__UTIL_STOKES_H_ +#endif // CARTA_SRC_UTIL_STOKES_H_ diff --git a/src/Util/String.cc b/src/Util/String.cc index fcb4f7f7f..597669595 100644 --- a/src/Util/String.cc +++ b/src/Util/String.cc @@ -1,5 +1,5 @@ /* This file is part of the CARTA Image Viewer: https://github.com/CARTAvis/carta-backend - Copyright 2018-2022 Academia Sinica Institute of Astronomy and Astrophysics (ASIAA), + Copyright 2018- Academia Sinica Institute of Astronomy and Astrophysics (ASIAA), Associated Universities, Inc. (AUI) and the Inter-University Institute for Data Intensive Astronomy (IDIA) SPDX-License-Identifier: GPL-3.0-or-later */ diff --git a/src/Util/String.h b/src/Util/String.h index 84612933f..4bc91b194 100644 --- a/src/Util/String.h +++ b/src/Util/String.h @@ -1,11 +1,11 @@ /* This file is part of the CARTA Image Viewer: https://github.com/CARTAvis/carta-backend - Copyright 2018-2022 Academia Sinica Institute of Astronomy and Astrophysics (ASIAA), + Copyright 2018- Academia Sinica Institute of Astronomy and Astrophysics (ASIAA), Associated Universities, Inc. (AUI) and the Inter-University Institute for Data Intensive Astronomy (IDIA) SPDX-License-Identifier: GPL-3.0-or-later */ -#ifndef CARTA_BACKEND__UTIL_STRING_H_ -#define CARTA_BACKEND__UTIL_STRING_H_ +#ifndef CARTA_SRC_UTIL_STRING_H_ +#define CARTA_SRC_UTIL_STRING_H_ #include #include @@ -27,4 +27,4 @@ bool ConstantTimeStringCompare(const std::string& a, const std::string& b); // Convert string to integer, return whether success bool StringToInt(const std::string& input, int& i); -#endif // CARTA_BACKEND__UTIL_STRING_H_ +#endif // CARTA_SRC_UTIL_STRING_H_ diff --git a/src/Util/Token.cc b/src/Util/Token.cc index 091784c89..091ac4c9f 100644 --- a/src/Util/Token.cc +++ b/src/Util/Token.cc @@ -1,5 +1,5 @@ /* This file is part of the CARTA Image Viewer: https://github.com/CARTAvis/carta-backend - Copyright 2018-2022 Academia Sinica Institute of Astronomy and Astrophysics (ASIAA), + Copyright 2018- Academia Sinica Institute of Astronomy and Astrophysics (ASIAA), Associated Universities, Inc. (AUI) and the Inter-University Institute for Data Intensive Astronomy (IDIA) SPDX-License-Identifier: GPL-3.0-or-later */ diff --git a/src/Util/Token.h b/src/Util/Token.h index afc0841bd..817c6235e 100644 --- a/src/Util/Token.h +++ b/src/Util/Token.h @@ -1,5 +1,5 @@ /* This file is part of the CARTA Image Viewer: https://github.com/CARTAvis/carta-backend - Copyright 2018-2022 Academia Sinica Institute of Astronomy and Astrophysics (ASIAA), + Copyright 2018- Academia Sinica Institute of Astronomy and Astrophysics (ASIAA), Associated Universities, Inc. (AUI) and the Inter-University Institute for Data Intensive Astronomy (IDIA) SPDX-License-Identifier: GPL-3.0-or-later */ @@ -8,8 +8,8 @@ * Utilities for working with authentication tokens. */ -#ifndef CARTA_BACKEND__UTIL_TOKEN_H_ -#define CARTA_BACKEND__UTIL_TOKEN_H_ +#ifndef CARTA_SRC_UTIL_TOKEN_H_ +#define CARTA_SRC_UTIL_TOKEN_H_ #include #include @@ -26,4 +26,4 @@ std::string NewAuthToken(); */ bool ValidateAuthToken(uWS::HttpRequest* http_request, const std::string& required_token); -#endif // CARTA_BACKEND__UTIL_TOKEN_H_ +#endif // CARTA_SRC_UTIL_TOKEN_H_ diff --git a/test/BackendModel.cc b/test/BackendModel.cc deleted file mode 100644 index e3784874b..000000000 --- a/test/BackendModel.cc +++ /dev/null @@ -1,282 +0,0 @@ -/* This file is part of the CARTA Image Viewer: https://github.com/CARTAvis/carta-backend - Copyright 2018-2022 Academia Sinica Institute of Astronomy and Astrophysics (ASIAA), - Associated Universities, Inc. (AUI) and the Inter-University Institute for Data Intensive Astronomy (IDIA) - SPDX-License-Identifier: GPL-3.0-or-later -*/ - -#include "BackendModel.h" -#include "ThreadingManager/ThreadingManager.h" -#include "Util/Message.h" - -#include -#include - -static const uint16_t DUMMY_ICD_VERSION(ICD_VERSION); -static const uint32_t DUMMY_REQUEST_ID(0); - -bool TestSession::TryPopMessagesQueue(std::pair, bool>& message) { - return _out_msgs.try_pop(message); -} - -void TestSession::ClearMessagesQueue() { - _out_msgs.clear(); -} - -std::unique_ptr BackendModel::GetDummyBackend() { - uint32_t session_id(0); - std::string address; - std::string top_level_folder("/"); - std::string starting_folder("data/images"); - bool read_only_mode(false); - bool enable_scripting(false); - - return std::make_unique( - nullptr, nullptr, session_id, address, top_level_folder, starting_folder, read_only_mode, enable_scripting); -} - -BackendModel::BackendModel(uWS::WebSocket* ws, uWS::Loop* loop, uint32_t session_id, std::string address, - std::string top_level_folder, std::string starting_folder, bool read_only_mode, bool enable_scripting) { - _file_list_handler = std::make_shared(top_level_folder, starting_folder); - _session = - new TestSession(session_id, address, top_level_folder, starting_folder, _file_list_handler, read_only_mode, enable_scripting); - - _session->IncreaseRefCount(); // increase the reference count to avoid being deleted by the OnMessageTask -} - -BackendModel::~BackendModel() { - if (_session) { - spdlog::info( - "Client {} [{}] Deleted. Remaining sessions: {}", _session->GetId(), _session->GetAddress(), Session::NumberOfSessions()); - _session->WaitForTaskCancellation(); - if (!_session->DecreaseRefCount()) { - delete _session; - } else { - spdlog::warn("Session reference count is not 0 ({}) on deletion!", _session->GetRefCount()); - } - } -} - -void BackendModel::Receive(CARTA::RegisterViewer message) { - carta::logger::LogReceivedEventType(CARTA::EventType::REGISTER_VIEWER); - _session->OnRegisterViewer(message, DUMMY_ICD_VERSION, DUMMY_REQUEST_ID); -} - -void BackendModel::Receive(CARTA::ResumeSession message) { - carta::logger::LogReceivedEventType(CARTA::EventType::RESUME_SESSION); - _session->OnResumeSession(message, DUMMY_REQUEST_ID); -} - -void BackendModel::Receive(CARTA::SetImageChannels message) { - carta::logger::LogReceivedEventType(CARTA::EventType::SET_IMAGE_CHANNELS); - OnMessageTask* tsk = nullptr; - _session->ImageChannelLock(message.file_id()); - if (!_session->ImageChannelTaskTestAndSet(message.file_id())) { - tsk = new SetImageChannelsTask(_session, message.file_id()); - } - // has its own queue to keep channels in order during animation - _session->AddToSetChannelQueue(message, DUMMY_REQUEST_ID); - _session->ImageChannelUnlock(message.file_id()); - if (tsk) { - ThreadManager::QueueTask(tsk); - } -} - -void BackendModel::Receive(CARTA::SetCursor message) { - carta::logger::LogReceivedEventType(CARTA::EventType::SET_CURSOR); - _session->AddCursorSetting(message, DUMMY_REQUEST_ID); - OnMessageTask* tsk = new SetCursorTask(_session, message.file_id()); - ThreadManager::QueueTask(tsk); -} - -void BackendModel::Receive(CARTA::SetHistogramRequirements message) { - carta::logger::LogReceivedEventType(CARTA::EventType::SET_HISTOGRAM_REQUIREMENTS); - if (message.histograms_size() == 0) { - _session->CancelSetHistRequirements(); - } else { - _session->ResetHistContext(); - OnMessageTask* tsk = new GeneralMessageTask(_session, message, DUMMY_REQUEST_ID); - ThreadManager::QueueTask(tsk); - } -} - -void BackendModel::Receive(CARTA::CloseFile message) { - carta::logger::LogReceivedEventType(CARTA::EventType::CLOSE_FILE); - _session->OnCloseFile(message); -} - -void BackendModel::Receive(CARTA::StartAnimation message) { - carta::logger::LogReceivedEventType(CARTA::EventType::START_ANIMATION); - _session->CancelExistingAnimation(); - OnMessageTask* tsk = new StartAnimationTask(_session, message, DUMMY_REQUEST_ID); - ThreadManager::QueueTask(tsk); -} - -void BackendModel::Receive(CARTA::StopAnimation message) { - carta::logger::LogReceivedEventType(CARTA::EventType::STOP_ANIMATION); - _session->StopAnimation(message.file_id(), message.end_frame()); -} - -void BackendModel::Receive(CARTA::AnimationFlowControl message) { - carta::logger::LogReceivedEventType(CARTA::EventType::ANIMATION_FLOW_CONTROL); - _session->HandleAnimationFlowControlEvt(message); -} - -void BackendModel::Receive(CARTA::FileInfoRequest message) { - carta::logger::LogReceivedEventType(CARTA::EventType::FILE_INFO_REQUEST); - _session->OnFileInfoRequest(message, DUMMY_REQUEST_ID); -} - -void BackendModel::Receive(CARTA::OpenFile message) { - carta::logger::LogReceivedEventType(CARTA::EventType::OPEN_FILE); - _session->CloseCachedImage(message.directory(), message.file()); - _session->OnOpenFile(message, DUMMY_REQUEST_ID); -} - -void BackendModel::Receive(CARTA::AddRequiredTiles message) { - carta::logger::LogReceivedEventType(CARTA::EventType::ADD_REQUIRED_TILES); - OnMessageTask* tsk = new GeneralMessageTask(_session, message, DUMMY_REQUEST_ID); - ThreadManager::QueueTask(tsk); -} - -void BackendModel::Receive(CARTA::RegionFileInfoRequest message) { - carta::logger::LogReceivedEventType(CARTA::EventType::REGION_FILE_INFO_REQUEST); - _session->OnRegionFileInfoRequest(message, DUMMY_REQUEST_ID); -} - -void BackendModel::Receive(CARTA::ImportRegion message) { - carta::logger::LogReceivedEventType(CARTA::EventType::IMPORT_REGION); - _session->OnImportRegion(message, DUMMY_REQUEST_ID); -} - -void BackendModel::Receive(CARTA::ExportRegion message) { - carta::logger::LogReceivedEventType(CARTA::EventType::EXPORT_REGION); - _session->OnExportRegion(message, DUMMY_REQUEST_ID); -} - -void BackendModel::Receive(CARTA::SetContourParameters message) { - carta::logger::LogReceivedEventType(CARTA::EventType::SET_CONTOUR_PARAMETERS); - OnMessageTask* tsk = new GeneralMessageTask(_session, message, DUMMY_REQUEST_ID); - ThreadManager::QueueTask(tsk); -} - -void BackendModel::Receive(CARTA::ScriptingResponse message) { - carta::logger::LogReceivedEventType(CARTA::EventType::SCRIPTING_RESPONSE); - _session->OnScriptingResponse(message, DUMMY_REQUEST_ID); -} - -void BackendModel::Receive(CARTA::SetRegion message) { - carta::logger::LogReceivedEventType(CARTA::EventType::SET_REGION); - _session->OnSetRegion(message, DUMMY_REQUEST_ID); -} - -void BackendModel::Receive(CARTA::RemoveRegion message) { - carta::logger::LogReceivedEventType(CARTA::EventType::REMOVE_REGION); - _session->OnRemoveRegion(message); -} - -void BackendModel::Receive(CARTA::SetSpectralRequirements message) { - carta::logger::LogReceivedEventType(CARTA::EventType::SET_SPECTRAL_REQUIREMENTS); - _session->OnSetSpectralRequirements(message); -} - -void BackendModel::Receive(CARTA::CatalogFileInfoRequest message) { - carta::logger::LogReceivedEventType(CARTA::EventType::CATALOG_FILE_INFO_REQUEST); - _session->OnCatalogFileInfo(message, DUMMY_REQUEST_ID); -} - -void BackendModel::Receive(CARTA::OpenCatalogFile message) { - carta::logger::LogReceivedEventType(CARTA::EventType::OPEN_CATALOG_FILE); - _session->OnOpenCatalogFile(message, DUMMY_REQUEST_ID); -} - -void BackendModel::Receive(CARTA::CloseCatalogFile message) { - carta::logger::LogReceivedEventType(CARTA::EventType::CLOSE_CATALOG_FILE); - _session->OnCloseCatalogFile(message); -} - -void BackendModel::Receive(CARTA::CatalogFilterRequest message) { - carta::logger::LogReceivedEventType(CARTA::EventType::CATALOG_FILTER_REQUEST); - _session->OnCatalogFilter(message, DUMMY_REQUEST_ID); -} - -void BackendModel::Receive(CARTA::StopMomentCalc message) { - carta::logger::LogReceivedEventType(CARTA::EventType::STOP_MOMENT_CALC); - _session->OnStopMomentCalc(message); -} - -void BackendModel::Receive(CARTA::SaveFile message) { - carta::logger::LogReceivedEventType(CARTA::EventType::SAVE_FILE); - _session->OnSaveFile(message, DUMMY_REQUEST_ID); -} - -void BackendModel::Receive(CARTA::ConcatStokesFiles message) { - carta::logger::LogReceivedEventType(CARTA::EventType::CONCAT_STOKES_FILES); - _session->OnConcatStokesFiles(message, DUMMY_REQUEST_ID); -} - -void BackendModel::Receive(CARTA::StopFileList message) { - carta::logger::LogReceivedEventType(CARTA::EventType::STOP_FILE_LIST); - if (message.file_list_type() == CARTA::Image) { - _session->StopImageFileList(); - } else { - _session->StopCatalogFileList(); - } -} - -void BackendModel::Receive(CARTA::SetSpatialRequirements message) { - carta::logger::LogReceivedEventType(CARTA::EventType::SET_SPATIAL_REQUIREMENTS); - OnMessageTask* tsk = new GeneralMessageTask(_session, message, DUMMY_REQUEST_ID); - ThreadManager::QueueTask(tsk); -} - -void BackendModel::Receive(CARTA::SetStatsRequirements message) { - carta::logger::LogReceivedEventType(CARTA::EventType::SET_STATS_REQUIREMENTS); - OnMessageTask* tsk = new GeneralMessageTask(_session, message, DUMMY_REQUEST_ID); - ThreadManager::QueueTask(tsk); -} - -void BackendModel::Receive(CARTA::MomentRequest message) { - carta::logger::LogReceivedEventType(CARTA::EventType::MOMENT_REQUEST); - OnMessageTask* tsk = new GeneralMessageTask(_session, message, DUMMY_REQUEST_ID); - ThreadManager::QueueTask(tsk); -} - -void BackendModel::Receive(CARTA::FileListRequest message) { - carta::logger::LogReceivedEventType(CARTA::EventType::FILE_LIST_REQUEST); - OnMessageTask* tsk = new GeneralMessageTask(_session, message, DUMMY_REQUEST_ID); - ThreadManager::QueueTask(tsk); -} - -void BackendModel::Receive(CARTA::RegionListRequest message) { - carta::logger::LogReceivedEventType(CARTA::EventType::REGION_LIST_REQUEST); - OnMessageTask* tsk = new GeneralMessageTask(_session, message, DUMMY_REQUEST_ID); - ThreadManager::QueueTask(tsk); -} - -void BackendModel::Receive(CARTA::CatalogListRequest message) { - carta::logger::LogReceivedEventType(CARTA::EventType::CATALOG_LIST_REQUEST); - OnMessageTask* tsk = new GeneralMessageTask(_session, message, DUMMY_REQUEST_ID); - ThreadManager::QueueTask(tsk); -} - -void BackendModel::Receive(CARTA::SetVectorOverlayParameters message) { - carta::logger::LogReceivedEventType(CARTA::EventType::SET_VECTOR_OVERLAY_PARAMETERS); - OnMessageTask* tsk = new GeneralMessageTask(_session, message, DUMMY_REQUEST_ID); - ThreadManager::QueueTask(tsk); -} - -//-------------------------------------------------------------- - -bool BackendModel::TryPopMessagesQueue(std::pair, bool>& message) { - return _session->TryPopMessagesQueue(message); -} - -void BackendModel::ClearMessagesQueue() { - _session->ClearMessagesQueue(); -} - -void BackendModel::WaitForJobFinished() { - while (_session->GetRefCount() > 1) { - std::this_thread::sleep_for(std::chrono::milliseconds(1)); - } -} diff --git a/test/BackendModel.h b/test/BackendModel.h deleted file mode 100644 index b42c02dfd..000000000 --- a/test/BackendModel.h +++ /dev/null @@ -1,76 +0,0 @@ -/* This file is part of the CARTA Image Viewer: https://github.com/CARTAvis/carta-backend - Copyright 2018-2022 Academia Sinica Institute of Astronomy and Astrophysics (ASIAA), - Associated Universities, Inc. (AUI) and the Inter-University Institute for Data Intensive Astronomy (IDIA) - SPDX-License-Identifier: GPL-3.0-or-later -*/ - -#ifndef CARTA_BACKEND_ICD_TEST_DUMMYBACKEND_H_ -#define CARTA_BACKEND_ICD_TEST_DUMMYBACKEND_H_ - -#include "Session/OnMessageTask.h" -#include "Session/Session.h" - -class TestSession : public Session { -public: - TestSession(uint32_t id, std::string address, std::string top_level_folder, std::string starting_folder, - std::shared_ptr file_list_handler, bool read_only_mode = false, bool enable_scripting = false) - : Session(nullptr, nullptr, id, address, top_level_folder, starting_folder, file_list_handler, read_only_mode, enable_scripting) {} - - bool TryPopMessagesQueue(std::pair, bool>& message); - void ClearMessagesQueue(); -}; - -class BackendModel { -public: - BackendModel(uWS::WebSocket* ws, uWS::Loop* loop, uint32_t session_id, std::string address, - std::string top_level_folder, std::string starting_folder, bool read_only_mode, bool enable_scripting); - ~BackendModel(); - - static std::unique_ptr GetDummyBackend(); - - void Receive(CARTA::RegisterViewer message); - void Receive(CARTA::ResumeSession message); - void Receive(CARTA::SetImageChannels message); - void Receive(CARTA::SetCursor message); - void Receive(CARTA::SetHistogramRequirements message); - void Receive(CARTA::CloseFile message); - void Receive(CARTA::StartAnimation message); - void Receive(CARTA::StopAnimation message); - void Receive(CARTA::AnimationFlowControl message); - void Receive(CARTA::FileInfoRequest message); - void Receive(CARTA::OpenFile message); - void Receive(CARTA::AddRequiredTiles message); - void Receive(CARTA::RegionFileInfoRequest message); - void Receive(CARTA::ImportRegion message); - void Receive(CARTA::ExportRegion message); - void Receive(CARTA::SetContourParameters message); - void Receive(CARTA::ScriptingResponse message); - void Receive(CARTA::SetRegion message); - void Receive(CARTA::RemoveRegion message); - void Receive(CARTA::SetSpectralRequirements message); - void Receive(CARTA::CatalogFileInfoRequest message); - void Receive(CARTA::OpenCatalogFile message); - void Receive(CARTA::CloseCatalogFile message); - void Receive(CARTA::CatalogFilterRequest message); - void Receive(CARTA::StopMomentCalc message); - void Receive(CARTA::SaveFile message); - void Receive(CARTA::ConcatStokesFiles message); - void Receive(CARTA::StopFileList message); - void Receive(CARTA::SetSpatialRequirements message); - void Receive(CARTA::SetStatsRequirements message); - void Receive(CARTA::MomentRequest message); - void Receive(CARTA::FileListRequest message); - void Receive(CARTA::RegionListRequest message); - void Receive(CARTA::CatalogListRequest message); - void Receive(CARTA::SetVectorOverlayParameters message); - - bool TryPopMessagesQueue(std::pair, bool>& message); - void ClearMessagesQueue(); - void WaitForJobFinished(); // wait for parallel calculations finished - -private: - std::shared_ptr _file_list_handler; - TestSession* _session; -}; - -#endif // CARTA_BACKEND_ICD_TEST_DUMMYBACKEND_H_ diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 897bd0707..be1972fa7 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -37,9 +37,9 @@ find_program(CONVERTER, fits2idia) set(BINARY ${CMAKE_PROJECT_NAME}_tests) set(TEST_SOURCES CommonTestUtilities.cc - BackendModel.cc TestBlockSmooth.cc TestContour.cc + TestCursorSpatialProfiles.cc TestExprImage.cc TestFileInfo.cc TestFileList.cc @@ -48,16 +48,20 @@ set(TEST_SOURCES TestHdf5Attributes.cc TestHdf5Image.cc TestHistogram.cc - TestIcd.cc TestImageFitting.cc - TestLineSpatialProfiles.cc TestMain.cc TestMoment.cc TestNormalizedUnits.cc TestProgramSettings.cc TestPvGenerator.cc + TestRegion.cc + TestRegionImportExport.cc + TestRegionHistogram.cc + TestRegionMatched.cc + TestRegionSpatialProfiles.cc + TestRegionSpectralProfiles.cc + TestRegionStats.cc TestRestApi.cc - TestSpatialProfiles.cc TestTileEncoding.cc TestUtil.cc TestVoTable.cc) diff --git a/test/CommonTestUtilities.cc b/test/CommonTestUtilities.cc index e8becef83..768049d51 100644 --- a/test/CommonTestUtilities.cc +++ b/test/CommonTestUtilities.cc @@ -1,5 +1,5 @@ /* This file is part of the CARTA Image Viewer: https://github.com/CARTAvis/carta-backend - Copyright 2018-2022 Academia Sinica Institute of Astronomy and Astrophysics (ASIAA), + Copyright 2018- Academia Sinica Institute of Astronomy and Astrophysics (ASIAA), Associated Universities, Inc. (AUI) and the Inter-University Institute for Data Intensive Astronomy (IDIA) SPDX-License-Identifier: GPL-3.0-or-later */ @@ -386,27 +386,8 @@ void CmpSpatialProfiles( const std::vector& data_vec, const std::pair, std::vector>& data_profiles) { EXPECT_EQ(data_vec.size(), 1); for (const auto& data : data_vec) { - CmpVectors(GetSpatialProfileValues(data.profiles(0)), data_profiles.first); - CmpVectors(GetSpatialProfileValues(data.profiles(1)), data_profiles.second); - } -} - -void CmpVectors(const std::vector& data1, const std::vector& data2, float abs_err) { - EXPECT_EQ(data1.size(), data2.size()); - if (data1.size() == data2.size()) { - for (int i = 0; i < data1.size(); ++i) { - CmpValues(data1[i], data2[i], abs_err); - } - } -} - -void CmpValues(float data1, float data2, float abs_err) { - if (!std::isnan(data1) || !std::isnan(data2)) { - if (abs_err > 0) { - EXPECT_NEAR(data1, data2, abs_err); - } else { - EXPECT_FLOAT_EQ(data1, data2); - } + CmpVectors(GetSpatialProfileValues(data.profiles(0)), data_profiles.first); + CmpVectors(GetSpatialProfileValues(data.profiles(1)), data_profiles.second); } } diff --git a/test/CommonTestUtilities.h b/test/CommonTestUtilities.h index aed6e0a4c..b3f5c8804 100644 --- a/test/CommonTestUtilities.h +++ b/test/CommonTestUtilities.h @@ -1,11 +1,11 @@ /* This file is part of the CARTA Image Viewer: https://github.com/CARTAvis/carta-backend - Copyright 2018-2022 Academia Sinica Institute of Astronomy and Astrophysics (ASIAA), + Copyright 2018- Academia Sinica Institute of Astronomy and Astrophysics (ASIAA), Associated Universities, Inc. (AUI) and the Inter-University Institute for Data Intensive Astronomy (IDIA) SPDX-License-Identifier: GPL-3.0-or-later */ -#ifndef CARTA_BACKEND__COMMON_TEST_UTILITIES_H_ -#define CARTA_BACKEND__COMMON_TEST_UTILITIES_H_ +#ifndef CARTA_TEST_COMMONTESTUTILITIES_H_ +#define CARTA_TEST_COMMONTESTUTILITIES_H_ #include @@ -92,12 +92,15 @@ void GetImageData(std::vector& data, std::shared_ptr std::vector GetSpectralProfileValues(const CARTA::SpectralProfile& profile); std::vector GetSpatialProfileValues(const CARTA::SpatialProfile& profile); -void CmpValues(float data1, float data2, float abs_err = 0); -void CmpVectors(const std::vector& data1, const std::vector& data2, float abs_err = 0); void CmpSpatialProfiles( const std::vector& data_vec, const std::pair, std::vector>& data_profiles); bool CmpHistograms(const carta::Histogram& hist1, const carta::Histogram& hist2); +template +void CmpValues(T data1, T data2, T abs_err = 0); +template +void CmpVectors(const std::vector& data1, const std::vector& data2, T abs_err = 0); + #include "CommonTestUtilities.tcc" -#endif // CARTA_BACKEND__COMMON_TEST_UTILITIES_H_ +#endif // CARTA_TEST_COMMONTESTUTILITIES_H_ diff --git a/test/CommonTestUtilities.tcc b/test/CommonTestUtilities.tcc index 45f4ffe62..a3fb02242 100644 --- a/test/CommonTestUtilities.tcc +++ b/test/CommonTestUtilities.tcc @@ -1,11 +1,11 @@ /* This file is part of the CARTA Image Viewer: https://github.com/CARTAvis/carta-backend - Copyright 2018-2022 Academia Sinica Institute of Astronomy and Astrophysics (ASIAA), + Copyright 2018- Academia Sinica Institute of Astronomy and Astrophysics (ASIAA), Associated Universities, Inc. (AUI) and the Inter-University Institute for Data Intensive Astronomy (IDIA) SPDX-License-Identifier: GPL-3.0-or-later */ -#ifndef CARTA_BACKEND__COMMON_TEST_UTILITIES_TCC_ -#define CARTA_BACKEND__COMMON_TEST_UTILITIES_TCC_ +#ifndef CARTA_TEST_COMMONTESTUTILITIES_TCC_ +#define CARTA_TEST_COMMONTESTUTILITIES_TCC_ template std::vector GetSpectralProfileValues(const CARTA::SpectralProfile& profile) { @@ -20,4 +20,21 @@ std::vector GetSpectralProfileValues(const CARTA::SpectralProfile& profile) { return values; } -#endif // CARTA_BACKEND__COMMON_TEST_UTILITIES_TCC_ +template +void CmpVectors(const std::vector& data1, const std::vector& data2, T abs_err) { + EXPECT_EQ(data1.size(), data2.size()); + if (data1.size() == data2.size()) { + for (int i = 0; i < data1.size(); ++i) { + CmpValues(data1[i], data2[i], abs_err); + } + } +} + +template +void CmpValues(T data1, T data2, T abs_err) { + if (!std::isnan(data1) || !std::isnan(data2)) { + EXPECT_NEAR(data1, data2, abs_err); + } +} + +#endif // CARTA_TEST_COMMONTESTUTILITIES_TCC_ diff --git a/test/TestBlockSmooth.cc b/test/TestBlockSmooth.cc index 8fe11d018..88fae2325 100644 --- a/test/TestBlockSmooth.cc +++ b/test/TestBlockSmooth.cc @@ -1,5 +1,5 @@ /* This file is part of the CARTA Image Viewer: https://github.com/CARTAvis/carta-backend - Copyright 2018-2022 Academia Sinica Institute of Astronomy and Astrophysics (ASIAA), + Copyright 2018- Academia Sinica Institute of Astronomy and Astrophysics (ASIAA), Associated Universities, Inc. (AUI) and the Inter-University Institute for Data Intensive Astronomy (IDIA) SPDX-License-Identifier: GPL-3.0-or-later */ diff --git a/test/TestContour.cc b/test/TestContour.cc index 0d4ce9d6e..b2c973473 100644 --- a/test/TestContour.cc +++ b/test/TestContour.cc @@ -1,5 +1,5 @@ /* This file is part of the CARTA Image Viewer: https://github.com/CARTAvis/carta-backend - Copyright 2018-2022 Academia Sinica Institute of Astronomy and Astrophysics (ASIAA), + Copyright 2018- Academia Sinica Institute of Astronomy and Astrophysics (ASIAA), Associated Universities, Inc. (AUI) and the Inter-University Institute for Data Intensive Astronomy (IDIA) SPDX-License-Identifier: GPL-3.0-or-later */ diff --git a/test/TestSpatialProfiles.cc b/test/TestCursorSpatialProfiles.cc similarity index 87% rename from test/TestSpatialProfiles.cc rename to test/TestCursorSpatialProfiles.cc index b1542be19..8aa36f078 100644 --- a/test/TestSpatialProfiles.cc +++ b/test/TestCursorSpatialProfiles.cc @@ -1,5 +1,5 @@ /* This file is part of the CARTA Image Viewer: https://github.com/CARTAvis/carta-backend - Copyright 2018-2022 Academia Sinica Institute of Astronomy and Astrophysics (ASIAA), + Copyright 2018- Academia Sinica Institute of Astronomy and Astrophysics (ASIAA), Associated Universities, Inc. (AUI) and the Inter-University Institute for Data Intensive Astronomy (IDIA) SPDX-License-Identifier: GPL-3.0-or-later */ @@ -19,7 +19,7 @@ using ::testing::Pointwise; static const std::string IMAGE_OPTS = "-s 0 -n row column -d 10"; -class SpatialProfileTest : public ::testing::Test, public ImageGenerator { +class CursorSpatialProfileTest : public ::testing::Test, public ImageGenerator { public: static std::tuple GetProfiles(CARTA::SpatialProfileData& data) { if (data.profiles(0).coordinate().back() == 'x') { @@ -99,7 +99,7 @@ class SpatialProfileTest : public ::testing::Test, public ImageGenerator { } }; -TEST_F(SpatialProfileTest, SmallFitsProfile) { +TEST_F(CursorSpatialProfileTest, SmallFitsProfile) { auto path_string = GeneratedFitsImagePath("10 10", IMAGE_OPTS); std::shared_ptr loader(carta::FileLoader::GetLoader(path_string)); std::unique_ptr frame(new Frame(0, loader, "0")); @@ -129,18 +129,18 @@ TEST_F(SpatialProfileTest, SmallFitsProfile) { EXPECT_EQ(x_profile.mip(), 0); auto x_vals = ProfileValues(x_profile); EXPECT_EQ(x_vals.size(), 10); - CmpVectors(x_vals, reader.ReadProfileX(5)); + CmpVectors(x_vals, reader.ReadProfileX(5)); EXPECT_EQ(y_profile.start(), 0); EXPECT_EQ(y_profile.end(), 10); EXPECT_EQ(y_profile.mip(), 0); auto y_vals = ProfileValues(y_profile); EXPECT_EQ(y_vals.size(), 10); - CmpVectors(y_vals, reader.ReadProfileY(5)); + CmpVectors(y_vals, reader.ReadProfileY(5)); } } -TEST_F(SpatialProfileTest, SmallHdf5Profile) { +TEST_F(CursorSpatialProfileTest, SmallHdf5Profile) { auto path_string = GeneratedHdf5ImagePath("10 10", IMAGE_OPTS); std::shared_ptr loader(carta::FileLoader::GetLoader(path_string)); std::unique_ptr frame(new Frame(0, loader, "0")); @@ -170,18 +170,18 @@ TEST_F(SpatialProfileTest, SmallHdf5Profile) { EXPECT_EQ(x_profile.mip(), 0); auto x_vals = ProfileValues(x_profile); EXPECT_EQ(x_vals.size(), 10); - CmpVectors(x_vals, reader.ReadProfileX(5)); + CmpVectors(x_vals, reader.ReadProfileX(5)); EXPECT_EQ(y_profile.start(), 0); EXPECT_EQ(y_profile.end(), 10); EXPECT_EQ(y_profile.mip(), 0); auto y_vals = ProfileValues(y_profile); EXPECT_EQ(y_vals.size(), 10); - CmpVectors(y_vals, reader.ReadProfileY(5)); + CmpVectors(y_vals, reader.ReadProfileY(5)); } } -TEST_F(SpatialProfileTest, LowResFitsProfile) { +TEST_F(CursorSpatialProfileTest, LowResFitsProfile) { auto path_string = GeneratedFitsImagePath("130 100", IMAGE_OPTS); std::shared_ptr loader(carta::FileLoader::GetLoader(path_string)); std::unique_ptr frame(new Frame(0, loader, "0")); @@ -205,18 +205,18 @@ TEST_F(SpatialProfileTest, LowResFitsProfile) { EXPECT_EQ(x_profile.mip(), 2); auto x_vals = ProfileValues(x_profile); EXPECT_EQ(x_vals.size(), 66); - CmpVectors(x_vals, Decimated(reader.ReadProfileX(50), 2)); + CmpVectors(x_vals, Decimated(reader.ReadProfileX(50), 2)); EXPECT_EQ(y_profile.start(), 0); EXPECT_EQ(y_profile.end(), 100); EXPECT_EQ(y_profile.mip(), 2); auto y_vals = ProfileValues(y_profile); EXPECT_EQ(y_vals.size(), 50); - CmpVectors(y_vals, Decimated(reader.ReadProfileY(50), 2)); + CmpVectors(y_vals, Decimated(reader.ReadProfileY(50), 2)); } } -TEST_F(SpatialProfileTest, LowResHdf5ProfileExactMipAvailable) { +TEST_F(CursorSpatialProfileTest, LowResHdf5ProfileExactMipAvailable) { auto path_string = GeneratedHdf5ImagePath("130 100", IMAGE_OPTS); std::shared_ptr loader(carta::FileLoader::GetLoader(path_string)); std::unique_ptr frame(new Frame(0, loader, "0")); @@ -240,18 +240,18 @@ TEST_F(SpatialProfileTest, LowResHdf5ProfileExactMipAvailable) { EXPECT_EQ(x_profile.mip(), 2); auto x_vals = ProfileValues(x_profile); EXPECT_EQ(x_vals.size(), 65); - CmpVectors(x_vals, Downsampled({reader.ReadProfileX(50), reader.ReadProfileX(51)}), 1e-5); + CmpVectors(x_vals, Downsampled({reader.ReadProfileX(50), reader.ReadProfileX(51)}), 1e-5); EXPECT_EQ(y_profile.start(), 0); EXPECT_EQ(y_profile.end(), 100); EXPECT_EQ(y_profile.mip(), 2); auto y_vals = ProfileValues(y_profile); EXPECT_EQ(y_vals.size(), 50); - CmpVectors(y_vals, Downsampled({reader.ReadProfileY(50), reader.ReadProfileY(51)}), 1e-5); + CmpVectors(y_vals, Downsampled({reader.ReadProfileY(50), reader.ReadProfileY(51)}), 1e-5); } } -TEST_F(SpatialProfileTest, LowResHdf5ProfileLowerMipAvailable) { +TEST_F(CursorSpatialProfileTest, LowResHdf5ProfileLowerMipAvailable) { auto path_string = GeneratedHdf5ImagePath("130 100", IMAGE_OPTS); std::shared_ptr loader(carta::FileLoader::GetLoader(path_string)); std::unique_ptr frame(new Frame(0, loader, "0")); @@ -277,18 +277,18 @@ TEST_F(SpatialProfileTest, LowResHdf5ProfileLowerMipAvailable) { EXPECT_EQ(x_profile.mip(), 2); auto x_vals = ProfileValues(x_profile); EXPECT_EQ(x_vals.size(), 65); - CmpVectors(x_vals, Downsampled({reader.ReadProfileX(50), reader.ReadProfileX(51)}), 1e-5); + CmpVectors(x_vals, Downsampled({reader.ReadProfileX(50), reader.ReadProfileX(51)}), 1e-5); EXPECT_EQ(y_profile.start(), 0); EXPECT_EQ(y_profile.end(), 100); EXPECT_EQ(y_profile.mip(), 2); auto y_vals = ProfileValues(y_profile); EXPECT_EQ(y_vals.size(), 50); - CmpVectors(y_vals, Downsampled({reader.ReadProfileY(50), reader.ReadProfileY(51)}), 1e-5); + CmpVectors(y_vals, Downsampled({reader.ReadProfileY(50), reader.ReadProfileY(51)}), 1e-5); } } -TEST_F(SpatialProfileTest, LowResHdf5ProfileNoMipAvailable) { +TEST_F(CursorSpatialProfileTest, LowResHdf5ProfileNoMipAvailable) { auto path_string = GeneratedHdf5ImagePath("120 100", IMAGE_OPTS); std::shared_ptr loader(carta::FileLoader::GetLoader(path_string)); std::unique_ptr frame(new Frame(0, loader, "0")); @@ -314,18 +314,18 @@ TEST_F(SpatialProfileTest, LowResHdf5ProfileNoMipAvailable) { EXPECT_EQ(x_profile.mip(), 2); auto x_vals = ProfileValues(x_profile); EXPECT_EQ(x_vals.size(), 60); - CmpVectors(x_vals, Decimated(reader.ReadProfileX(50), 2)); + CmpVectors(x_vals, Decimated(reader.ReadProfileX(50), 2)); EXPECT_EQ(y_profile.start(), 0); EXPECT_EQ(y_profile.end(), 100); EXPECT_EQ(y_profile.mip(), 2); auto y_vals = ProfileValues(y_profile); EXPECT_EQ(y_vals.size(), 50); - CmpVectors(y_vals, Decimated(reader.ReadProfileY(50), 2)); + CmpVectors(y_vals, Decimated(reader.ReadProfileY(50), 2)); } } -TEST_F(SpatialProfileTest, FullResFitsStartEnd) { +TEST_F(CursorSpatialProfileTest, FullResFitsStartEnd) { auto path_string = GeneratedFitsImagePath("400 300", IMAGE_OPTS); std::shared_ptr loader(carta::FileLoader::GetLoader(path_string)); std::unique_ptr frame(new Frame(0, loader, "0")); @@ -349,18 +349,18 @@ TEST_F(SpatialProfileTest, FullResFitsStartEnd) { EXPECT_EQ(x_profile.mip(), 0); auto x_vals = ProfileValues(x_profile); EXPECT_EQ(x_vals.size(), 100); - CmpVectors(x_vals, Segment(reader.ReadProfileX(150), 100, 200)); + CmpVectors(x_vals, Segment(reader.ReadProfileX(150), 100, 200)); EXPECT_EQ(y_profile.start(), 100); EXPECT_EQ(y_profile.end(), 200); EXPECT_EQ(y_profile.mip(), 0); auto y_vals = ProfileValues(y_profile); EXPECT_EQ(y_vals.size(), 100); - CmpVectors(y_vals, Segment(reader.ReadProfileY(150), 100, 200)); + CmpVectors(y_vals, Segment(reader.ReadProfileY(150), 100, 200)); } } -TEST_F(SpatialProfileTest, FullResHdf5StartEnd) { +TEST_F(CursorSpatialProfileTest, FullResHdf5StartEnd) { auto path_string = GeneratedHdf5ImagePath("400 300", IMAGE_OPTS); std::shared_ptr loader(carta::FileLoader::GetLoader(path_string)); std::unique_ptr frame(new Frame(0, loader, "0")); @@ -384,18 +384,18 @@ TEST_F(SpatialProfileTest, FullResHdf5StartEnd) { EXPECT_EQ(x_profile.mip(), 0); auto x_vals = ProfileValues(x_profile); EXPECT_EQ(x_vals.size(), 100); - CmpVectors(x_vals, Segment(reader.ReadProfileX(150), 100, 200)); + CmpVectors(x_vals, Segment(reader.ReadProfileX(150), 100, 200)); EXPECT_EQ(y_profile.start(), 100); EXPECT_EQ(y_profile.end(), 200); EXPECT_EQ(y_profile.mip(), 0); auto y_vals = ProfileValues(y_profile); EXPECT_EQ(y_vals.size(), 100); - CmpVectors(y_vals, Segment(reader.ReadProfileY(150), 100, 200)); + CmpVectors(y_vals, Segment(reader.ReadProfileY(150), 100, 200)); } } -TEST_F(SpatialProfileTest, LowResFitsStartEnd) { +TEST_F(CursorSpatialProfileTest, LowResFitsStartEnd) { auto path_string = GeneratedFitsImagePath("400 300", IMAGE_OPTS); std::shared_ptr loader(carta::FileLoader::GetLoader(path_string)); std::unique_ptr frame(new Frame(0, loader, "0")); @@ -420,7 +420,7 @@ TEST_F(SpatialProfileTest, LowResFitsStartEnd) { auto x_vals = ProfileValues(x_profile); EXPECT_EQ(x_vals.size(), 24); // Data to decimate has endpoints rounded up to mip*2 - CmpVectors(x_vals, Decimated(Segment(reader.ReadProfileX(150), 104, 200), 4)); + CmpVectors(x_vals, Decimated(Segment(reader.ReadProfileX(150), 104, 200), 4)); EXPECT_EQ(y_profile.start(), 100); EXPECT_EQ(y_profile.end(), 200); @@ -428,11 +428,11 @@ TEST_F(SpatialProfileTest, LowResFitsStartEnd) { auto y_vals = ProfileValues(y_profile); EXPECT_EQ(y_vals.size(), 24); // Data to decimate has endpoints rounded up to mip*2 - CmpVectors(y_vals, Decimated(Segment(reader.ReadProfileY(150), 104, 200), 4)); + CmpVectors(y_vals, Decimated(Segment(reader.ReadProfileY(150), 104, 200), 4)); } } -TEST_F(SpatialProfileTest, LowResHdf5StartEnd) { +TEST_F(CursorSpatialProfileTest, LowResHdf5StartEnd) { auto path_string = GeneratedHdf5ImagePath("400 300", IMAGE_OPTS); std::shared_ptr loader(carta::FileLoader::GetLoader(path_string)); std::unique_ptr frame(new Frame(0, loader, "0")); @@ -457,7 +457,7 @@ TEST_F(SpatialProfileTest, LowResHdf5StartEnd) { auto x_vals = ProfileValues(x_profile); EXPECT_EQ(x_vals.size(), 25); // Downsampled region is selected so that it includes the requested row - CmpVectors(x_vals, + CmpVectors(x_vals, Segment(Downsampled({reader.ReadProfileX(148), reader.ReadProfileX(149), reader.ReadProfileX(150), reader.ReadProfileX(151)}), 25, 50), 1e-5); @@ -468,14 +468,14 @@ TEST_F(SpatialProfileTest, LowResHdf5StartEnd) { auto y_vals = ProfileValues(y_profile); EXPECT_EQ(y_vals.size(), 25); // Downsampled region is selected so that it includes the requested column - CmpVectors(y_vals, + CmpVectors(y_vals, Segment(Downsampled({reader.ReadProfileY(148), reader.ReadProfileY(149), reader.ReadProfileY(150), reader.ReadProfileY(151)}), 25, 50), 1e-5); } } -TEST_F(SpatialProfileTest, Hdf5MultipleChunkFullRes) { +TEST_F(CursorSpatialProfileTest, Hdf5MultipleChunkFullRes) { auto path_string = GeneratedHdf5ImagePath("3000 2000", IMAGE_OPTS); std::shared_ptr loader(carta::FileLoader::GetLoader(path_string)); std::unique_ptr frame(new Frame(0, loader, "0")); @@ -498,18 +498,18 @@ TEST_F(SpatialProfileTest, Hdf5MultipleChunkFullRes) { EXPECT_EQ(x_profile.mip(), 0); auto x_vals = ProfileValues(x_profile); EXPECT_EQ(x_vals.size(), 3000); - CmpVectors(x_vals, reader.ReadProfileX(150)); + CmpVectors(x_vals, reader.ReadProfileX(150)); EXPECT_EQ(y_profile.start(), 0); EXPECT_EQ(y_profile.end(), 2000); EXPECT_EQ(y_profile.mip(), 0); auto y_vals = ProfileValues(y_profile); EXPECT_EQ(y_vals.size(), 2000); - CmpVectors(y_vals, reader.ReadProfileY(150)); + CmpVectors(y_vals, reader.ReadProfileY(150)); } } -TEST_F(SpatialProfileTest, Hdf5MultipleChunkFullResStartEnd) { +TEST_F(CursorSpatialProfileTest, Hdf5MultipleChunkFullResStartEnd) { auto path_string = GeneratedHdf5ImagePath("3000 2000", IMAGE_OPTS); std::shared_ptr loader(carta::FileLoader::GetLoader(path_string)); std::unique_ptr frame(new Frame(0, loader, "0")); @@ -533,18 +533,18 @@ TEST_F(SpatialProfileTest, Hdf5MultipleChunkFullResStartEnd) { EXPECT_EQ(x_profile.mip(), 0); auto x_vals = ProfileValues(x_profile); EXPECT_EQ(x_vals.size(), 500); - CmpVectors(x_vals, Segment(reader.ReadProfileX(1250), 1000, 1500)); + CmpVectors(x_vals, Segment(reader.ReadProfileX(1250), 1000, 1500)); EXPECT_EQ(y_profile.start(), 1000); EXPECT_EQ(y_profile.end(), 1500); EXPECT_EQ(y_profile.mip(), 0); auto y_vals = ProfileValues(y_profile); EXPECT_EQ(y_vals.size(), 500); - CmpVectors(y_vals, Segment(reader.ReadProfileY(1250), 1000, 1500)); + CmpVectors(y_vals, Segment(reader.ReadProfileY(1250), 1000, 1500)); } } -TEST_F(SpatialProfileTest, FitsChannelChange) { +TEST_F(CursorSpatialProfileTest, FitsChannelChange) { auto path_string = GeneratedFitsImagePath("10 10 2", IMAGE_OPTS); std::shared_ptr loader(carta::FileLoader::GetLoader(path_string)); std::unique_ptr frame(new Frame(0, loader, "0")); @@ -576,18 +576,18 @@ TEST_F(SpatialProfileTest, FitsChannelChange) { EXPECT_EQ(x_profile.mip(), 0); auto x_vals = ProfileValues(x_profile); EXPECT_EQ(x_vals.size(), 10); - CmpVectors(x_vals, reader.ReadProfileX(5, 1)); + CmpVectors(x_vals, reader.ReadProfileX(5, 1)); EXPECT_EQ(y_profile.start(), 0); EXPECT_EQ(y_profile.end(), 10); EXPECT_EQ(y_profile.mip(), 0); auto y_vals = ProfileValues(y_profile); EXPECT_EQ(y_vals.size(), 10); - CmpVectors(y_vals, reader.ReadProfileY(5, 1)); + CmpVectors(y_vals, reader.ReadProfileY(5, 1)); } } -TEST_F(SpatialProfileTest, FitsChannelStokesChange) { +TEST_F(CursorSpatialProfileTest, FitsChannelStokesChange) { auto path_string = GeneratedFitsImagePath("10 10 2 2", IMAGE_OPTS); std::shared_ptr loader(carta::FileLoader::GetLoader(path_string)); std::unique_ptr frame(new Frame(0, loader, "0")); @@ -625,18 +625,18 @@ TEST_F(SpatialProfileTest, FitsChannelStokesChange) { EXPECT_EQ(x_profile.mip(), 0); auto x_vals = ProfileValues(x_profile); EXPECT_EQ(x_vals.size(), 10); - CmpVectors(x_vals, reader.ReadProfileX(y, channel, spatial_config_stokes)); + CmpVectors(x_vals, reader.ReadProfileX(y, channel, spatial_config_stokes)); EXPECT_EQ(y_profile.start(), 0); EXPECT_EQ(y_profile.end(), 10); EXPECT_EQ(y_profile.mip(), 0); auto y_vals = ProfileValues(y_profile); EXPECT_EQ(y_vals.size(), 10); - CmpVectors(y_vals, reader.ReadProfileY(x, channel, spatial_config_stokes)); + CmpVectors(y_vals, reader.ReadProfileY(x, channel, spatial_config_stokes)); } } -TEST_F(SpatialProfileTest, ContiguousHDF5ChannelChange) { +TEST_F(CursorSpatialProfileTest, ContiguousHDF5ChannelChange) { auto path_string = GeneratedHdf5ImagePath("10 10 2", IMAGE_OPTS); std::shared_ptr loader(carta::FileLoader::GetLoader(path_string)); std::unique_ptr frame(new Frame(0, loader, "0")); @@ -668,18 +668,18 @@ TEST_F(SpatialProfileTest, ContiguousHDF5ChannelChange) { EXPECT_EQ(x_profile.mip(), 0); auto x_vals = ProfileValues(x_profile); EXPECT_EQ(x_vals.size(), 10); - CmpVectors(x_vals, reader.ReadProfileX(5, 1)); + CmpVectors(x_vals, reader.ReadProfileX(5, 1)); EXPECT_EQ(y_profile.start(), 0); EXPECT_EQ(y_profile.end(), 10); EXPECT_EQ(y_profile.mip(), 0); auto y_vals = ProfileValues(y_profile); EXPECT_EQ(y_vals.size(), 10); - CmpVectors(y_vals, reader.ReadProfileY(5, 1)); + CmpVectors(y_vals, reader.ReadProfileY(5, 1)); } } -TEST_F(SpatialProfileTest, ChunkedHDF5ChannelChange) { +TEST_F(CursorSpatialProfileTest, ChunkedHDF5ChannelChange) { auto path_string = GeneratedHdf5ImagePath("1000 1000 2", IMAGE_OPTS); std::shared_ptr loader(carta::FileLoader::GetLoader(path_string)); std::unique_ptr frame(new Frame(0, loader, "0")); @@ -711,18 +711,18 @@ TEST_F(SpatialProfileTest, ChunkedHDF5ChannelChange) { EXPECT_EQ(x_profile.mip(), 0); auto x_vals = ProfileValues(x_profile); EXPECT_EQ(x_vals.size(), 1000); - CmpVectors(x_vals, reader.ReadProfileX(5, 1)); + CmpVectors(x_vals, reader.ReadProfileX(5, 1)); EXPECT_EQ(y_profile.start(), 0); EXPECT_EQ(y_profile.end(), 1000); EXPECT_EQ(y_profile.mip(), 0); auto y_vals = ProfileValues(y_profile); EXPECT_EQ(y_vals.size(), 1000); - CmpVectors(y_vals, reader.ReadProfileY(5, 1)); + CmpVectors(y_vals, reader.ReadProfileY(5, 1)); } } -TEST_F(SpatialProfileTest, ChunkedHDF5ChannelStokesChange) { +TEST_F(CursorSpatialProfileTest, ChunkedHDF5ChannelStokesChange) { auto path_string = GeneratedHdf5ImagePath("1000 1000 2 2", IMAGE_OPTS); std::shared_ptr loader(carta::FileLoader::GetLoader(path_string)); std::unique_ptr frame(new Frame(0, loader, "0")); @@ -760,13 +760,13 @@ TEST_F(SpatialProfileTest, ChunkedHDF5ChannelStokesChange) { EXPECT_EQ(x_profile.mip(), 0); auto x_vals = ProfileValues(x_profile); EXPECT_EQ(x_vals.size(), 1000); - CmpVectors(x_vals, reader.ReadProfileX(y, channel, spatial_config_stokes)); + CmpVectors(x_vals, reader.ReadProfileX(y, channel, spatial_config_stokes)); EXPECT_EQ(y_profile.start(), 0); EXPECT_EQ(y_profile.end(), 1000); EXPECT_EQ(y_profile.mip(), 0); auto y_vals = ProfileValues(y_profile); EXPECT_EQ(y_vals.size(), 1000); - CmpVectors(y_vals, reader.ReadProfileY(x, channel, spatial_config_stokes)); + CmpVectors(y_vals, reader.ReadProfileY(x, channel, spatial_config_stokes)); } } diff --git a/test/TestExprImage.cc b/test/TestExprImage.cc index 0ede68cc7..dd85e8fbe 100644 --- a/test/TestExprImage.cc +++ b/test/TestExprImage.cc @@ -1,5 +1,5 @@ /* This file is part of the CARTA Image Viewer: https://github.com/CARTAvis/carta-backend - Copyright 2018-2022 Academia Sinica Institute of Astronomy and Astrophysics (ASIAA), + Copyright 2018- Academia Sinica Institute of Astronomy and Astrophysics (ASIAA), Associated Universities, Inc. (AUI) and the Inter-University Institute for Data Intensive Astronomy (IDIA) SPDX-License-Identifier: GPL-3.0-or-later */ @@ -74,10 +74,10 @@ class ImageExprTest : public ::testing::Test { EXPECT_EQ(image_shape, expr_shape); // Compare image xprofile * 2 to expr xprofile for_each(image_xprofile.begin(), image_xprofile.end(), [](float& a) { a *= 2; }); - CmpVectors(image_xprofile, expr_xprofile.tovector()); + CmpVectors(image_xprofile, expr_xprofile.tovector()); // Compare image yprofile * 2 to expr yprofile for_each(image_yprofile.begin(), image_yprofile.end(), [](float& a) { a *= 2; }); - CmpVectors(image_yprofile, expr_yprofile.tovector()); + CmpVectors(image_yprofile, expr_yprofile.tovector()); } void SaveImageExpr(const std::string& file_name, const std::string& hdu, CARTA::FileType file_type) { diff --git a/test/TestFileInfo.cc b/test/TestFileInfo.cc index 7ab5240ff..d1d60fefc 100644 --- a/test/TestFileInfo.cc +++ b/test/TestFileInfo.cc @@ -1,5 +1,5 @@ /* This file is part of the CARTA Image Viewer: https://github.com/CARTAvis/carta-backend - Copyright 2018-2022 Academia Sinica Institute of Astronomy and Astrophysics (ASIAA), + Copyright 2018- Academia Sinica Institute of Astronomy and Astrophysics (ASIAA), Associated Universities, Inc. (AUI) and the Inter-University Institute for Data Intensive Astronomy (IDIA) SPDX-License-Identifier: GPL-3.0-or-later */ @@ -179,7 +179,7 @@ class SessionFileInfoTest : public ::testing::Test { class TestSession : public Session { public: - TestSession() : Session(nullptr, nullptr, 0, "", "/", "", nullptr, -1, false) {} + TestSession() : Session(nullptr, nullptr, 0, "", nullptr) {} void TestFileInfo(const std::string& request_filename, const CARTA::FileType& request_file_type, const std::string& request_hdu = "") { auto request = Message::FileInfoRequest(SAMPLE_FILES_PATH, request_filename, request_hdu); diff --git a/test/TestFileList.cc b/test/TestFileList.cc index 4dd7d0bb5..9fc292dbb 100644 --- a/test/TestFileList.cc +++ b/test/TestFileList.cc @@ -1,5 +1,5 @@ /* This file is part of the CARTA Image Viewer: https://github.com/CARTAvis/carta-backend - Copyright 2018-2022 Academia Sinica Institute of Astronomy and Astrophysics (ASIAA), + Copyright 2018- Academia Sinica Institute of Astronomy and Astrophysics (ASIAA), Associated Universities, Inc. (AUI) and the Inter-University Institute for Data Intensive Astronomy (IDIA) SPDX-License-Identifier: GPL-3.0-or-later */ diff --git a/test/TestFitsImage.cc b/test/TestFitsImage.cc index 2df63c046..aa865fc36 100644 --- a/test/TestFitsImage.cc +++ b/test/TestFitsImage.cc @@ -1,5 +1,5 @@ /* This file is part of the CARTA Image Viewer: https://github.com/CARTAvis/carta-backend - Copyright 2018-2022 Academia Sinica Institute of Astronomy and Astrophysics (ASIAA), + Copyright 2018- Academia Sinica Institute of Astronomy and Astrophysics (ASIAA), Associated Universities, Inc. (AUI) and the Inter-University Institute for Data Intensive Astronomy (IDIA) SPDX-License-Identifier: GPL-3.0-or-later */ diff --git a/test/TestFitsTable.cc b/test/TestFitsTable.cc index 4d6a098da..c74b2926a 100644 --- a/test/TestFitsTable.cc +++ b/test/TestFitsTable.cc @@ -1,5 +1,5 @@ /* This file is part of the CARTA Image Viewer: https://github.com/CARTAvis/carta-backend - Copyright 2018-2022 Academia Sinica Institute of Astronomy and Astrophysics (ASIAA), + Copyright 2018- Academia Sinica Institute of Astronomy and Astrophysics (ASIAA), Associated Universities, Inc. (AUI) and the Inter-University Institute for Data Intensive Astronomy (IDIA) SPDX-License-Identifier: GPL-3.0-or-later */ diff --git a/test/TestHdf5Attributes.cc b/test/TestHdf5Attributes.cc index 0294b75a9..becbd1ed6 100644 --- a/test/TestHdf5Attributes.cc +++ b/test/TestHdf5Attributes.cc @@ -1,5 +1,5 @@ /* This file is part of the CARTA Image Viewer: https://github.com/CARTAvis/carta-backend - Copyright 2018-2022 Academia Sinica Institute of Astronomy and Astrophysics (ASIAA), + Copyright 2018- Academia Sinica Institute of Astronomy and Astrophysics (ASIAA), Associated Universities, Inc. (AUI) and the Inter-University Institute for Data Intensive Astronomy (IDIA) SPDX-License-Identifier: GPL-3.0-or-later */ diff --git a/test/TestHdf5Image.cc b/test/TestHdf5Image.cc index 8b38aed15..fa572af57 100644 --- a/test/TestHdf5Image.cc +++ b/test/TestHdf5Image.cc @@ -1,5 +1,5 @@ /* This file is part of the CARTA Image Viewer: https://github.com/CARTAvis/carta-backend - Copyright 2018-2022 Academia Sinica Institute of Astronomy and Astrophysics (ASIAA), + Copyright 2018- Academia Sinica Institute of Astronomy and Astrophysics (ASIAA), Associated Universities, Inc. (AUI) and the Inter-University Institute for Data Intensive Astronomy (IDIA) SPDX-License-Identifier: GPL-3.0-or-later */ diff --git a/test/TestHistogram.cc b/test/TestHistogram.cc index 4be3d988f..3576e29d9 100644 --- a/test/TestHistogram.cc +++ b/test/TestHistogram.cc @@ -1,5 +1,5 @@ /* This file is part of the CARTA Image Viewer: https://github.com/CARTAvis/carta-backend - Copyright 2018-2022 Academia Sinica Institute of Astronomy and Astrophysics (ASIAA), + Copyright 2018- Academia Sinica Institute of Astronomy and Astrophysics (ASIAA), Associated Universities, Inc. (AUI) and the Inter-University Institute for Data Intensive Astronomy (IDIA) SPDX-License-Identifier: GPL-3.0-or-later */ diff --git a/test/TestIcd.cc b/test/TestIcd.cc deleted file mode 100644 index 2aa3bd8b1..000000000 --- a/test/TestIcd.cc +++ /dev/null @@ -1,553 +0,0 @@ -/* This file is part of the CARTA Image Viewer: https://github.com/CARTAvis/carta-backend - Copyright 2018-2022 Academia Sinica Institute of Astronomy and Astrophysics (ASIAA), - Associated Universities, Inc. (AUI) and the Inter-University Institute for Data Intensive Astronomy (IDIA) - SPDX-License-Identifier: GPL-3.0-or-later -*/ - -#include -#include - -#include "BackendModel.h" -#include "CommonTestUtilities.h" -#include "Timer/Timer.h" -#include "Util/Message.h" - -class IcdTest : public ::testing::Test, public FileFinder { - std::unique_ptr _dummy_backend; - std::pair, bool> _message_pair; // Resulting message - int _message_count = 0; - -public: - IcdTest() { - _dummy_backend = BackendModel::GetDummyBackend(); - } - ~IcdTest() = default; - - void AccessCarta(uint32_t session_id, string api_key, uint32_t client_feature_flags, CARTA::SessionType expected_session_type, - bool expected_message) { - CARTA::RegisterViewer register_viewer = Message::RegisterViewer(session_id, api_key, client_feature_flags); - - carta::Timer t; - - _dummy_backend->Receive(register_viewer); - - EXPECT_LT(t.Elapsed().ms(), 100); // expect the process time within 100 ms - - _message_count = 0; - - while (_dummy_backend->TryPopMessagesQueue(_message_pair)) { - std::vector message = _message_pair.first; - CARTA::EventType event_type = Message::EventType(message); - - if (event_type == CARTA::EventType::REGISTER_VIEWER_ACK) { - CARTA::RegisterViewerAck register_viewer_ack = Message::DecodeMessage(message); - EXPECT_TRUE(register_viewer_ack.success()); - EXPECT_EQ(register_viewer_ack.session_id(), session_id); - EXPECT_EQ(register_viewer_ack.session_type(), expected_session_type); - EXPECT_EQ(register_viewer_ack.user_preferences_size(), 0); - EXPECT_EQ(register_viewer_ack.user_layouts_size(), 0); - if (expected_message) { - EXPECT_GT(register_viewer_ack.message().length(), 0); - } else { - EXPECT_EQ(register_viewer_ack.message().length(), 0); - } - } - ++_message_count; - } - - EXPECT_EQ(_message_count, 1); - } - - void AnimatorDataStream() { - // Generate a FITS image - auto filename_path_string = ImageGenerator::GeneratedFitsImagePath("640 800 25 1"); - std::filesystem::path filename_path(filename_path_string); - - CARTA::OpenFile open_file = - Message::OpenFile(filename_path.parent_path(), filename_path.filename(), false, "0", 0, CARTA::RenderMode::RASTER); - - _dummy_backend->Receive(open_file); - - _message_count = 0; - - while (_dummy_backend->TryPopMessagesQueue(_message_pair)) { - std::vector message = _message_pair.first; - CARTA::EventType event_type = Message::EventType(message); - - if (event_type == CARTA::EventType::OPEN_FILE_ACK) { - CARTA::OpenFileAck open_file_ack = Message::DecodeMessage(message); - EXPECT_TRUE(open_file_ack.success()); - } - - if (event_type == CARTA::EventType::REGION_HISTOGRAM_DATA) { - CARTA::RegionHistogramData region_histogram_data = Message::DecodeMessage(message); - EXPECT_EQ(region_histogram_data.file_id(), 0); - EXPECT_EQ(region_histogram_data.region_id(), -1); - EXPECT_EQ(region_histogram_data.channel(), 0); - EXPECT_EQ(region_histogram_data.stokes(), 0); - EXPECT_EQ(region_histogram_data.progress(), 1); - EXPECT_TRUE(region_histogram_data.has_histograms()); - } - ++_message_count; - } - - EXPECT_EQ(_message_count, 2); // OPEN_FILE_ACK x1 + REGION_HISTOGRAM_DATA x1 - - CARTA::SetImageChannels set_image_channels = Message::SetImageChannels(0, 0, 0, CARTA::CompressionType::ZFP, 11); - - _dummy_backend->Receive(set_image_channels); - - _dummy_backend->WaitForJobFinished(); - - _message_count = 0; - - while (_dummy_backend->TryPopMessagesQueue(_message_pair)) { - std::vector message = _message_pair.first; - CARTA::EventType event_type = Message::EventType(message); - - if (event_type == CARTA::EventType::RASTER_TILE_DATA) { - CARTA::RasterTileData raster_tile_data = Message::DecodeMessage(message); - EXPECT_EQ(raster_tile_data.file_id(), 0); - EXPECT_EQ(raster_tile_data.channel(), 0); - EXPECT_EQ(raster_tile_data.stokes(), 0); - } - ++_message_count; - } - - EXPECT_EQ(_message_count, 3); // RASTER_TILE_DATA x3 - - set_image_channels = Message::SetImageChannels(0, 12, 0, CARTA::CompressionType::ZFP, 11); - - _dummy_backend->Receive(set_image_channels); - - _dummy_backend->WaitForJobFinished(); - - _message_count = 0; - - while (_dummy_backend->TryPopMessagesQueue(_message_pair)) { - std::vector message = _message_pair.first; - CARTA::EventType event_type = Message::EventType(message); - - if (event_type == CARTA::EventType::RASTER_TILE_DATA) { - CARTA::RasterTileData raster_tile_data = Message::DecodeMessage(message); - EXPECT_EQ(raster_tile_data.file_id(), 0); - EXPECT_EQ(raster_tile_data.channel(), 12); - EXPECT_EQ(raster_tile_data.stokes(), 0); - } - - if (event_type == CARTA::EventType::REGION_HISTOGRAM_DATA) { - CARTA::RegionHistogramData region_histogram_data = Message::DecodeMessage(message); - EXPECT_EQ(region_histogram_data.file_id(), 0); - EXPECT_EQ(region_histogram_data.region_id(), -1); - EXPECT_EQ(region_histogram_data.channel(), 12); - EXPECT_EQ(region_histogram_data.stokes(), 0); - EXPECT_EQ(region_histogram_data.progress(), 1); - EXPECT_TRUE(region_histogram_data.has_histograms()); - } - ++_message_count; - } - - EXPECT_EQ(_message_count, 4); // RASTER_TILE_DATA x3 + REGION_HISTOGRAM_DATA x1 - } - - void AnimatorNavigation() { - // Generate two HDF5 images - auto first_filename_path_string = ImageGenerator::GeneratedHdf5ImagePath("1049 1049 5 3"); - std::filesystem::path first_filename_path(first_filename_path_string); - - auto second_filename_path_string = ImageGenerator::GeneratedHdf5ImagePath("640 800 25 1"); - std::filesystem::path second_filename_path(second_filename_path_string); - - CARTA::OpenFile open_file = - Message::OpenFile(first_filename_path.parent_path(), first_filename_path.filename(), false, "0", 0, CARTA::RenderMode::RASTER); - - _dummy_backend->Receive(open_file); - - _dummy_backend->ClearMessagesQueue(); - - CARTA::SetImageChannels set_image_channels = Message::SetImageChannels(0, 0, 0, CARTA::CompressionType::ZFP, 11); - - _dummy_backend->Receive(set_image_channels); - - _dummy_backend->WaitForJobFinished(); - - _message_count = 0; - - while (_dummy_backend->TryPopMessagesQueue(_message_pair)) { - std::vector message = _message_pair.first; - CARTA::EventType event_type = Message::EventType(message); - if (event_type == CARTA::EventType::RASTER_TILE_DATA) { - CARTA::RasterTileData raster_tile_data = Message::DecodeMessage(message); - EXPECT_EQ(raster_tile_data.file_id(), 0); - EXPECT_EQ(raster_tile_data.channel(), 0); - EXPECT_EQ(raster_tile_data.stokes(), 0); - } - ++_message_count; - } - - EXPECT_EQ(_message_count, 3); - - open_file = Message::OpenFile( - second_filename_path.parent_path(), second_filename_path.filename(), false, "0", 1, CARTA::RenderMode::RASTER); - - _dummy_backend->Receive(open_file); - - _message_count = 0; - - while (_dummy_backend->TryPopMessagesQueue(_message_pair)) { - std::vector message = _message_pair.first; - CARTA::EventType event_type = Message::EventType(message); - if (event_type == CARTA::EventType::OPEN_FILE_ACK) { - CARTA::OpenFileAck open_file_ack = Message::DecodeMessage(message); - EXPECT_TRUE(open_file_ack.success()); - } - - if (event_type == CARTA::EventType::REGION_HISTOGRAM_DATA) { - CARTA::RegionHistogramData region_histogram_data = Message::DecodeMessage(message); - EXPECT_EQ(region_histogram_data.file_id(), 1); - EXPECT_EQ(region_histogram_data.region_id(), -1); - EXPECT_EQ(region_histogram_data.channel(), 0); - EXPECT_EQ(region_histogram_data.stokes(), 0); - EXPECT_EQ(region_histogram_data.progress(), 1); - EXPECT_TRUE(region_histogram_data.has_histograms()); - } - ++_message_count; - } - - EXPECT_EQ(_message_count, 2); - - set_image_channels = Message::SetImageChannels(0, 2, 1, CARTA::CompressionType::ZFP, 11); - - _dummy_backend->Receive(set_image_channels); - - _dummy_backend->WaitForJobFinished(); - - _message_count = 0; - - while (_dummy_backend->TryPopMessagesQueue(_message_pair)) { - std::vector message = _message_pair.first; - CARTA::EventType event_type = Message::EventType(message); - if (event_type == CARTA::EventType::RASTER_TILE_DATA) { - CARTA::RasterTileData raster_tile_data = Message::DecodeMessage(message); - EXPECT_EQ(raster_tile_data.file_id(), 0); - EXPECT_EQ(raster_tile_data.channel(), 2); - EXPECT_EQ(raster_tile_data.stokes(), 1); - } - ++_message_count; - } - - EXPECT_EQ(_message_count, 4); - - set_image_channels = Message::SetImageChannels(1, 12, 0, CARTA::CompressionType::ZFP, 11); - - _dummy_backend->Receive(set_image_channels); - - _dummy_backend->WaitForJobFinished(); - - _message_count = 0; - - while (_dummy_backend->TryPopMessagesQueue(_message_pair)) { - std::vector message = _message_pair.first; - CARTA::EventType event_type = Message::EventType(message); - if (event_type == CARTA::EventType::RASTER_TILE_DATA) { - CARTA::RasterTileData raster_tile_data = Message::DecodeMessage(message); - EXPECT_EQ(raster_tile_data.file_id(), 1); - EXPECT_EQ(raster_tile_data.channel(), 12); - EXPECT_EQ(raster_tile_data.stokes(), 0); - } - ++_message_count; - } - - EXPECT_EQ(_message_count, 4); - } - - void AnimatorPlayback() { - auto filename_path_string = ImageGenerator::GeneratedFitsImagePath("640 800 25 1"); - std::filesystem::path filename_path(filename_path_string); - - CARTA::OpenFile open_file = - Message::OpenFile(filename_path.parent_path(), filename_path.filename(), false, "0", 0, CARTA::RenderMode::RASTER); - - _dummy_backend->Receive(open_file); - - _message_count = 0; - - while (_dummy_backend->TryPopMessagesQueue(_message_pair)) { - std::vector message = _message_pair.first; - auto event_type = Message::EventType(message); - ++_message_count; - } - - EXPECT_EQ(_message_count, 2); - - std::vector tiles = {33558529.0, 33558528.0, 33562625.0, 33554433.0, 33562624.0, 33558530.0, 33554432.0, 33562626.0, - 33554434.0, 33566721.0, 33566720.0, 33566722.0}; - - auto add_required_tiles = Message::AddRequiredTiles(0, CARTA::CompressionType::ZFP, 11, tiles); - - _dummy_backend->Receive(add_required_tiles); - - _dummy_backend->WaitForJobFinished(); - - _message_count = 0; - - while (_dummy_backend->TryPopMessagesQueue(_message_pair)) { - std::vector message = _message_pair.first; - auto event_type = Message::EventType(message); - ++_message_count; - } - - EXPECT_EQ(_message_count, 14); - - // Play animation forward - - int first_channel(0); - int start_channel(1); - int end_channel(10); - int last_channel(24); - int delta_channel(1); - int frame_rate(5); - int stokes(0); - - std::pair first_frame = std::make_pair(first_channel, stokes); - std::pair start_frame = std::make_pair(start_channel, stokes); - std::pair end_frame = std::make_pair(end_channel, stokes); - std::pair last_frame = std::make_pair(last_channel, stokes); - std::pair delta_frame = std::make_pair(delta_channel, stokes); - - tiles = {33554432.0, 33558528.0, 33562624.0, 33566720.0, 33554433.0, 33558529.0, 33562625.0, 33566721.0, 33554434.0, 33558530.0, - 33562626.0, 33566722.0}; - - auto start_animation = Message::StartAnimation( - 0, first_frame, start_frame, last_frame, delta_frame, CARTA::CompressionType::ZFP, 9, tiles, frame_rate); - - auto stop_animation = Message::StopAnimation(0, end_frame); - - _dummy_backend->Receive(start_animation); - - _message_count = 0; - - int expected_channel = start_channel; - - // (end_channel - start_channel + 1) * (RASTER_TILE_DATA x tiles number + REGION_HISTOGRAM_DATA x1 + RASTER_TILE_SYNC x2) + - // START_ANIMATION_ACK x1 - int expected_response_messages = (end_channel - start_channel + 1) * (tiles.size() + 1 + 2) + 1; - - auto t_start = std::chrono::high_resolution_clock::now(); - - auto is_timeout = [&]() { - auto t_end = std::chrono::high_resolution_clock::now(); - auto dt = std::chrono::duration_cast(t_end - t_start).count(); - if (dt > 10) { - spdlog::error("Animation timeout: can not receive all data messages within 10 seconds."); - return true; - } - return false; - }; - - while (true) { - while (!_dummy_backend->TryPopMessagesQueue(_message_pair)) { // wait for the data stream - std::this_thread::sleep_for(std::chrono::milliseconds(1)); - if (is_timeout()) { - break; - } - } - if (is_timeout()) { - break; - } - - ++_message_count; - std::vector message = _message_pair.first; - auto event_type = Message::EventType(message); - - if (event_type == CARTA::EventType::RASTER_TILE_SYNC) { - CARTA::RasterTileSync raster_tile_sync = Message::DecodeMessage(message); - if (raster_tile_sync.end_sync()) { - int sync_channel = raster_tile_sync.channel(); - EXPECT_DOUBLE_EQ(sync_channel, expected_channel); // received image channels should be in sequence - expected_channel += delta_channel; - - int sync_stokes = raster_tile_sync.stokes(); - auto animation_flow_control = Message::AnimationFlowControl(0, std::make_pair(sync_channel, sync_stokes)); - _dummy_backend->Receive(animation_flow_control); - if (sync_channel >= end_channel) { - _dummy_backend->Receive(stop_animation); // stop the animation - break; - } - } - } - } - - _dummy_backend->WaitForJobFinished(); - - EXPECT_EQ(_message_count, expected_response_messages); - - // Play animation backward - - first_channel = 9; - start_channel = 19; - end_channel = 10; - last_channel = 19; - delta_channel = -1; - - first_frame = std::make_pair(first_channel, stokes); - start_frame = std::make_pair(start_channel, stokes); - last_frame = std::make_pair(last_channel, stokes); - delta_frame = std::make_pair(delta_channel, stokes); - - start_animation = Message::StartAnimation( - 0, first_frame, start_frame, last_frame, delta_frame, CARTA::CompressionType::ZFP, 9, tiles, frame_rate); - - end_frame = std::make_pair(end_channel, stokes); - - stop_animation = Message::StopAnimation(0, end_frame); - - _dummy_backend->Receive(start_animation); - - _message_count = 0; - - expected_channel = start_channel; - expected_response_messages = (start_channel - end_channel + 1) * (tiles.size() + 1 + 2) + 1; - - t_start = std::chrono::high_resolution_clock::now(); - - while (true) { - while (!_dummy_backend->TryPopMessagesQueue(_message_pair)) { // wait for the data stream - std::this_thread::sleep_for(std::chrono::milliseconds(1)); - if (is_timeout()) { - break; - } - } - if (is_timeout()) { - break; - } - - ++_message_count; - std::vector message = _message_pair.first; - auto event_type = Message::EventType(message); - - if (event_type == CARTA::EventType::RASTER_TILE_SYNC) { - CARTA::RasterTileSync raster_tile_sync = Message::DecodeMessage(message); - if (raster_tile_sync.end_sync()) { - int sync_channel = raster_tile_sync.channel(); - EXPECT_DOUBLE_EQ(sync_channel, expected_channel); // received image channels should be in sequence - expected_channel += delta_channel; - - int sync_stokes = raster_tile_sync.stokes(); - auto animation_flow_control = Message::AnimationFlowControl(0, std::make_pair(sync_channel, sync_stokes)); - _dummy_backend->Receive(animation_flow_control); - if (sync_channel <= end_channel) { - _dummy_backend->Receive(stop_animation); // stop the animation - break; - } - } - } - } - - _dummy_backend->WaitForJobFinished(); - - EXPECT_EQ(_message_count, expected_response_messages); - } - - void RegionRegister() { - // Generate a FITS image - auto filename_path_string = ImageGenerator::GeneratedFitsImagePath("640 800 25 1"); - std::filesystem::path filename_path(filename_path_string); - - CARTA::OpenFile open_file = - Message::OpenFile(filename_path.parent_path(), filename_path.filename(), false, "0", 0, CARTA::RenderMode::RASTER); - - _dummy_backend->Receive(open_file); - - _dummy_backend->ClearMessagesQueue(); - - auto set_region = Message::SetRegion(0, -1, CARTA::RegionType::RECTANGLE, {Message::Point(197, 489), Message::Point(10, 10)}, 0.0); - - _dummy_backend->Receive(set_region); - - _message_count = 0; - - while (_dummy_backend->TryPopMessagesQueue(_message_pair)) { - std::vector message = _message_pair.first; - auto event_type = Message::EventType(message); - if (event_type == CARTA::EventType::SET_REGION_ACK) { - auto set_region_ack = Message::DecodeMessage(message); - EXPECT_EQ(set_region_ack.region_id(), 1); - } - ++_message_count; - } - - EXPECT_EQ(_message_count, 1); - - set_region = Message::SetRegion(0, -1, CARTA::RegionType::RECTANGLE, {Message::Point(306, 670), Message::Point(20, 48)}, 27); - - _dummy_backend->Receive(set_region); - - _message_count = 0; - - while (_dummy_backend->TryPopMessagesQueue(_message_pair)) { - std::vector message = _message_pair.first; - auto event_type = Message::EventType(message); - if (event_type == CARTA::EventType::SET_REGION_ACK) { - auto set_region_ack = Message::DecodeMessage(message); - EXPECT_EQ(set_region_ack.region_id(), 2); - } - ++_message_count; - } - - EXPECT_EQ(_message_count, 1); - - set_region = Message::SetRegion(0, 1, CARTA::RegionType::RECTANGLE, {Message::Point(84.0, 491.0), Message::Point(10.0, 10.0)}, 0); - - _dummy_backend->Receive(set_region); - - _message_count = 0; - - while (_dummy_backend->TryPopMessagesQueue(_message_pair)) { - std::vector message = _message_pair.first; - auto event_type = Message::EventType(message); - if (event_type == CARTA::EventType::SET_REGION_ACK) { - auto set_region_ack = Message::DecodeMessage(message); - EXPECT_EQ(set_region_ack.region_id(), 1); - } - ++_message_count; - } - - EXPECT_EQ(_message_count, 1); - - _dummy_backend->WaitForJobFinished(); - } -}; - -TEST_F(IcdTest, AccessCartaDefault) { - AccessCarta(0, "", 5, CARTA::SessionType::NEW, true); -} - -TEST_F(IcdTest, AccessCartaKnownDefault) { - AccessCarta(9999, "", 5, CARTA::SessionType::RESUMED, true); -} - -TEST_F(IcdTest, AccessCartaNoClientFeature) { - AccessCarta(0, "", 0, CARTA::SessionType::NEW, true); -} - -TEST_F(IcdTest, AccessCartaSameIdTwice) { - AccessCarta(12345, "", 5, CARTA::SessionType::RESUMED, true); - AccessCarta(12345, "", 5, CARTA::SessionType::RESUMED, true); -} - -TEST_F(IcdTest, AnimatorDataStream) { - AnimatorDataStream(); -} - -TEST_F(IcdTest, AnimatorNavigation) { - AnimatorNavigation(); -} - -TEST_F(IcdTest, AnimatorPlayback) { - AnimatorPlayback(); -} - -TEST_F(IcdTest, RegionRegister) { - RegionRegister(); -} diff --git a/test/TestImageFitting.cc b/test/TestImageFitting.cc index 033bab8f0..d417c3955 100644 --- a/test/TestImageFitting.cc +++ b/test/TestImageFitting.cc @@ -1,5 +1,5 @@ /* This file is part of the CARTA Image Viewer: https://github.com/CARTAvis/carta-backend - Copyright 2018-2022 Academia Sinica Institute of Astronomy and Astrophysics (ASIAA), + Copyright 2018- Academia Sinica Institute of Astronomy and Astrophysics (ASIAA), Associated Universities, Inc. (AUI) and the Inter-University Institute for Data Intensive Astronomy (IDIA) SPDX-License-Identifier: GPL-3.0-or-later */ @@ -77,25 +77,13 @@ class ImageFittingTest : public ::testing::Test { frame->GetImageRegion(file_id, AxisRange(frame->CurrentZ()), frame->CurrentStokes(), output_stokes_region); casa::SPIIF image(loader->GetStokesImage(output_stokes_region.stokes_source)); success = image_fitter->GetGeneratedImages( - image, output_stokes_region.image_region, file_id, frame->GetFileName(), model_image, residual_image, fitting_response); + image, output_stokes_region.image_region, frame->GetFileName(), model_image, residual_image, fitting_response); EXPECT_TRUE(success); CompareImageResults(model_image, residual_image, fitting_response, frame->GetFileName(), frame->GetImageCacheData()); } } - void GetGeneratedImageWithIncorrectFileId() { - std::unique_ptr image_fitter(new carta::ImageFitter()); - GeneratedImage model_image; - GeneratedImage residual_image; - CARTA::FittingResponse fitting_response; - bool success = - image_fitter->GetGeneratedImages(nullptr, casacore::ImageRegion(), -1001, "", model_image, residual_image, fitting_response); - - EXPECT_FALSE(success); - EXPECT_EQ(fitting_response.message(), "generating images from generated PV and model/residual images is not supported"); - } - void FitImageWithFov(std::vector gaussian_model, int region_id, std::string failed_message = "") { std::string file_path = GetGeneratedFilePath(gaussian_model); std::shared_ptr loader(carta::FileLoader::GetLoader(file_path)); @@ -173,14 +161,12 @@ class ImageFittingTest : public ::testing::Test { fitting_response.result_values().begin(), fitting_response.result_values().end()); std::vector expect_model_data = Gaussian(result_values); - EXPECT_EQ(model_image.file_id, -999); EXPECT_EQ(model_image.name, filename.substr(0, filename.length() - 5) + "_model.fits"); EXPECT_EQ(model_data.size(), 128 * 128); for (size_t i = 0; i < model_data.size(); i++) { EXPECT_NEAR(model_data[i], expect_model_data[i], 1e-6); } - EXPECT_EQ(residual_image.file_id, -998); EXPECT_EQ(residual_image.name, filename.substr(0, filename.length() - 5) + "_residual.fits"); EXPECT_EQ(residual_data.size(), 128 * 128); for (size_t i = 0; i < residual_data.size(); i++) { @@ -261,17 +247,13 @@ TEST_F(ImageFittingTest, BackgroundUnfixedFitting) { FitImage(gaussian_model); } -TEST_F(ImageFittingTest, IncorrectFileId) { - GetGeneratedImageWithIncorrectFileId(); -} - TEST_F(ImageFittingTest, FittingWithFov) { std::vector gaussian_model = {1, 64, 64, 20, 20, 10, 135}; std::vector fixed_params(6, false); fixed_params.push_back(true); SetInitialValues(gaussian_model); SetFixedParams(fixed_params); - SetFov(CARTA::RegionType::RECTANGLE, {63.5, 63.5, 64, 64}, 10); + SetFov(CARTA::RegionType::RECTANGLE, {63.5, 63.5, 96, 96}, 10); FitImageWithFov(gaussian_model, 0); } diff --git a/test/TestLineSpatialProfiles.cc b/test/TestLineSpatialProfiles.cc deleted file mode 100644 index f3c86edd9..000000000 --- a/test/TestLineSpatialProfiles.cc +++ /dev/null @@ -1,222 +0,0 @@ -/* This file is part of the CARTA Image Viewer: https://github.com/CARTAvis/carta-backend - Copyright 2018-2022 Academia Sinica Institute of Astronomy and Astrophysics (ASIAA), - Associated Universities, Inc. (AUI) and the Inter-University Institute for Data Intensive Astronomy (IDIA) - SPDX-License-Identifier: GPL-3.0-or-later -*/ - -#include -#include - -#include "CommonTestUtilities.h" -#include "ImageData/FileLoader.h" -#include "Region/Region.h" -#include "Region/RegionHandler.h" -#include "src/Frame/Frame.h" - -using namespace carta; - -using ::testing::FloatNear; -using ::testing::Pointwise; - -class LineSpatialProfileTest : public ::testing::Test { -public: - static bool SetLineRegion(carta::RegionHandler& region_handler, int file_id, int& region_id, const std::vector& endpoints, - std::shared_ptr csys, bool is_annotation) { - std::vector control_points; - for (auto i = 0; i < endpoints.size(); i += 2) { - control_points.push_back(Message::Point(endpoints[i], endpoints[i + 1])); - } - - // Define RegionState for line region and set region (region_id updated) - auto npoints(control_points.size()); - CARTA::RegionType region_type(CARTA::RegionType::LINE); - if (is_annotation) { - if (npoints > 2) { - region_type = CARTA::RegionType::ANNPOLYLINE; - } else { - region_type = CARTA::RegionType::ANNLINE; - } - } else if (npoints > 2) { - region_type = CARTA::RegionType::POLYLINE; - } - RegionState region_state(file_id, region_type, control_points, 0.0); - return region_handler.SetRegion(region_id, region_state, csys); - } - - static bool GetLineProfiles(const std::string& image_path, const std::vector& endpoints, - const std::vector& spatial_reqs, CARTA::SpatialProfileData& spatial_profile, - bool is_annotation = false) { - std::shared_ptr loader(carta::FileLoader::GetLoader(image_path)); - std::shared_ptr frame(new Frame(0, loader, "0")); - carta::RegionHandler region_handler; - - // Set line region - int file_id(0), region_id(-1); - auto csys = frame->CoordinateSystem(); - if (!SetLineRegion(region_handler, file_id, region_id, endpoints, csys, is_annotation)) { - return false; - } - - // Get spatial profiles - if (!region_handler.SetSpatialRequirements(region_id, file_id, frame, spatial_reqs)) { - return false; - } - - return region_handler.FillLineSpatialProfileData( - file_id, region_id, [&](CARTA::SpatialProfileData profile_data) { spatial_profile = profile_data; }); - } - - static std::vector ProfileValues(CARTA::SpatialProfile& profile) { - std::string buffer = profile.raw_values_fp32(); - std::vector values(buffer.size() / sizeof(float)); - memcpy(values.data(), buffer.data(), buffer.size()); - return values; - } - - void SetUp() { - setenv("HDF5_USE_FILE_LOCKING", "FALSE", 0); - } - - static void TestAveragingWidthRange(int width, bool expected_width_range) { - std::string image_path = FileFinder::FitsImagePath("noise_3d.fits"); - std::vector endpoints = {0.0, 0.0, 9.0, 9.0}; - int start(0), end(0), mip(0); - std::vector spatial_reqs = {Message::SpatialConfig("x", start, end, mip, width)}; - CARTA::SpatialProfileData spatial_profile; - - if (expected_width_range) { - ASSERT_TRUE(GetLineProfiles(image_path, endpoints, spatial_reqs, spatial_profile)); - ASSERT_EQ(spatial_profile.profiles_size(), 1); - } else { - ASSERT_FALSE(GetLineProfiles(image_path, endpoints, spatial_reqs, spatial_profile)); - ASSERT_EQ(spatial_profile.profiles_size(), 0); - } - } -}; - -TEST_F(LineSpatialProfileTest, FitsLineProfile) { - std::string image_path = FileFinder::FitsImagePath("noise_3d.fits"); - std::vector endpoints = {0.0, 0.0, 9.0, 9.0}; - int start(0), end(0), mip(0), width(3); - std::vector spatial_reqs = {Message::SpatialConfig("x", start, end, mip, width)}; - - CARTA::SpatialProfileData spatial_profile; - bool ok = GetLineProfiles(image_path, endpoints, spatial_reqs, spatial_profile); - - ASSERT_TRUE(ok); - ASSERT_EQ(spatial_profile.profiles_size(), 1); -} - -TEST_F(LineSpatialProfileTest, Hdf5LineProfile) { - std::string image_path = FileFinder::Hdf5ImagePath("noise_10px_10px.hdf5"); - std::vector endpoints = {0.0, 0.0, 9.0, 9.0}; - int start(0), end(0), mip(0), width(3); - std::vector spatial_reqs = {Message::SpatialConfig("x", start, end, mip, width)}; - - CARTA::SpatialProfileData spatial_profile; - bool ok = GetLineProfiles(image_path, endpoints, spatial_reqs, spatial_profile); - - ASSERT_TRUE(ok); - ASSERT_EQ(spatial_profile.profiles_size(), 1); -} - -TEST_F(LineSpatialProfileTest, FitsHorizontalCutProfile) { - std::string image_path = FileFinder::FitsImagePath("noise_3d.fits"); - std::vector endpoints = {9.0, 5.0, 1.0, 5.0}; // Set line region at y=5 - int start(0), end(0), mip(0), width(1); - std::vector spatial_reqs = {Message::SpatialConfig("x", start, end, mip, width)}; - - CARTA::SpatialProfileData spatial_profile; - bool ok = GetLineProfiles(image_path, endpoints, spatial_reqs, spatial_profile); - - ASSERT_TRUE(ok); - ASSERT_EQ(spatial_profile.profiles_size(), 1); - - // Profile data - auto profile = spatial_profile.profiles(0); - std::vector profile_data = ProfileValues(profile); - EXPECT_EQ(profile_data.size(), 9); - - // Read image data slice for first channel - FitsDataReader reader(image_path); - auto image_data = reader.ReadRegion({1, 5, 0}, {10, 6, 1}); - - // Profile data width=1 of horizontal line is same as slice - CmpVectors(profile_data, image_data); -} - -TEST_F(LineSpatialProfileTest, FitsVerticalCutProfile) { - std::string image_path = FileFinder::FitsImagePath("noise_3d.fits"); - std::vector endpoints = {5.0, 9.0, 5.0, 1.0}; // Set line region at x=5 - int start(0), end(0), mip(0), width(1); - std::vector spatial_reqs = {Message::SpatialConfig("y", start, end, mip, width)}; - - CARTA::SpatialProfileData spatial_profile; - bool ok = GetLineProfiles(image_path, endpoints, spatial_reqs, spatial_profile); - - ASSERT_TRUE(ok); - ASSERT_EQ(spatial_profile.profiles_size(), 1); - - // Profile data - auto profile = spatial_profile.profiles(0); - std::vector profile_data = ProfileValues(profile); - EXPECT_EQ(profile_data.size(), 9); - - // Read image data slice for first channel - FitsDataReader reader(image_path); - auto image_data = reader.ReadRegion({5, 1, 0}, {6, 10, 1}); - - // Profile data width=1 of horizontal line is same as slice - CmpVectors(profile_data, image_data); -} - -TEST_F(LineSpatialProfileTest, FitsPolylineProfile) { - std::string image_path = FileFinder::FitsImagePath("noise_3d.fits"); - std::vector endpoints = {1.0, 1.0, 9.0, 1.0, 9.0, 5.0}; - int start(0), end(0), mip(0), width(1); - std::vector spatial_reqs = {Message::SpatialConfig("x", start, end, mip, width)}; - std::vector spatial_profiles; - - CARTA::SpatialProfileData spatial_profile; - bool ok = GetLineProfiles(image_path, endpoints, spatial_reqs, spatial_profile); - ASSERT_TRUE(ok); - ASSERT_EQ(spatial_profile.profiles_size(), 1); - - // Profile data - auto profile = spatial_profile.profiles(0); - std::vector profile_data = ProfileValues(profile); - EXPECT_EQ(profile_data.size(), 13); - - // Read image data slice for first channel - FitsDataReader reader(image_path); - auto line0_data = reader.ReadRegion({1, 1, 0}, {10, 2, 1}); - // Trim line 1: [9, 1] already covered by line 0 - auto line1_data = reader.ReadRegion({9, 2, 0}, {10, 6, 1}); - auto image_data = line0_data; - for (size_t i = 0; i < line1_data.size(); ++i) { - image_data.push_back(line1_data[i]); - } - - // Profile data width=1 of polyline is same as slices - CmpVectors(profile_data, image_data); -} - -TEST_F(LineSpatialProfileTest, AveragingWidthRange) { - TestAveragingWidthRange(0, false); - TestAveragingWidthRange(1, true); - TestAveragingWidthRange(20, true); - TestAveragingWidthRange(21, false); -} - -TEST_F(LineSpatialProfileTest, FitsAnnotationLineProfile) { - std::string image_path = FileFinder::FitsImagePath("noise_3d.fits"); - std::vector endpoints = {0.0, 0.0, 9.0, 9.0}; - int start(0), end(0), mip(0), width(3); - std::vector spatial_reqs = {Message::SpatialConfig("x", start, end, mip, width)}; - - CARTA::SpatialProfileData spatial_profile; - bool is_annotation(true); - bool ok = GetLineProfiles(image_path, endpoints, spatial_reqs, spatial_profile, is_annotation); - - ASSERT_FALSE(ok); -} diff --git a/test/TestMain.cc b/test/TestMain.cc index 9332e79d9..668fff30a 100644 --- a/test/TestMain.cc +++ b/test/TestMain.cc @@ -1,5 +1,5 @@ /* This file is part of the CARTA Image Viewer: https://github.com/CARTAvis/carta-backend - Copyright 2018-2022 Academia Sinica Institute of Astronomy and Astrophysics (ASIAA), + Copyright 2018- Academia Sinica Institute of Astronomy and Astrophysics (ASIAA), Associated Universities, Inc. (AUI) and the Inter-University Institute for Data Intensive Astronomy (IDIA) SPDX-License-Identifier: GPL-3.0-or-later */ @@ -12,6 +12,7 @@ #include "CommonTestUtilities.h" #include "Logger/Logger.h" +#include "Main/ProgramSettings.h" #include "ThreadingManager/ThreadingManager.h" #define TASK_THREAD_COUNT 3 @@ -22,10 +23,6 @@ int main(int argc, char** argv) { testing::AddGlobalTestEnvironment(new CartaEnvironment()); // Set global environment - int verbosity(0); - bool no_log(true); - bool log_performance(false); - bool log_protocol_messages(false); int omp_threads(omp_get_num_procs()); cxxopts::Options options("carta-icd-test", "CARTA ICD test"); @@ -34,7 +31,7 @@ int main(int argc, char** argv) { options.add_options() ("h,help", "print usage") ("verbosity", "display verbose logging from this level", - cxxopts::value()->default_value(std::to_string(verbosity)), "") + cxxopts::value()->default_value(std::to_string(0)), "") ("no_log", "do not log output to a log file", cxxopts::value()->default_value("true")) ("log_performance", "enable performance debug logs", cxxopts::value()->default_value("false")) ("log_protocol_messages", "enable protocol message debug logs", cxxopts::value()->default_value("false")) @@ -49,10 +46,11 @@ int main(int argc, char** argv) { return 0; } - verbosity = result["verbosity"].as(); - no_log = result["no_log"].as(); - log_performance = result["log_performance"].as(); - log_protocol_messages = result["log_protocol_messages"].as(); + auto& settings = ProgramSettings::GetInstance(); + settings.verbosity = result["verbosity"].as(); + settings.no_log = result["no_log"].as(); + settings.log_performance = result["log_performance"].as(); + settings.log_protocol_messages = result["log_protocol_messages"].as(); omp_threads = result["omp_threads"].as(); if (omp_threads < 0) { @@ -62,8 +60,8 @@ int main(int argc, char** argv) { carta::ThreadManager::StartEventHandlingThreads(TASK_THREAD_COUNT); carta::ThreadManager::SetThreadLimit(omp_threads); - fs::path user_directory = fs::path(getenv("HOME")) / CARTA_USER_FOLDER_PREFIX; - carta::logger::InitLogger(no_log, verbosity, log_performance, log_protocol_messages, user_directory); + ProgramSettings::GetInstance().user_directory = fs::path(getenv("HOME")) / CARTA_USER_FOLDER_PREFIX; + carta::logger::InitLogger(); int run_all_tests = RUN_ALL_TESTS(); diff --git a/test/TestMoment.cc b/test/TestMoment.cc index 7db82f79a..0082f2058 100644 --- a/test/TestMoment.cc +++ b/test/TestMoment.cc @@ -1,5 +1,5 @@ /* This file is part of the CARTA Image Viewer: https://github.com/CARTAvis/carta-backend - Copyright 2018-2022 Academia Sinica Institute of Astronomy and Astrophysics (ASIAA), + Copyright 2018- Academia Sinica Institute of Astronomy and Astrophysics (ASIAA), Associated Universities, Inc. (AUI) and the Inter-University Institute for Data Intensive Astronomy (IDIA) SPDX-License-Identifier: GPL-3.0-or-later */ diff --git a/test/TestNormalizedUnits.cc b/test/TestNormalizedUnits.cc index 4658e3533..86c528fa9 100644 --- a/test/TestNormalizedUnits.cc +++ b/test/TestNormalizedUnits.cc @@ -1,5 +1,5 @@ /* This file is part of the CARTA Image Viewer: https://github.com/CARTAvis/carta-backend - Copyright 2018-2022 Academia Sinica Institute of Astronomy and Astrophysics (ASIAA), + Copyright 2018- Academia Sinica Institute of Astronomy and Astrophysics (ASIAA), Associated Universities, Inc. (AUI) and the Inter-University Institute for Data Intensive Astronomy (IDIA) SPDX-License-Identifier: GPL-3.0-or-later */ diff --git a/test/TestProgramSettings.cc b/test/TestProgramSettings.cc index a179630f7..a740f06f5 100644 --- a/test/TestProgramSettings.cc +++ b/test/TestProgramSettings.cc @@ -1,5 +1,5 @@ /* This file is part of the CARTA Image Viewer: https://github.com/CARTAvis/carta-backend - Copyright 2018-2022 Academia Sinica Institute of Astronomy and Astrophysics (ASIAA), + Copyright 2018- Academia Sinica Institute of Astronomy and Astrophysics (ASIAA), Associated Universities, Inc. (AUI) and the Inter-University Institute for Data Intensive Astronomy (IDIA) SPDX-License-Identifier: GPL-3.0-or-later */ @@ -55,6 +55,58 @@ class ProgramSettingsTest : public ::testing::Test, public FileFinder { fs::current_path(working_directory); } + static void CheckConsistency(const ProgramSettings& p1, const ProgramSettings& p2) { + EXPECT_TRUE(p1.version == p2.version); + EXPECT_TRUE(p1.help == p2.help); + if (!p1.port.empty() || !p2.port.empty()) { + EXPECT_TRUE(p1.port == p2.port); + } + EXPECT_TRUE(p1.omp_thread_count == p2.omp_thread_count); + EXPECT_TRUE(p1.event_thread_count == p2.event_thread_count); + EXPECT_TRUE(p1.top_level_folder == p2.top_level_folder); + EXPECT_TRUE(p1.starting_folder == p2.starting_folder); + EXPECT_TRUE(p1.host == p2.host); + if (!p1.files.empty() || !p2.files.empty()) { + EXPECT_TRUE(p1.files == p2.files); + } + if (!p1.file_paths.empty() || !p2.file_paths.empty()) { + EXPECT_TRUE(p1.file_paths == p2.file_paths); + } + EXPECT_TRUE(p1.frontend_folder == p2.frontend_folder); + EXPECT_TRUE(p1.no_http == p2.no_http); + EXPECT_TRUE(p1.no_frontend == p2.no_frontend); + EXPECT_TRUE(p1.no_database == p2.no_database); + EXPECT_TRUE(p1.no_runtime_config == p2.no_runtime_config); + EXPECT_TRUE(p1.debug_no_auth == p2.debug_no_auth); + EXPECT_TRUE(p1.no_browser == p2.no_browser); + EXPECT_TRUE(p1.no_log == p2.no_log); + EXPECT_TRUE(p1.log_performance == p2.log_performance); + EXPECT_TRUE(p1.log_protocol_messages == p2.log_protocol_messages); + EXPECT_TRUE(p1.verbosity == p2.verbosity); + EXPECT_TRUE(p1.wait_time == p2.wait_time); + EXPECT_TRUE(p1.init_wait_time == p2.init_wait_time); + EXPECT_TRUE(p1.idle_session_wait_time == p2.idle_session_wait_time); + EXPECT_TRUE(p1.read_only_mode == p2.read_only_mode); + EXPECT_TRUE(p1.enable_scripting == p2.enable_scripting); + EXPECT_TRUE(p1.controller_deployment == p2.controller_deployment); + EXPECT_TRUE(p1.browser == p2.browser); + EXPECT_TRUE(p1.no_user_config == p2.no_user_config); + EXPECT_TRUE(p1.no_system_config == p2.no_system_config); + if (!p1.command_line_settings.empty() || !p2.command_line_settings.empty()) { + EXPECT_TRUE(p1.command_line_settings == p2.command_line_settings); + } + EXPECT_TRUE(p1.system_settings_json_exists == p2.system_settings_json_exists); + EXPECT_TRUE(p1.user_settings_json_exists == p2.user_settings_json_exists); + EXPECT_TRUE(p1.user_directory == p2.user_directory); + if (!p1.warning_msgs.empty() || !p2.warning_msgs.empty()) { + EXPECT_TRUE(p1.warning_msgs == p2.warning_msgs); + } + std::vector debug_msgs; + if (!p1.debug_msgs.empty() || !p2.debug_msgs.empty()) { + EXPECT_TRUE(p1.debug_msgs == p2.debug_msgs); + } + } + private: fs::path working_directory; }; @@ -86,9 +138,9 @@ TEST_F(ProgramSettingsTest, DefaultConstructor) { TEST_F(ProgramSettingsTest, EmptyArugments) { auto settings = SettingsFromVector({"carta_backend"}); - EXPECT_TRUE(settings == default_settings); + CheckConsistency(settings, default_settings); settings = SettingsFromVector({"carta_backend", ""}); - EXPECT_TRUE(settings == default_settings); + CheckConsistency(settings, default_settings); ASSERT_THROW(settings = SettingsFromVector({"carta_backend", "--top_level_folder"}), cxxopts::OptionException); } diff --git a/test/TestPvGenerator.cc b/test/TestPvGenerator.cc index a0fc89cc8..0e7bcb9fb 100644 --- a/test/TestPvGenerator.cc +++ b/test/TestPvGenerator.cc @@ -1,5 +1,5 @@ /* This file is part of the CARTA Image Viewer: https://github.com/CARTAvis/carta-backend - Copyright 2018-2022 Academia Sinica Institute of Astronomy and Astrophysics (ASIAA), + Copyright 2018- Academia Sinica Institute of Astronomy and Astrophysics (ASIAA), Associated Universities, Inc. (AUI) and the Inter-University Institute for Data Intensive Astronomy (IDIA) SPDX-License-Identifier: GPL-3.0-or-later */ @@ -39,7 +39,7 @@ class PvGeneratorTest : public ::testing::Test, public ImageGenerator { } static void TestAveragingWidthRange(int width, bool expected_width_range) { - auto image_path = TestRoot() / "data/images/fits/noise_3d.fits"; // 10x10x10 image + auto image_path = FileFinder::FitsImagePath("noise_3d.fits"); // 10x10x10 image std::shared_ptr loader(carta::FileLoader::GetLoader(image_path)); std::shared_ptr frame(new Frame(0, loader, "0")); carta::RegionHandler region_handler; @@ -69,7 +69,7 @@ class PvGeneratorTest : public ::testing::Test, public ImageGenerator { }; TEST_F(PvGeneratorTest, FitsPvImage) { - auto image_path = TestRoot() / "data/images/fits/noise_3d.fits"; // 10x10x10 image + auto image_path = FileFinder::FitsImagePath("noise_3d.fits"); // 10x10x10 image std::shared_ptr loader(carta::FileLoader::GetLoader(image_path)); std::shared_ptr frame(new Frame(0, loader, "0")); @@ -139,7 +139,7 @@ TEST_F(PvGeneratorTest, FitsPvImage) { } TEST_F(PvGeneratorTest, FitsPvImageHorizontalCut) { - auto image_path = TestRoot() / "data/images/fits/noise_3d.fits"; // 10x10x10 image + auto image_path = FileFinder::FitsImagePath("noise_3d.fits"); // 10x10x10 image std::shared_ptr loader(carta::FileLoader::GetLoader(image_path)); std::shared_ptr frame(new Frame(0, loader, "0")); @@ -208,7 +208,7 @@ TEST_F(PvGeneratorTest, FitsPvImageHorizontalCut) { EXPECT_EQ(pv_data.shape()(1), frame->Depth()); // Read image data slice - FitsDataReader reader(image_path.string()); + FitsDataReader reader(image_path); auto image_data = reader.ReadRegion({1, 5, 0}, {10, 6, 10}); EXPECT_EQ(pv_data.size(), image_data.size()); @@ -216,7 +216,7 @@ TEST_F(PvGeneratorTest, FitsPvImageHorizontalCut) { } TEST_F(PvGeneratorTest, FitsPvImageVerticalCut) { - auto image_path = TestRoot() / "data/images/fits/noise_3d.fits"; // 10x10x10 image + auto image_path = FileFinder::FitsImagePath("noise_3d.fits"); // 10x10x10 image std::shared_ptr loader(carta::FileLoader::GetLoader(image_path)); std::shared_ptr frame(new Frame(0, loader, "0")); @@ -285,7 +285,7 @@ TEST_F(PvGeneratorTest, FitsPvImageVerticalCut) { EXPECT_EQ(pv_data.shape()(1), frame->Depth()); // Read image data slice - FitsDataReader reader(image_path.string()); + FitsDataReader reader(image_path); auto image_data = reader.ReadRegion({5, 1, 0}, {6, 10, 10}); EXPECT_EQ(pv_data.size(), image_data.size()); @@ -293,7 +293,6 @@ TEST_F(PvGeneratorTest, FitsPvImageVerticalCut) { } TEST_F(PvGeneratorTest, TestNoSpectralAxis) { - auto image_path = TestRoot() / "data/images/hdf5/noise_10px_10px.hdf5"; auto path_string = GeneratedHdf5ImagePath("10 10 10"); std::shared_ptr loader(carta::FileLoader::GetLoader(path_string)); std::shared_ptr frame(new Frame(0, loader, "0")); @@ -327,7 +326,7 @@ TEST_F(PvGeneratorTest, AveragingWidthRange) { TEST_F(PvGeneratorTest, PvImageSpectralRange) { // FITS - auto image_path = TestRoot() / "data/images/fits/noise_3d.fits"; // 10x10x10 image + auto image_path = FileFinder::FitsImagePath("noise_3d.fits"); // 10x10x10 image std::shared_ptr loader(carta::FileLoader::GetLoader(image_path)); std::shared_ptr frame(new Frame(0, loader, "0")); auto csys = frame->CoordinateSystem(); @@ -357,7 +356,7 @@ TEST_F(PvGeneratorTest, PvImageSpectralRange) { } TEST_F(PvGeneratorTest, PvImageReversedAxes) { - auto image_path = TestRoot() / "data/images/fits/noise_3d.fits"; // 10x10x10 image + auto image_path = FileFinder::FitsImagePath("noise_3d.fits"); // 10x10x10 image std::shared_ptr loader(carta::FileLoader::GetLoader(image_path)); std::shared_ptr frame(new Frame(0, loader, "0")); auto csys = frame->CoordinateSystem(); @@ -397,7 +396,7 @@ TEST_F(PvGeneratorTest, PvImageReversedAxes) { } TEST_F(PvGeneratorTest, PvImageKeep) { - auto image_path = TestRoot() / "data/images/fits/noise_3d.fits"; // 10x10x10 image + auto image_path = FileFinder::FitsImagePath("noise_3d.fits"); // 10x10x10 image std::shared_ptr loader(carta::FileLoader::GetLoader(image_path)); std::shared_ptr frame(new Frame(0, loader, "0")); auto csys = frame->CoordinateSystem(); @@ -419,7 +418,6 @@ TEST_F(PvGeneratorTest, PvImageKeep) { // Check PV image file_id and name int index(0); EXPECT_EQ(pv_response.success(), true); - EXPECT_EQ(pv_image.file_id, PV_ID_MULTIPLIER - index); EXPECT_TRUE(pv_image.name.find("pv.fits") != std::string::npos); // Request PV image, keeping the first @@ -431,7 +429,6 @@ TEST_F(PvGeneratorTest, PvImageKeep) { // Check PV image file_id and name index++; EXPECT_EQ(pv_response2.success(), true); - EXPECT_EQ(pv_image2.file_id, PV_ID_MULTIPLIER - index); EXPECT_TRUE(pv_image2.name.find("pv1.fits") != std::string::npos); // Request PV image, replace all and reset index @@ -443,12 +440,11 @@ TEST_F(PvGeneratorTest, PvImageKeep) { // Check PV image file_id and name index = 0; EXPECT_EQ(pv_response3.success(), true); - EXPECT_EQ(pv_image3.file_id, PV_ID_MULTIPLIER - index); EXPECT_TRUE(pv_image3.name.find("pv.fits") != std::string::npos); } TEST_F(PvGeneratorTest, FitsPvAnnotationLine) { - auto image_path = TestRoot() / "data/images/fits/noise_3d.fits"; // 10x10x10 image + auto image_path = FileFinder::FitsImagePath("noise_3d.fits"); // 10x10x10 image std::shared_ptr loader(carta::FileLoader::GetLoader(image_path)); std::shared_ptr frame(new Frame(0, loader, "0")); carta::RegionHandler region_handler; diff --git a/test/TestRegion.cc b/test/TestRegion.cc new file mode 100644 index 000000000..c2a6d7d0f --- /dev/null +++ b/test/TestRegion.cc @@ -0,0 +1,418 @@ +/* This file is part of the CARTA Image Viewer: https://github.com/CARTAvis/carta-backend + Copyright 2018- Academia Sinica Institute of Astronomy and Astrophysics (ASIAA), + Associated Universities, Inc. (AUI) and the Inter-University Institute for Data Intensive Astronomy (IDIA) + SPDX-License-Identifier: GPL-3.0-or-later +*/ + +#include +#include + +#include "CommonTestUtilities.h" +#include "ImageData/FileLoader.h" +#include "Region/Region.h" +#include "Region/RegionHandler.h" +#include "src/Frame/Frame.h" + +using namespace carta; + +class RegionTest : public ::testing::Test { +public: + static bool SetRegion(carta::RegionHandler& region_handler, int file_id, int& region_id, CARTA::RegionType type, + const std::vector& points, float rotation, std::shared_ptr csys) { + std::vector control_points; + for (auto i = 0; i < points.size(); i += 2) { + control_points.push_back(Message::Point(points[i], points[i + 1])); + } + + // Define RegionState and set region (region_id updated) + RegionState region_state(file_id, type, control_points, rotation); + return region_handler.SetRegion(region_id, region_state, csys); + } +}; + +TEST_F(RegionTest, TestSetUpdateRemoveRegion) { + std::string image_path = FileFinder::FitsImagePath("noise_3d.fits"); + std::shared_ptr loader(carta::FileLoader::GetLoader(image_path)); + std::shared_ptr frame(new Frame(0, loader, "0")); + + // Set region + carta::RegionHandler region_handler; + int file_id(0), region_id(-1); + CARTA::RegionType region_type = CARTA::RegionType::RECTANGLE; + std::vector points = {5.0, 5.0, 4.0, 3.0}; + float rotation(0.0); + auto csys = frame->CoordinateSystem(); + bool ok = SetRegion(region_handler, file_id, region_id, region_type, points, rotation, csys); + // RegionHandler checks + ASSERT_TRUE(ok); + ASSERT_FALSE(region_handler.IsPointRegion(region_id)); + ASSERT_FALSE(region_handler.IsLineRegion(region_id)); + ASSERT_TRUE(region_handler.IsClosedRegion(region_id)); + + // Region checks + auto region = region_handler.GetRegion(region_id); + ASSERT_TRUE(region); // shared_ptr + ASSERT_TRUE(region->IsValid()); + ASSERT_FALSE(region->IsPoint()); + ASSERT_FALSE(region->IsLineType()); + ASSERT_FALSE(region->IsAnnotation()); + ASSERT_FALSE(region->RegionChanged()); + ASSERT_TRUE(region->IsConnected()); + ASSERT_EQ(region->CoordinateSystem(), csys); + auto region_state = region->GetRegionState(); + ASSERT_FALSE(region_state.IsRotbox()); + + // Update region + rotation = 30.0; + ok = SetRegion(region_handler, file_id, region_id, region_type, points, rotation, csys); + ASSERT_TRUE(ok); + ASSERT_TRUE(region->IsValid()); + ASSERT_TRUE(region->RegionChanged()); + auto new_region_state = region->GetRegionState(); + ASSERT_FALSE(region_state == new_region_state); + ASSERT_TRUE(new_region_state.IsRotbox()); + + // Remove region and frame (not set, should not cause error) + region_handler.RemoveRegion(region_id); + auto no_region = region_handler.GetRegion(region_id); + ASSERT_FALSE(no_region); +} + +TEST_F(RegionTest, TestReferenceImageRectangleLCRegion) { + std::string image_path = FileFinder::FitsImagePath("noise_3d.fits"); + std::shared_ptr loader(carta::FileLoader::GetLoader(image_path)); + std::shared_ptr frame(new Frame(0, loader, "0")); + + // Set region + carta::RegionHandler region_handler; + int file_id(0), region_id(-1); + CARTA::RegionType region_type = CARTA::RegionType::RECTANGLE; + std::vector points = {5.0, 5.0, 4.0, 3.0}; + float rotation(0.0); + auto csys = frame->CoordinateSystem(); + bool ok = SetRegion(region_handler, file_id, region_id, region_type, points, rotation, csys); + ASSERT_TRUE(ok); + // Get Region as 3D LCRegion + auto region = region_handler.GetRegion(region_id); + ASSERT_TRUE(region); // shared_ptr + auto image_shape = frame->ImageShape(); + auto lc_region = region->GetImageRegion(file_id, csys, image_shape); + ASSERT_TRUE(lc_region); // shared_ptr + ASSERT_EQ(lc_region->ndim(), 2); + ASSERT_EQ(lc_region->latticeShape()(0), image_shape(0)); + ASSERT_EQ(lc_region->latticeShape()(1), image_shape(1)); + ASSERT_EQ(lc_region->shape(), casacore::IPosition(2, 5, 3)); +} + +TEST_F(RegionTest, TestReferenceImageRotboxLCRegion) { + std::string image_path = FileFinder::FitsImagePath("noise_3d.fits"); // 10x10x10 + std::shared_ptr loader(carta::FileLoader::GetLoader(image_path)); + std::shared_ptr frame(new Frame(0, loader, "0")); + + // Set region + carta::RegionHandler region_handler; + int file_id(0), region_id(-1); + CARTA::RegionType region_type = CARTA::RegionType::RECTANGLE; + std::vector points = {5.0, 5.0, 4.0, 3.0}; + float rotation(30.0); + auto csys = frame->CoordinateSystem(); + bool ok = SetRegion(region_handler, file_id, region_id, region_type, points, rotation, csys); + ASSERT_TRUE(ok); + + // Get Region as 3D LCRegion + auto region = region_handler.GetRegion(region_id); + ASSERT_TRUE(region); // shared_ptr + auto image_shape = frame->ImageShape(); + auto lc_region = region->GetImageRegion(file_id, csys, image_shape); + ASSERT_TRUE(lc_region); // shared_ptr + ASSERT_EQ(lc_region->ndim(), 2); + ASSERT_EQ(lc_region->latticeShape()(0), image_shape(0)); + ASSERT_EQ(lc_region->latticeShape()(1), image_shape(1)); + ASSERT_EQ(lc_region->shape(), casacore::IPosition(2, 5, 5)); +} + +TEST_F(RegionTest, TestReferenceImageEllipseLCRegion) { + std::string image_path = FileFinder::FitsImagePath("noise_3d.fits"); + std::shared_ptr loader(carta::FileLoader::GetLoader(image_path)); + std::shared_ptr frame(new Frame(0, loader, "0")); + + // Set region + carta::RegionHandler region_handler; + int file_id(0), region_id(-1); + CARTA::RegionType region_type = CARTA::RegionType::ELLIPSE; + std::vector points = {5.0, 5.0, 4.0, 3.0}; + float rotation(0.0); + auto csys = frame->CoordinateSystem(); + bool ok = SetRegion(region_handler, file_id, region_id, region_type, points, rotation, csys); + ASSERT_TRUE(ok); + + // Get Region as 3D LCRegion + auto region = region_handler.GetRegion(region_id); + ASSERT_TRUE(region); // shared_ptr + auto image_shape = frame->ImageShape(); + auto lc_region = region->GetImageRegion(file_id, csys, image_shape); + ASSERT_TRUE(lc_region); // shared_ptr + ASSERT_EQ(lc_region->ndim(), 2); + ASSERT_EQ(lc_region->latticeShape()(0), image_shape(0)); + ASSERT_EQ(lc_region->latticeShape()(1), image_shape(1)); + ASSERT_EQ(lc_region->shape(), casacore::IPosition(2, 7, 9)); +} + +TEST_F(RegionTest, TestReferenceImagePolygonLCRegion) { + std::string image_path = FileFinder::FitsImagePath("noise_3d.fits"); + std::shared_ptr loader(carta::FileLoader::GetLoader(image_path)); + std::shared_ptr frame(new Frame(0, loader, "0")); + + // Set region + carta::RegionHandler region_handler; + int file_id(0), region_id(-1); + CARTA::RegionType region_type = CARTA::RegionType::POLYGON; + std::vector points = {5.0, 5.0, 4.0, 3.0, 1.0, 6.0, 3.0, 8.0}; + float rotation(0.0); + auto csys = frame->CoordinateSystem(); + bool ok = SetRegion(region_handler, file_id, region_id, region_type, points, rotation, csys); + ASSERT_TRUE(ok); + + // Get Region as 3D LCRegion + auto region = region_handler.GetRegion(region_id); + ASSERT_TRUE(region); // shared_ptr + auto image_shape = frame->ImageShape(); + auto lc_region = region->GetImageRegion(file_id, csys, image_shape); + ASSERT_TRUE(lc_region); // shared_ptr + ASSERT_EQ(lc_region->ndim(), 2); + ASSERT_EQ(lc_region->latticeShape()(0), image_shape(0)); + ASSERT_EQ(lc_region->latticeShape()(1), image_shape(1)); + ASSERT_EQ(lc_region->shape(), casacore::IPosition(2, 5, 6)); +} + +TEST_F(RegionTest, TestReferenceImagePointRecord) { + std::string image_path = FileFinder::FitsImagePath("noise_3d.fits"); + std::shared_ptr loader(carta::FileLoader::GetLoader(image_path)); + std::shared_ptr frame(new Frame(0, loader, "0")); + + // Set region + carta::RegionHandler region_handler; + int file_id(0), region_id(-1); + CARTA::RegionType region_type = CARTA::RegionType::POINT; + std::vector points = {4.0, 2.0}; + float rotation(0.0); + auto csys = frame->CoordinateSystem(); + bool ok = SetRegion(region_handler, file_id, region_id, region_type, points, rotation, csys); + ASSERT_TRUE(ok); + + // Get Region as casacore::Record + auto region = region_handler.GetRegion(region_id); + ASSERT_TRUE(region); // shared_ptr + auto image_shape = frame->ImageShape(); + auto region_record = region->GetImageRegionRecord(file_id, csys, image_shape); + ASSERT_GT(region_record.nfields(), 0); + ASSERT_EQ(region_record.asInt("isRegion"), 1); + ASSERT_EQ(region_record.asString("name"), "LCBox"); + ASSERT_FALSE(region_record.asBool("oneRel")); + auto blc = region_record.asArrayFloat("blc").tovector(); + auto trc = region_record.asArrayFloat("trc").tovector(); + ASSERT_EQ(blc.size(), 3); + ASSERT_EQ(trc.size(), 3); + ASSERT_FLOAT_EQ(blc[0], points[0]); + ASSERT_FLOAT_EQ(blc[1], points[1]); + ASSERT_FLOAT_EQ(blc[2], 0.0); // min channel + ASSERT_FLOAT_EQ(trc[0], points[0]); + ASSERT_FLOAT_EQ(trc[1], points[1]); + ASSERT_FLOAT_EQ(trc[2], 9.0); // max channel +} + +TEST_F(RegionTest, TestReferenceImageLineRecord) { + std::string image_path = FileFinder::FitsImagePath("noise_3d.fits"); + std::shared_ptr loader(carta::FileLoader::GetLoader(image_path)); + std::shared_ptr frame(new Frame(0, loader, "0")); + + // Set region + carta::RegionHandler region_handler; + int file_id(0), region_id(-1); + CARTA::RegionType region_type = CARTA::RegionType::LINE; + std::vector points = {5.0, 5.0, 4.0, 3.0}; + float rotation(0.0); + auto csys = frame->CoordinateSystem(); + bool ok = SetRegion(region_handler, file_id, region_id, region_type, points, rotation, csys); + ASSERT_TRUE(ok); + + // Get Region as casacore::Record (from control points, no casacore Line region) + auto region = region_handler.GetRegion(region_id); + ASSERT_TRUE(region); // shared_ptr + auto image_shape = frame->ImageShape(); + auto region_record = region->GetImageRegionRecord(file_id, csys, image_shape); + ASSERT_GT(region_record.nfields(), 0); + ASSERT_EQ(region_record.asInt("isRegion"), 1); + ASSERT_EQ(region_record.asString("name"), "line"); + ASSERT_FALSE(region_record.asBool("oneRel")); + auto x = region_record.asArrayFloat("x").tovector(); + auto y = region_record.asArrayFloat("y").tovector(); + ASSERT_EQ(x.size(), 2); + ASSERT_EQ(y.size(), 2); + ASSERT_FLOAT_EQ(x[0], points[0]); + ASSERT_FLOAT_EQ(x[1], points[2]); + ASSERT_FLOAT_EQ(y[0], points[1]); + ASSERT_FLOAT_EQ(y[1], points[3]); +} + +TEST_F(RegionTest, TestReferenceImageRectangleRecord) { + std::string image_path = FileFinder::FitsImagePath("noise_3d.fits"); + std::shared_ptr loader(carta::FileLoader::GetLoader(image_path)); + std::shared_ptr frame(new Frame(0, loader, "0")); + + // Set region + carta::RegionHandler region_handler; + int file_id(0), region_id(-1); + CARTA::RegionType region_type = CARTA::RegionType::RECTANGLE; + std::vector points = {5.0, 5.0, 4.0, 3.0}; + float rotation(0.0); + auto csys = frame->CoordinateSystem(); + bool ok = SetRegion(region_handler, file_id, region_id, region_type, points, rotation, csys); + ASSERT_TRUE(ok); + + // Get Region as casacore::Record + auto region = region_handler.GetRegion(region_id); + ASSERT_TRUE(region); // shared_ptr + auto image_shape = frame->ImageShape(); + auto region_record = region->GetImageRegionRecord(file_id, csys, image_shape); + ASSERT_GT(region_record.nfields(), 0); + ASSERT_EQ(region_record.asInt("isRegion"), 1); + ASSERT_EQ(region_record.asString("name"), "LCPolygon"); // box corners set as polygon + ASSERT_FALSE(region_record.asBool("oneRel")); + // x, y order is [blc, brc, trc, tlc, blc] + auto x = region_record.asArrayDouble("x").tovector(); + auto y = region_record.asArrayDouble("y").tovector(); + float left_x = points[0] - (points[2] / 2.0); + float right_x = points[0] + (points[2] / 2.0); + float bottom_y = points[1] - (points[3] / 2.0); + float top_y = points[1] + (points[3] / 2.0); + ASSERT_EQ(x.size(), 5); // casacore repeats first point to close polygon + ASSERT_EQ(y.size(), 5); // casacore repeats first point to close polygon + ASSERT_FLOAT_EQ(x[0], left_x); + ASSERT_FLOAT_EQ(x[1], right_x); + ASSERT_FLOAT_EQ(x[2], right_x); + ASSERT_FLOAT_EQ(x[3], left_x); + ASSERT_FLOAT_EQ(x[4], left_x); + ASSERT_FLOAT_EQ(y[0], bottom_y); + ASSERT_FLOAT_EQ(y[1], bottom_y); + ASSERT_FLOAT_EQ(y[2], top_y); + ASSERT_FLOAT_EQ(y[3], top_y); + ASSERT_FLOAT_EQ(y[4], bottom_y); +} + +TEST_F(RegionTest, TestReferenceImageRotboxRecord) { + // Record is for unrotated rectangle; RegionState used for angle in export + std::string image_path = FileFinder::FitsImagePath("noise_3d.fits"); // 10x10x10 + std::shared_ptr loader(carta::FileLoader::GetLoader(image_path)); + std::shared_ptr frame(new Frame(0, loader, "0")); + + // Set region + carta::RegionHandler region_handler; + int file_id(0), region_id(-1); + CARTA::RegionType region_type = CARTA::RegionType::RECTANGLE; + std::vector points = {5.0, 5.0, 4.0, 3.0}; + float rotation(30.0); + auto csys = frame->CoordinateSystem(); + bool ok = SetRegion(region_handler, file_id, region_id, region_type, points, rotation, csys); + ASSERT_TRUE(ok); + + // Get Region as casacore::Record + auto region = region_handler.GetRegion(region_id); + ASSERT_TRUE(region); // shared_ptr + auto image_shape = frame->ImageShape(); + auto region_record = region->GetImageRegionRecord(file_id, csys, image_shape); + ASSERT_GT(region_record.nfields(), 0); + ASSERT_EQ(region_record.asInt("isRegion"), 1); + ASSERT_EQ(region_record.asString("name"), "LCPolygon"); // box corners set as polygon + ASSERT_FALSE(region_record.asBool("oneRel")); + // x, y order is [blc, brc, trc, tlc, blc] + auto x = region_record.asArrayDouble("x").tovector(); + auto y = region_record.asArrayDouble("y").tovector(); + float left_x = points[0] - (points[2] / 2.0); + float right_x = points[0] + (points[2] / 2.0); + float bottom_y = points[1] - (points[3] / 2.0); + float top_y = points[1] + (points[3] / 2.0); + ASSERT_EQ(x.size(), 5); // repeats first point to close polygon + ASSERT_EQ(y.size(), 5); // repeats first point to close polygon + ASSERT_FLOAT_EQ(x[0], left_x); + ASSERT_FLOAT_EQ(x[1], right_x); + ASSERT_FLOAT_EQ(x[2], right_x); + ASSERT_FLOAT_EQ(x[3], left_x); + ASSERT_FLOAT_EQ(x[4], left_x); + ASSERT_FLOAT_EQ(y[0], bottom_y); + ASSERT_FLOAT_EQ(y[1], bottom_y); + ASSERT_FLOAT_EQ(y[2], top_y); + ASSERT_FLOAT_EQ(y[3], top_y); + ASSERT_FLOAT_EQ(y[4], bottom_y); +} + +TEST_F(RegionTest, TestReferenceImageEllipseRecord) { + std::string image_path = FileFinder::FitsImagePath("noise_3d.fits"); + std::shared_ptr loader(carta::FileLoader::GetLoader(image_path)); + std::shared_ptr frame(new Frame(0, loader, "0")); + + // Set region + carta::RegionHandler region_handler; + int file_id(0), region_id(-1); + CARTA::RegionType region_type = CARTA::RegionType::ELLIPSE; + std::vector points = {5.0, 5.0, 4.0, 3.0}; + float rotation(0.0); + auto csys = frame->CoordinateSystem(); + bool ok = SetRegion(region_handler, file_id, region_id, region_type, points, rotation, csys); + ASSERT_TRUE(ok); + + // Get Region as casacore::Record + auto region = region_handler.GetRegion(region_id); + ASSERT_TRUE(region); // shared_ptr + auto image_shape = frame->ImageShape(); + auto region_record = region->GetImageRegionRecord(file_id, csys, image_shape); + ASSERT_GT(region_record.nfields(), 0); + ASSERT_EQ(region_record.asInt("isRegion"), 1); + ASSERT_EQ(region_record.asString("name"), "LCEllipsoid"); + ASSERT_FALSE(region_record.asBool("oneRel")); + auto center = region_record.asArrayFloat("center").tovector(); + ASSERT_FLOAT_EQ(center[0], points[0]); + ASSERT_FLOAT_EQ(center[1], points[1]); + auto radii = region_record.asArrayFloat("radii").tovector(); + ASSERT_FLOAT_EQ(radii[0], points[2]); + ASSERT_FLOAT_EQ(radii[1], points[3]); +} + +TEST_F(RegionTest, TestReferenceImagePolygonRecord) { + std::string image_path = FileFinder::FitsImagePath("noise_3d.fits"); + std::shared_ptr loader(carta::FileLoader::GetLoader(image_path)); + std::shared_ptr frame(new Frame(0, loader, "0")); + + // Set region + carta::RegionHandler region_handler; + int file_id(0), region_id(-1); + CARTA::RegionType region_type = CARTA::RegionType::POLYGON; + std::vector points = {5.0, 5.0, 4.0, 3.0, 1.0, 6.0, 3.0, 8.0}; // 4 points + float rotation(0.0); + auto csys = frame->CoordinateSystem(); + bool ok = SetRegion(region_handler, file_id, region_id, region_type, points, rotation, csys); + ASSERT_TRUE(ok); + + // Get Region as casacore::Record + auto region = region_handler.GetRegion(region_id); + ASSERT_TRUE(region); // shared_ptr + auto image_shape = frame->ImageShape(); + auto region_record = region->GetImageRegionRecord(file_id, csys, image_shape); + ASSERT_GT(region_record.nfields(), 0); + ASSERT_EQ(region_record.asInt("isRegion"), 1); + ASSERT_EQ(region_record.asString("name"), "LCPolygon"); + ASSERT_FALSE(region_record.asBool("oneRel")); + // x, y points + auto x = region_record.asArrayFloat("x").tovector(); + auto y = region_record.asArrayFloat("y").tovector(); + ASSERT_EQ(x.size(), 5); // casacore repeats first point to close polygon + ASSERT_EQ(y.size(), 5); // casacore repeats first point to close polygon + ASSERT_FLOAT_EQ(x[0], points[0]); + ASSERT_FLOAT_EQ(x[1], points[2]); + ASSERT_FLOAT_EQ(x[2], points[4]); + ASSERT_FLOAT_EQ(x[3], points[6]); + ASSERT_FLOAT_EQ(y[0], points[1]); + ASSERT_FLOAT_EQ(y[1], points[3]); + ASSERT_FLOAT_EQ(y[2], points[5]); + ASSERT_FLOAT_EQ(y[3], points[7]); +} diff --git a/test/TestRegionHistogram.cc b/test/TestRegionHistogram.cc new file mode 100644 index 000000000..3ac57d5b1 --- /dev/null +++ b/test/TestRegionHistogram.cc @@ -0,0 +1,94 @@ +/* This file is part of the CARTA Image Viewer: https://github.com/CARTAvis/carta-backend + Copyright 2018- Academia Sinica Institute of Astronomy and Astrophysics (ASIAA), + Associated Universities, Inc. (AUI) and the Inter-University Institute for Data Intensive Astronomy (IDIA) + SPDX-License-Identifier: GPL-3.0-or-later +*/ + +#include +#include + +#include "CommonTestUtilities.h" +#include "ImageData/FileLoader.h" +#include "Region/Region.h" +#include "Region/RegionHandler.h" +#include "src/Frame/Frame.h" + +using namespace carta; + +class RegionHistogramTest : public ::testing::Test { +public: + static bool SetRegion(carta::RegionHandler& region_handler, int file_id, int& region_id, const std::vector& points, + std::shared_ptr csys, bool is_annotation) { + std::vector control_points; + for (auto i = 0; i < points.size(); i += 2) { + control_points.push_back(Message::Point(points[i], points[i + 1])); + } + + // Define RegionState for line region and set region (region_id updated) + auto npoints(control_points.size()); + CARTA::RegionType region_type = CARTA::RegionType::POLYGON; + if (is_annotation) { + region_type = CARTA::RegionType::ANNPOLYGON; + } + RegionState region_state(file_id, region_type, control_points, 0.0); + return region_handler.SetRegion(region_id, region_state, csys); + } + + static bool RegionHistogram(const std::string& image_path, const std::vector& endpoints, + CARTA::RegionHistogramData& region_histogram, bool is_annotation = false) { + std::shared_ptr loader(carta::FileLoader::GetLoader(image_path)); + std::shared_ptr frame(new Frame(0, loader, "0")); + carta::RegionHandler region_handler; + + // Set polygon region + int file_id(0), region_id(-1); + auto csys = frame->CoordinateSystem(); + if (!SetRegion(region_handler, file_id, region_id, endpoints, csys, is_annotation)) { + return false; + } + + // Set histogram requirements + auto histogram_req_message = Message::SetHistogramRequirements(file_id, region_id); + std::vector histogram_configs = { + histogram_req_message.histograms().begin(), histogram_req_message.histograms().end()}; + if (!region_handler.SetHistogramRequirements(region_id, file_id, frame, histogram_configs)) { + return false; + } + + // Get histogram + return region_handler.FillRegionHistogramData( + [&](CARTA::RegionHistogramData histogram_data) { region_histogram = histogram_data; }, region_id, file_id); + } +}; + +TEST_F(RegionHistogramTest, TestFitsRegionHistogram) { + std::string image_path = FileFinder::FitsImagePath("noise_3d.fits"); + std::vector endpoints = {1.0, 1.0, 1.0, 4.0, 4.0, 4.0, 4.0, 1.0}; + CARTA::RegionHistogramData histogram_data; + bool ok = RegionHistogram(image_path, endpoints, histogram_data); + + // Check histogram data fields + ASSERT_TRUE(ok); + ASSERT_EQ(histogram_data.file_id(), 0); + ASSERT_EQ(histogram_data.region_id(), 1); + ASSERT_EQ(histogram_data.channel(), 0); + ASSERT_EQ(histogram_data.stokes(), 0); + ASSERT_TRUE(histogram_data.has_histograms()); + ASSERT_EQ(histogram_data.progress(), 1.0); + ASSERT_TRUE(histogram_data.has_config()); + int expected_num_bins = sqrt(4 * 4); // region bounding box is 4x4 + ASSERT_EQ(histogram_data.histograms().num_bins(), expected_num_bins); + + FitsDataReader reader(image_path); + auto image_data = reader.ReadRegion({1, 1, 0}, {5, 5, 1}); + double expected_mean = std::accumulate(image_data.begin(), image_data.end(), 0.0) / image_data.size(); + ASSERT_DOUBLE_EQ(histogram_data.histograms().mean(), expected_mean); +} + +TEST_F(RegionHistogramTest, TestFitsAnnotationRegionHistogram) { + std::string image_path = FileFinder::FitsImagePath("noise_3d.fits"); + std::vector endpoints = {0.0, 0.0, 0.0, 3.0, 3.0, 3.0, 3.0, 0.0}; + CARTA::RegionHistogramData histogram_data; + bool ok = RegionHistogram(image_path, endpoints, histogram_data, true); + ASSERT_FALSE(ok); +} diff --git a/test/TestRegionImportExport.cc b/test/TestRegionImportExport.cc new file mode 100644 index 000000000..31bae0f9b --- /dev/null +++ b/test/TestRegionImportExport.cc @@ -0,0 +1,362 @@ +/* This file is part of the CARTA Image Viewer: https://github.com/CARTAvis/carta-backend + Copyright 2018- Academia Sinica Institute of Astronomy and Astrophysics (ASIAA), + Associated Universities, Inc. (AUI) and the Inter-University Institute for Data Intensive Astronomy (IDIA) + SPDX-License-Identifier: GPL-3.0-or-later +*/ + +#include +#include + +#include + +#include "CommonTestUtilities.h" +#include "ImageData/FileLoader.h" +#include "Region/Region.h" +#include "Region/RegionHandler.h" +#include "src/Frame/Frame.h" + +using namespace carta; + +class RegionImportExportTest : public ::testing::Test { +public: + static bool SetRegion(carta::RegionHandler& region_handler, int file_id, int& region_id, CARTA::RegionType type, + const std::vector& points, float rotation, std::shared_ptr csys) { + std::vector control_points; + for (auto i = 0; i < points.size(); i += 2) { + control_points.push_back(Message::Point(points[i], points[i + 1])); + } + + // Define RegionState and set region (region_id updated) + RegionState region_state(file_id, type, control_points, rotation); + return region_handler.SetRegion(region_id, region_state, csys); + } + + static int SetAllRegions(carta::RegionHandler& region_handler, int file_id, std::shared_ptr csys) { + std::vector point_points = {5.0, 5.0}; + std::vector line_rectangle_ellipse_points = {5.0, 5.0, 4.0, 3.0}; + std::vector circle_points = {5.0, 5.0, 3.0, 3.0}; + std::vector poly_points = {5.0, 5.0, 4.0, 3.0, 1.0, 6.0, 3.0, 8.0}; + std::unordered_map> region_points = {{CARTA::POINT, point_points}, + {CARTA::LINE, line_rectangle_ellipse_points}, {CARTA::POLYLINE, poly_points}, {CARTA::RECTANGLE, line_rectangle_ellipse_points}, + {CARTA::ELLIPSE, line_rectangle_ellipse_points}, {CARTA::POLYGON, poly_points}, {CARTA::ANNPOINT, point_points}, + {CARTA::ANNLINE, line_rectangle_ellipse_points}, {CARTA::ANNPOLYLINE, poly_points}, + {CARTA::ANNRECTANGLE, line_rectangle_ellipse_points}, {CARTA::ANNELLIPSE, line_rectangle_ellipse_points}, + {CARTA::ANNPOLYGON, poly_points}, {CARTA::ANNVECTOR, line_rectangle_ellipse_points}, + {CARTA::ANNRULER, line_rectangle_ellipse_points}, {CARTA::ANNTEXT, line_rectangle_ellipse_points}, + {CARTA::ANNCOMPASS, circle_points}}; + float rotation(0.0); + int num_regions(0); + + // Add all region types + int region_id(-1); + for (int i = 0; i < CARTA::RegionType_ARRAYSIZE; ++i) { + CARTA::RegionType type = static_cast(i); + if (type == CARTA::RegionType::ANNULUS) { + continue; + } + auto points = region_points[type]; + if (!SetRegion(region_handler, file_id, region_id, type, points, rotation, csys)) { + return 0; + } + num_regions++; + region_id = -1; + } + + // Add special region types: rotbox, circle (analytical and annotation) + rotation = 30.0; + if (!SetRegion(region_handler, file_id, region_id, CARTA::RECTANGLE, line_rectangle_ellipse_points, rotation, csys)) { + return 0; + } + num_regions++; + + region_id = -1; + if (!SetRegion(region_handler, file_id, region_id, CARTA::ANNRECTANGLE, line_rectangle_ellipse_points, rotation, csys)) { + return 0; + } + num_regions++; + + region_id = -1; + rotation = 0.0; + if (!SetRegion(region_handler, file_id, region_id, CARTA::ELLIPSE, circle_points, 0.0, csys)) { + return 0; + } + num_regions++; + + region_id = -1; + if (!SetRegion(region_handler, file_id, region_id, CARTA::ANNELLIPSE, circle_points, 0.0, csys)) { + return 0; + } + num_regions++; + + return num_regions; + } + + static CARTA::RegionStyle GetRegionStyle(CARTA::RegionType type) { + bool is_annotation = type > CARTA::POLYGON; + std::string color = is_annotation ? "#FFBA01" : "#2EE6D6"; + + CARTA::RegionStyle region_style; + region_style.set_color(color); + region_style.set_line_width(2); + + if (is_annotation) { + // Default fields set by frontend + auto annotation_style = region_style.mutable_annotation_style(); + if (type == CARTA::ANNPOINT) { + annotation_style->set_point_shape(CARTA::PointAnnotationShape::SQUARE); + annotation_style->set_point_width(6); + } else if (type == CARTA::ANNTEXT || type == CARTA::ANNCOMPASS || type == CARTA::ANNRULER) { + annotation_style->set_font("Helvetica"); + annotation_style->set_font_size(20); + annotation_style->set_font_style("Normal"); + if (type == CARTA::ANNTEXT) { + annotation_style->set_text_label0("Text"); + annotation_style->set_text_position(CARTA::TextAnnotationPosition::CENTER); + } else if (type == CARTA::ANNCOMPASS) { + annotation_style->set_coordinate_system("PIXEL"); + annotation_style->set_is_east_arrow(true); + annotation_style->set_is_north_arrow(true); + annotation_style->set_text_label0("N"); + annotation_style->set_text_label1("E"); + } else if (type == CARTA::ANNRULER) { + annotation_style->set_coordinate_system("PIXEL"); + } + } + } + return region_style; + } + + static std::string ConcatContents(const std::vector& string_vector) { + std::string one_string; + for (auto& item : string_vector) { + one_string.append(item + "\n"); + } + return one_string; + } +}; + +TEST_F(RegionImportExportTest, TestCrtfPixExportImport) { + // frame 0 + std::string image_path0 = FileFinder::FitsImagePath("noise_10px_10px.fits"); + std::shared_ptr loader0(carta::FileLoader::GetLoader(image_path0)); + std::shared_ptr frame0(new Frame(0, loader0, "0")); + // frame 1 + std::string image_path1 = FileFinder::Hdf5ImagePath("noise_10px_10px.hdf5"); + std::shared_ptr loader1(carta::FileLoader::GetLoader(image_path1)); + std::shared_ptr frame1(new Frame(0, loader1, "0")); + + // Set all region types in frame0 + carta::RegionHandler region_handler; + int file_id(0); + int num_regions = SetAllRegions(region_handler, file_id, frame0->CoordinateSystem()); + // All CARTA regions except ANNULUS (- 1) plus 2 rotbox and 2 circle (+ 4) + ASSERT_EQ(num_regions, CARTA::RegionType_ARRAYSIZE - 1 + 4); + + // Set RegionStyle map for export + std::map region_style_map; + for (int i = 0; i < num_regions; ++i) { + int region_id = i + 1; // region 0 is cursor + CARTA::RegionType region_type = region_handler.GetRegion(region_id)->GetRegionState().type; + CARTA::RegionStyle style = GetRegionStyle(region_type); + region_style_map[region_id] = style; + } + std::string filename; // do not export to file + + // Export all regions in frame0 (reference image) + CARTA::ExportRegionAck export_ack0; + region_handler.ExportRegion(file_id, frame0, CARTA::CRTF, CARTA::PIXEL, region_style_map, filename, export_ack0); + // Check that all regions were exported + ASSERT_EQ(export_ack0.contents_size(), num_regions + 2); // header, textbox for text + + // Import all regions in frame0 (reference image) + std::vector export_contents = {export_ack0.contents().begin(), export_ack0.contents().end()}; + auto contents_string = ConcatContents(export_contents); + bool file_is_filename(false); + CARTA::ImportRegionAck import_ack0; + region_handler.ImportRegion(file_id, frame0, CARTA::CRTF, contents_string, file_is_filename, import_ack0); + // Check that all regions were imported + ASSERT_EQ(import_ack0.regions_size(), num_regions); + + // Export all regions in frame1 (matched image) + file_id = 1; + CARTA::ExportRegionAck export_ack1; + region_handler.ExportRegion(file_id, frame1, CARTA::CRTF, CARTA::PIXEL, region_style_map, filename, export_ack1); + // Check that all regions were exported + ASSERT_EQ(export_ack1.contents_size(), num_regions + 2); // header, textbox for text + + // Import all regions in frame1 (matched image) + export_contents = {export_ack1.contents().begin(), export_ack1.contents().end()}; + contents_string = ConcatContents(export_contents); + CARTA::ImportRegionAck import_ack1; + region_handler.ImportRegion(file_id, frame1, CARTA::CRTF, contents_string, file_is_filename, import_ack1); + // Check that all regions were imported + ASSERT_EQ(import_ack1.regions_size(), num_regions); +} + +TEST_F(RegionImportExportTest, TestCrtfWorldExportImport) { + // frame 0 + std::string image_path0 = FileFinder::FitsImagePath("noise_10px_10px.fits"); + std::shared_ptr loader0(carta::FileLoader::GetLoader(image_path0)); + std::shared_ptr frame0(new Frame(0, loader0, "0")); + // frame 1 + std::string image_path1 = FileFinder::Hdf5ImagePath("noise_10px_10px.hdf5"); + std::shared_ptr loader1(carta::FileLoader::GetLoader(image_path1)); + std::shared_ptr frame1(new Frame(0, loader1, "0")); + + // Set all region types in frame0 + carta::RegionHandler region_handler; + int file_id(0); + int num_regions = SetAllRegions(region_handler, file_id, frame0->CoordinateSystem()); + // All CARTA regions except ANNULUS (- 1) plus 2 rotbox and 2 circle (+ 4) + ASSERT_EQ(num_regions, CARTA::RegionType_ARRAYSIZE - 1 + 4); + + // Export all regions in frame0 (reference image) + std::map region_style_map; + for (int i = 0; i < num_regions; ++i) { + int region_id = i + 1; // region 0 is cursor + CARTA::RegionType region_type = region_handler.GetRegion(region_id)->GetRegionState().type; + CARTA::RegionStyle style = GetRegionStyle(region_type); + region_style_map[region_id] = style; + } + std::string filename; // do not export to file + CARTA::ExportRegionAck export_ack0; + region_handler.ExportRegion(file_id, frame0, CARTA::CRTF, CARTA::WORLD, region_style_map, filename, export_ack0); + // Check that all regions were exported + ASSERT_EQ(export_ack0.contents_size(), num_regions + 2); // header, textbox for text + + // Import all regions in frame0 (reference image) + std::vector export_contents = {export_ack0.contents().begin(), export_ack0.contents().end()}; + auto contents_string = ConcatContents(export_contents); + bool file_is_filename(false); + CARTA::ImportRegionAck import_ack0; + region_handler.ImportRegion(file_id, frame0, CARTA::CRTF, contents_string, file_is_filename, import_ack0); + // Check that all regions were imported + ASSERT_EQ(import_ack0.regions_size(), num_regions); + + // Export all regions in frame1 (matched image) + file_id = 1; + CARTA::ExportRegionAck export_ack1; + region_handler.ExportRegion(file_id, frame1, CARTA::CRTF, CARTA::WORLD, region_style_map, filename, export_ack1); + // Check that all regions were exported + ASSERT_EQ(export_ack1.contents_size(), num_regions + 2); // header, textbox for text + + // Import all regions in frame1 (matched image) + export_contents = {export_ack1.contents().begin(), export_ack1.contents().end()}; + contents_string = ConcatContents(export_contents); + CARTA::ImportRegionAck import_ack1; + region_handler.ImportRegion(file_id, frame1, CARTA::CRTF, contents_string, file_is_filename, import_ack1); + // Check that all regions were imported + ASSERT_EQ(import_ack1.regions_size(), num_regions); +} + +TEST_F(RegionImportExportTest, TestDs9PixExportImport) { + // frame 0 + std::string image_path0 = FileFinder::FitsImagePath("noise_10px_10px.fits"); + std::shared_ptr loader0(carta::FileLoader::GetLoader(image_path0)); + std::shared_ptr frame0(new Frame(0, loader0, "0")); + // frame 1 + std::string image_path1 = FileFinder::Hdf5ImagePath("noise_10px_10px.hdf5"); + std::shared_ptr loader1(carta::FileLoader::GetLoader(image_path1)); + std::shared_ptr frame1(new Frame(0, loader1, "0")); + + // Set all region types in frame0 + carta::RegionHandler region_handler; + int file_id(0); + int num_regions = SetAllRegions(region_handler, file_id, frame0->CoordinateSystem()); + // All CARTA regions except ANNULUS (- 1) plus 2 rotbox and 2 circle (+ 4) + ASSERT_EQ(num_regions, CARTA::RegionType_ARRAYSIZE - 1 + 4); + + // Export all regions in frame0 (reference image) + std::map region_style_map; + for (int i = 0; i < num_regions; ++i) { + int region_id = i + 1; // region 0 is cursor + CARTA::RegionType region_type = region_handler.GetRegion(region_id)->GetRegionState().type; + CARTA::RegionStyle style = GetRegionStyle(region_type); + region_style_map[region_id] = style; + } + std::string filename; // do not export to file + CARTA::ExportRegionAck export_ack0; + region_handler.ExportRegion(file_id, frame0, CARTA::DS9_REG, CARTA::PIXEL, region_style_map, filename, export_ack0); + // Check that all regions were exported + ASSERT_EQ(export_ack0.contents_size(), num_regions + 3); // header + globals, coord sys, textbox for text + + // Import all regions in frame0 (reference image) + std::vector export_contents = {export_ack0.contents().begin(), export_ack0.contents().end()}; + auto contents_string = ConcatContents(export_contents); + bool file_is_filename(false); + CARTA::ImportRegionAck import_ack0; + region_handler.ImportRegion(file_id, frame0, CARTA::DS9_REG, contents_string, file_is_filename, import_ack0); + // Check that all regions were imported + ASSERT_EQ(import_ack0.regions_size(), num_regions); + + // Export all regions in frame1 (matched image) + file_id = 1; + CARTA::ExportRegionAck export_ack1; + region_handler.ExportRegion(file_id, frame1, CARTA::DS9_REG, CARTA::PIXEL, region_style_map, filename, export_ack1); + // Check that all regions were exported + ASSERT_EQ(export_ack1.contents_size(), num_regions + 2); // header + globals, coord sys (textbox + text in same string) + + // Import all regions in frame1 (matched image) + export_contents = {export_ack1.contents().begin(), export_ack1.contents().end()}; + contents_string = ConcatContents(export_contents); + CARTA::ImportRegionAck import_ack1; + region_handler.ImportRegion(file_id, frame1, CARTA::DS9_REG, contents_string, file_is_filename, import_ack1); + // Check that all regions were imported + ASSERT_EQ(import_ack1.regions_size(), num_regions); +} + +TEST_F(RegionImportExportTest, TestDs9WorldExportImport) { + // frame 0 + std::string image_path0 = FileFinder::FitsImagePath("noise_10px_10px.fits"); + std::shared_ptr loader0(carta::FileLoader::GetLoader(image_path0)); + std::shared_ptr frame0(new Frame(0, loader0, "0")); + // frame 1 + std::string image_path1 = FileFinder::Hdf5ImagePath("noise_10px_10px.hdf5"); + std::shared_ptr loader1(carta::FileLoader::GetLoader(image_path1)); + std::shared_ptr frame1(new Frame(0, loader1, "0")); + + // Set all region types in frame0 + carta::RegionHandler region_handler; + int file_id(0); + int num_regions = SetAllRegions(region_handler, file_id, frame0->CoordinateSystem()); + // All CARTA regions except ANNULUS (- 1) plus 2 rotbox and 2 circle (+ 4) + ASSERT_EQ(num_regions, CARTA::RegionType_ARRAYSIZE - 1 + 4); + + // Export all regions in frame0 (reference image) + std::map region_style_map; + for (int i = 0; i < num_regions; ++i) { + int region_id = i + 1; // region 0 is cursor + CARTA::RegionType region_type = region_handler.GetRegion(region_id)->GetRegionState().type; + CARTA::RegionStyle style = GetRegionStyle(region_type); + region_style_map[region_id] = style; + } + std::string filename; // do not export to file + CARTA::ExportRegionAck export_ack0; + region_handler.ExportRegion(file_id, frame0, CARTA::DS9_REG, CARTA::WORLD, region_style_map, filename, export_ack0); + // Check that all regions were exported + ASSERT_EQ(export_ack0.contents_size(), num_regions + 2); // header + globals, coord sys (textbox + text in same string) + + // Import all regions in frame0 (reference image) + std::vector export_contents = {export_ack0.contents().begin(), export_ack0.contents().end()}; + auto contents_string = ConcatContents(export_contents); + bool file_is_filename(false); + CARTA::ImportRegionAck import_ack0; + region_handler.ImportRegion(file_id, frame0, CARTA::DS9_REG, contents_string, file_is_filename, import_ack0); + // Check that all regions were imported + ASSERT_EQ(import_ack0.regions_size(), num_regions); + + // Export all regions in frame1 (matched image) + file_id = 1; + CARTA::ExportRegionAck export_ack1; + region_handler.ExportRegion(file_id, frame1, CARTA::DS9_REG, CARTA::WORLD, region_style_map, filename, export_ack1); + // Check that all regions were exported + ASSERT_EQ(export_ack1.contents_size(), num_regions + 2); // header + globals, coord sys (textbox + text in same string) + + // Import all regions in frame1 (matched image) + export_contents = {export_ack1.contents().begin(), export_ack1.contents().end()}; + contents_string = ConcatContents(export_contents); + CARTA::ImportRegionAck import_ack1; + region_handler.ImportRegion(file_id, frame1, CARTA::DS9_REG, contents_string, file_is_filename, import_ack1); + // Check that all regions were imported + ASSERT_EQ(import_ack1.regions_size(), num_regions); +} diff --git a/test/TestRegionMatched.cc b/test/TestRegionMatched.cc new file mode 100644 index 000000000..5da734e3e --- /dev/null +++ b/test/TestRegionMatched.cc @@ -0,0 +1,447 @@ +/* This file is part of the CARTA Image Viewer: https://github.com/CARTAvis/carta-backend + Copyright 2018- Academia Sinica Institute of Astronomy and Astrophysics (ASIAA), + Associated Universities, Inc. (AUI) and the Inter-University Institute for Data Intensive Astronomy (IDIA) + SPDX-License-Identifier: GPL-3.0-or-later +*/ + +#include +#include + +#include "CommonTestUtilities.h" +#include "ImageData/FileLoader.h" +#include "Region/Region.h" +#include "Region/RegionHandler.h" +#include "src/Frame/Frame.h" + +using namespace carta; + +class RegionMatchedTest : public ::testing::Test { +public: + static bool SetRegion(carta::RegionHandler& region_handler, int file_id, int& region_id, CARTA::RegionType type, + const std::vector& points, float rotation, std::shared_ptr csys) { + std::vector control_points; + for (auto i = 0; i < points.size(); i += 2) { + control_points.push_back(Message::Point(points[i], points[i + 1])); + } + + // Define RegionState and set region (region_id updated) + RegionState region_state(file_id, type, control_points, rotation); + return region_handler.SetRegion(region_id, region_state, csys); + } +}; + +TEST_F(RegionMatchedTest, TestMatchedImageRectangleLCRegion) { + // frame 0 + std::string image_path0 = FileFinder::FitsImagePath("noise_10px_10px.fits"); + std::shared_ptr loader0(carta::FileLoader::GetLoader(image_path0)); + std::shared_ptr frame0(new Frame(0, loader0, "0")); + // frame 1 + std::string image_path1 = FileFinder::Hdf5ImagePath("noise_10px_10px.hdf5"); + std::shared_ptr loader1(carta::FileLoader::GetLoader(image_path1)); + std::shared_ptr frame1(new Frame(0, loader1, "0")); + + // Set rectangle in frame 0 + carta::RegionHandler region_handler; + int file_id(0), region_id(-1); + CARTA::RegionType region_type = CARTA::RegionType::RECTANGLE; + std::vector points = {5.0, 5.0, 4.0, 3.0}; + float rotation(0.0); + auto csys = frame0->CoordinateSystem(); + bool ok = SetRegion(region_handler, file_id, region_id, region_type, points, rotation, csys); + ASSERT_TRUE(ok); + auto region = region_handler.GetRegion(region_id); + ASSERT_TRUE(region); // shared_ptr + + // Get Region as 2D LCRegion in frame1 + file_id = 1; + csys = frame1->CoordinateSystem(); + auto image_shape = frame1->ImageShape(); + auto lc_region = region->GetImageRegion(file_id, csys, image_shape); + + // Check LCRegion + ASSERT_TRUE(lc_region); // shared_ptr + ASSERT_EQ(lc_region->ndim(), image_shape.size()); + ASSERT_EQ(lc_region->latticeShape(), image_shape); + ASSERT_EQ(lc_region->shape(), casacore::IPosition(2, 5, 3)); +} + +TEST_F(RegionMatchedTest, TestMatchedImageRotboxLCRegion) { + // frame 0 + std::string image_path0 = FileFinder::FitsImagePath("noise_10px_10px.fits"); + std::shared_ptr loader0(carta::FileLoader::GetLoader(image_path0)); + std::shared_ptr frame0(new Frame(0, loader0, "0")); + // frame 1 + std::string image_path1 = FileFinder::Hdf5ImagePath("noise_10px_10px.hdf5"); + std::shared_ptr loader1(carta::FileLoader::GetLoader(image_path1)); + std::shared_ptr frame1(new Frame(0, loader1, "0")); + + // Set rectangle in frame 0 + carta::RegionHandler region_handler; + int file_id(0), region_id(-1); + CARTA::RegionType region_type = CARTA::RegionType::RECTANGLE; + std::vector points = {5.0, 5.0, 4.0, 3.0}; + float rotation(30.0); + auto csys = frame0->CoordinateSystem(); + bool ok = SetRegion(region_handler, file_id, region_id, region_type, points, rotation, csys); + ASSERT_TRUE(ok); + auto region = region_handler.GetRegion(region_id); + ASSERT_TRUE(region); // shared_ptr + + // Get Region as 2D LCRegion in frame1 + file_id = 1; + csys = frame1->CoordinateSystem(); + auto image_shape = frame1->ImageShape(); + auto lc_region = region->GetImageRegion(file_id, csys, image_shape); + + // Check LCRegion + ASSERT_TRUE(lc_region); // shared_ptr + ASSERT_EQ(lc_region->ndim(), image_shape.size()); + ASSERT_EQ(lc_region->latticeShape(), image_shape); + ASSERT_EQ(lc_region->shape(), casacore::IPosition(2, 5, 5)); +} + +TEST_F(RegionMatchedTest, TestMatchedImageEllipseLCRegion) { + // frame 0 + std::string image_path0 = FileFinder::FitsImagePath("noise_10px_10px.fits"); + std::shared_ptr loader0(carta::FileLoader::GetLoader(image_path0)); + std::shared_ptr frame0(new Frame(0, loader0, "0")); + // frame 1 + std::string image_path1 = FileFinder::Hdf5ImagePath("noise_10px_10px.hdf5"); + std::shared_ptr loader1(carta::FileLoader::GetLoader(image_path1)); + std::shared_ptr frame1(new Frame(0, loader1, "0")); + + // Set region in frame0 + carta::RegionHandler region_handler; + int file_id(0), region_id(-1); + CARTA::RegionType region_type = CARTA::RegionType::ELLIPSE; + std::vector points = {5.0, 5.0, 4.0, 3.0}; + float rotation(0.0); + auto csys = frame0->CoordinateSystem(); + bool ok = SetRegion(region_handler, file_id, region_id, region_type, points, rotation, csys); + ASSERT_TRUE(ok); + auto region = region_handler.GetRegion(region_id); + ASSERT_TRUE(region); // shared_ptr + + // Get Region as 2D LCRegion in frame1 + file_id = 1; + csys = frame1->CoordinateSystem(); + auto image_shape = frame1->ImageShape(); + auto lc_region = region->GetImageRegion(file_id, csys, image_shape); + + // Check LCRegion + ASSERT_TRUE(lc_region); // shared_ptr + ASSERT_EQ(lc_region->ndim(), image_shape.size()); + ASSERT_EQ(lc_region->latticeShape(), image_shape); + ASSERT_EQ(lc_region->shape(), casacore::IPosition(2, 7, 9)); +} + +TEST_F(RegionMatchedTest, TestMatchedImagePolygonLCRegion) { + // frame 0 + std::string image_path0 = FileFinder::FitsImagePath("noise_10px_10px.fits"); + std::shared_ptr loader0(carta::FileLoader::GetLoader(image_path0)); + std::shared_ptr frame0(new Frame(0, loader0, "0")); + // frame 1 + std::string image_path1 = FileFinder::Hdf5ImagePath("noise_10px_10px.hdf5"); + std::shared_ptr loader1(carta::FileLoader::GetLoader(image_path1)); + std::shared_ptr frame1(new Frame(0, loader1, "0")); + + // Set region in frame0 + carta::RegionHandler region_handler; + int file_id(0), region_id(-1); + CARTA::RegionType region_type = CARTA::RegionType::POLYGON; + std::vector points = {5.0, 5.0, 4.0, 3.0, 1.0, 6.0, 3.0, 8.0}; + float rotation(0.0); + auto csys = frame0->CoordinateSystem(); + bool ok = SetRegion(region_handler, file_id, region_id, region_type, points, rotation, csys); + ASSERT_TRUE(ok); + auto region = region_handler.GetRegion(region_id); + ASSERT_TRUE(region); // shared_ptr + + // Get Region as 2D LCRegion in frame1 + file_id = 1; + csys = frame1->CoordinateSystem(); + auto image_shape = frame1->ImageShape(); + auto lc_region = region->GetImageRegion(file_id, csys, image_shape); + + // Check LCRegion + ASSERT_TRUE(lc_region); // shared_ptr + ASSERT_EQ(lc_region->ndim(), image_shape.size()); + ASSERT_EQ(lc_region->latticeShape(), image_shape); + ASSERT_EQ(lc_region->shape(), casacore::IPosition(2, 5, 6)); +} + +TEST_F(RegionMatchedTest, TestMatchedImagePointRecord) { + // frame 0 + std::string image_path0 = FileFinder::FitsImagePath("noise_10px_10px.fits"); + std::shared_ptr loader0(carta::FileLoader::GetLoader(image_path0)); + std::shared_ptr frame0(new Frame(0, loader0, "0")); + // frame 1 + std::string image_path1 = FileFinder::Hdf5ImagePath("noise_10px_10px.hdf5"); + std::shared_ptr loader1(carta::FileLoader::GetLoader(image_path1)); + std::shared_ptr frame1(new Frame(0, loader1, "0")); + + // Set region in frame0 + carta::RegionHandler region_handler; + int file_id(0), region_id(-1); + CARTA::RegionType region_type = CARTA::RegionType::POINT; + std::vector points = {4.0, 2.0}; + float rotation(0.0); + auto csys = frame0->CoordinateSystem(); + bool ok = SetRegion(region_handler, file_id, region_id, region_type, points, rotation, csys); + ASSERT_TRUE(ok); + auto region = region_handler.GetRegion(region_id); + ASSERT_TRUE(region); // shared_ptr + + // Get Region as casacore::Record in frame1 + file_id = 1; + csys = frame1->CoordinateSystem(); + auto image_shape = frame1->ImageShape(); + auto region_record = region->GetImageRegionRecord(file_id, csys, image_shape); + + // Check record + ASSERT_GT(region_record.nfields(), 0); + ASSERT_EQ(region_record.asInt("isRegion"), 1); + ASSERT_EQ(region_record.asString("name"), "LCBox"); // box with blc = trc + ASSERT_TRUE(region_record.asBool("oneRel")); // 1-based pixels + auto blc = region_record.asArrayFloat("blc").tovector(); + auto trc = region_record.asArrayFloat("trc").tovector(); + ASSERT_EQ(blc.size(), 2); + ASSERT_EQ(trc.size(), 2); + ASSERT_FLOAT_EQ(blc[0], points[0] + 1.0); + ASSERT_FLOAT_EQ(blc[1], points[1] + 1.0); + ASSERT_FLOAT_EQ(trc[0], points[0] + 1.0); + ASSERT_FLOAT_EQ(trc[1], points[1] + 1.0); +} + +TEST_F(RegionMatchedTest, TestMatchedImageLineRecord) { + // frame 0 + std::string image_path0 = FileFinder::FitsImagePath("noise_10px_10px.fits"); + std::shared_ptr loader0(carta::FileLoader::GetLoader(image_path0)); + std::shared_ptr frame0(new Frame(0, loader0, "0")); + // frame 1 + std::string image_path1 = FileFinder::Hdf5ImagePath("noise_10px_10px.hdf5"); + std::shared_ptr loader1(carta::FileLoader::GetLoader(image_path1)); + std::shared_ptr frame1(new Frame(0, loader1, "0")); + + // Set region in frame0 + carta::RegionHandler region_handler; + int file_id(0), region_id(-1); + CARTA::RegionType region_type = CARTA::RegionType::LINE; + std::vector points = {5.0, 5.0, 4.0, 3.0}; + float rotation(0.0); + auto csys = frame0->CoordinateSystem(); + bool ok = SetRegion(region_handler, file_id, region_id, region_type, points, rotation, csys); + ASSERT_TRUE(ok); + auto region = region_handler.GetRegion(region_id); + ASSERT_TRUE(region); // shared_ptr + + // Get Region as casacore::Record in frame1 + file_id = 1; + csys = frame1->CoordinateSystem(); + auto image_shape = frame1->ImageShape(); + auto region_record = region->GetImageRegionRecord(file_id, csys, image_shape); + + // Check record + ASSERT_GT(region_record.nfields(), 0); + ASSERT_EQ(region_record.asInt("isRegion"), 1); + ASSERT_EQ(region_record.asString("name"), "line"); + ASSERT_FALSE(region_record.asBool("oneRel")); + auto x = region_record.asArrayDouble("x").tovector(); + auto y = region_record.asArrayDouble("y").tovector(); + ASSERT_EQ(x.size(), 2); + ASSERT_EQ(y.size(), 2); + ASSERT_FLOAT_EQ(x[0], points[0]); + ASSERT_FLOAT_EQ(x[1], points[2]); + ASSERT_FLOAT_EQ(y[0], points[1]); + ASSERT_FLOAT_EQ(y[1], points[3]); +} + +TEST_F(RegionMatchedTest, TestMatchedImageRectangleRecord) { + // frame 0 + std::string image_path0 = FileFinder::FitsImagePath("noise_10px_10px.fits"); + std::shared_ptr loader0(carta::FileLoader::GetLoader(image_path0)); + std::shared_ptr frame0(new Frame(0, loader0, "0")); + // frame 1 + std::string image_path1 = FileFinder::Hdf5ImagePath("noise_10px_10px.hdf5"); + std::shared_ptr loader1(carta::FileLoader::GetLoader(image_path1)); + std::shared_ptr frame1(new Frame(0, loader1, "0")); + + // Set region in frame0 + carta::RegionHandler region_handler; + int file_id(0), region_id(-1); + CARTA::RegionType region_type = CARTA::RegionType::RECTANGLE; + std::vector points = {5.0, 5.0, 4.0, 3.0}; + float rotation(0.0); + auto csys = frame0->CoordinateSystem(); + bool ok = SetRegion(region_handler, file_id, region_id, region_type, points, rotation, csys); + ASSERT_TRUE(ok); + auto region = region_handler.GetRegion(region_id); + ASSERT_TRUE(region); // shared_ptr + + // Get Region as casacore::Record in frame1 + file_id = 1; + csys = frame1->CoordinateSystem(); + auto image_shape = frame1->ImageShape(); + auto region_record = region->GetImageRegionRecord(file_id, csys, image_shape); + + // Check record + ASSERT_GT(region_record.nfields(), 0); + ASSERT_EQ(region_record.asInt("isRegion"), 1); + ASSERT_EQ(region_record.asString("name"), "LCPolygon"); // box corners set as polygon + ASSERT_TRUE(region_record.asBool("oneRel")); // 1-based pixels + // x, y order is [blc, brc, trc, tlc, blc] + auto x = region_record.asArrayFloat("x").tovector(); + auto y = region_record.asArrayFloat("y").tovector(); + float left_x = points[0] - (points[2] / 2.0) + 1.0; + float right_x = points[0] + (points[2] / 2.0) + 1.0; + float bottom_y = points[1] - (points[3] / 2.0) + 1.0; + float top_y = points[1] + (points[3] / 2.0) + 1.0; + ASSERT_EQ(x.size(), 5); // casacore repeats first point to close polygon + ASSERT_EQ(y.size(), 5); // casacore repeats first point to close polygon + ASSERT_FLOAT_EQ(x[0], left_x); + ASSERT_FLOAT_EQ(x[1], right_x); + ASSERT_FLOAT_EQ(y[0], bottom_y); + ASSERT_FLOAT_EQ(y[2], top_y); +} + +TEST_F(RegionMatchedTest, TestMatchedImageRotboxRecord) { + // frame 0 + std::string image_path0 = FileFinder::FitsImagePath("noise_10px_10px.fits"); + std::shared_ptr loader0(carta::FileLoader::GetLoader(image_path0)); + std::shared_ptr frame0(new Frame(0, loader0, "0")); + // frame 1 + std::string image_path1 = FileFinder::Hdf5ImagePath("noise_10px_10px.hdf5"); + std::shared_ptr loader1(carta::FileLoader::GetLoader(image_path1)); + std::shared_ptr frame1(new Frame(0, loader1, "0")); + + // Set region in frame0 + carta::RegionHandler region_handler; + int file_id(0), region_id(-1); + CARTA::RegionType region_type = CARTA::RegionType::RECTANGLE; + std::vector points = {5.0, 5.0, 4.0, 3.0}; + float rotation(30.0); + auto csys = frame0->CoordinateSystem(); + bool ok = SetRegion(region_handler, file_id, region_id, region_type, points, rotation, csys); + ASSERT_TRUE(ok); + auto region = region_handler.GetRegion(region_id); + ASSERT_TRUE(region); // shared_ptr + + // Get Region as casacore::Record in frame1 + file_id = 1; + csys = frame1->CoordinateSystem(); + auto image_shape = frame1->ImageShape(); + auto region_record = region->GetImageRegionRecord(file_id, csys, image_shape); + + // Check record + ASSERT_GT(region_record.nfields(), 0); + ASSERT_EQ(region_record.asInt("isRegion"), 1); + ASSERT_EQ(region_record.asString("name"), "LCPolygon"); // box corners set as polygon + ASSERT_FALSE(region_record.asBool("oneRel")); + // x, y order is [blc, brc, trc, tlc, blc] + auto x = region_record.asArrayFloat("x").tovector(); + auto y = region_record.asArrayFloat("y").tovector(); + // keep original rectangle pixel points with rotation for export + float left_x = points[0] - (points[2] / 2.0); + float right_x = points[0] + (points[2] / 2.0); + float bottom_y = points[1] - (points[3] / 2.0); + float top_y = points[1] + (points[3] / 2.0); + ASSERT_EQ(x.size(), 4); + ASSERT_EQ(y.size(), 4); + ASSERT_FLOAT_EQ(x[0], left_x); + ASSERT_FLOAT_EQ(x[1], right_x); + ASSERT_FLOAT_EQ(x[2], right_x); + ASSERT_FLOAT_EQ(x[3], left_x); + ASSERT_FLOAT_EQ(y[0], bottom_y); + ASSERT_FLOAT_EQ(y[1], bottom_y); + ASSERT_FLOAT_EQ(y[2], top_y); + ASSERT_FLOAT_EQ(y[3], top_y); +} + +TEST_F(RegionMatchedTest, TestMatchedImageEllipseRecord) { + // frame 0 + std::string image_path0 = FileFinder::FitsImagePath("noise_10px_10px.fits"); + std::shared_ptr loader0(carta::FileLoader::GetLoader(image_path0)); + std::shared_ptr frame0(new Frame(0, loader0, "0")); + // frame 1 + std::string image_path1 = FileFinder::Hdf5ImagePath("noise_10px_10px.hdf5"); + std::shared_ptr loader1(carta::FileLoader::GetLoader(image_path1)); + std::shared_ptr frame1(new Frame(0, loader1, "0")); + + // Set region in frame0 + carta::RegionHandler region_handler; + int file_id(0), region_id(-1); + CARTA::RegionType region_type = CARTA::RegionType::ELLIPSE; + std::vector points = {5.0, 5.0, 4.0, 3.0}; + float rotation(0.0); + auto csys = frame0->CoordinateSystem(); + bool ok = SetRegion(region_handler, file_id, region_id, region_type, points, rotation, csys); + ASSERT_TRUE(ok); + auto region = region_handler.GetRegion(region_id); + ASSERT_TRUE(region); // shared_ptr + + // Get Region as casacore::Record in frame1 + file_id = 1; + csys = frame1->CoordinateSystem(); + auto image_shape = frame1->ImageShape(); + auto region_record = region->GetImageRegionRecord(file_id, csys, image_shape); + + // Check record + ASSERT_GT(region_record.nfields(), 0); + ASSERT_EQ(region_record.asInt("isRegion"), 1); + ASSERT_EQ(region_record.asString("name"), "LCEllipsoid"); + ASSERT_TRUE(region_record.asBool("oneRel")); // 1-based pixels + auto center = region_record.asArrayFloat("center").tovector(); + ASSERT_FLOAT_EQ(center[0], points[0] + 1.0); + ASSERT_FLOAT_EQ(center[1], points[1] + 1.0); + auto radii = region_record.asArrayFloat("radii").tovector(); + ASSERT_FLOAT_EQ(radii[0], points[3]); + ASSERT_FLOAT_EQ(radii[1], points[2]); +} + +TEST_F(RegionMatchedTest, TestMatchedImagePolygonRecord) { + // frame 0 + std::string image_path0 = FileFinder::FitsImagePath("noise_10px_10px.fits"); + std::shared_ptr loader0(carta::FileLoader::GetLoader(image_path0)); + std::shared_ptr frame0(new Frame(0, loader0, "0")); + // frame 1 + std::string image_path1 = FileFinder::Hdf5ImagePath("noise_10px_10px.hdf5"); + std::shared_ptr loader1(carta::FileLoader::GetLoader(image_path1)); + std::shared_ptr frame1(new Frame(0, loader1, "0")); + + // Set region in frame0 + carta::RegionHandler region_handler; + int file_id(0), region_id(-1); + CARTA::RegionType region_type = CARTA::RegionType::POLYGON; + std::vector points = {5.0, 5.0, 4.0, 3.0, 1.0, 6.0, 3.0, 8.0}; // 4 points + float rotation(0.0); + auto csys = frame0->CoordinateSystem(); + bool ok = SetRegion(region_handler, file_id, region_id, region_type, points, rotation, csys); + ASSERT_TRUE(ok); + auto region = region_handler.GetRegion(region_id); + ASSERT_TRUE(region); // shared_ptr + + // Get Region as casacore::Record in frame1 + file_id = 1; + csys = frame1->CoordinateSystem(); + auto image_shape = frame1->ImageShape(); + auto region_record = region->GetImageRegionRecord(file_id, csys, image_shape); + + // Check record + ASSERT_GT(region_record.nfields(), 0); + ASSERT_EQ(region_record.asInt("isRegion"), 1); + ASSERT_EQ(region_record.asString("name"), "LCPolygon"); + ASSERT_TRUE(region_record.asBool("oneRel")); // 1-based pixels + // x, y points + auto x = region_record.asArrayFloat("x").tovector(); + auto y = region_record.asArrayFloat("y").tovector(); + ASSERT_EQ(x.size(), 5); // casacore repeats first point to close polygon + ASSERT_EQ(y.size(), 5); // casacore repeats first point to close polygon + ASSERT_FLOAT_EQ(x[0], points[0] + 1.0); + ASSERT_FLOAT_EQ(x[1], points[2] + 1.0); + ASSERT_FLOAT_EQ(x[2], points[4] + 1.0); + ASSERT_FLOAT_EQ(x[3], points[6] + 1.0); + ASSERT_FLOAT_EQ(y[0], points[1] + 1.0); + ASSERT_FLOAT_EQ(y[1], points[3] + 1.0); + ASSERT_FLOAT_EQ(y[2], points[5] + 1.0); + ASSERT_FLOAT_EQ(y[3], points[7] + 1.0); +} diff --git a/test/TestRegionSpatialProfiles.cc b/test/TestRegionSpatialProfiles.cc new file mode 100644 index 000000000..849fc0cab --- /dev/null +++ b/test/TestRegionSpatialProfiles.cc @@ -0,0 +1,346 @@ +/* This file is part of the CARTA Image Viewer: https://github.com/CARTAvis/carta-backend + Copyright 2018- Academia Sinica Institute of Astronomy and Astrophysics (ASIAA), + Associated Universities, Inc. (AUI) and the Inter-University Institute for Data Intensive Astronomy (IDIA) + SPDX-License-Identifier: GPL-3.0-or-later +*/ + +#include +#include + +#include "CommonTestUtilities.h" +#include "ImageData/FileLoader.h" +#include "Region/Region.h" +#include "Region/RegionHandler.h" +#include "src/Frame/Frame.h" + +using namespace carta; + +class RegionSpatialProfileTest : public ::testing::Test { +public: + static bool SetRegion(carta::RegionHandler& region_handler, int file_id, int& region_id, const std::vector& points, + std::shared_ptr csys, bool is_annotation) { + std::vector control_points; + for (auto i = 0; i < points.size(); i += 2) { + control_points.push_back(Message::Point(points[i], points[i + 1])); + } + + // Define RegionState for line region and set region (region_id updated) + auto npoints(control_points.size()); + CARTA::RegionType region_type; + if (npoints == 1) { + if (is_annotation) { + region_type = CARTA::RegionType::ANNPOINT; + } else { + region_type = CARTA::RegionType::POINT; + } + } else { + region_type = CARTA::RegionType::LINE; + if (is_annotation) { + if (npoints > 2) { + region_type = CARTA::RegionType::ANNPOLYLINE; + } else { + region_type = CARTA::RegionType::ANNLINE; + } + } else { + if (npoints > 2) { + region_type = CARTA::RegionType::POLYLINE; + } else { + region_type = CARTA::RegionType::LINE; + } + } + } + RegionState region_state(file_id, region_type, control_points, 0.0); + return region_handler.SetRegion(region_id, region_state, csys); + } + + static bool RegionSpatialProfile(const std::string& image_path, const std::vector& endpoints, + const std::vector& spatial_reqs, CARTA::SpatialProfileData& spatial_profile, + bool is_annotation = false) { + std::shared_ptr loader(carta::FileLoader::GetLoader(image_path)); + std::shared_ptr frame(new Frame(0, loader, "0")); + carta::RegionHandler region_handler; + + // Set line region + int file_id(0), region_id(-1); + auto csys = frame->CoordinateSystem(); + if (!SetRegion(region_handler, file_id, region_id, endpoints, csys, is_annotation)) { + return false; + } + + // Set spatial requirements + if (!region_handler.SetSpatialRequirements(region_id, file_id, frame, spatial_reqs)) { + return false; + } + + // Get spatial profiles + if (endpoints.size() == 2) { + std::vector profiles; + bool ok = region_handler.FillPointSpatialProfileData(file_id, region_id, profiles); + if (ok) { + spatial_profile = profiles[0]; + } + return ok; + } else { + return region_handler.FillLineSpatialProfileData( + file_id, region_id, [&](CARTA::SpatialProfileData profile_data) { spatial_profile = profile_data; }); + } + } + + static std::vector ProfileValues(CARTA::SpatialProfile& profile) { + std::string buffer = profile.raw_values_fp32(); + std::vector values(buffer.size() / sizeof(float)); + memcpy(values.data(), buffer.data(), buffer.size()); + return values; + } + + void SetUp() { + setenv("HDF5_USE_FILE_LOCKING", "FALSE", 0); + } + + static void TestAveragingWidthRange(int width, bool expected_width_range) { + std::string image_path = FileFinder::FitsImagePath("noise_3d.fits"); + std::vector endpoints = {0.0, 0.0, 9.0, 9.0}; + int start(0), end(0), mip(0); + std::vector spatial_reqs = {Message::SpatialConfig("x", start, end, mip, width)}; + CARTA::SpatialProfileData spatial_profile; + + if (expected_width_range) { + ASSERT_TRUE(RegionSpatialProfile(image_path, endpoints, spatial_reqs, spatial_profile)); + ASSERT_EQ(spatial_profile.profiles_size(), 1); + } else { + ASSERT_FALSE(RegionSpatialProfile(image_path, endpoints, spatial_reqs, spatial_profile)); + ASSERT_EQ(spatial_profile.profiles_size(), 0); + } + } +}; + +TEST_F(RegionSpatialProfileTest, TestSpatialRequirements) { + std::string image_path = FileFinder::FitsImagePath("noise_3d.fits"); + std::shared_ptr loader(carta::FileLoader::GetLoader(image_path)); + std::shared_ptr frame(new Frame(0, loader, "0")); + + // Set line region + carta::RegionHandler region_handler; + int file_id(0), region_id(-1); + std::vector endpoints = {0.0, 0.0, 9.0, 9.0}; + auto csys = frame->CoordinateSystem(); + bool is_annotation(false); + bool ok = SetRegion(region_handler, file_id, region_id, endpoints, csys, is_annotation); + ASSERT_TRUE(ok); + + // Set spatial requirements + int start(0), end(0), mip(0), width(3); + std::vector spatial_reqs = {Message::SpatialConfig("x", start, end, mip, width)}; + ok = region_handler.SetSpatialRequirements(region_id, file_id, frame, spatial_reqs); + ASSERT_TRUE(ok); + + // Get regions with spatial requirements for file id + auto region_ids = region_handler.GetSpatialReqRegionsForFile(file_id); + ASSERT_EQ(region_ids.size(), 1); + ASSERT_EQ(region_ids[0], region_id); + + // Get files with spatial requirements for region id + auto file_ids = region_handler.GetSpatialReqFilesForRegion(region_id); + ASSERT_EQ(file_ids.size(), 1); + ASSERT_EQ(file_ids[0], file_id); +} + +TEST_F(RegionSpatialProfileTest, FitsLineProfile) { + std::string image_path = FileFinder::FitsImagePath("noise_3d.fits"); + std::vector endpoints = {0.0, 0.0, 9.0, 9.0}; + int start(0), end(0), mip(0), width(3); + std::vector spatial_reqs = {Message::SpatialConfig("x", start, end, mip, width)}; + + CARTA::SpatialProfileData spatial_profile; + bool ok = RegionSpatialProfile(image_path, endpoints, spatial_reqs, spatial_profile); + + ASSERT_TRUE(ok); + ASSERT_EQ(spatial_profile.profiles_size(), 1); + + // Check file/region ids for requirements +} + +TEST_F(RegionSpatialProfileTest, Hdf5LineProfile) { + std::string image_path = FileFinder::Hdf5ImagePath("noise_10px_10px.hdf5"); + std::vector endpoints = {0.0, 0.0, 9.0, 9.0}; + int start(0), end(0), mip(0), width(3); + std::vector spatial_reqs = {Message::SpatialConfig("x", start, end, mip, width)}; + + CARTA::SpatialProfileData spatial_profile; + bool ok = RegionSpatialProfile(image_path, endpoints, spatial_reqs, spatial_profile); + + ASSERT_TRUE(ok); + ASSERT_EQ(spatial_profile.profiles_size(), 1); +} + +TEST_F(RegionSpatialProfileTest, FitsHorizontalCutProfile) { + std::string image_path = FileFinder::FitsImagePath("noise_3d.fits"); + std::vector endpoints = {9.0, 5.0, 1.0, 5.0}; // Set line region at y=5 + int start(0), end(0), mip(0), width(1); + std::vector spatial_reqs = {Message::SpatialConfig("x", start, end, mip, width)}; + + CARTA::SpatialProfileData spatial_profile; + bool ok = RegionSpatialProfile(image_path, endpoints, spatial_reqs, spatial_profile); + + ASSERT_TRUE(ok); + ASSERT_EQ(spatial_profile.profiles_size(), 1); + + // Profile data + auto profile = spatial_profile.profiles(0); + std::vector profile_data = ProfileValues(profile); + EXPECT_EQ(profile_data.size(), 9); + + // Read image data slice for first channel + // Profile data of horizontal line with width=1 is same as slice + FitsDataReader reader(image_path); + auto image_data = reader.ReadRegion({1, 5, 0}, {10, 6, 1}); + CmpVectors(profile_data, image_data); +} + +TEST_F(RegionSpatialProfileTest, FitsVerticalCutProfile) { + std::string image_path = FileFinder::FitsImagePath("noise_3d.fits"); + std::vector endpoints = {5.0, 9.0, 5.0, 1.0}; // Set line region at x=5 + int start(0), end(0), mip(0), width(1); + std::vector spatial_reqs = {Message::SpatialConfig("y", start, end, mip, width)}; + + CARTA::SpatialProfileData spatial_profile; + bool ok = RegionSpatialProfile(image_path, endpoints, spatial_reqs, spatial_profile); + + ASSERT_TRUE(ok); + ASSERT_EQ(spatial_profile.profiles_size(), 1); + + // Profile data + auto profile = spatial_profile.profiles(0); + std::vector profile_data = ProfileValues(profile); + EXPECT_EQ(profile_data.size(), 9); + + // Read image data slice for first channel + // Profile data of vertical line with width=1 is same as slice + FitsDataReader reader(image_path); + auto image_data = reader.ReadRegion({5, 1, 0}, {6, 10, 1}); + CmpVectors(profile_data, image_data); +} + +TEST_F(RegionSpatialProfileTest, FitsPolylineProfile) { + std::string image_path = FileFinder::FitsImagePath("noise_3d.fits"); + std::vector endpoints = {1.0, 1.0, 9.0, 1.0, 9.0, 5.0}; + int start(0), end(0), mip(0), width(1); + std::vector spatial_reqs = {Message::SpatialConfig("x", start, end, mip, width)}; + std::vector spatial_profiles; + + CARTA::SpatialProfileData spatial_profile; + bool ok = RegionSpatialProfile(image_path, endpoints, spatial_reqs, spatial_profile); + ASSERT_TRUE(ok); + ASSERT_EQ(spatial_profile.profiles_size(), 1); + + // Profile data + auto profile = spatial_profile.profiles(0); + std::vector profile_data = ProfileValues(profile); + EXPECT_EQ(profile_data.size(), 13); + + // Read image data slice for first channel + FitsDataReader reader(image_path); + auto line0_data = reader.ReadRegion({1, 1, 0}, {10, 2, 1}); + // Trim line 1: [9, 1] already covered by line 0 + auto line1_data = reader.ReadRegion({9, 2, 0}, {10, 6, 1}); + auto image_data = line0_data; + for (size_t i = 0; i < line1_data.size(); ++i) { + image_data.push_back(line1_data[i]); + } + + // Profile data of polyline with width=1 is same as image data slices + CmpVectors(profile_data, image_data); +} + +TEST_F(RegionSpatialProfileTest, AveragingWidthRange) { + TestAveragingWidthRange(0, false); + TestAveragingWidthRange(1, true); + TestAveragingWidthRange(20, true); + TestAveragingWidthRange(21, false); +} + +TEST_F(RegionSpatialProfileTest, FitsAnnotationLineProfile) { + std::string image_path = FileFinder::FitsImagePath("noise_3d.fits"); + std::vector endpoints = {0.0, 0.0, 9.0, 9.0}; + int start(0), end(0), mip(0), width(3); + std::vector spatial_reqs = {Message::SpatialConfig("x", start, end, mip, width)}; + + CARTA::SpatialProfileData spatial_profile; + bool is_annotation(true); + bool ok = RegionSpatialProfile(image_path, endpoints, spatial_reqs, spatial_profile, is_annotation); + + ASSERT_FALSE(ok); +} + +TEST_F(RegionSpatialProfileTest, FitsPointProfile) { + std::string image_path = FileFinder::FitsImagePath("noise_3d.fits"); + std::vector endpoints = {0.0, 0.0}; + int start(0), end(0), mip(0), width(1); + std::vector spatial_reqs = {Message::SpatialConfig("x", start, end, mip, width)}; + + CARTA::SpatialProfileData spatial_profile; + bool ok = RegionSpatialProfile(image_path, endpoints, spatial_reqs, spatial_profile); + + ASSERT_TRUE(ok); + ASSERT_EQ(spatial_profile.profiles_size(), 1); + + // Profile data for 10 x 10 image frame + auto profile = spatial_profile.profiles(0); + std::vector profile_data = ProfileValues(profile); + EXPECT_EQ(profile_data.size(), 10); + + // Read image data slice for first channel + // Profile data of point is same as slice + FitsDataReader reader(image_path); + auto image_data = reader.ReadRegion({0, 0, 0}, {10, 1, 1}); + CmpVectors(profile_data, image_data); +} + +TEST_F(RegionSpatialProfileTest, Hdf5PointProfile) { + std::string image_path = FileFinder::Hdf5ImagePath("noise_10px_10px.hdf5"); + std::vector points = {0.0, 0.0}; + int start(0), end(0), mip(0), width(1); + std::vector spatial_reqs = {Message::SpatialConfig("x", start, end, mip, width)}; + + CARTA::SpatialProfileData spatial_profile; + bool ok = RegionSpatialProfile(image_path, points, spatial_reqs, spatial_profile); + + ASSERT_TRUE(ok); + ASSERT_EQ(spatial_profile.profiles_size(), 1); + + // Profile data for 10 x 10 image frame + auto profile = spatial_profile.profiles(0); + std::vector profile_data = ProfileValues(profile); + EXPECT_EQ(profile_data.size(), 10); + + // Read image data slice for first channel + // Profile data of point is same as slice + Hdf5DataReader reader(image_path); + auto image_data = reader.ReadRegion({0, 0, 0}, {10, 1, 1}); + CmpVectors(profile_data, image_data); +} + +TEST_F(RegionSpatialProfileTest, FitsPointProfileOutsideImage) { + std::string image_path = FileFinder::FitsImagePath("noise_3d.fits"); + std::vector endpoints = {-2.0, -2.0}; + int start(0), end(0), mip(0), width(1); + std::vector spatial_reqs = {Message::SpatialConfig("x", start, end, mip, width)}; + + CARTA::SpatialProfileData spatial_profile; + bool ok = RegionSpatialProfile(image_path, endpoints, spatial_reqs, spatial_profile); + + ASSERT_FALSE(ok); +} + +TEST_F(RegionSpatialProfileTest, FitsAnnotationPointProfile) { + std::string image_path = FileFinder::FitsImagePath("noise_3d.fits"); + std::vector point = {0.0, 0.0}; + int start(0), end(0), mip(0), width(3); + std::vector spatial_reqs = {Message::SpatialConfig("x", start, end, mip, width)}; + + CARTA::SpatialProfileData spatial_profile; + bool is_annotation(true); + bool ok = RegionSpatialProfile(image_path, point, spatial_reqs, spatial_profile, is_annotation); + + ASSERT_FALSE(ok); +} diff --git a/test/TestRegionSpectralProfiles.cc b/test/TestRegionSpectralProfiles.cc new file mode 100644 index 000000000..441ae5f4f --- /dev/null +++ b/test/TestRegionSpectralProfiles.cc @@ -0,0 +1,173 @@ +/* This file is part of the CARTA Image Viewer: https://github.com/CARTAvis/carta-backend + Copyright 2018- Academia Sinica Institute of Astronomy and Astrophysics (ASIAA), + Associated Universities, Inc. (AUI) and the Inter-University Institute for Data Intensive Astronomy (IDIA) + SPDX-License-Identifier: GPL-3.0-or-later +*/ + +#include +#include + +#include "CommonTestUtilities.h" +#include "ImageData/FileLoader.h" +#include "Region/Region.h" +#include "Region/RegionHandler.h" +#include "src/Frame/Frame.h" + +using namespace carta; + +class RegionSpectralProfileTest : public ::testing::Test { +public: + static bool SetRegion(carta::RegionHandler& region_handler, int file_id, int& region_id, const std::vector& points, + std::shared_ptr csys, bool is_annotation) { + std::vector control_points; + for (auto i = 0; i < points.size(); i += 2) { + control_points.push_back(Message::Point(points[i], points[i + 1])); + } + + // Define RegionState and set region + CARTA::RegionType region_type; + if (control_points.size() > 1) { + region_type = is_annotation ? CARTA::ANNPOLYGON : CARTA::POLYGON; + } else { + region_type = is_annotation ? CARTA::ANNPOINT : CARTA::POINT; + } + RegionState region_state(file_id, region_type, control_points, 0.0); + return region_handler.SetRegion(region_id, region_state, csys); + } + + static bool SpectralProfile(const std::string& image_path, const std::vector& points, CARTA::SpectralProfileData& spectral_data, + bool is_annotation = false) { + std::shared_ptr loader(carta::FileLoader::GetLoader(image_path)); + std::shared_ptr frame(new Frame(0, loader, "0")); + carta::RegionHandler region_handler; + + // Set polygon or point region + int file_id(0), region_id(-1); + auto csys = frame->CoordinateSystem(); + if (!SetRegion(region_handler, file_id, region_id, points, csys, is_annotation)) { + return false; + } + + // Set spectral requirements (Message requests 10 stats types) + auto spectral_req_message = Message::SetSpectralRequirements(file_id, region_id, "z"); + std::vector spectral_requirements = { + spectral_req_message.spectral_profiles().begin(), spectral_req_message.spectral_profiles().end()}; + if (!region_handler.SetSpectralRequirements(region_id, file_id, frame, spectral_requirements)) { + return false; + } + + // Get spectral profile + return region_handler.FillSpectralProfileData( + [&](CARTA::SpectralProfileData profile_data) { spectral_data = profile_data; }, region_id, file_id, false); + } + + static std::vector GetExpectedMeanProfile(std::string& image_path, int num_channels, CARTA::RegionType type) { + // Read image for profile for box region blc (0,0) trc (3,3) or point (3,3) + FitsDataReader reader(image_path); + std::vector profile; + if (type == CARTA::POLYGON) { + for (hsize_t i = 0; i < num_channels; ++i) { + auto channel_data = reader.ReadRegion({0, 0, i}, {4, 4, i + 1}); + double sum = std::accumulate(channel_data.begin(), channel_data.end(), 0.0); + double mean = sum / channel_data.size(); + profile.push_back(mean); + } + } else { + hsize_t chan = num_channels; + auto channel_data = reader.ReadRegion({3, 3, 0}, {4, 4, chan}); + for (float data : channel_data) { + profile.push_back(data); + } + } + return profile; + } +}; + +TEST_F(RegionSpectralProfileTest, TestPolygonSpectralProfile) { + // Box described as 4-corner polygon + std::string image_path = FileFinder::FitsImagePath("noise_3d.fits"); + int num_channels = 10; + std::vector points = {0.0, 0.0, 0.0, 3.0, 3.0, 3.0, 3.0, 0.0}; + CARTA::SpectralProfileData spectral_data; + bool ok = SpectralProfile(image_path, points, spectral_data); + + // Check spectral profiles + ASSERT_TRUE(ok); + ASSERT_EQ(spectral_data.file_id(), 0); + ASSERT_EQ(spectral_data.region_id(), 1); + ASSERT_EQ(spectral_data.stokes(), 0); + ASSERT_EQ(spectral_data.progress(), 1.0); + + auto num_profiles = spectral_data.profiles_size(); + // Expected types set in Message::SetSpectralRequirements + std::vector expected_types = {CARTA::StatsType::NumPixels, CARTA::StatsType::Sum, CARTA::StatsType::FluxDensity, + CARTA::StatsType::Mean, CARTA::StatsType::RMS, CARTA::StatsType::Sigma, CARTA::StatsType::SumSq, CARTA::StatsType::Min, + CARTA::StatsType::Max, CARTA::StatsType::Extrema}; + ASSERT_EQ(num_profiles, expected_types.size()); + + for (int i = 0; i < num_profiles; ++i) { + auto profile = spectral_data.profiles(i); + ASSERT_EQ(profile.coordinate(), "z"); + ASSERT_EQ(profile.stats_type(), expected_types[i]); + auto values = profile.raw_values_fp64(); // string (bytes) + ASSERT_EQ(values.size(), num_channels * 8); // double (8-byte) values + + if (profile.stats_type() == CARTA::StatsType::Mean) { + auto expected_profile = GetExpectedMeanProfile(image_path, num_channels, CARTA::POLYGON); + auto actual_profile = GetSpectralProfileValues(profile); + CmpVectors(actual_profile, expected_profile); + } + } +} + +TEST_F(RegionSpectralProfileTest, TestAnnPolygonSpectralProfile) { + std::string image_path = FileFinder::FitsImagePath("noise_3d.fits"); + std::vector points = {0.0, 0.0, 0.0, 3.0, 3.0, 3.0, 3.0, 0.0}; + CARTA::SpectralProfileData spectral_data; + bool ok = SpectralProfile(image_path, points, spectral_data, true); + ASSERT_FALSE(ok); +} + +TEST_F(RegionSpectralProfileTest, TestPointSpectralProfile) { + std::string image_path = FileFinder::FitsImagePath("noise_3d.fits"); + int num_channels = 10; + std::vector points = {3.0, 3.0}; + CARTA::SpectralProfileData spectral_data; + bool ok = SpectralProfile(image_path, points, spectral_data); + + // Check spectral profiles + ASSERT_TRUE(ok); + ASSERT_EQ(spectral_data.file_id(), 0); + ASSERT_EQ(spectral_data.region_id(), 1); + ASSERT_EQ(spectral_data.stokes(), 0); + ASSERT_EQ(spectral_data.progress(), 1.0); + + auto num_profiles = spectral_data.profiles_size(); + // Expected types set in Message::SetSpectralRequirements + std::vector expected_types = {CARTA::StatsType::NumPixels, CARTA::StatsType::Sum, CARTA::StatsType::FluxDensity, + CARTA::StatsType::Mean, CARTA::StatsType::RMS, CARTA::StatsType::Sigma, CARTA::StatsType::SumSq, CARTA::StatsType::Min, + CARTA::StatsType::Max, CARTA::StatsType::Extrema}; + ASSERT_EQ(num_profiles, expected_types.size()); + + for (int i = 0; i < num_profiles; ++i) { + auto profile = spectral_data.profiles(i); + ASSERT_EQ(profile.coordinate(), "z"); + ASSERT_EQ(profile.stats_type(), expected_types[i]); + auto values = profile.raw_values_fp64(); // string (bytes) + ASSERT_EQ(values.size(), num_channels * 8); // double (8-byte) values + + if (profile.stats_type() == CARTA::StatsType::Mean) { + auto expected_profile = GetExpectedMeanProfile(image_path, num_channels, CARTA::POINT); + auto actual_profile = GetSpectralProfileValues(profile); + CmpVectors(actual_profile, expected_profile); + } + } +} + +TEST_F(RegionSpectralProfileTest, TestAnnPointSpectralProfile) { + std::string image_path = FileFinder::FitsImagePath("noise_3d.fits"); + std::vector points = {3.0, 3.0}; + CARTA::SpectralProfileData spectral_data; + bool ok = SpectralProfile(image_path, points, spectral_data, true); + ASSERT_FALSE(ok); +} diff --git a/test/TestRegionStats.cc b/test/TestRegionStats.cc new file mode 100644 index 000000000..c3b6c7399 --- /dev/null +++ b/test/TestRegionStats.cc @@ -0,0 +1,108 @@ +/* This file is part of the CARTA Image Viewer: https://github.com/CARTAvis/carta-backend + Copyright 2018- Academia Sinica Institute of Astronomy and Astrophysics (ASIAA), + Associated Universities, Inc. (AUI) and the Inter-University Institute for Data Intensive Astronomy (IDIA) + SPDX-License-Identifier: GPL-3.0-or-later +*/ + +#include +#include + +#include "CommonTestUtilities.h" +#include "ImageData/FileLoader.h" +#include "Region/Region.h" +#include "Region/RegionHandler.h" +#include "src/Frame/Frame.h" + +using namespace carta; + +class RegionStatsTest : public ::testing::Test { +public: + static bool SetRegion(carta::RegionHandler& region_handler, int file_id, int& region_id, const std::vector& points, + std::shared_ptr csys, bool is_annotation) { + std::vector control_points; + for (auto i = 0; i < points.size(); i += 2) { + control_points.push_back(Message::Point(points[i], points[i + 1])); + } + + // Define RegionState for line region and set region (region_id updated) + auto npoints(control_points.size()); + CARTA::RegionType region_type = CARTA::RegionType::POLYGON; + if (is_annotation) { + region_type = CARTA::RegionType::ANNPOLYGON; + } + RegionState region_state(file_id, region_type, control_points, 0.0); + return region_handler.SetRegion(region_id, region_state, csys); + } + + static bool RegionStats(const std::string& image_path, const std::vector& endpoints, CARTA::RegionStatsData& region_stats, + bool is_annotation = false) { + std::shared_ptr loader(carta::FileLoader::GetLoader(image_path)); + std::shared_ptr frame(new Frame(0, loader, "0")); + carta::RegionHandler region_handler; + + // Set polygon region + int file_id(0), region_id(-1); + auto csys = frame->CoordinateSystem(); + if (!SetRegion(region_handler, file_id, region_id, endpoints, csys, is_annotation)) { + return false; + } + + // Set stats requirements + auto stats_req_message = Message::SetStatsRequirements(file_id, region_id); + std::vector stats_configs = { + stats_req_message.stats_configs().begin(), stats_req_message.stats_configs().end()}; + if (!region_handler.SetStatsRequirements(region_id, file_id, frame, stats_configs)) { + return false; + } + + // Get stats + return region_handler.FillRegionStatsData( + [&](CARTA::RegionStatsData stats_data) { region_stats = stats_data; }, region_id, file_id); + } +}; + +TEST_F(RegionStatsTest, TestFitsRegionStats) { + std::string image_path = FileFinder::FitsImagePath("noise_3d.fits"); + std::vector endpoints = {1.0, 1.0, 1.0, 4.0, 4.0, 4.0, 4.0, 1.0}; + CARTA::RegionStatsData stats_data; + bool ok = RegionStats(image_path, endpoints, stats_data); + + // Check stats fields + ASSERT_TRUE(ok); + ASSERT_EQ(stats_data.file_id(), 0); + ASSERT_EQ(stats_data.region_id(), 1); + ASSERT_EQ(stats_data.channel(), 0); + ASSERT_EQ(stats_data.stokes(), 0); + ASSERT_GT(stats_data.statistics_size(), 0); + + // Calc expected stats from image data + FitsDataReader reader(image_path); + auto image_data = reader.ReadRegion({1, 1, 0}, {5, 5, 1}); + double expected_sum = std::accumulate(image_data.begin(), image_data.end(), 0.0); + double expected_mean = expected_sum / image_data.size(); + double expected_min = *std::min_element(image_data.begin(), image_data.end()); + double expected_max = *std::max_element(image_data.begin(), image_data.end()); + + // Check some stats + for (size_t i = 0; i < stats_data.statistics_size(); ++i) { + if (stats_data.statistics(i).stats_type() == CARTA::StatsType::NumPixels) { + ASSERT_DOUBLE_EQ(stats_data.statistics(i).value(), (double)image_data.size()); + } else if (stats_data.statistics(i).stats_type() == CARTA::StatsType::Sum) { + ASSERT_DOUBLE_EQ(stats_data.statistics(i).value(), expected_sum); + } else if (stats_data.statistics(i).stats_type() == CARTA::StatsType::Mean) { + ASSERT_DOUBLE_EQ(stats_data.statistics(i).value(), expected_mean); + } else if (stats_data.statistics(i).stats_type() == CARTA::StatsType::Min) { + ASSERT_DOUBLE_EQ(stats_data.statistics(i).value(), expected_min); + } else if (stats_data.statistics(i).stats_type() == CARTA::StatsType::Max) { + ASSERT_DOUBLE_EQ(stats_data.statistics(i).value(), expected_max); + } + } +} + +TEST_F(RegionStatsTest, TestFitsAnnotationRegionStats) { + std::string image_path = FileFinder::FitsImagePath("noise_3d.fits"); + std::vector endpoints = {0.0, 0.0, 0.0, 3.0, 3.0, 3.0, 3.0, 0.0}; + CARTA::RegionStatsData stats_data; + bool ok = RegionStats(image_path, endpoints, stats_data, true); + ASSERT_FALSE(ok); +} diff --git a/test/TestRestApi.cc b/test/TestRestApi.cc index 621daed94..325fd12a9 100644 --- a/test/TestRestApi.cc +++ b/test/TestRestApi.cc @@ -1,5 +1,5 @@ /* This file is part of the CARTA Image Viewer: https://github.com/CARTAvis/carta-backend - Copyright 2018-2022 Academia Sinica Institute of Astronomy and Astrophysics (ASIAA), + Copyright 2018- Academia Sinica Institute of Astronomy and Astrophysics (ASIAA), Associated Universities, Inc. (AUI) and the Inter-University Institute for Data Intensive Astronomy (IDIA) SPDX-License-Identifier: GPL-3.0-or-later */ diff --git a/test/TestTileEncoding.cc b/test/TestTileEncoding.cc index 7fe59ff31..3ebbda50e 100644 --- a/test/TestTileEncoding.cc +++ b/test/TestTileEncoding.cc @@ -1,5 +1,5 @@ /* This file is part of the CARTA Image Viewer: https://github.com/CARTAvis/carta-backend - Copyright 2018-2022 Academia Sinica Institute of Astronomy and Astrophysics (ASIAA), + Copyright 2018- Academia Sinica Institute of Astronomy and Astrophysics (ASIAA), Associated Universities, Inc. (AUI) and the Inter-University Institute for Data Intensive Astronomy (IDIA) SPDX-License-Identifier: GPL-3.0-or-later */ diff --git a/test/TestUtil.cc b/test/TestUtil.cc index 25e5ad19f..0352d4aed 100644 --- a/test/TestUtil.cc +++ b/test/TestUtil.cc @@ -1,5 +1,5 @@ /* This file is part of the CARTA Image Viewer: https://github.com/CARTAvis/carta-backend - Copyright 2018-2022 Academia Sinica Institute of Astronomy and Astrophysics (ASIAA), + Copyright 2018- Academia Sinica Institute of Astronomy and Astrophysics (ASIAA), Associated Universities, Inc. (AUI) and the Inter-University Institute for Data Intensive Astronomy (IDIA) SPDX-License-Identifier: GPL-3.0-or-later */ diff --git a/test/TestVoTable.cc b/test/TestVoTable.cc index c69a72f13..0f3c74a59 100644 --- a/test/TestVoTable.cc +++ b/test/TestVoTable.cc @@ -1,5 +1,5 @@ /* This file is part of the CARTA Image Viewer: https://github.com/CARTAvis/carta-backend - Copyright 2018-2022 Academia Sinica Institute of Astronomy and Astrophysics (ASIAA), + Copyright 2018- Academia Sinica Institute of Astronomy and Astrophysics (ASIAA), Associated Universities, Inc. (AUI) and the Inter-University Institute for Data Intensive Astronomy (IDIA) SPDX-License-Identifier: GPL-3.0-or-later */ diff --git a/third-party/install/uWebSockets.cmake b/third-party/install/uWebSockets.cmake index 7af7928d1..de7d94cf5 100644 --- a/third-party/install/uWebSockets.cmake +++ b/third-party/install/uWebSockets.cmake @@ -1,23 +1,56 @@ macro(install_uWebSockets) + # Avoid the warning about DOWNLOAD_EXTRACT_TIMESTAMP + if (CMAKE_VERSION VERSION_GREATER_EQUAL "3.24.0") + cmake_policy(SET CMP0135 NEW) + endif() - set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -DLIBUS_NO_SSL") + INCLUDE(FetchContent) - include_directories(${CMAKE_SOURCE_DIR}/third-party/uWebSockets/uSockets/src) - include_directories(${CMAKE_SOURCE_DIR}/third-party/uWebSockets/src) + # Build uSocket + SET(USOCKETS_SOURCE_DIR ${CMAKE_SOURCE_DIR}/third-party/uSockets) - set(USOCKETS_SOURCE_FILES - ${SOURCE_FILES} - ${CMAKE_SOURCE_DIR}/third-party/uWebSockets/uSockets/src/crypto/openssl.c - ${CMAKE_SOURCE_DIR}/third-party/uWebSockets/uSockets/src/crypto/wolfssl.c - ${CMAKE_SOURCE_DIR}/third-party/uWebSockets/uSockets/src/eventing/epoll_kqueue.c - ${CMAKE_SOURCE_DIR}/third-party/uWebSockets/uSockets/src/eventing/gcd.c - ${CMAKE_SOURCE_DIR}/third-party/uWebSockets/uSockets/src/eventing/libuv.c - ${CMAKE_SOURCE_DIR}/third-party/uWebSockets/uSockets/src/bsd.c - ${CMAKE_SOURCE_DIR}/third-party/uWebSockets/uSockets/src/context.c - ${CMAKE_SOURCE_DIR}/third-party/uWebSockets/uSockets/src/loop.c - ${CMAKE_SOURCE_DIR}/third-party/uWebSockets/uSockets/src/socket.c) + FetchContent_Declare( + uSockets-build + URL https://github.com/uNetworking/uSockets/archive/refs/tags/v0.8.6.zip + SOURCE_DIR ${USOCKETS_SOURCE_DIR} + ) - add_library(uSockets ${USOCKETS_SOURCE_FILES}) + FetchContent_GetProperties(uSockets-build) + if (NOT uSockets-build_POPULATED) + FetchContent_Populate(uSockets-build) + endif () -endmacro() + INCLUDE_DIRECTORIES(${USOCKETS_SOURCE_DIR}/src) + + AUX_SOURCE_DIRECTORY(${USOCKETS_SOURCE_DIR}/src USOCKETS_FILES) + AUX_SOURCE_DIRECTORY(${USOCKETS_SOURCE_DIR}/src/crypto USOCKETS_CRYPTO_FILES) + AUX_SOURCE_DIRECTORY(${USOCKETS_SOURCE_DIR}/src/eventing USOCKETS_EVENTING_FILES) + AUX_SOURCE_DIRECTORY(${USOCKETS_SOURCE_DIR}/src/internal USOCKETS_INTERNAL_FILES) + AUX_SOURCE_DIRECTORY(${USOCKETS_SOURCE_DIR}/src/io_uring USOCKETS_IO_URING_FILES) + + SET(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -DLIBUS_NO_SSL") + + ADD_LIBRARY(uSockets + ${USOCKETS_FILES} + ${USOCKETS_CRYPTO_FILES} + ${USOCKETS_EVENTING_FILES} + ${USOCKETS_INTERNAL_FILES} + ${USOCKETS_IO_URING_FILES}) + + # Build uWebSockets + SET(UWEBSOCKETS_SOURCE_DIR ${CMAKE_SOURCE_DIR}/third-party/uWebSockets) + FetchContent_Declare( + uWebSockets-build + URL https://github.com/uNetworking/uWebSockets/archive/refs/tags/v20.46.0.zip + SOURCE_DIR ${UWEBSOCKETS_SOURCE_DIR} + ) + + FetchContent_GetProperties(uWebSockets-build) + if (NOT uWebSockets-build_POPULATED) + FetchContent_Populate(uWebSockets-build) + endif () + + INCLUDE_DIRECTORIES(${UWEBSOCKETS_SOURCE_DIR}/src) + +endmacro() diff --git a/third-party/uWebSockets b/third-party/uWebSockets deleted file mode 160000 index b7c82d791..000000000 --- a/third-party/uWebSockets +++ /dev/null @@ -1 +0,0 @@ -Subproject commit b7c82d791010bd4c9b45528846f95b9886e9f984