pax_global_header00006660000000000000000000000064152453641650014525gustar00rootroot0000000000000052 comment=be5e29168d4aff238409d60424812df66aac919f colmap-4.2.0/000077500000000000000000000000001524536416500130035ustar00rootroot00000000000000colmap-4.2.0/.clang-format000077500000000000000000000005661524536416500153700ustar00rootroot00000000000000BasedOnStyle: Google BinPackArguments: false BinPackParameters: false DerivePointerAlignment: false IncludeBlocks: Regroup IncludeCategories: - Regex: '^"colmap' Priority: 1 - Regex: '^"pycolmap' Priority: 2 - Regex: '^"thirdparty' Priority: 3 - Regex: '^<[[:alnum:]_]+>' Priority: 4 - Regex: '.*' Priority: 5 SortIncludes: true colmap-4.2.0/.clang-tidy000066400000000000000000000011511524536416500150350ustar00rootroot00000000000000Checks: > performance-*, concurrency-*, bugprone-*, -clang-analyzer-security.ArrayBound, -bugprone-easily-swappable-parameters, -bugprone-exception-escape, -bugprone-implicit-widening-of-multiplication-result, -bugprone-narrowing-conversions, -bugprone-reserved-identifier, -bugprone-unchecked-optional-access, -performance-enum-size, cppcoreguidelines-virtual-class-destructor, google-explicit-constructor, google-build-using-namespace, readability-avoid-const-params-in-decls, clang-analyzer-core*, clang-analyzer-cplusplus*, WarningsAsErrors: '*' FormatStyle: 'file' User: 'user' colmap-4.2.0/.dockerignore000066400000000000000000000000331524536416500154530ustar00rootroot00000000000000.git build* !build/.ccache colmap-4.2.0/.github/000077500000000000000000000000001524536416500143435ustar00rootroot00000000000000colmap-4.2.0/.github/ISSUE_TEMPLATE/000077500000000000000000000000001524536416500165265ustar00rootroot00000000000000colmap-4.2.0/.github/ISSUE_TEMPLATE/bug_report.md000066400000000000000000000011531524536416500212200ustar00rootroot00000000000000--- name: Bug report about: Create a report to help us improve --- **Describe the bug** A clear and concise description of what the bug is. **To Reproduce** Steps to reproduce the behavior: 1. Go to '...' 2. Click on '....' 3. Scroll down to '....' 4. See error **Expected behavior** A clear and concise description of what you expected to happen. **Screenshots** If applicable, add screenshots to help explain your problem. Input images and/or output reconstructions are usually helpful. **Environment:** - OS: [e.g. Windows 11] - COLMAP Version [e.g. 3.8 or git commit hash] - Capture Device [e.g. iPhone X] colmap-4.2.0/.github/ISSUE_TEMPLATE/feature_request.md000066400000000000000000000010601524536416500222500ustar00rootroot00000000000000--- name: Feature request about: Suggest an idea for this project --- **Is your feature request related to a problem? Please describe.** A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] **Describe the solution you'd like** A clear and concise description of what you want to happen. **Describe alternatives you've considered** A clear and concise description of any alternative solutions or features you've considered. **Additional context** Add any other context or screenshots about the feature request here. colmap-4.2.0/.github/actions/000077500000000000000000000000001524536416500160035ustar00rootroot00000000000000colmap-4.2.0/.github/actions/compiler-cache/000077500000000000000000000000001524536416500206565ustar00rootroot00000000000000colmap-4.2.0/.github/actions/compiler-cache/action.yml000066400000000000000000000130011524536416500226510ustar00rootroot00000000000000name: Compiler cache description: Restore or save the weekly compiler cache inputs: mode: description: Whether to restore or save the cache required: true path: description: Local compiler-cache directory required: true key-prefix: description: Cache family prefix used for restore required: false default: colmap-compiler key-suffix: description: Configuration-specific cache key suffix used for restore required: false cache-version: description: Cache schema version required: false default: "2" key: description: Exact cache key returned by an earlier restore required: false cache-hit: description: Whether the exact cache key was restored before the build required: false default: "false" ccache-dir: description: Optional ccache directory to prune and recompress before saving required: false evict-older-than: description: Remove ccache entries unused for this long before saving required: false default: 1d recompression-level: description: Zstandard level used to recompress ccache entries before saving required: false default: "6" outputs: cache-hit: description: Whether the exact weekly cache was restored value: ${{ steps.restore.outputs.cache-hit }} key: description: Exact weekly key to use when saving the cache value: ${{ inputs.key-prefix }}-v${{ inputs.cache-version }}-${{ inputs.key-suffix }}-${{ steps.epoch.outputs.value }} runs: using: composite steps: - name: Validate mode if: ${{ inputs.mode != 'restore' && inputs.mode != 'save' }} shell: bash run: | echo "Unsupported compiler-cache mode: ${{ inputs.mode }}" >&2 exit 1 - name: Set cache epoch if: ${{ inputs.mode == 'restore' }} id: epoch shell: bash # GitHub cache entries are immutable. Rotate weekly so changed objects # are incorporated without creating a new archive for every run. run: echo "value=$(date -u +%G-%V)" >> "$GITHUB_OUTPUT" - name: Restore compiler cache if: ${{ inputs.mode == 'restore' }} id: restore uses: actions/cache/restore@v6 with: key: ${{ inputs.key-prefix }}-v${{ inputs.cache-version }}-${{ inputs.key-suffix }}-${{ steps.epoch.outputs.value }} restore-keys: ${{ inputs.key-prefix }}-v${{ inputs.cache-version }}-${{ inputs.key-suffix }}- path: ${{ inputs.path }} - name: Compact ccache (Linux/macOS) if: ${{ success() && inputs.mode == 'save' && runner.os != 'Windows' && (github.event_name == 'push' || github.event_name == 'release') && inputs.cache-hit != 'true' && inputs.ccache-dir != '' }} shell: bash run: | set -euo pipefail cache_root="${{ inputs.path }}" if [ -x "${cache_root}/bin/ccache" ]; then ccache="${cache_root}/bin/ccache" elif [ -x "${cache_root}/bin/ccache.exe" ]; then ccache="${cache_root}/bin/ccache.exe" elif command -v ccache >/dev/null 2>&1; then ccache="$(command -v ccache)" else echo "ccache executable not found" >&2 exit 1 fi # CCACHE_DIR is remapped to a container path by the PyCOLMAP Linux job, # so it must be overridden here. CCACHE_MAXSIZE needs no override: the # job-level value is inherited by composite action steps as-is. export CCACHE_DIR="${{ inputs.ccache-dir }}" "${ccache}" --evict-older-than "${{ inputs.evict-older-than }}" "${ccache}" --recompress "${{ inputs.recompression-level }}" "${ccache}" --cleanup "${ccache}" --show-compression "${ccache}" --show-stats --verbose - name: Compact ccache (Windows) if: ${{ success() && inputs.mode == 'save' && runner.os == 'Windows' && (github.event_name == 'push' || github.event_name == 'release') && inputs.cache-hit != 'true' && inputs.ccache-dir != '' }} shell: pwsh run: | $ErrorActionPreference = "Stop" $cacheRoot = "${{ inputs.path }}" $ccache = @( (Join-Path $cacheRoot "bin/ccache"), (Join-Path $cacheRoot "bin/ccache.exe") ) | Where-Object { Test-Path -PathType Leaf $_ } | Select-Object -First 1 if (!$ccache) { $ccache = (Get-Command ccache -ErrorAction SilentlyContinue).Source } if (!$ccache) { throw "ccache executable not found" } # CCACHE_DIR is remapped to a container path by the PyCOLMAP Linux job, # so it must be overridden here. CCACHE_MAXSIZE needs no override: the # job-level value is inherited by composite action steps as-is. $env:CCACHE_DIR = "${{ inputs.ccache-dir }}" & $ccache --evict-older-than "${{ inputs.evict-older-than }}" if ($LASTEXITCODE -ne 0) { throw "ccache eviction failed" } & $ccache --recompress "${{ inputs.recompression-level }}" if ($LASTEXITCODE -ne 0) { throw "ccache recompression failed" } & $ccache --cleanup if ($LASTEXITCODE -ne 0) { throw "ccache cleanup failed" } & $ccache --show-compression if ($LASTEXITCODE -ne 0) { throw "ccache compression stats failed" } & $ccache --show-stats --verbose if ($LASTEXITCODE -ne 0) { throw "ccache stats failed" } - name: Save compiler cache if: ${{ success() && inputs.mode == 'save' && (github.event_name == 'push' || github.event_name == 'release') && inputs.cache-hit != 'true' }} uses: actions/cache/save@v6 with: key: ${{ inputs.key }} path: ${{ inputs.path }} colmap-4.2.0/.github/workflows/000077500000000000000000000000001524536416500164005ustar00rootroot00000000000000colmap-4.2.0/.github/workflows/build-docker.yml000066400000000000000000000056371524536416500215020ustar00rootroot00000000000000name: COLMAP (Docker) on: push: branches: - main - release/* pull_request: types: [ assigned, opened, synchronize, reopened ] release: types: [ published, edited ] env: IS_RELEASE: ${{ github.event_name == 'release' || startsWith(github.ref, 'refs/tags') }} CCACHE_DIR: build/.ccache CCACHE_MAXSIZE: 100M jobs: build: name: ubuntu-24.04 runs-on: ubuntu-24.04 steps: - name: Free disk space uses: jlumbroso/free-disk-space@v1.3.1 with: tool-cache: false android: true dotnet: true haskell: true large-packages: true docker-images: true swap-storage: true - uses: actions/checkout@v7 - name: Restore compiler cache uses: ./.github/actions/compiler-cache id: cache-ccache with: mode: restore path: ${{ env.CCACHE_DIR }} key-suffix: docker-release_${{ env.IS_RELEASE }} - name: Set up QEMU uses: docker/setup-qemu-action@v4 - name: Set up Docker Buildx uses: docker/setup-buildx-action@v4 - name: Login to Docker Hub if: ${{ env.IS_RELEASE == 'true' }} uses: docker/login-action@v4 with: username: ${{ secrets.DOCKERHUB_USERNAME }} password: ${{ secrets.DOCKERHUB_TOKEN }} - name: Build and push run: | dockertag=$(date +%Y%m%d).${{ github.run_number }} if [[ "${{ env.IS_RELEASE }}" == "true" ]]; then cuda_archs="50;60;70;75;90" else cuda_archs="50" fi mkdir -p build/.ccache docker_args=( --file docker/Dockerfile --build-arg CUDA_ARCHITECTURES="$cuda_archs" --build-arg CCACHE_MAXSIZE="${CCACHE_MAXSIZE}" ) docker build . \ "${docker_args[@]}" \ --tag colmap/colmap:$dockertag # Extract updated build caches from the builder stage. docker build . \ "${docker_args[@]}" \ --target cache-export \ --output type=local,dest=build/cache-export rm -rf build/.ccache mv build/cache-export/.ccache build/.ccache rm -rf build/cache-export if [[ "${{ env.IS_RELEASE }}" == "true" ]]; then docker tag colmap/colmap:$dockertag colmap/colmap:latest docker push colmap/colmap:$dockertag docker push colmap/colmap:latest fi - name: Cleanup compiler cache run: | set -x sudo apt-get install -y ccache ccache --cleanup ccache --show-stats --verbose - name: Save compiler cache uses: ./.github/actions/compiler-cache with: mode: save key: ${{ steps.cache-ccache.outputs.key }} path: ${{ env.CCACHE_DIR }} cache-hit: ${{ steps.cache-ccache.outputs.cache-hit }} colmap-4.2.0/.github/workflows/build-mac.yml000066400000000000000000000057601524536416500207700ustar00rootroot00000000000000name: COLMAP (Mac) concurrency: group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} cancel-in-progress: ${{ github.event_name == 'pull_request' }} on: push: branches: - main - release/* pull_request: types: [ assigned, opened, synchronize, reopened ] release: types: [ published, edited ] jobs: build: name: ${{ matrix.config.os }} ${{ matrix.config.arch }} ${{ matrix.config.cmakeBuildType }} runs-on: ${{ matrix.config.os }} strategy: matrix: config: [ { os: macos-15, arch: arm64, cmakeBuildType: Release, }, ] env: COMPILER_CACHE_DIR: ${{ github.workspace }}/compiler-cache CCACHE_DIR: ${{ github.workspace }}/compiler-cache/ccache CCACHE_BASEDIR: ${{ github.workspace }} CCACHE_NOHASHDIR: "true" CCACHE_COMPILERCHECK: content CCACHE_MAXSIZE: 100M GLOG_v: 2 GLOG_logtostderr: 1 steps: - uses: actions/checkout@v7 - name: Restore compiler cache uses: ./.github/actions/compiler-cache id: cache-compiler with: mode: restore path: ${{ env.COMPILER_CACHE_DIR }} key-suffix: ${{ matrix.config.os }}-${{ matrix.config.arch }}-${{ matrix.config.cmakeBuildType }} - name: Setup Mac run: | brew install \ cmake \ ninja \ boost \ eigen \ openimageio \ curl \ metis \ glog \ googletest \ ceres-solver \ qt \ glew \ cgal \ sqlite3 \ ccache \ libomp brew link --force libomp ccache --zero-stats - name: Configure and build run: | export PATH="/usr/local/opt/qt/bin:$PATH" cmake --version mkdir -p build cd build cmake .. \ -GNinja \ -DCMAKE_BUILD_TYPE=${{ matrix.config.cmakeBuildType }} \ -DTESTS_ENABLED=ON \ -DWERROR_ENABLED=ON \ -DQt6_DIR="$(brew --prefix qt)/lib/cmake/Qt6" ninja - name: Run tests run: | cd build set +e ctest --output-on-failure - name: Export package run: | ./scripts/shell/build_mac_app.sh build/src/colmap/exe/colmap - name: Upload package uses: actions/upload-artifact@v7 with: name: colmap-arm64-macos path: build/src/colmap/exe/COLMAP-mac.zip - name: Cleanup compiler cache run: | set -x ccache --cleanup ccache --show-stats --verbose - name: Save compiler cache uses: ./.github/actions/compiler-cache with: mode: save key: ${{ steps.cache-compiler.outputs.key }} path: ${{ env.COMPILER_CACHE_DIR }} cache-hit: ${{ steps.cache-compiler.outputs.cache-hit }} colmap-4.2.0/.github/workflows/build-pycolmap.yml000066400000000000000000000460751524536416500220600ustar00rootroot00000000000000name: PyCOLMAP concurrency: group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} cancel-in-progress: ${{ github.event_name == 'pull_request' }} on: workflow_dispatch: push: branches: - main - release/* pull_request: types: [ assigned, opened, synchronize, reopened ] release: types: [ published, edited ] jobs: build: name: ${{ matrix.config.os }} ${{ matrix.config.arch }} ${{ matrix.config.cudaEnabled && 'CUDA' || '' }} runs-on: ${{ matrix.config.os }} strategy: matrix: config: [ {os: ubuntu-24.04, cudaEnabled: false, ccacheMaxSize: 800M}, {os: macos-14, arch: arm64, deploymentTarget: 14.0, cudaEnabled: false, ccacheMaxSize: 350M}, {os: windows-2025-vs2026, cudaEnabled: false, ccacheMaxSize: 1000M}, {os: ubuntu-24.04, cudaEnabled: true, ccacheMaxSize: 850M}, ] env: COMPILER_CACHE_DIR: ${{ github.workspace }}/compiler-cache CCACHE_DIR: ${{ github.workspace }}/compiler-cache/ccache CCACHE_BASEDIR: ${{ github.workspace }} CACHE_PLATFORM: ${{ matrix.config.os }}-arch_${{ matrix.config.arch || 'x64' }}-cuda_${{ matrix.config.cudaEnabled }}-deployment_${{ matrix.config.deploymentTarget || 'default' }} # cibuildwheel creates a different temporary build directory for every # wheel and run. Do not include that directory in compiler cache keys. CCACHE_NOHASHDIR: "true" # Hosted runner compiler mtimes can change even when their contents do # not, which would otherwise invalidate the cache. CCACHE_COMPILERCHECK: content # Keep enough history for the COLMAP and pycolmap objects without # exhausting the repository-wide GitHub Actions cache quota. CCACHE_MAXSIZE: ${{ matrix.config.ccacheMaxSize }} FETCHCONTENT_CACHE_DIR: ${{ github.workspace }}/fetchcontent-cache MACOSX_DEPLOYMENT_TARGET: ${{ matrix.config.deploymentTarget }} # For faster builds in PRs, build only two Python versions instead of all: # the oldest supported one, so that version-specific breakage does not # reach main, and cp312, the oldest version supported by the pinned Sphinx # used in the docs job, which reuses this wheel. PULL_REQUEST_CIBW_BUILD: cp3{10,12}-{macosx,manylinux,win}* steps: - name: Free disk space if: runner.os == 'Linux' && matrix.config.cudaEnabled uses: jlumbroso/free-disk-space@v1.3.1 with: tool-cache: false android: true dotnet: true haskell: true large-packages: true docker-images: true swap-storage: true - uses: actions/checkout@v7 - name: Restore compiler cache uses: ./.github/actions/compiler-cache id: cache-compiler with: mode: restore path: ${{ env.COMPILER_CACHE_DIR }} key-prefix: pycolmap-compiler key-suffix: ${{ env.CACHE_PLATFORM }} - name: Select Python if: github.event_name == 'pull_request' && runner.os != 'Windows' run: | echo "CIBW_BUILD=${PULL_REQUEST_CIBW_BUILD}" >> "$GITHUB_ENV" - name: Select Python if: ${{ github.event_name == 'pull_request' && runner.os == 'Windows' }} shell: pwsh run: | echo "CIBW_BUILD=${env:PULL_REQUEST_CIBW_BUILD}" >> "${env:GITHUB_ENV}" - name: Set env (macOS) if: runner.os == 'macOS' run: | VCPKG_TARGET_TRIPLET="arm64-osx-release" echo "VCPKG_TARGET_TRIPLET=${VCPKG_TARGET_TRIPLET}" >> "$GITHUB_ENV" VCPKG_INSTALLATION_ROOT="/Users/runner/work/vcpkg" CMAKE_TOOLCHAIN_FILE="${VCPKG_INSTALLATION_ROOT}/scripts/buildsystems/vcpkg.cmake" CMAKE_OSX_ARCHITECTURES=${{ matrix.config.arch }} echo "VCPKG_INSTALLATION_ROOT=${VCPKG_INSTALLATION_ROOT}" >> "$GITHUB_ENV" echo "CMAKE_TOOLCHAIN_FILE=${CMAKE_TOOLCHAIN_FILE}" >> "$GITHUB_ENV" echo "CMAKE_OSX_ARCHITECTURES=${CMAKE_OSX_ARCHITECTURES}" >> "$GITHUB_ENV" echo "ARCHFLAGS=-arch ${CMAKE_OSX_ARCHITECTURES}" >> "$GITHUB_ENV" # Fix: cibuildhweel cannot interpolate env variables. CONFIG_SETTINGS="cmake.define.CMAKE_TOOLCHAIN_FILE=${CMAKE_TOOLCHAIN_FILE}" CONFIG_SETTINGS="${CONFIG_SETTINGS} cmake.define.VCPKG_TARGET_TRIPLET=${VCPKG_TARGET_TRIPLET}" CONFIG_SETTINGS="${CONFIG_SETTINGS} cmake.define.VCPKG_INSTALLED_DIR=${{ github.workspace }}/build/vcpkg_installed" CONFIG_SETTINGS="${CONFIG_SETTINGS} cmake.define.CMAKE_OSX_ARCHITECTURES=${CMAKE_OSX_ARCHITECTURES}" CONFIG_SETTINGS="${CONFIG_SETTINGS} cmake.define.pybind11_DIR=${COMPILER_CACHE_DIR}/build-requirements/pybind11/share/cmake/pybind11" CONFIG_SETTINGS="${CONFIG_SETTINGS} cmake.define.WERROR_ENABLED=ON" echo "CIBW_CONFIG_SETTINGS_MACOS=${CONFIG_SETTINGS}" >> "$GITHUB_ENV" echo "FETCHCONTENT_BASE_DIR=${FETCHCONTENT_CACHE_DIR}" >> "$GITHUB_ENV" # vcpkg binary caching # !!!PLEASE!!! be nice and don't use this cache for your own purposes. This is only meant for CI purposes in this repository. VCPKG_BINARY_SOURCES="clear;x-azblob,https://colmap.blob.core.windows.net/github-actions-cache,sp=r&st=2024-12-10T17:29:32Z&se=2030-12-31T01:29:32Z&spr=https&sv=2022-11-02&sr=c&sig=bWydkilTMjRn3LHKTxLgdWrFpV4h%2Finzoe9QCOcPpYQ%3D,read" if [ -n "${{ secrets.VCPKG_BINARY_CACHE_AZBLOB_URL }}" ]; then # The secrets are only accessible in runs triggered from within the target repository and not forks. VCPKG_BINARY_SOURCES="${VCPKG_BINARY_SOURCES};x-azblob,${{ secrets.VCPKG_BINARY_CACHE_AZBLOB_URL }},${{ secrets.VCPKG_BINARY_CACHE_AZBLOB_SAS }},write" fi echo "VCPKG_BINARY_SOURCES=${VCPKG_BINARY_SOURCES}" >> "$GITHUB_ENV" - name: Set env (Windows) if: runner.os == 'Windows' shell: pwsh run: | echo "${{ env.COMPILER_CACHE_DIR }}/bin" >> "${env:GITHUB_PATH}" # The Visual Studio generator ignores CMake compiler launchers. Use # Ninja for wheel builds and persist the MSVC developer environment # that Ninja needs into the following cibuildwheel step. & "./scripts/shell/enter_vs_dev_shell.ps1" echo "PATH=${env:PATH}" >> "${env:GITHUB_ENV}" echo "INCLUDE=${env:INCLUDE}" >> "${env:GITHUB_ENV}" echo "LIB=${env:LIB}" >> "${env:GITHUB_ENV}" echo "LIBPATH=${env:LIBPATH}" >> "${env:GITHUB_ENV}" $VCPKG_INSTALLATION_ROOT = "${{ github.workspace }}/vcpkg" echo "VCPKG_INSTALLATION_ROOT=${VCPKG_INSTALLATION_ROOT}" >> "${env:GITHUB_ENV}" $CMAKE_TOOLCHAIN_FILE = "${VCPKG_INSTALLATION_ROOT}/scripts/buildsystems/vcpkg.cmake" echo "CMAKE_TOOLCHAIN_FILE=${CMAKE_TOOLCHAIN_FILE}" >> "${env:GITHUB_ENV}" $VCPKG_TARGET_TRIPLET = "x64-windows-release" echo "VCPKG_TARGET_TRIPLET=${VCPKG_TARGET_TRIPLET}" >> "${env:GITHUB_ENV}" # Fix: cibuildhweel cannot interpolate env variables. $CMAKE_TOOLCHAIN_FILE = $CMAKE_TOOLCHAIN_FILE.replace('\', '/') $VCPKG_INSTALLED_DIR = "${{ github.workspace }}/build/vcpkg_installed" $VCPKG_INSTALLED_DIR = $VCPKG_INSTALLED_DIR.replace('\', '/') $CONFIG_SETTINGS = "cmake.define.CMAKE_TOOLCHAIN_FILE=${CMAKE_TOOLCHAIN_FILE}" $CONFIG_SETTINGS = "${CONFIG_SETTINGS} cmake.define.VCPKG_TARGET_TRIPLET=${VCPKG_TARGET_TRIPLET}" $CONFIG_SETTINGS = "${CONFIG_SETTINGS} cmake.define.VCPKG_INSTALLED_DIR=${VCPKG_INSTALLED_DIR}" $PYBIND11_DIR = "${env:COMPILER_CACHE_DIR}/build-requirements/pybind11/share/cmake/pybind11".replace('\', '/') $CONFIG_SETTINGS = "${CONFIG_SETTINGS} cmake.define.pybind11_DIR=${PYBIND11_DIR}" $CONFIG_SETTINGS = "${CONFIG_SETTINGS} cmake.args=-GNinja" echo "CIBW_CONFIG_SETTINGS_WINDOWS=${CONFIG_SETTINGS}" >> "${env:GITHUB_ENV}" $CCACHE_DIR = "${env:CCACHE_DIR}".replace('\', '/') $CCACHE_BASEDIR = "${env:CCACHE_BASEDIR}".replace('\', '/') $CIBW_ENVIRONMENT = "BUILD_CUDA_ENABLED=${{ matrix.config.cudaEnabled }}" $CIBW_ENVIRONMENT = "${CIBW_ENVIRONMENT} CCACHE_DIR=${CCACHE_DIR}" $CIBW_ENVIRONMENT = "${CIBW_ENVIRONMENT} CCACHE_BASEDIR=${CCACHE_BASEDIR}" $CIBW_ENVIRONMENT = "${CIBW_ENVIRONMENT} CCACHE_NOHASHDIR=${env:CCACHE_NOHASHDIR}" $CIBW_ENVIRONMENT = "${CIBW_ENVIRONMENT} CCACHE_COMPILERCHECK=${env:CCACHE_COMPILERCHECK}" $CIBW_ENVIRONMENT = "${CIBW_ENVIRONMENT} CCACHE_MAXSIZE=${env:CCACHE_MAXSIZE}" echo "CIBW_ENVIRONMENT_WINDOWS=${CIBW_ENVIRONMENT}" >> "${env:GITHUB_ENV}" $CIBW_REPAIR_WHEEL_COMMAND = "delvewheel repair -v --add-path ${VCPKG_INSTALLED_DIR}/${VCPKG_TARGET_TRIPLET}/bin -w {dest_dir} {wheel}" echo "CIBW_REPAIR_WHEEL_COMMAND_WINDOWS=${CIBW_REPAIR_WHEEL_COMMAND}" >> "${env:GITHUB_ENV}" $FETCHCONTENT_BASE_DIR = "${env:FETCHCONTENT_CACHE_DIR}".replace('\', '/') echo "FETCHCONTENT_BASE_DIR=${FETCHCONTENT_BASE_DIR}" >> "${env:GITHUB_ENV}" # vcpkg binary caching # !!!PLEASE!!! be nice and don't use this cache for your own purposes. This is only meant for CI purposes in this repository. $VCPKG_BINARY_SOURCES = "clear;x-azblob,https://colmap.blob.core.windows.net/github-actions-cache,sp=r&st=2024-12-10T17:29:32Z&se=2030-12-31T01:29:32Z&spr=https&sv=2022-11-02&sr=c&sig=bWydkilTMjRn3LHKTxLgdWrFpV4h%2Finzoe9QCOcPpYQ%3D,read" if ("${{ secrets.VCPKG_BINARY_CACHE_AZBLOB_URL }}") { # The secrets are only accessible in runs triggered from within the target repository and not forks. $VCPKG_BINARY_SOURCES += ";x-azblob,${{ secrets.VCPKG_BINARY_CACHE_AZBLOB_URL }},${{ secrets.VCPKG_BINARY_CACHE_AZBLOB_SAS }},write" } echo "VCPKG_BINARY_SOURCES=${VCPKG_BINARY_SOURCES}" >> "${env:GITHUB_ENV}" - name: Set env (Linux) if: runner.os == 'Linux' run: | VCPKG_TARGET_TRIPLET="x64-linux-release" echo "VCPKG_TARGET_TRIPLET=${VCPKG_TARGET_TRIPLET}" >> "$GITHUB_ENV" VCPKG_INSTALLATION_ROOT="${{ github.workspace }}/vcpkg" CMAKE_TOOLCHAIN_FILE="${VCPKG_INSTALLATION_ROOT}/scripts/buildsystems/vcpkg.cmake" echo "VCPKG_INSTALLATION_ROOT=${VCPKG_INSTALLATION_ROOT}" >> "$GITHUB_ENV" echo "CMAKE_TOOLCHAIN_FILE=${CMAKE_TOOLCHAIN_FILE}" >> "$GITHUB_ENV" # Fix: cibuildhweel cannot interpolate env variables. CONFIG_SETTINGS="cmake.define.CMAKE_TOOLCHAIN_FILE=${CMAKE_TOOLCHAIN_FILE}" CONFIG_SETTINGS="${CONFIG_SETTINGS} cmake.define.VCPKG_TARGET_TRIPLET=${VCPKG_TARGET_TRIPLET}" CONFIG_SETTINGS="${CONFIG_SETTINGS} cmake.define.VCPKG_INSTALLED_DIR=/project/build/vcpkg_installed" CONFIG_SETTINGS="${CONFIG_SETTINGS} cmake.define.pybind11_DIR=/compiler-cache/build-requirements/pybind11/share/cmake/pybind11" CONFIG_SETTINGS="${CONFIG_SETTINGS} cmake.define.WERROR_ENABLED=ON" echo "CIBW_CONFIG_SETTINGS_LINUX=${CONFIG_SETTINGS}" >> "$GITHUB_ENV" # Remap caching paths to the container CONTAINER_COMPILER_CACHE_DIR="/compiler-cache" CONTAINER_FETCHCONTENT_CACHE_DIR="/fetchcontent-cache" CIBW_CONTAINER_ENGINE="docker; create_args: -v ${COMPILER_CACHE_DIR}:${CONTAINER_COMPILER_CACHE_DIR} -v ${FETCHCONTENT_CACHE_DIR}:${CONTAINER_FETCHCONTENT_CACHE_DIR}" echo "CIBW_CONTAINER_ENGINE=${CIBW_CONTAINER_ENGINE}" >> "$GITHUB_ENV" echo "CONTAINER_COMPILER_CACHE_DIR=${CONTAINER_COMPILER_CACHE_DIR}" >> "$GITHUB_ENV" echo "CCACHE_DIR=${CONTAINER_COMPILER_CACHE_DIR}/ccache" >> "$GITHUB_ENV" echo "CCACHE_BASEDIR=/project" >> "$GITHUB_ENV" echo "FETCHCONTENT_BASE_DIR=${CONTAINER_FETCHCONTENT_CACHE_DIR}" >> "$GITHUB_ENV" # vcpkg binary caching # !!!PLEASE!!! be nice and don't use this cache for your own purposes. This is only meant for CI purposes in this repository. VCPKG_BINARY_SOURCES="clear;x-azblob,https://colmap.blob.core.windows.net/github-actions-cache,sp=r&st=2024-12-10T17:29:32Z&se=2030-12-31T01:29:32Z&spr=https&sv=2022-11-02&sr=c&sig=bWydkilTMjRn3LHKTxLgdWrFpV4h%2Finzoe9QCOcPpYQ%3D,read" if [ -n "${{ secrets.VCPKG_BINARY_CACHE_AZBLOB_URL }}" ]; then # The secrets are only accessible in runs triggered from within the target repository and not forks. VCPKG_BINARY_SOURCES="${VCPKG_BINARY_SOURCES};x-azblob,${{ secrets.VCPKG_BINARY_CACHE_AZBLOB_URL }},${{ secrets.VCPKG_BINARY_CACHE_AZBLOB_SAS }},write" fi echo "VCPKG_BINARY_SOURCES=${VCPKG_BINARY_SOURCES}" >> "$GITHUB_ENV" CIBW_ENVIRONMENT_PASS_LINUX="VCPKG_TARGET_TRIPLET VCPKG_INSTALLATION_ROOT CMAKE_TOOLCHAIN_FILE VCPKG_BINARY_SOURCES CONTAINER_COMPILER_CACHE_DIR CCACHE_DIR CCACHE_BASEDIR CCACHE_NOHASHDIR CCACHE_COMPILERCHECK CCACHE_MAXSIZE FETCHCONTENT_BASE_DIR" echo "CIBW_ENVIRONMENT_PASS_LINUX=${CIBW_ENVIRONMENT_PASS_LINUX}" >> "$GITHUB_ENV" if [ "${{ matrix.config.cudaEnabled }}" = "true" ]; then CIBW_MANYLINUX_X86_64_IMAGE="pytorch/manylinux2_28-builder:cuda12.9" CIBW_REPAIR_WHEEL_COMMAND="auditwheel repair --exclude libcudart* --exclude libcurand* -w {dest_dir} {wheel}" echo "CIBW_REPAIR_WHEEL_COMMAND=${CIBW_REPAIR_WHEEL_COMMAND}" >> "$GITHUB_ENV" # Edit pyproject.toml to change package name to pycolmap-cuda12 and add CUDA Runtime native Libraries and CURAND native runtime libraries to dependencies pip install tomlkit python python/ci/update_pyproject_toml.py --name "pycolmap-cuda12" --add-deps "cuda-toolkit[cudart,curand]>=12,<13" else CIBW_MANYLINUX_X86_64_IMAGE="quay.io/pypa/manylinux_2_28_x86_64" fi echo "CIBW_MANYLINUX_X86_64_IMAGE=${CIBW_MANYLINUX_X86_64_IMAGE}" >> "$GITHUB_ENV" - name: Build wheels uses: pypa/cibuildwheel@v4.1.0 with: package-dir: ./ env: CIBW_ARCHS_MACOS: ${{ matrix.config.arch }} CIBW_ENVIRONMENT: > BUILD_CUDA_ENABLED=${{ matrix.config.cudaEnabled }} - name: Show compiler cache stats (Linux/macOS) if: always() && runner.os != 'Windows' shell: bash run: | if [ "${RUNNER_OS}" = "Linux" ] && [ -x "${COMPILER_CACHE_DIR}/bin/ccache" ]; then export CCACHE_DIR="${COMPILER_CACHE_DIR}/ccache" CCACHE="${COMPILER_CACHE_DIR}/bin/ccache" elif command -v ccache >/dev/null 2>&1; then CCACHE=ccache else echo "ccache is unavailable" exit 0 fi "${CCACHE}" --cleanup "${CCACHE}" --show-stats --verbose - name: Show compiler cache stats (Windows) if: always() && runner.os == 'Windows' shell: pwsh run: | $ccache = "${env:COMPILER_CACHE_DIR}/bin/ccache.exe" if (!(Test-Path -PathType Leaf $ccache)) { Write-Output "ccache is unavailable" exit 0 } & $ccache --cleanup & $ccache --show-stats --verbose - name: Archive wheels uses: actions/upload-artifact@v7 with: name: pycolmap-${{ matrix.config.os }}-${{ matrix.config.arch }}${{ matrix.config.cudaEnabled && '-CUDA' || '' }} path: wheelhouse/pycolmap*.whl - name: Save compiler cache uses: ./.github/actions/compiler-cache with: mode: save key: ${{ steps.cache-compiler.outputs.key }} path: ${{ env.COMPILER_CACHE_DIR }} cache-hit: ${{ steps.cache-compiler.outputs.cache-hit }} ccache-dir: ${{ env.COMPILER_CACHE_DIR }}/ccache pypi-publish: name: Publish wheels to PyPI needs: build runs-on: ubuntu-latest environment: name: pypi # This URL is only for display in the GitHub UI url: https://pypi.org/p/pycolmap permissions: id-token: write # We publish the wheel to pypi when a new tag is pushed, # either by creating a new GitHub release or explicitly with `git tag` if: ${{ github.event_name == 'release' || startsWith(github.ref, 'refs/tags') }} steps: - name: Download wheels uses: actions/download-artifact@v8 with: path: ./artifacts/ - name: Move wheels run: mkdir ./wheelhouse && mv ./artifacts/**/*.whl ./wheelhouse/ - name: Publish package uses: pypa/gh-action-pypi-publish@v1.14.0 with: skip-existing: true packages-dir: ./wheelhouse/ docs: name: Build and deploy documentation needs: build runs-on: ubuntu-24.04 steps: - uses: actions/checkout@v7 # conf.py's get_git_revision() needs a git checkout # Detect whether docs-relevant files changed (gates the deploy/preview steps). - uses: dorny/paths-filter@v3 id: filter with: filters: | docs: - 'doc/**' - 'CHANGELOG.rst' - 'python/**' # pycolmap bindings feed the autodoc API pages - uses: actions/setup-python@v5 with: python-version: '3.12' # Sphinx 9.1 needs >=3.12; matches the cp312 wheel - uses: actions/setup-node@v6 with: node-version: '24' cache: npm cache-dependency-path: doc/package-lock.json # Wheel from the `build` job (same run) so autodoc introspects a pycolmap # that matches the commit. - uses: actions/download-artifact@v8 with: name: pycolmap-ubuntu-24.04- path: wheelhouse - name: Install pycolmap + Sphinx deps run: | python -m pip install --upgrade pip pip install wheelhouse/pycolmap-*cp312*manylinux*_x86_64.whl pip install -r doc/requirements.txt - name: Install and test viewer run: | npm ci --prefix doc npm run typecheck --prefix doc python doc/tests/camera_models_registry_test.py npm test --prefix doc npx --prefix doc playwright install --with-deps chromium npm run test:browser --prefix doc - name: Build docs run: make -C doc html # -> doc/_build/html # PR dry-run: publish the generated HTML as a downloadable artifact # (no deploy) so reviewers can inspect the rendered docs. - name: Upload docs preview artifact if: github.event_name == 'pull_request' && steps.filter.outputs.docs == 'true' uses: actions/upload-artifact@v7 with: name: docs-preview path: doc/_build/html - name: Deploy to colmap.github.io if: >- github.event_name == 'push' && github.ref == 'refs/heads/main' && steps.filter.outputs.docs == 'true' uses: peaceiris/actions-gh-pages@v4 with: deploy_key: ${{ secrets.COLMAP_GITHUB_IO_DEPLOY_KEY }} external_repository: colmap/colmap.github.io publish_branch: master publish_dir: ./doc/_build/html keep_files: true commit_message: "Update docs from colmap@${{ github.sha }}" full_commit_message: "Update docs from colmap@${{ github.sha }}" colmap-4.2.0/.github/workflows/build-ubuntu.yml000066400000000000000000000324021524536416500215430ustar00rootroot00000000000000name: COLMAP (Ubuntu) concurrency: group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} cancel-in-progress: ${{ github.event_name == 'pull_request' }} on: push: branches: - main - release/* pull_request: types: [ assigned, opened, synchronize, reopened ] release: types: [ published, edited ] jobs: build: name: ${{ matrix.config.os }} ${{ matrix.config.cmakeBuildType }} ${{ matrix.config.cudaEnabled && 'CUDA' || '' }} ${{ matrix.config.asanEnabled && 'ASan' || '' }} ${{ matrix.config.coverageEnabled && 'Coverage' || '' }} runs-on: ${{ matrix.config.os }} strategy: matrix: config: [ { os: ubuntu-26.04, qtVersion: 6, cmakeBuildType: Release, asanEnabled: false, guiEnabled: true, cudaEnabled: false, casparEnabled: false, e2eTests: false, checkCodeFormat: false, coverageEnabled: false, ccacheMaxSize: 100M, }, { os: ubuntu-24.04, qtVersion: 6, cmakeBuildType: Release, asanEnabled: false, guiEnabled: true, cudaEnabled: false, casparEnabled: false, e2eTests: false, checkCodeFormat: true, coverageEnabled: true, ccacheMaxSize: 120M, }, { os: ubuntu-22.04, qtVersion: 6, cmakeBuildType: Release, asanEnabled: false, guiEnabled: true, cudaEnabled: false, casparEnabled: false, e2eTests: true, checkCodeFormat: true, coverageEnabled: false, ccacheMaxSize: 100M, }, { os: ubuntu-22.04, qtVersion: 5, cmakeBuildType: Release, asanEnabled: false, guiEnabled: false, cudaEnabled: true, casparEnabled: true, e2eTests: false, checkCodeFormat: false, coverageEnabled: false, ccacheMaxSize: 120M, }, { os: ubuntu-24.04, qtVersion: 6, cmakeBuildType: Release, asanEnabled: true, guiEnabled: false, cudaEnabled: false, casparEnabled: false, e2eTests: false, checkCodeFormat: false, coverageEnabled: false, ccacheMaxSize: 120M, }, { os: ubuntu-24.04, qtVersion: 6, cmakeBuildType: ClangTidy, asanEnabled: false, guiEnabled: false, cudaEnabled: false, casparEnabled: false, e2eTests: false, checkCodeFormat: false, coverageEnabled: false, ccacheMaxSize: 150M, }, ] env: COMPILER_CACHE_DIR: ${{ github.workspace }}/compiler-cache CCACHE_DIR: ${{ github.workspace }}/compiler-cache/ccache CCACHE_BASEDIR: ${{ github.workspace }} CCACHE_NOHASHDIR: "true" CCACHE_COMPILERCHECK: content CCACHE_MAXSIZE: ${{ matrix.config.ccacheMaxSize }} CTCACHE_DIR: ${{ github.workspace }}/compiler-cache/ctcache GLOG_v: 2 GLOG_logtostderr: 1 steps: - uses: actions/checkout@v7 - name: Check code format if: matrix.config.checkCodeFormat run: | set +x -euo pipefail python -m pip install ruff==0.15.20 clang-format==22.1.5 ./scripts/format/c++.sh --all ./scripts/format/python.sh --all git diff --name-only git diff --exit-code || (echo "Code formatting failed" && exit 1) - name: Restore compiler cache uses: ./.github/actions/compiler-cache id: cache-compiler with: mode: restore path: ${{ env.COMPILER_CACHE_DIR }} key-suffix: ${{ matrix.config.os }}-${{ matrix.config.cmakeBuildType }}-qt_${{ matrix.config.qtVersion }}-gui_${{ matrix.config.guiEnabled }}-cuda_${{ matrix.config.cudaEnabled }}-asan_${{ matrix.config.asanEnabled }}-coverage_${{ matrix.config.coverageEnabled }}-caspar_${{ matrix.config.casparEnabled }} - name: Install compiler cache run: | mkdir -p "$CCACHE_DIR" "$CTCACHE_DIR" echo "$COMPILER_CACHE_DIR/bin" >> $GITHUB_PATH if [ ! -f "$COMPILER_CACHE_DIR/bin/ccache" ]; then set -x wget https://github.com/ccache/ccache/releases/download/v4.13.6/ccache-4.13.6-linux-x86_64-glibc.tar.xz echo "508b2a1217dc6e04a23e967c7b95a0fb45d8a7e16fde9e180919698f2e2be060 ccache-4.13.6-linux-x86_64-glibc.tar.xz" | sha256sum --check tar xfv ccache-4.13.6-linux-x86_64-glibc.tar.xz mkdir -p "$COMPILER_CACHE_DIR/bin" mv ./ccache-4.13.6-linux-x86_64-glibc/ccache "$COMPILER_CACHE_DIR/bin" ctcache_commit_id="66c3614175fc650591488519333c411b2eac15a3" wget https://github.com/matus-chochlik/ctcache/archive/${ctcache_commit_id}.zip echo "108b087f156a9fe7da0c796de1ef73f5855d2a33a27983769ea39061359a40fc ${ctcache_commit_id}.zip" | sha256sum --check unzip "${ctcache_commit_id}.zip" mv ctcache-${ctcache_commit_id}/clang-tidy* "$COMPILER_CACHE_DIR/bin" fi "$COMPILER_CACHE_DIR/bin/ccache" --zero-stats - name: Setup Ubuntu run: | if [ "${{ matrix.config.qtVersion }}" == "5" ]; then qt_packages="qtbase5-dev libqt5opengl5-dev libqt5svg5-dev libcgal-qt5-dev" elif [ "${{ matrix.config.qtVersion }}" == "6" ]; then qt_packages="qt6-base-dev libqt6opengl6-dev libqt6openglwidgets6" # The Svg dev package is named qt6-svg-dev on Ubuntu 24.04+ but # libqt6svg6-dev on Ubuntu 22.04. if [ "${{ matrix.config.os }}" == "ubuntu-22.04" ]; then qt_packages="$qt_packages libqt6svg6-dev" else qt_packages="$qt_packages qt6-svg-dev" fi fi # Ubuntu's apt Boost (1.74 on 22.04, 1.83 on 24.04) predates # boost::unordered_node_map (Boost >= 1.84), so COLMAP auto-selects the # STD hash map backend here. The BOOST backend is exercised on the # macOS (brew) and Windows (vcpkg) jobs. See COLMAP_HASH_MAP_BACKEND. sudo apt-get update && sudo apt-get install -y \ build-essential \ cmake \ ninja-build \ libboost-program-options-dev \ libboost-graph-dev \ libboost-system-dev \ libeigen3-dev \ libceres-dev \ libopenimageio-dev \ openimageio-tools \ libsuitesparse-dev \ libmetis-dev \ libgoogle-glog-dev \ libgtest-dev \ libgmock-dev \ libsqlite3-dev \ libglew-dev \ $qt_packages \ libcgal-dev \ libgl1-mesa-dri \ libunwind-dev \ libcurl4-openssl-dev \ libmkl-full-dev \ xvfb # Fix issue in Ubuntu's openimageio CMake config. # We don't depend on any of openimageio's OpenCV functionality, # but it still requires the OpenCV include directory to exist. sudo mkdir -p /usr/include/opencv4 if [ "${{ matrix.config.cudaEnabled }}" == "true" ]; then if [ "${{ matrix.config.os }}" == "ubuntu-22.04" ]; then sudo apt-get install -y \ nvidia-cuda-toolkit \ nvidia-cuda-toolkit-gcc \ gcc-10 g++-10 echo "CC=/usr/bin/gcc-10" >> $GITHUB_ENV echo "CXX=/usr/bin/g++-10" >> $GITHUB_ENV echo "CUDAHOSTCXX=/usr/bin/g++-10" >> $GITHUB_ENV fi fi if [ "${{ matrix.config.asanEnabled }}" == "true" ]; then sudo apt-get install -y clang-18 libomp-18-dev echo "CC=/usr/bin/clang-18" >> $GITHUB_ENV echo "CXX=/usr/bin/clang++-18" >> $GITHUB_ENV fi if [ "${{ matrix.config.cmakeBuildType }}" == "ClangTidy" ]; then sudo apt-get install -y clang-18 clang-tidy-18 libomp-18-dev echo "CC=/usr/bin/clang-18" >> $GITHUB_ENV echo "CXX=/usr/bin/clang++-18" >> $GITHUB_ENV fi if [ "${{ matrix.config.coverageEnabled }}" == "true" ]; then sudo apt-get install -y gcovr fi - name: Configure and build run: | set -x cmake --version mkdir -p build cd build cmake .. \ -GNinja \ -DCMAKE_BUILD_TYPE=${{ matrix.config.cmakeBuildType }} \ -DCMAKE_INSTALL_PREFIX=./install \ -DCMAKE_CUDA_ARCHITECTURES=75 \ -DTESTS_ENABLED=ON \ -DWERROR_ENABLED=ON \ -DCUDA_ENABLED=${{ matrix.config.cudaEnabled }} \ -DCASPAR_ENABLED=${{ matrix.config.casparEnabled }} \ -DGUI_ENABLED=${{ matrix.config.guiEnabled }} \ -DASAN_ENABLED=${{ matrix.config.asanEnabled }} \ -DCOVERAGE_ENABLED=${{ matrix.config.coverageEnabled }} \ -DBLA_VENDOR=Intel10_64lp ninja -k 10000 - name: Install and build sample if: ${{ matrix.config.cmakeBuildType != 'ClangTidy' && !matrix.config.asanEnabled && !matrix.config.coverageEnabled }} run: | set -x cd build ninja install cd ../doc/sample-project mkdir build cd build export colmap_DIR=${{ github.workspace }}/build/install/share/colmap cmake .. \ -GNinja \ -DCMAKE_CUDA_ARCHITECTURES=75 ninja ./hello_world --message "world" - name: Run tests if: ${{ matrix.config.cmakeBuildType != 'ClangTidy' }} run: | if [ "${{ matrix.config.cudaEnabled }}" == "true" ]; then ctestExclusions="(feature/sift_test)|(mvs/gpu_mat_test)" fi if [ "${{ matrix.config.casparEnabled }}" == "true" ]; then ctestExclusions="${ctestExclusions}|(estimators/bundle_adjustment_caspar_test)" fi export DISPLAY=":99.0" export QT_QPA_PLATFORM="offscreen" Xvfb :99 & sleep 3 cd build ctest -E "$ctestExclusions" --output-on-failure - name: Run E2E tests if: matrix.config.e2eTests run: | export DISPLAY=":99.0" export QT_QPA_PLATFORM="offscreen" Xvfb :99 & sleep 3 sudo apt install 7zip mkdir eth3d_benchmark # Error thresholds in degrees and meters, # as the ETH3D groundtruth has metric scale. GLOG_v=1 python ./python/ci/test_regression_eth3d.py \ --workspace_path ./eth3d_benchmark \ --colmap_path ./build/src/colmap/exe/colmap \ --dataset_names boulders door \ --max_rotation_error 1.0 \ --max_proj_center_error 0.05 \ --use_cpu - name: Generate coverage report if: matrix.config.coverageEnabled run: | set -x cd build ../scripts/shell/generate_coverage_report.sh - name: Upload coverage HTML report uses: actions/upload-artifact@v7 if: matrix.config.coverageEnabled with: name: code-coverage path: build/coverage-html - name: Generate Github code coverage report if: matrix.config.coverageEnabled uses: irongut/CodeCoverageSummary@v1.3.0 with: filename: build/coverage-cobertura.xml badge: true fail_below_min: false format: markdown hide_branch_rate: false hide_complexity: true indicators: true output: both thresholds: '75 90' # TODO: Add code coverage comment to PR. Currently, this action reports # coverage for the entire repository, not just the changed files in the PR. # We could manually filter the coverage report to only include the changed # files in the PR, but this is non-trivial and may not be worth the effort. # - name: Add Github PR code coverage comment # uses: marocchino/sticky-pull-request-comment@v2 # if: ${{ matrix.config.coverageEnabled && github.event_name == 'pull_request' }} # with: # recreate: true # path: code-coverage-results.md - name: Cleanup compiler cache run: | set -x ccache --cleanup ccache --show-stats --verbose echo "Size of ctcache before: $(du -sh $CTCACHE_DIR)" echo "Number of ctcache files before: $(find $CTCACHE_DIR | wc -l)" # Delete cache older than 10 days. find "$CTCACHE_DIR"/*/ -mtime +10 -print0 | xargs -0 rm -rf echo "Size of ctcache after: $(du -sh $CTCACHE_DIR)" echo "Number of ctcache files after: $(find $CTCACHE_DIR | wc -l)" - name: Save compiler cache uses: ./.github/actions/compiler-cache with: mode: save key: ${{ steps.cache-compiler.outputs.key }} path: ${{ env.COMPILER_CACHE_DIR }} cache-hit: ${{ steps.cache-compiler.outputs.cache-hit }} colmap-4.2.0/.github/workflows/build-windows.yml000066400000000000000000000247471524536416500217300ustar00rootroot00000000000000name: COLMAP (Windows) concurrency: group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} cancel-in-progress: ${{ github.event_name == 'pull_request' }} on: push: branches: - main - release/* pull_request: types: [ assigned, opened, synchronize, reopened ] release: types: [ published, edited ] jobs: build: name: ${{ matrix.config.os }} ${{ matrix.config.cmakeBuildType }} ${{ matrix.config.cudaEnabled && 'CUDA' || '' }} runs-on: ${{ matrix.config.os }} strategy: matrix: config: [ { os: windows-2025-vs2026, cmakeBuildType: Release, cudaEnabled: true, testsEnabled: true, exportPackage: true, ccacheMaxSize: 200M, }, { os: windows-2025-vs2026, cmakeBuildType: Release, cudaEnabled: false, testsEnabled: true, exportPackage: true, ccacheMaxSize: 300M, }, ] env: COMPILER_CACHE_DIR: ${{ github.workspace }}/compiler-cache CCACHE_DIR: ${{ github.workspace }}/compiler-cache/ccache CCACHE_BASEDIR: ${{ github.workspace }} CCACHE_NOHASHDIR: "true" CCACHE_COMPILERCHECK: content CCACHE_MAXSIZE: ${{ matrix.config.ccacheMaxSize }} GLOG_v: 2 GLOG_logtostderr: 1 CUDA_MAJOR_VERSION: 13 CUDA_MINOR_VERSION: 2 CUDA_PATCH_VERSION: 0 steps: - uses: actions/checkout@v7 # We define the vcpkg binary sources using separate variables for read and # write operations: # * Read sources are defined as inline. These can be read by anyone and, # in particular, pull requests from forks. Unfortunately, we cannot # define these as action environment variables. See: # https://github.com/orgs/community/discussions/44322 # * Write sources are defined as action secret variables. These cannot be # read by pull requests from forks but only from pull requests from # within the target repository (i.e., created by a repository owner). # This protects us from malicious actors accessing our secrets and # gaining write access to our binary cache. For more information, see: # https://securitylab.github.com/resources/github-actions-preventing-pwn-requests/ - name: Setup vcpkg binary cache shell: pwsh run: | # !!!PLEASE!!! be nice and don't use this cache for your own purposes. This is only meant for CI purposes in this repository. $VCPKG_BINARY_SOURCES = "clear;x-azblob,https://colmap.blob.core.windows.net/github-actions-cache,sp=r&st=2024-12-10T17:29:32Z&se=2030-12-31T01:29:32Z&spr=https&sv=2022-11-02&sr=c&sig=bWydkilTMjRn3LHKTxLgdWrFpV4h%2Finzoe9QCOcPpYQ%3D,read" if ("${{ secrets.VCPKG_BINARY_CACHE_AZBLOB_URL }}") { # The secrets are only accessible in runs triggered from within the target repository and not forks. $VCPKG_BINARY_SOURCES += ";x-azblob,${{ secrets.VCPKG_BINARY_CACHE_AZBLOB_URL }},${{ secrets.VCPKG_BINARY_CACHE_AZBLOB_SAS }},write" } echo "VCPKG_BINARY_SOURCES=${VCPKG_BINARY_SOURCES}" >> "${env:GITHUB_ENV}" - name: Restore compiler cache uses: ./.github/actions/compiler-cache id: cache-compiler with: mode: restore path: ${{ env.COMPILER_CACHE_DIR }} key-suffix: ${{ matrix.config.os }}-${{ matrix.config.cmakeBuildType }}-cuda_${{ matrix.config.cudaEnabled }} - name: Install compile cache shell: pwsh run: | New-Item -ItemType Directory -Force -Path "${{ env.CCACHE_DIR }}" echo "${{ env.COMPILER_CACHE_DIR }}/bin" | Out-File -Encoding utf8 -Append -FilePath $env:GITHUB_PATH if (!(Test-Path -PathType Leaf "${{ env.COMPILER_CACHE_DIR }}/bin/ccache.exe")) { .github/workflows/install-ccache.ps1 -Destination "${{ env.COMPILER_CACHE_DIR }}/bin" } & "${{ env.COMPILER_CACHE_DIR }}/bin/ccache.exe" --zero-stats - name: Install CUDA uses: Jimver/cuda-toolkit@v0.2.35 if: matrix.config.cudaEnabled id: cuda-toolkit with: cuda: '${{ env.CUDA_MAJOR_VERSION }}.${{ env.CUDA_MINOR_VERSION }}.${{ env.CUDA_PATCH_VERSION }}' # CUDA 13 reorganized the toolkit, so the compiler now needs four # extra components beyond the old list: crt (crt/host_config.h, # included by cuda_runtime.h), nvvm (the cicc device compiler + # libdevice; no longer bundled in the nvcc subpackage), # nvptxcompiler, and thrust (Thrust/CUB headers, on Windows the # subpackage is "thrust", not "cccl"). We avoid a full install to # skip the large math/Nsight subpackages COLMAP does not use. sub-packages: '["nvcc", "crt", "nvvm", "nvptxcompiler", "thrust", "nvtx", "cudart", "curand", "curand_dev", "nvrtc_dev"]' method: 'network' - name: Setup vcpkg shell: pwsh run: | ./scripts/shell/enter_vs_dev_shell.ps1 cd ${{ github.workspace }} git clone https://github.com/microsoft/vcpkg cd vcpkg ./bootstrap-vcpkg.bat - name: Install CMake and Ninja uses: lukka/get-cmake@latest with: # CMake >= 4.1 knows the CUDA 13 architecture set, so "all-major" # expands to Turing..Blackwell (75..120). Older CMake predates CUDA 13 # and would expand it to a stale set including Maxwell/Pascal/Volta # (compute_50/60/70), which CUDA 13 no longer supports. cmakeVersion: "4.3.2" ninjaVersion: "1.12.1" - name: Configure and build shell: pwsh run: | ./scripts/shell/enter_vs_dev_shell.ps1 cd ${{ github.workspace }} ./vcpkg/vcpkg.exe integrate install mkdir -Force build cd build if ($${{ matrix.config.cudaEnabled }}) { # The windows-2025 runner ships a newer MSVC toolset (VS 18, # _MSC_VER 19.5x) than older CUDA host_config.h versions whitelist, # which otherwise aborts CUDA compiler detection with "unsupported # Microsoft Visual Studio version". Kept as a safety net in case the # pinned CUDA toolkit still lags the runner's MSVC; it is a no-op # when the host compiler is already supported. CMake reads CUDAFLAGS # during CUDA compiler detection. $env:CUDAFLAGS = "-allow-unsupported-compiler" } cmake .. ` -GNinja ` -DCMAKE_MAKE_PROGRAM=ninja ` -DCMAKE_BUILD_TYPE=Release ` -DTESTS_ENABLED=${{ matrix.config.testsEnabled }} ` -DGUI_ENABLED=ON ` -DCUDA_ENABLED=${{ matrix.config.cudaEnabled }} ` -DCMAKE_CUDA_ARCHITECTURES=all-major ` -DCUDAToolkit_ROOT="${{ steps.cuda-toolkit.outputs.CUDA_PATH }}" ` -DCMAKE_TOOLCHAIN_FILE="${{ github.workspace }}/vcpkg/scripts/buildsystems/vcpkg.cmake" ` -DVCPKG_TARGET_TRIPLET=x64-windows-release ` -DVCPKG_USE_LEGACY_APPLOCAL=ON ` -DCMAKE_INSTALL_PREFIX=install ninja - name: Run tests shell: pwsh run: | ./vcpkg/vcpkg.exe integrate install cd build $EXCLUDED_TESTS = "(feature/sift_test)|(util/opengl_utils_test)|(mvs/gpu_mat_test)" ctest -E ${EXCLUDED_TESTS} --output-on-failure - name: Export package if: matrix.config.exportPackage shell: pwsh run: | ./vcpkg/vcpkg.exe integrate install cd build ninja install ../vcpkg/vcpkg.exe install ` --triplet=x64-windows-release ` --x-feature=gui ` --x-feature=cgal ` $(if ($${{ matrix.config.testsEnabled }}) { echo "--x-feature=tests" }) ` $(if ($${{ matrix.config.cudaEnabled }}) { echo "--x-feature=cuda" }) ../vcpkg/vcpkg.exe export --raw --output-dir vcpkg_export --output colmap cp vcpkg_export/colmap/installed/x64-windows/bin/*.dll install/bin cp vcpkg_export/colmap/installed/x64-windows-release/bin/*.dll install/bin # Deploy the Qt plugins from the x64-windows-release triplet, which is # the triplet COLMAP is actually built against. qtsvg is only installed # for that triplet, so copying from x64-windows would miss the SVG # plugins (imageformats/qsvg.dll and iconengines/qsvgicon.dll) that Qt # needs to render the SVG toolbar/menu icons, leaving them blank. cp -r vcpkg_export/colmap/installed/x64-windows-release/Qt6/plugins install # Fail loudly if the SVG plugins are missing: without them Qt cannot # render the SVG icons and the toolbar/menu icons show up blank, but # the build itself succeeds, so the regression is otherwise silent. if (-not (Test-Path install/plugins/iconengines/qsvgicon.dll)) { throw "Missing Qt SVG icon engine plugin (iconengines/qsvgicon.dll); SVG icons would not render." } if (-not (Test-Path install/plugins/imageformats/qsvg.dll)) { throw "Missing Qt SVG image format plugin (imageformats/qsvg.dll); SVG icons would not render." } if ($${{ matrix.config.cudaEnabled }}) { cp "${{ steps.cuda-toolkit.outputs.CUDA_PATH }}/bin/cudart64_*.dll" install/bin cp "${{ steps.cuda-toolkit.outputs.CUDA_PATH }}/bin/curand64_*.dll" install/bin } Remove-Item -Recurse -Force -ErrorAction SilentlyContinue install/include,install/lib,install/share - name: Upload package uses: actions/upload-artifact@v7 if: ${{ matrix.config.exportPackage && matrix.config.cudaEnabled }} with: name: colmap-x64-windows-cuda path: build/install - name: Upload package uses: actions/upload-artifact@v7 if: ${{ matrix.config.exportPackage && !matrix.config.cudaEnabled }} with: name: colmap-x64-windows-nocuda path: build/install - name: Cleanup compiler cache shell: pwsh run: | ccache --cleanup ccache --show-stats --verbose - name: Save compiler cache uses: ./.github/actions/compiler-cache with: mode: save key: ${{ steps.cache-compiler.outputs.key }} path: ${{ env.COMPILER_CACHE_DIR }} cache-hit: ${{ steps.cache-compiler.outputs.cache-hit }} colmap-4.2.0/.github/workflows/install-ccache.ps1000066400000000000000000000023741524536416500217050ustar00rootroot00000000000000[CmdletBinding()] param ( [Parameter(Mandatory = $true)] [string] $Destination ) $version = "4.13.6" $folder="ccache-$version-windows-x86_64" $url = "https://github.com/ccache/ccache/releases/download/v$version/$folder.zip" $expectedSha256 = "3D7CEBB05850AD704E197B3F1D3F0F924AB6C9FDFC561578E146184FE9D89380" $ErrorActionPreference = "Stop" Set-StrictMode -Version Latest $PSNativeCommandUseErrorActionPreference = $true try { New-Item -Path "$Destination" -ItemType Container -ErrorAction SilentlyContinue Write-Host "Download CCache" $zipFilePath = Join-Path "$env:TEMP" "$folder.zip" Invoke-WebRequest -Uri $url -UseBasicParsing -OutFile "$zipFilePath" -MaximumRetryCount 3 $hash = Get-FileHash $zipFilePath -Algorithm "sha256" if ($hash.Hash -ne $expectedSha256) { throw "File $Path hash $hash.Hash did not match expected hash $expectedHash" } Write-Host "Unzip CCache" Expand-Archive -Path "$zipFilePath" -DestinationPath "$env:TEMP" Write-Host "Move CCache" Move-Item -Force "$env:TEMP/$folder/ccache.exe" "$Destination" Remove-Item "$zipFilePath" Remove-Item -Recurse "$env:TEMP/$folder" } catch { Write-Host "Installation failed with an error" $_.Exception | Format-List exit -1 } colmap-4.2.0/.gitignore000077500000000000000000000012521524536416500147760ustar00rootroot00000000000000# Custom files .python-version LocalConfig.cmake src/colmap/util/version.cc CMakeUserPresets.json .clangd compile_commands.json .claude/settings.local.json # Custom directories .idea/ .vscode .vs .DS_Store .cache build*/ install*/ data/ benchmark/reconstruction/data/ benchmark/reconstruction/runs/ doc/_build doc/node_modules/ doc/playwright-report/ doc/_static/viewer/ doc/test-results/ vcpkg_installed/ # Compiled Object files *.slo *.lo *.o *.obj *.pyc # Precompiled Headers *.gch *.pch # Compiled Dynamic libraries *.so *.dylib *.dll # Fortran module files *.mod # Compiled Static libraries *.lai *.la *.a *.lib # Executables *.exe *.out *.app # backup files *~ *.orig colmap-4.2.0/.gitmodules000066400000000000000000000000001524536416500151460ustar00rootroot00000000000000colmap-4.2.0/AGENTS.md000066400000000000000000000235411524536416500143130ustar00rootroot00000000000000# AGENTS.md — COLMAP Guide ## Project Overview COLMAP is a general-purpose Structure-from-Motion (SfM) and Multi-View Stereo (MVS) pipeline that reconstructs 3D models from 2D image collections. Written in C++17 with optional CUDA support. Single binary (colmap) with many subcommands, a Qt GUI, and Python bindings (pycolmap). ## Directory Structure | Path | Description | |------|-------------| | CMakeLists.txt | Root build config | | cmake/CMakeHelper.cmake | COLMAP_ADD_LIBRARY / _EXECUTABLE / _TEST macros | | cmake/FindDependencies.cmake | All dependency discovery (Eigen, Ceres, CUDA, Qt, etc.) | | cmake/Find*.cmake | Custom find modules | | src/colmap/ | Primary C++ source (see Architecture below) | | src/pycolmap/ | pybind11 C++ bindings | | src/thirdparty/ | Bundled (VLFeat, SiftGPU, PoissonRecon, LSD) and fetched (PoseLib, faiss, ONNX Runtime) | | python/pycolmap/ | Python package (__init__.py, utilities) | | python/CMakeLists.txt | scikit-build-core build for pycolmap | | doc/ | Sphinx/RST documentation | | docker/ | Dockerfile + build/run scripts | | scripts/format/ | c++.sh (clang-format), python.sh (ruff) | | benchmark/ | Reconstruction + runtime benchmarks | | .github/workflows/ | CI: Ubuntu, macOS, Windows, Docker, pycolmap | | vcpkg.json | vcpkg manifest (Windows/macOS deps) | | pyproject.toml | Python build config (scikit-build-core, cibuildwheel) | | .clang-format | C++ formatting style | | ruff.toml | Python linting/formatting config | ## Module Dependency Layers (bottom → top) | Module | Description | |--------|-------------| | util/ | Threading, logging, caching, PLY I/O, CUDA/OpenGL helpers | | math/ | Random, polynomials, graph algorithms (cuts, union-find, spanning trees) | | geometry/ | Rigid3d, Sim3d, essential/homography matrices, triangulation, GPS | | sensor/ | Camera distortion models, Bitmap (image I/O), Rig, sensor specs DB | | feature/ | SIFT (CPU/GPU), ALIKED (ONNX), LightGlue, descriptor indexing (FAISS) | | optim/ | RANSAC, LO-RANSAC, SPRT, samplers, support measurers | | scene/ | Camera, Image, Frame, Point2D/3D, Track, Reconstruction, Database (SQLite), CorrespondenceGraph | | estimators/ | Bundle adjustment (Ceres), absolute/relative pose, two-view geometry, triangulation, alignment | | estimators/solvers/ | Minimal solvers: P3P, 5-pt essential, 7/8-pt fundamental, homography (via PoseLib) | | estimators/cost_functions/ | Ceres cost functors: reprojection, Sampson, alignment, pose prior | | sfm/ | IncrementalMapper, GlobalMapper, IncrementalTriangulator, ObservationManager | | mvs/ | PatchMatch stereo (CUDA), depth/normal maps, fusion, meshing | | image/ | Image undistortion, warping, line detection | | retrieval/ | Vocabulary tree (VisualIndex), inverted index, vote-and-verify | | controllers/ | AutomaticReconstruction, IncrementalPipeline, GlobalPipeline, HierarchicalPipeline, OptionManager | | exe/ | CLI command implementations (colmap.cc dispatcher + per-domain .cc files) | | ui/ | Qt GUI: MainWindow, ModelViewerWidget, OpenGL painters, config dialogs | ## Key Classes & Files | Class/File | Location | Purpose | |------------|----------|---------| | Reconstruction | scene/reconstruction.h | Top-level container: cameras, rigs, images, frames, points, tracks | | Camera | scene/camera.h | Intrinsics (focal, principal pt, distortion model) | | Rig | sensor/rig.h | Multi-sensor rig with sensor_from_rig transforms | | Image | scene/image.h | Exposure: name, Point2D observations, camera_id, frame_id | | Frame | scene/frame.h | Posed rig instantiation: rig_from_world + sensor data | | Point3D | scene/point3d.h | Triangulated 3D point: xyz, color, error, Track | | Track | scene/track.h | List of (image_id, point2D_idx) observations | | Database | scene/database.h | Abstract DB interface (SQLite impl in database_sqlite.h) | | DatabaseCache | scene/database_cache.h | In-memory cache + CorrespondenceGraph | | CorrespondenceGraph | scene/correspondence_graph.h | Feature-to-feature correspondences across images | | Rigid3d | geometry/rigid3.h | 6-DOF rigid transform (quaternion + translation) | | Sim3d | geometry/sim3.h | 7-DOF similarity transform (Rigid3d + scale) | | FeatureExtractor | feature/extractor.h | Abstract extractor (SIFT, ALIKED); factory Create() | | FeatureMatcher | feature/matcher.h | Abstract matcher; supports Match() and MatchGuided() | | FeatureKeypoint | feature/types.h | x, y + affine shape (a11, a12, a21, a22) | | BundleAdjuster | estimators/bundle_adjustment.h | Abstract BA; Ceres impl via CreateDefaultBundleAdjuster() | | BundleAdjustmentConfig | estimators/bundle_adjustment.h | What to optimize vs. hold constant | | EstimateAbsolutePose() | estimators/pose.h | P3P RANSAC from 2D-3D correspondences | | EstimateTwoViewGeometry() | estimators/two_view_geometry.h | Essential/fundamental/homography estimation | | EstimateTriangulation() | estimators/triangulation.h | Robust multi-view triangulation | | IncrementalMapper | sfm/incremental_mapper.h | Core incremental SfM engine | | GlobalMapper | sfm/global_mapper.h | Global SfM (rotation averaging + global positioning) | | IncrementalTriangulator | sfm/incremental_triangulator.h | Point creation, track merging/completion | | ObservationManager | sfm/observation_manager.h | Per-image visibility stats, filtering | | PatchMatch | mvs/patch_match.h | CPU wrapper for CUDA PatchMatch stereo | | PatchMatchController | mvs/patch_match.h | Orchestrates multi-GPU depth estimation | | StereoFusion | mvs/fusion.h | Fuses depth maps into 3D point cloud | | AutomaticReconstructionController | controllers/automatic_reconstruction.h | End-to-end pipeline (extract, match, SfM, MVS) | | IncrementalPipeline | controllers/incremental_pipeline.h | Manages incremental SfM loop + multi-model | | OptionManager | controllers/option_manager.h | Centralized CLI option parsing | | Camera models | sensor/models.h | SimplePinhole, Radial, OpenCV, Fisheye, etc. | | Bitmap | sensor/bitmap.h | Image I/O wrapper (OpenImageIO), EXIF extraction | CLI entry point: exe/colmap.cc (subcommand dispatcher). ## Build Instructions ```bash mkdir build && cd build cmake .. -GNinja -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=../install ninja # No GUI, no CUDA (minimal) cmake .. -GNinja -DCMAKE_BUILD_TYPE=Release -DGUI_ENABLED=OFF -DCUDA_ENABLED=OFF # With tests cmake .. -GNinja -DCMAKE_BUILD_TYPE=Release -DTESTS_ENABLED=ON ``` ### Build pycolmap First build and install the C++ code: ```bash mkdir build && cd build cmake .. -GNinja -DCMAKE_INSTALL_PREFIX=../install ninja install ``` Then build pycolmap (from the repo root): ```bash colmap_DIR=./install ./python/incremental_build.sh # Fast incremental build colmap_DIR=./install ./python/build.sh # Clean build (slower) ``` If there is a local `.python-version` file, use pyenv/uv for Python commands, `pip install`, and building pycolmap. ## Testing Follow C++ and Python build instructions above. Run ctest from the build directory: ```bash cd build ctest --output-on-failure # All C++ tests ctest -R "util/cache_test" # Specific test ctest -E "(feature/sift_test)" # Exclude GPU tests ``` Run Python tests from the repo root: ```bash pytest # All Python tests (config in pyproject.toml) ``` - Test files (`*_test.cc`) across all modules, created via `COLMAP_ADD_TEST()` macro - Framework: GTest/GMock with custom main (`util/gtest_main.cc`) - Test utilities: `util/testing.h`, Eigen matchers: `util/eigen_matchers.h`, transform matchers: `geometry/rigid3_matchers.h`, `geometry/sim3_matchers.h` - CTest names: `module/test_name` (e.g., `estimators/alignment_test`) ## Code Style & Conventions ### Naming - Classes: `PascalCase` - Methods/functions: `PascalCase` (e.g., `FindNextImages()`) - Member variables: `snake_case_` (trailing underscore) - Local variables: `snake_case` - Constants/enums: `kPascalCase` or `UPPER_SNAKE_CASE` - Files: `snake_case.h` / `snake_case.cc` / `snake_case_test.cc` - Transforms: `target_from_source` (e.g., `cam_from_world`) - Coordinates: `x_in_y` (e.g., `point3D_in_world`) ### Index and Identifier Types (util/types.h) - Generic indexes: int, size_t - Special identifiers: camera_t, image_t, image_pair_t, frame_t, rig_t, point2D_t, point3D_t, sensor_t, data_t, pose_prior_t ### Hash containers (util/hash_containers.h) Use the `colmap::{Flat,Node}Hash{Map,Set}` aliases, not `std::unordered_map/set`. - `FlatHashMap`/`FlatHashSet` — default; fastest, but invalidate element references/iterators on insert/erase, so no iterator-erase loops (erase by key). - `NodeHashMap`/`NodeHashSet` — reference-stable; use when a reference/pointer/ iterator to an element is held across a mutation of the same container (e.g. `Reconstruction::points3D_`). Custom keys reuse the `std::hash` specializations / `PairHash` in `util/types.h`. Keep `std::map`/`std::set` only when sorted iteration is required (deterministic output, Ceres block order, `lower_bound`). ### Formatting ```bash scripts/format/c++.sh scripts/format/python.sh ``` ## External Dependencies ### Core | Library | Role | |---------|------| | Eigen3 | Linear algebra, matrices, geometry | | Ceres Solver | Nonlinear optimization | | Boost | Graph algorithms, CLI options, etc. | | glog | Structured logging | | SQLite3 | Feature/match database | | OpenImageIO | Image I/O and processing | | CHOLMOD | Sparse Cholesky | | Metis | Graph partitioning | | PoseLib | Minimal pose solvers | | FAISS | Fast ANN for descriptor matching | ### Optional | Library | Role | Gate | |---------|------|------| | CUDA | GPU PatchMatch, SiftGPU, Ceres GPU BA | CUDA_ENABLED | | ONNX Runtime | ALIKED, LightGlue neural features | ONNX_ENABLED | | Qt5/6 | GUI | GUI_ENABLED | | OpenGL/GLEW | 3D visualization, SiftGPU | OPENGL_ENABLED | | CGAL | Delaunay meshing | CGAL_ENABLED | ### Bundled (src/thirdparty/) | Library | Role | |---------|------| | VLFeat | CPU SIFT | | SiftGPU | GPU SIFT | | PoissonRecon | Surface reconstruction | | LSD | Line detection | colmap-4.2.0/CHANGELOG.rst000066400000000000000000005513701524536416500150370ustar00rootroot00000000000000Changelog ========= ------------------------- COLMAP 4.2.0 (08/31/2026) ------------------------- New Features ------------ * Added multi-component support to the global mapper. Disconnected view-graph components are reconstructed independently and returned as separate models, including components revealed after filtering outlier relative rotations. The behavior is controlled by ``GlobalMapper.multiple_models`` and ``GlobalMapper.min_model_size`` and is also available through pycolmap. * Added LoMa learned feature extraction and matching through ONNX, including ``LOMA_B`` and ``LOMA_B128`` descriptors, brute-force matching, and multiple dedicated matcher variants. LoMa is available through the CLI, GUI, and pycolmap, with optional BF16 inference. * Added ROCm/HIP acceleration for ``patch_match_stereo``, enabling dense reconstruction on supported AMD GPUs through the ``HIP_ENABLED`` build option. CUDA and HIP builds are mutually exclusive. * Added a browser-based, local-only 3D viewer for sparse binary reconstructions, including camera and point inspection, source images, and reprojections. * Added incremental, global, and hierarchical mapper selection to the GUI, with mapper-specific configuration and progress rendering. * Added complete pycolmap bindings for hierarchical mapping, including ``hierarchical_mapping``, ``HierarchicalPipeline``, and scene-clustering options. Expanded the global pipeline bindings and callbacks. * Added 6-point shared-focal and one-sided-focal relative-pose solvers, improving reconstruction when camera intrinsics are unknown or only partially known. * Added optional DEGENSAC fundamental-matrix estimation for scenes dominated by a plane. It is disabled by default and available through ``TwoViewGeometry.use_degensac``. * Added nanosecond-resolution timestamps and conversion utilities in C++ and pycolmap. * Added graceful shutdown for long-running CLI processing pipelines. A first ``SIGINT`` or ``SIGTERM`` preserves usable intermediate results, while a second signal terminates immediately. PyCOLMAP exposes the same cooperative cancellation through ``CancellationToken``. Improvements ------------ * Replaced Sampson error on camera bearings with pixel-consistent tangent Sampson error for calibrated two-view geometry, relative-pose refinement, guided matching, and generalized pose estimation. This particularly improves wide-field-of-view and spherical cameras. * Added camera and image-space point overloads for ``pycolmap.estimate_relative_pose`` and ``refine_relative_pose`` while retaining the camera-ray overloads for backwards compatibility. * Estimate homographies on camera rays for distorted and spherical cameras, enabling geometrically correct planar and panoramic detection. * Refine fundamental matrices with Sampson error by default and use MSAC for robust two-view geometry estimation. Significantly improved accuracy for recovered two-view geometries with small improvements on e2e metrics. * Added analytical reprojection Jacobians for every camera model and fixed-pose bundle adjustment. Benchmarks show approximately 1.2--1.55x faster incremental mapping for common pinhole and OpenCV camera configurations. * Added selectable standard or Boost hash-map backends. Recent Boost versions can improve incremental-mapping performance, especially for large scenes. * Optimized image warping for modest downscales, with reported 4--7x improvements in common undistortion cases. Added configurable nearest-neighbor or bilinear interpolation in C++ and pycolmap. * Accelerated image preprocessing by using antialiased triangle filtering for bilinear bitmap downscaling and vectorizing RGB-to-grayscale conversion. Bitmap rescaling now also honors the requested bilinear or box filter. * Added CoreML as an ONNX execution provider on macOS, with automatic CPU fallback for unsupported models. * Added optional match-count weighting for global rotation averaging. * Improved sequential pairing for camera rigs by avoiding unrelated cross-sensor temporal pairs. * Added ``SequentialMatching.loop_detection_min_index_distance`` to exclude nearby frames from sequential loop detection without consuming the retrieval budget. * Added configurable loading of all images during image registration. * Expanded reconstruction benchmarks with TartanAir panoramas, IMC2025 datasets, multi-seed comparisons, and end-to-end incremental-mapping benchmarks. * Modernized the documentation site, deployment, landing page, mobile installation selector, and camera-model documentation. Bug Fixes --------- * Fix pose-prior alignment and scale preservation for multi-camera rigs. * Fix spatial matching with missing pose priors and large projected coordinates. * Fix hierarchical clustering overlap propagation and leaf-size enforcement. * Fix corrupted PatchMatch source textures when input images have different widths. * Fix guided matching for spherical cameras. * Fix Caspar option handling and the fixed-rotation stage of global bundle adjustment. * Fall back to CPU Ceres bundle adjustment when CUDA support is compiled in but no compatible GPU is available. * Fix reconstruction merges with inconsistent image ID/name mappings. * Fix point-triangulator image-list handling. * Restore the ``gflags::gflags`` target for downstream CMake consumers. * Fix stale reconstruction statistics and elapsed time in the viewer after clearing a reconstruction. * Fix dense reconstruction from the GUI in HIP-only builds. * Fix pose-prior bundle adjustment changing rig poses configured as constant. Breaking Changes ---------------- * The global mapper now reconstructs every connected component by default and may therefore return multiple models for disconnected datasets. Set ``GlobalMapper.multiple_models`` to false to retain the previous largest-component-only behavior. Models with fewer than ``GlobalMapper.min_model_size`` registered frames are discarded. * The ``hierarchical_mapper`` options ``num_threads``, ``num_workers``, ``image_overlap``, and ``leaf_max_num_images`` are now prefixed with ``HierarchicalMapper.``. * ``pycolmap.estimate_essential_matrix`` no longer returns ``inlier_points3D``. * ``pycolmap.cost_functions.SampsonErrorCost`` now accepts image-plane points instead of camera rays. * The C++ ``HierarchicalPipeline::Options`` type was renamed to ``HierarchicalPipelineOptions``. * Several C++ relative-pose APIs now use ``CamRayWithJac``. * ``PoseFromEssentialMatrix`` now returns valid correspondence indices instead of triangulated points. * The C++ ``CameraModelIsFisheye`` function was renamed to ``CameraModelIsPerspectiveFisheye``. * The global ``std::hash>`` specializations were removed. Downstream unordered containers using pair keys should explicitly use ``colmap::PairHash``. ------------------------- COLMAP 4.1.1 (07/17/2026) ------------------------- Improvements ------------ * Load the mapper database lazily instead of at GUI startup, avoiding a redundant database read when opening the GUI or a project. * Tint UI icons to the palette for improved dark theme legibility. * Show image/point viewer metadata when the source images are missing on disk. * Fail the Caspar build early with a clear error on CUDA architectures below 7.0. Bug Fixes --------- * Fix feature matching slowdown (~4-6x) caused by a process-global OpenMP critical section in ``RANSAC``/``LORANSAC``. * Fix rescaling of already-undistorted images when ``max_image_size`` is provided. * Fix missing SVG icons in the distributed Windows binaries. * Fix Caspar CUDA build with MSVC forced includes. * Fix glog color support version detection. * Fix typos in a user-facing help string and the FAQ. Breaking Changes ---------------- * Renamed the misspelled pycolmap enum ``GPSTransfromEllipsoid`` to ``GPSTransformEllipsoid``. The old name has been removed without a backwards-compatible alias; update any references accordingly. ------------------------- COLMAP 4.1.0 (06/26/2026) ------------------------- New Features ------------ * Added Caspar, a GPU-accelerated bundle adjustment backend, selectable as an alternative to the default Ceres solver. Includes rig support, GPU device selection, and pycolmap bindings for choosing the bundle adjustment and mapper backends. Caspar is often 1-2 orders of magnitude faster than the Ceres CUDA backend for medium- to large-scale problems, leading to drastic speedups especially for the incremental mapper. * Added the spherical (equirectangular) camera models. This enables native reconstruction of 360 panoramic images and is generally faster but less accurate than rendering perspective views, as performed in the ``panorama_sfm`` example. * Extended the ``panorama_sfm`` example to now convert perspective cameras back to equirectangular and added global mapping support. * Added Enhanced Unified Camera Model (EUCM). * Added advancing-front surface reconstruction meshing. * Added support for extracting gravity pose priors from EXIF orientation tags. * Added ``CamRayFromImg`` bearing-vector unprojection interface and bindings. * Estimate separate ``fx``/``fy`` in the p4pf solver for two-focal camera models. * Added a new ``version`` CLI command to print the COLMAP version. * Added ``MVS_ENABLED`` build option to compile without the MVS module. * Added ``GlobalMapper.keep_max_num_tracks`` option to bound the number of established tracks. * Added rotation averaging options to the GlobalMapper configuration. * Added pycolmap bindings for ``ReprojectionErrorType`` and additional point filter methods. * Added support for showing camera up vector in the viewer. * Replaced the GUI icons with Material Symbols (Apache 2.0). * Added keyboard shortcuts for model import/export in the GUI. Improvements ------------ * Accelerated the exhaustive matcher by using ``IndexIVFScalarQuantizer`` instead of ``IndexIVFFlat``. * Accelerated extraction of colors through parallelization across all images. * Accelerated incremental triangulator through reused BFS allocations and simplified ``merge_trials_``. * Added and updated sensor specs to the camera database. * Avoided a forced copy in ``mvs::Image::SetBitmap``. * Support incremental ``CorrespondenceGraph`` and ``ObservationManager`` construction, decoupling ``reg_stats`` from ``ObservationManager``. * Return ``std::optional`` from ``Bitmap::GetPixel``/``Interpolate*``. * Refit missing E/F/H in ``MaybeDecomposeRelativePoses`` for old databases. * Inherit all bundle adjustment options in the global mapper. * Guard against frame mutation after reconstruction insertion via ``FinalizeDataIds()``. * Move semantics for ``SetPoints2D`` (take ``Point2D`` vector by value). * Conditional initialization of Google logging. * Numerous benchmarking improvements: live progress display, ``--fast`` mode, better parallelism and per-step logging, per-dataset/overall summary rows, GT-covisibility-based filtering, and dynamic scheduling. * Added compiler warning flags and a ``WERROR`` option to the pycolmap build. * Robustified gravity-aligned rotation averaging against 180deg flips. * Clipping extreme pixels in fisheye undistortion. * Various tutorial and docstring improvements. * For other minor improvements, see the full list of changes below. Bug Fixes --------- * Fix sequential matching hang with loop detection verification. * Fix SiftGPU device selection to include device 0. * Fix vocab tree selection in the UI. * Fix broken integration of SIFT features with the LightGlue matcher. * Fix ``global_mapper`` ``point3D.error`` units for ``model_analyzer``. * Fix ``PoissonRecon`` ``num_threads`` handling and undefined behavior. * Fix ``pycolmap.Database()`` abort on garbage collection. * Fix Windows Unicode path handling in ``Bitmap::Read``/``Bitmap::Write``. * Fix NaN failures in rotation averaging from non-PD Cholesky. * Fix rotation averaging crash with multi-image rigs. * Fix missing rig pose manifold in ``RefineGeneralizedAbsolutePose`` and only set the manifold when residuals exist. * Fix applying ``refine_sensor_from_rig`` to all stages of the global mapper. * Fix ``mesh_texturer`` occlusion check edge case. * Fix SIGABRT in ``RegisterNextStructureLessImage`` when ``NumRegImages < 2``. * Fix thread pool worker processes crashing on shutdown. * Fix onnxruntime DLL copy error in ``Findonnxruntime.cmake``. * Change ``std::filesystem::relative`` to ``lexically_relative`` for ``NormalizePath``. * Fix crash in ``AdjustGlobalBundle`` after aggressive frame filtering. * Fix stale ``reg_stats``/observation stats and add underflow guards in mapper bookkeeping, and clear ``num_reg_images``. * Fix empty PatchMatch results on Blackwell GPUs (sm_100+). * Fix locale-dependent float parsing/formatting. * Fix thread oversubscription in the hierarchical mapper. * Fix mask usage log never printing in the feature writer thread. * Fix ``pyceres`` ``.problem`` attribute on ``CeresBundleAdjuster`` factory. * Fix conditional Eigen alignment for the 3.4.0 pre-release version. * Fix optional access in guided matching. * Fix reading of dynamic matrices in the SQLite database. * Fix using the ALIKED feature extractor with pycolmap and ALIKED ``min_score`` keypoint filtering. * Fix various pycolmap and PoissonRecon installation/build issues. * Fix missing mesh simplification reset and duplicate sources. * Add destructor to ``ModelViewerWidget`` to call ``makeCurrent()``. * Fix checks in the ``GpuMat`` constructor. * Add missing LightGlue matcher type to the GUI. * Log an error instead of crashing on unknown EXIF orientation. ------------------------- COLMAP 4.0.4 (04/27/2026) ------------------------- Bug Fixes --------- * Log an error instead of crash on unknown EXIF orientation * Fix crash in AdjustGlobalBundle after aggressive frame filtering * Change std::filesystem::relative to lexically_relative for NormalizePath * Fix onnxruntime DLL copy error under Windows in Findonnxruntime.cmake ------------------------- COLMAP 4.0.3 (04/06/2026) ------------------------- Bug Fixes --------- * Fix various issues in incremental mapper's reg_stats bookkeeping * Fix reading of dynamic matrices in SQLite3 database * Fix optional access in guided matching * Fix conditional Eigen alignment for 3.4.0 pre-release version * Fix ceres::GradientChecker constructor for older Ceres versions * Fix for pycolmap installation related to ONNX * Fix bug where num_reg_images was not cleared * Fix mask usage log never printing in feature writer thread * Fix undefined behavior in PoissonRecon * Fix locale-dependent float parsing/formatting * Fix empty PatchMatch results on Blackwell GPUs (sm_100+) * Fix pyceres .problem attribute on CeresBundleAdjuster returned by factory functions * Fix missing rotation averaging options to global mapper * Fix piping of bundle adjustment options in global mapper Improvements ------------ * Handle CHOLMOD includes not being in a subdirectory * Use non-deprecated SQLite3::SQLite3 CMake target * Reduce thread oversubscription in hierarchical mapper ------------------------- COLMAP 4.0.2 (03/18/2026) ------------------------- Bug Fixes --------- * Fix ALIKED keypoint score filtering * Fix ALIKED extraction with pycolmap * Fix missing reset of mesh simplification options ------------------------- COLMAP 4.0.1 (03/15/2026) ------------------------- Bug Fixes --------- * Add missing LightGlue matcher type to GUI * Fix checks in GpuMat constructor * Add destructor to ModelViewerWidget to call makeCurrent() ------------------------- COLMAP 4.0.0 (03/14/2026) ------------------------- New Features ------------ * Integrated GLOMAP global SfM pipeline into COLMAP as a first-class alternative to the incremental/hierarchical mappers, available via the ``global_mapper`` and ``automatic_reconstructor --mapper GLOBAL`` commands. Many fixes and improvements have been applied to the GLOMAP codebase as part of this migration. GLOMAP is maintained through the COLMAP repository going forward. The global pipeline uses view graph calibration to estimate intrinsics from two-view geometries, which may produce different camera parameters compared to the incremental pipeline's self-calibration. * Added ALIKED (N16Rot/N32) feature extraction through ONNX support. Support for brute-force and LightGlue matching as well as scalable matching with pre-trained vocabulary trees. * Added LightGlue ONNX feature matching for SIFT and ALIKED. * Added Python bindings for feature extractor and matcher. * Added support for reading image orientation from EXIF and auto-rotating images during feature extraction/matching for better robustness against rotational viewpoint changes. * Added structure-less image registration fallback using generalized relative pose estimator for registering images without 3-view overlap. * Added division camera models (SIMPLE_DIVISION, DIVISION). * Added fisheye camera model without distortion parameters (FISHEYE). * Improved Ceres bundle adjustment performance by ~10% through single pose parameter block. * Improved Ceres bundle adjustment performance by ~15% for SIMPLE_RADIAL camera model and trivial frames through analytical Jacobians. * Added abstract general bundle adjuster and solver summary in preparation for supporting different optimization backends. * Replaced FreeImage with OpenImageIO for ~2.5x faster image I/O, support for more image formats, and using an actively maintained dependency with security fixes, etc. * Added guided geometric verification using a known reconstruction. * Added view graph calibration as a new module. * Added model clustering to partition large reconstructions into smaller, manageable sub-models based on scene connectivity. * Added mesh simplification using Quadric Error Metric (QEM) decimation. * Added mesh texture mapping for producing textured meshes from calibrated images. * Added option to specify JPEG quality during image undistortion. * Added support for loading bitmaps with alpha channel. * Added option to control refinement of 3D points in bundle adjuster. * Added optional support for position prior in absolute pose refinement. * Added supernodal CHOLMOD L1 solver support. * Added multi-threading support for (LO)RANSAC loop. * Added support for custom SSL certificate locations through SSL_CERT_FILE and SSL_CERT_DIR environment variables. * Added support for static Windows builds with CUDA. * Exposed ``pycolmap.match_from_pairs`` for custom pair matching on GPU. * Added Python bindings for depth and normal maps. * Added Python type checking (mypy) for all code. * Improved incremental mapper and triangulator Python bindings. * Added support for visualizing (textured) meshes in the GUI. * Added drag-and-drop folder support for model import in the GUI. * Added drag-and-drop support for PLY import in the GUI. * Switched to native menu bar in GUI on macOS. * Added support for building shared libraries. * Improved patch match stereo performance by reading inputs in parallel. * Reduced model viewer memory usage in the GUI. * Added automatic fallback to BA CPU solver if GPU solver fails. * For other minor improvements, see full list of changes below. Bug Fixes --------- * Fixed guided matching for calibrated non-linear cameras. * Fixed database race conditions in mapping pipelines. * Fixed MSVC compatibility with ``isnan``. * Fixed undefined behavior in feature match swapping. * Fixed SVD computation in affine transform. * Fixed 2D association issue at ``AddPoint3D`` with ``point3D_id``. * Fixed several issues in database merging. * Fixed MVS workspace image downsizing. * Fixed pycolmap to respect ``fix_existing_frames`` option in the incremental pipeline. * Fixed transcription of image IDs in reconstruction from database. * Fixed ``BundleAdjustmentConfig::NumResiduals`` ignoring point statistics. * Fixed incorrect propagation of incremental options in automatic reconstructor. * Fixed triangulation angle computation in patch match sampling. * Fixed inverted units in ``ExifFocalLength`` metadata extraction. * Fixed slow performance of generalized absolute pose estimator verification. * Propagate exceptions from ``ThreadPool::Wait``. * Fixed empty patch match results on CUDA compute >= 100 (Blackwell GPUs). * Fixed ``filter_frames_`` usage in ``FindNextImages``. * Fixed ``model_aligner`` crash by using new pose prior API. * For other bug fixes, see full list of changes below. Breaking Changes ---------------- * Replaced FreeImage with OpenImageIO for image I/O. There may be slight differences in terms of supported image formats and loaded pixel values. * Dropped support for Python 3.9 due to EOL. * ``TwoViewGeometry`` fields ``cam2_from_cam1``, ``E``, ``F``, ``H`` are now ``std::optional``. * ``Rigid3d`` and ``Sim3d`` parameters stored as single vector. Use ``rotation()`` and ``translation()`` accessor methods instead of direct member access. * Bundle adjustment cost functors use single pose parameter blocks. * ``BundleAdjuster`` now returns ``std::shared_ptr`` instead of ``ceres::SolverSummary``. * Ceres-related CLI options are prefixed with ``BundleAdjustmentCeres.*``. * GPS/ENU coordinate conversion functionality cleaned up, breaking the Python interface. * Feature descriptors are now associated with a type that is propagated through the pipeline and storage. Fails matching if descriptor types are inconsistent. * Pose priors associated with generic sensor measurement data. * ``DatabaseCache`` initialization consolidated into a single options struct instead of scattered arguments. * Switched paths from ``std::string`` to ``std::filesystem::path`` throughout the C++ codebase. Python ``pathlib.Path`` objects are now automatically converted. No breaking impact on the Python interface. Full Change List (sorted temporally) ------------------------------------ * Fix MSVC compatibility with ``isnan`` by @nharbiso in https://github.com/colmap/colmap/pull/3720 * Include v3.13 to legacy dropdown. by @B1ueber2y in https://github.com/colmap/colmap/pull/3724 * (bugfix) initializing added_two_view_geometry_options to false by @AlePuglisi in https://github.com/colmap/colmap/pull/3727 * Update the tag of the doc submodule to the latest commit. by @B1ueber2y in https://github.com/colmap/colmap/pull/3729 * Fix duplicated parameters for pycolmap.extract_features and fix pycolmap README. by @B1ueber2y in https://github.com/colmap/colmap/pull/3728 * Document how to speed up bundle adjustment by @StonerLing in https://github.com/colmap/colmap/pull/3730 * Fix reconstruction benchmark by @ahojnnes in https://github.com/colmap/colmap/pull/3731 * Remove doc submodule for easier maintenance. by @B1ueber2y in https://github.com/colmap/colmap/pull/3732 * Fix undefined behavior in feature match swapping by @ahojnnes in https://github.com/colmap/colmap/pull/3733 * Fix SVD computation in affine transform by @ahojnnes in https://github.com/colmap/colmap/pull/3734 * Fix random seed stability tests for incremental mapper by @ahojnnes in https://github.com/colmap/colmap/pull/3735 * Structure-less image registration using generalized relative pose estimator by @ahojnnes in https://github.com/colmap/colmap/pull/2829 * Add supernodal cholmod L1 solver support and more tests by @ahojnnes in https://github.com/colmap/colmap/pull/3737 * Add union-find implementation by @ahojnnes in https://github.com/colmap/colmap/pull/3738 * Add unit tests for base controller by @ahojnnes in https://github.com/colmap/colmap/pull/3739 * Add unit tests for PLY utils by @ahojnnes in https://github.com/colmap/colmap/pull/3742 * Inline SQLite3 utils in database implementation by @ahojnnes in https://github.com/colmap/colmap/pull/3741 * Allow reuse of cholesky decomposition in L1 solver by @ahojnnes in https://github.com/colmap/colmap/pull/3743 * Add unit tests for controller thread by @ahojnnes in https://github.com/colmap/colmap/pull/3740 * Add drag-and-drop folder support for model import in the GUI by @StonerLing in https://github.com/colmap/colmap/pull/3744 * Update to latest vcpkg by @ahojnnes in https://github.com/colmap/colmap/pull/3745 * Include instructions to update documentation after new releases by @chipironcin in https://github.com/colmap/colmap/pull/3716 * Remove unused CMake warning level variable by @ahojnnes in https://github.com/colmap/colmap/pull/3747 * Import of GLOMAP source code by @ahojnnes in https://github.com/colmap/colmap/pull/3748 * Correct the spelling of GeometricVerifier. by @B1ueber2y in https://github.com/colmap/colmap/pull/3749 * Update Windows ccache to 4.12.1 by @ahojnnes in https://github.com/colmap/colmap/pull/3750 * Remove custom glomap constants by @ahojnnes in https://github.com/colmap/colmap/pull/3751 * Remove unnecessary glomap Camera by @ahojnnes in https://github.com/colmap/colmap/pull/3753 * Misc code quality improvements for max spanning tree by @ahojnnes in https://github.com/colmap/colmap/pull/3755 * Configure clang-format for glomap code by @ahojnnes in https://github.com/colmap/colmap/pull/3756 * Support two view geometry with empty inlier matches in the database. by @B1ueber2y in https://github.com/colmap/colmap/pull/3754 * Modularize glomap libraries by @ahojnnes in https://github.com/colmap/colmap/pull/3757 * Use colmap reconstruction matchers in global mapper tests by @ahojnnes in https://github.com/colmap/colmap/pull/3758 * Fix WriteTwoViewGeometry when inlier matches are empty. by @B1ueber2y in https://github.com/colmap/colmap/pull/3761 * Add support for geometric verification given known reconstruction. by @B1ueber2y in https://github.com/colmap/colmap/pull/3752 * Enable glomap tests in CI by @ahojnnes in https://github.com/colmap/colmap/pull/3759 * Replace custom glomap Track with colmap Point3D by @ahojnnes in https://github.com/colmap/colmap/pull/3762 * Add missing option: FeatureMatching.skip_image_pairs_in_same_frame by @B1ueber2y in https://github.com/colmap/colmap/pull/3765 * Use fundamental/essential matrix utils from colmap by @ahojnnes in https://github.com/colmap/colmap/pull/3768 * Clean up redundant/unnecessary glomap rigid3d helper functions by @ahojnnes in https://github.com/colmap/colmap/pull/3764 * Add option to skip geometric verification at feature matching. by @B1ueber2y in https://github.com/colmap/colmap/pull/3766 * Fix missing colmap namespace in util/file.h macro. by @B1ueber2y in https://github.com/colmap/colmap/pull/3770 * Add missing header for the enum macros and remove colmap namespace. by @B1ueber2y in https://github.com/colmap/colmap/pull/3771 * Upgrade to latest vcpkg. by @B1ueber2y in https://github.com/colmap/colmap/pull/3774 * Add convenience method for filtering data by sensor type by @ahojnnes in https://github.com/colmap/colmap/pull/3776 * Cleanup redundant sqlite database merge implementation, consistent variable naming by @ahojnnes in https://github.com/colmap/colmap/pull/3778 * Add missing bindings for Bitmap and cleanup by @sarlinpe in https://github.com/colmap/colmap/pull/3780 * Set frame_id when reading database images by @ahojnnes in https://github.com/colmap/colmap/pull/3779 * Handle near zero matrices or identity transformations in gmock matchers by @ahojnnes in https://github.com/colmap/colmap/pull/3782 * Fix pycolmap to respect fix_existing_frames option by @S-o-T in https://github.com/colmap/colmap/pull/3781 * Add non-inserting FindIfExists to UnionFind by @B1ueber2y in https://github.com/colmap/colmap/pull/3783 * Use existing ceres function to stringify termination type by @ahojnnes in https://github.com/colmap/colmap/pull/3784 * Add convenience Python constructors for data_t/sensor_t by @ahojnnes in https://github.com/colmap/colmap/pull/3785 * Add utility function to compute Rigid3d origin by @ahojnnes in https://github.com/colmap/colmap/pull/3773 * Allow running glomap from colmap main executable by @ahojnnes in https://github.com/colmap/colmap/pull/3769 * Associate pose priors to generic sensor measurement data by @ahojnnes in https://github.com/colmap/colmap/pull/3777 * Enable compiler warnings as errors in GCC/CLang CI builds by @ahojnnes in https://github.com/colmap/colmap/pull/3788 * Remove debug log statement in db management widget by @ahojnnes in https://github.com/colmap/colmap/pull/3789 * Rename PosePrior::IsValid to HasPosition by @ahojnnes in https://github.com/colmap/colmap/pull/3786 * Add gravity to pose prior by @ahojnnes in https://github.com/colmap/colmap/pull/3787 * Disable editing of images in database management widget by @ahojnnes in https://github.com/colmap/colmap/pull/3792 * Fix and simplify database pose prior migration by @ahojnnes in https://github.com/colmap/colmap/pull/3791 * Synthesize gravities and move pose prior noise synthesis by @ahojnnes in https://github.com/colmap/colmap/pull/3795 * Replace FreeImage with OpenImageIO by @ahojnnes in https://github.com/colmap/colmap/pull/3459 * Add support on adding camera/image with trivial rig/frame to reconstruction. by @B1ueber2y in https://github.com/colmap/colmap/pull/3800 * Rename HasTrivialFrame to IsRefInFrame. by @B1ueber2y in https://github.com/colmap/colmap/pull/3801 * Ensure that the rig has only one sensor at AddImageWithTrivialFrame. by @B1ueber2y in https://github.com/colmap/colmap/pull/3802 * Fix meta data cloning when cloning bitmap by @ahojnnes in https://github.com/colmap/colmap/pull/3812 * Remove unused includes in MVS image by @ahojnnes in https://github.com/colmap/colmap/pull/3813 * Create global mapper pipeline by @ahojnnes in https://github.com/colmap/colmap/pull/3808 * Fix stand-alone python visualizer. by @B1ueber2y in https://github.com/colmap/colmap/pull/3816 * Remove the deprecated read_write_model.py from colmap. by @B1ueber2y in https://github.com/colmap/colmap/pull/3817 * refactor: Replace OpenSqliteDatabase with Database::Open by @whuaegeanse in https://github.com/colmap/colmap/pull/3818 * Replace glomap gravity info with pose prior by @ahojnnes in https://github.com/colmap/colmap/pull/3794 * Relax CUDA requirements in Python wheels by @sarlinpe in https://github.com/colmap/colmap/pull/3799 * Fix copying behaviors of empty bitmap. by @B1ueber2y in https://github.com/colmap/colmap/pull/3823 * Don't build CUDA wheels for all Python versions in PRs by @sarlinpe in https://github.com/colmap/colmap/pull/3824 * Fix NumResiduals ignore point statistics and update test assertion by @whuaegeanse in https://github.com/colmap/colmap/pull/3820 * Add unit tests for MVS workspace by @ahojnnes in https://github.com/colmap/colmap/pull/3825 * Fix transcription of image IDs in reconstruction from database by @ahojnnes in https://github.com/colmap/colmap/pull/3826 * Towards removing custom glomap image class by @ahojnnes in https://github.com/colmap/colmap/pull/3805 * Add unit tests for MVS image by @ahojnnes in https://github.com/colmap/colmap/pull/3828 * Remove unused includes in MVS fusion by @ahojnnes in https://github.com/colmap/colmap/pull/3829 * Add simple integration test for MVS fusion by @ahojnnes in https://github.com/colmap/colmap/pull/3830 * Remove custom glomap image class by @ahojnnes in https://github.com/colmap/colmap/pull/3835 * Add integration tests for meshing, update poisson recon library to 18.75 by @ahojnnes in https://github.com/colmap/colmap/pull/3834 * Consolidate calculation of angle between vectors by @ahojnnes in https://github.com/colmap/colmap/pull/3836 * Clean up unused includes from geometry folder by @ahojnnes in https://github.com/colmap/colmap/pull/3837 * Clean up redundant glomap executable by @ahojnnes in https://github.com/colmap/colmap/pull/3838 * Update FAQ on dense stereo stage requirements by @The-Cyber-Captain in https://github.com/colmap/colmap/pull/3840 * Improve glomap cost function, add tests by @ahojnnes in https://github.com/colmap/colmap/pull/3839 * Refactor general cost function utils and add NormalPriorCost and NormalErrorCost. by @B1ueber2y in https://github.com/colmap/colmap/pull/3843 * Various improvements and fixes on glomap gravity impl. by @B1ueber2y in https://github.com/colmap/colmap/pull/3844 * Fix misleading logging for structure-less registration. by @B1ueber2y in https://github.com/colmap/colmap/pull/3848 * Remove glomap types_sfm.h and only include necessary headers. by @B1ueber2y in https://github.com/colmap/colmap/pull/3849 * Improvements and fixes on glomap two view geometry. by @B1ueber2y in https://github.com/colmap/colmap/pull/3847 * Move cluster id logic to top level and remove custom glomap Frame class. by @B1ueber2y in https://github.com/colmap/colmap/pull/3850 * Migrate glomap gravity utilities into colmap and add tests. by @B1ueber2y in https://github.com/colmap/colmap/pull/3845 * Move InlierThresholdOptions and remove unnecessary includes. by @B1ueber2y in https://github.com/colmap/colmap/pull/3851 * Minor: throw instead of LOG(ERROR) when SQLITE3_EXEC fails. by @B1ueber2y in https://github.com/colmap/colmap/pull/3855 * Add integration tests for image undistorters by @ahojnnes in https://github.com/colmap/colmap/pull/3856 * Pass database as pointer instead of reference in global mapper. Drop GlobalMapperResume. by @B1ueber2y in https://github.com/colmap/colmap/pull/3846 * Use colmap::Reconstruction instead of scattered variables in glomap. by @B1ueber2y in https://github.com/colmap/colmap/pull/3853 * Use temporary storage for camera centers in global positioning. by @B1ueber2y in https://github.com/colmap/colmap/pull/3854 * Fix workspace image downsizing and improve tests by @ahojnnes in https://github.com/colmap/colmap/pull/3857 * Fix bitmap copy assignment test by @ahojnnes in https://github.com/colmap/colmap/pull/3859 * Add test for BitmapColor printing by @ahojnnes in https://github.com/colmap/colmap/pull/3860 * [Improve RA] Add PairConstraint, remove image_id_to_idx and improve naming/comments. by @B1ueber2y in https://github.com/colmap/colmap/pull/3858 * Add test for spatial verification in image retrieval, fix crash for invalid transformations by @ahojnnes in https://github.com/colmap/colmap/pull/3862 * Attempt to make visual index spatial verification tests deterministic by @ahojnnes in https://github.com/colmap/colmap/pull/3870 * Minor: rename glomap cost_function.h to cost_functions.h. by @B1ueber2y in https://github.com/colmap/colmap/pull/3872 * Fix 2D association issue at AddPoint3D with point3D_id. by @B1ueber2y in https://github.com/colmap/colmap/pull/3867 * [Improve RA] Abstract RotationAveragingProblem and RotationAveragingSolver. by @B1ueber2y in https://github.com/colmap/colmap/pull/3861 * Move relative pose filtering utilities into ViewGraph. by @B1ueber2y in https://github.com/colmap/colmap/pull/3864 * Fix database race conditions in mapping pipelines by @ahojnnes in https://github.com/colmap/colmap/pull/3865 * Improve stability of visual index spatial verification test by @ahojnnes in https://github.com/colmap/colmap/pull/3874 * Fix glomap track retriangulation refinement termination status by @ahojnnes in https://github.com/colmap/colmap/pull/3873 * Propagate exceptions from ThreadPool::Wait by @ahojnnes in https://github.com/colmap/colmap/pull/3869 * Catch AggregateException for ThreadPool. by @B1ueber2y in https://github.com/colmap/colmap/pull/3875 * Use DeRegisterFrame over ResetPose in view graph and track retriangulation. by @B1ueber2y in https://github.com/colmap/colmap/pull/3877 * Add Reconstruction::IsValid to check if a reconstruction object is not broken. by @B1ueber2y in https://github.com/colmap/colmap/pull/3878 * Improve the horrendous logic and naming in rotation initializer. by @B1ueber2y in https://github.com/colmap/colmap/pull/3876 * Use colmap ObservationManager for track filtering in glomap. by @B1ueber2y in https://github.com/colmap/colmap/pull/3871 * Centralize random logic to use colmap SetPRNGSeed. Add deterministic test. by @B1ueber2y in https://github.com/colmap/colmap/pull/3879 * Abstract general minimum spanning tree algorithm in colmap. by @B1ueber2y in https://github.com/colmap/colmap/pull/3883 * Use logging over std::cout in glomap to avoid flooding logs in gtest. by @B1ueber2y in https://github.com/colmap/colmap/pull/3880 * Abstract base option manager and consolidate usage. by @B1ueber2y in https://github.com/colmap/colmap/pull/3881 * [Improve RA] Move stratified logic into estimators and only keep one option class. by @B1ueber2y in https://github.com/colmap/colmap/pull/3863 * ImagePair inherits colmap::TwoViewGeometry. Remove image id fields. by @B1ueber2y in https://github.com/colmap/colmap/pull/3882 * Further cleanup of std::cout in the codebase. by @B1ueber2y in https://github.com/colmap/colmap/pull/3884 * Rename SwapImagePair to ShouldSwapImagePair. by @B1ueber2y in https://github.com/colmap/colmap/pull/3887 * Move validity logic into ViewGraph and use filter_view in ValidImagePairs. by @B1ueber2y in https://github.com/colmap/colmap/pull/3885 * Directly use COLMAP bundle adjustment interface for glomap BA. by @B1ueber2y in https://github.com/colmap/colmap/pull/3868 * Add rotation averaging controller in colmap and consolidate glomap exe functions. by @B1ueber2y in https://github.com/colmap/colmap/pull/3888 * Remove legacy logging in global positioning. by @B1ueber2y in https://github.com/colmap/colmap/pull/3892 * Minor: improve the namings in global mapper options. by @B1ueber2y in https://github.com/colmap/colmap/pull/3891 * Add type annotations to python example scripts by @ahojnnes in https://github.com/colmap/colmap/pull/3889 * Remove OptimizationBase and use colmap::GetEffectiveNumThreads(). by @B1ueber2y in https://github.com/colmap/colmap/pull/3890 * Simplify the logic and naming of reconstruction pruning. by @B1ueber2y in https://github.com/colmap/colmap/pull/3886 * Fix wrongly migrated cuda option in glomap. by @B1ueber2y in https://github.com/colmap/colmap/pull/3895 * Centralize poselib type conversion. by @B1ueber2y in https://github.com/colmap/colmap/pull/3896 * Drop weight support for different relative poses in rotation averaging. by @B1ueber2y in https://github.com/colmap/colmap/pull/3897 * Simplify colmap_io in glomap thanks to the unification. by @B1ueber2y in https://github.com/colmap/colmap/pull/3900 * Reduce code duplication in pycolmap MVS bindings by @sarlinpe in https://github.com/colmap/colmap/pull/3899 * Use colmap IterativeGlobalRefinement for retriangulation and refinement in glomap. by @B1ueber2y in https://github.com/colmap/colmap/pull/3902 * Drop support for Python 3.9 due to EOL by @ahojnnes in https://github.com/colmap/colmap/pull/3910 * Add single-thread deterministic test for global mapper and rotation averaging controller. by @B1ueber2y in https://github.com/colmap/colmap/pull/3906 * Replace the usage of inliers in ImagePair with inlier_matches. by @B1ueber2y in https://github.com/colmap/colmap/pull/3909 * Update global mapper signature to align with colmap::IncrementalMapper. by @B1ueber2y in https://github.com/colmap/colmap/pull/3904 * Implement AddAndRegisterDefaultEnumOption and propagate to codebase. by @B1ueber2y in https://github.com/colmap/colmap/pull/3903 * Cleanup unused method declaration in GlobalMapper. by @B1ueber2y in https://github.com/colmap/colmap/pull/3912 * Refactor view graph calibration. by @B1ueber2y in https://github.com/colmap/colmap/pull/3911 * Delete legacy Python scripts by @ahojnnes in https://github.com/colmap/colmap/pull/3914 * Add support for computing recall scores by @ahojnnes in https://github.com/colmap/colmap/pull/3913 * Add Python bindings for depth and normal maps, retire legacy scripts by @ahojnnes in https://github.com/colmap/colmap/pull/3916 * Update and improve formatting of pycolmap readme by @ahojnnes in https://github.com/colmap/colmap/pull/3917 * Add Python type checking for all code by @ahojnnes in https://github.com/colmap/colmap/pull/3893 * Recompute F from E in the top level of CalibrateViewGraph. by @B1ueber2y in https://github.com/colmap/colmap/pull/3915 * Make DatabaseCache::Load/Create args optional except database. by @B1ueber2y in https://github.com/colmap/colmap/pull/3919 * Use DatabaseCache to load reconstruction in glomap and remove the custom loader. by @B1ueber2y in https://github.com/colmap/colmap/pull/3918 * Bugfix for the rotation averaging CLI. by @B1ueber2y in https://github.com/colmap/colmap/pull/3927 * Drop database member in SfM controllers for better modularity and safety. by @B1ueber2y in https://github.com/colmap/colmap/pull/3926 * Add tests for reconstruction benchmark utils by @ahojnnes in https://github.com/colmap/colmap/pull/3930 * Use new optional typing by @ahojnnes in https://github.com/colmap/colmap/pull/3931 * Switch to colmap backend for two view re-estimation after view graph calibration. by @B1ueber2y in https://github.com/colmap/colmap/pull/3923 * Fix sign of GPS longitude from EXIF by @ahojnnes in https://github.com/colmap/colmap/pull/3935 * Fix camera prior loading in benchmark code. by @B1ueber2y in https://github.com/colmap/colmap/pull/3933 * Remove Database::Clone method by @ahojnnes in https://github.com/colmap/colmap/pull/3929 * Cleanup unused glomap includes by @ahojnnes in https://github.com/colmap/colmap/pull/3937 * Improved code for view graph calibration cost functions by @ahojnnes in https://github.com/colmap/colmap/pull/3936 * Move and type annotate model visualization example by @ahojnnes in https://github.com/colmap/colmap/pull/3939 * Move end to end regression test script and annotate types by @ahojnnes in https://github.com/colmap/colmap/pull/3938 * Delete more legacy Python scripts by @ahojnnes in https://github.com/colmap/colmap/pull/3940 * Move and type annotate flickr downloader by @ahojnnes in https://github.com/colmap/colmap/pull/3941 * Add option to evaluate self-calibration in reconstruction benchmark by @ahojnnes in https://github.com/colmap/colmap/pull/3942 * Use std::optional for cam2_from_cam1 in TwoViewGeometry. by @B1ueber2y in https://github.com/colmap/colmap/pull/3932 * Use custom two view options for global pipeline in automatic reconstructor. by @B1ueber2y in https://github.com/colmap/colmap/pull/3943 * Add tests for estimating multiple two view geometries and minor perf optimization by @ahojnnes in https://github.com/colmap/colmap/pull/3947 * Add tests for uncovered edge cases in polynomial solvers by @ahojnnes in https://github.com/colmap/colmap/pull/3946 * Add unit tests for export to various external reconstruction formats by @ahojnnes in https://github.com/colmap/colmap/pull/3945 * Only create QApplication or OpenGL context if needed by @ahojnnes in https://github.com/colmap/colmap/pull/3944 * Simplify CSVToVector and add tests by @ahojnnes in https://github.com/colmap/colmap/pull/3953 * Migrate glomap exe into colmap. by @B1ueber2y in https://github.com/colmap/colmap/pull/3952 * Add unit tests for colmap version helpers by @ahojnnes in https://github.com/colmap/colmap/pull/3955 * Use make_unique instead of new by @ahojnnes in https://github.com/colmap/colmap/pull/3957 * Early exit pycolmap windows build on errors by @ahojnnes in https://github.com/colmap/colmap/pull/3958 * Make view graph calibration a stand-alone estimator module on colmap::Database and add CLI. by @B1ueber2y in https://github.com/colmap/colmap/pull/3951 * Replace ImagePair with RelativePoseData and rename ViewGraph to PoseGraph. by @B1ueber2y in https://github.com/colmap/colmap/pull/3954 * Make rotation averaging work with reconstruction with frames unregistered. by @B1ueber2y in https://github.com/colmap/colmap/pull/3949 * Replace 3-level option names with 2-level names for INI compatibility. by @B1ueber2y in https://github.com/colmap/colmap/pull/3962 * Add test for reading/writing options from ini by @ahojnnes in https://github.com/colmap/colmap/pull/3963 * Add relative poses to DatabaseCache and update the signature of global mapper. by @B1ueber2y in https://github.com/colmap/colmap/pull/3960 * Move module-specific functional methods outside PoseGraph. by @B1ueber2y in https://github.com/colmap/colmap/pull/3964 * Add tests for file utils, use enum utils by @ahojnnes in https://github.com/colmap/colmap/pull/3956 * Always try to decompose relative poses at the start of global pipeline. by @B1ueber2y in https://github.com/colmap/colmap/pull/3966 * Add unit tests for incremental triangulator by @ahojnnes in https://github.com/colmap/colmap/pull/3967 * Bugfix in pose decomposition interface. Update pipeline tests. by @B1ueber2y in https://github.com/colmap/colmap/pull/3969 * Switch paths from std::string to std::filesystem::path by @sarlinpe in https://github.com/colmap/colmap/pull/3901 * Add option to synthesize sparse view graph by @ahojnnes in https://github.com/colmap/colmap/pull/3968 * Optionally include runtime benchmarks in main project by @ahojnnes in https://github.com/colmap/colmap/pull/3970 * Refactor and improve track establishment. by @B1ueber2y in https://github.com/colmap/colmap/pull/3965 * Inline track establishment logic into GlobalMapper. by @B1ueber2y in https://github.com/colmap/colmap/pull/3972 * Minor: inline gravity cost functor into gravity refinement. by @B1ueber2y in https://github.com/colmap/colmap/pull/3973 * Store two view geometries in correspondence graph by @ahojnnes in https://github.com/colmap/colmap/pull/3975 * Support custom SSL certificate locations by @ahojnnes in https://github.com/colmap/colmap/pull/3976 * Fix database merging and improve tests by @ahojnnes in https://github.com/colmap/colmap/pull/3977 * Support loading bitmaps with alpha channel by @ahojnnes in https://github.com/colmap/colmap/pull/3978 * Reorder fields of core data structs for better packing by @ahojnnes in https://github.com/colmap/colmap/pull/3979 * Add tests for reading/writing bitmaps in popular image formats. by @ahojnnes in https://github.com/colmap/colmap/pull/3980 * Fix includes in feature index/matcher by @ahojnnes in https://github.com/colmap/colmap/pull/3982 * Do not store inlier matches in pose graph by @ahojnnes in https://github.com/colmap/colmap/pull/3981 * Setup priors when loading database, filter watermarks when creating database cache from another cache by @ahojnnes in https://github.com/colmap/colmap/pull/3983 * Fix guided matching for calibrated non-linear cameras by @ahojnnes in https://github.com/colmap/colmap/pull/3986 * Clear and log OpenImageIO errors by @ahojnnes in https://github.com/colmap/colmap/pull/3987 * Misc view graph calibration and global mapper improvements by @ahojnnes in https://github.com/colmap/colmap/pull/3985 * Do not crash but return error on unknown sensor_from_rig poses in incremental mapper by @ahojnnes in https://github.com/colmap/colmap/pull/3988 * Consistent and improved logging of headings by @ahojnnes in https://github.com/colmap/colmap/pull/3991 * Fix faiss linker warning under MSVC by @ahojnnes in https://github.com/colmap/colmap/pull/3994 * Make E F H optional in TwoViewGeometry struct. by @B1ueber2y in https://github.com/colmap/colmap/pull/3989 * Update Python incremental pipeline to match C++ by @ahojnnes in https://github.com/colmap/colmap/pull/3993 * Improve Reconstruction::NumRegImages from O(N) to O(1) by @ahojnnes in https://github.com/colmap/colmap/pull/3996 * Reduce incremental mapper's minimum size for small models by @ahojnnes in https://github.com/colmap/colmap/pull/3992 * Switch to Python 3.11 in the PR build. Disable mypy for Python 3.10. by @B1ueber2y in https://github.com/colmap/colmap/pull/3998 * Add missing Python bindings by @ahojnnes in https://github.com/colmap/colmap/pull/3997 * Do not override test commands for Python 3.10 on windows. by @B1ueber2y in https://github.com/colmap/colmap/pull/4000 * Rename num_iterations_ba to ba_num_iterations for consistency by @ahojnnes in https://github.com/colmap/colmap/pull/4001 * Return std::optional from Bitmap::Exif* methods by @ahojnnes in https://github.com/colmap/colmap/pull/4002 * Use database in rotation averaging CLI. Add a python example for legacy format. by @B1ueber2y in https://github.com/colmap/colmap/pull/3990 * Cleanup some includes in sfm folder by @ahojnnes in https://github.com/colmap/colmap/pull/4005 * Switch paths from std::string to std::filesystem::path (continued) by @sarlinpe in https://github.com/colmap/colmap/pull/4004 * Remove dangling declaration of JoinPaths. by @B1ueber2y in https://github.com/colmap/colmap/pull/4009 * Make reconstruction clustering logic a stand-alone module by @lpanaf in https://github.com/colmap/colmap/pull/4008 * Switch to perform clustering on all registered frames by @lpanaf in https://github.com/colmap/colmap/pull/4007 * Add option to control refinement of points 3D in bundle adjuster by @ahojnnes in https://github.com/colmap/colmap/pull/4012 * Minor code improvement in triangulation estimator by @ahojnnes in https://github.com/colmap/colmap/pull/4015 * Add support for division camera models by @ahojnnes in https://github.com/colmap/colmap/pull/4013 * Add nominal tests on reconstruction accuracy for bundle adjustment. by @B1ueber2y in https://github.com/colmap/colmap/pull/4019 * Use colmap::BundleAdjustmentOptions in global pipeline and drop custom option class. by @B1ueber2y in https://github.com/colmap/colmap/pull/4016 * Add test for and fix bitmap jpeg quality setting by @ahojnnes in https://github.com/colmap/colmap/pull/4025 * Remove unimplemented GetFileSize function by @ahojnnes in https://github.com/colmap/colmap/pull/4026 * Drop experimental modes in global positioning. Add module signature and improve naming. by @B1ueber2y in https://github.com/colmap/colmap/pull/3920 * Add option to specify jpeg quality during undistortion by @ahojnnes in https://github.com/colmap/colmap/pull/4027 * Analytical Jacobians for reprojection error cost function with SimpleRadial cameras by @ahojnnes in https://github.com/colmap/colmap/pull/4017 * Fix backwards compatibility of reading database created before Jul 2023. by @B1ueber2y in https://github.com/colmap/colmap/pull/4030 * Bug fix for generalized absolute pose estimator. by @B1ueber2y in https://github.com/colmap/colmap/pull/4031 * Clear model outputs in all estimators and RANSACs by @ahojnnes in https://github.com/colmap/colmap/pull/4032 * Move undistortion controllers to controllers folder by @ahojnnes in https://github.com/colmap/colmap/pull/4033 * Remove the iterative logic for reconstruction clustering and use directly the strongly connected component by @lpanaf in https://github.com/colmap/colmap/pull/4035 * Add rig scale alignment support for the global pipeline. by @B1ueber2y in https://github.com/colmap/colmap/pull/4038 * Performance improvement on generalized absolute pose. by @B1ueber2y in https://github.com/colmap/colmap/pull/4037 * Bundle adjustment benchmark by @ahojnnes in https://github.com/colmap/colmap/pull/4022 * Add fisheye (equidistant) camera model by @lpanaf in https://github.com/colmap/colmap/pull/4039 * Store Rigid3/Sim3 params as single vector by @ahojnnes in https://github.com/colmap/colmap/pull/4041 * Move minimal solvers in estimators to sub-folder by @ahojnnes in https://github.com/colmap/colmap/pull/4047 * Move generalized absolute pose solver to sub-folder by @ahojnnes in https://github.com/colmap/colmap/pull/4048 * Use static Eigen array segment sizes in rotation averaging by @ahojnnes in https://github.com/colmap/colmap/pull/4049 * Single pose parameter block and angle axis for BA cost functions by @ahojnnes in https://github.com/colmap/colmap/pull/4029 * Automatically deep copy feature extraction/matching options by @ahojnnes in https://github.com/colmap/colmap/pull/4051 * Add test for rig verification and fixes by @ahojnnes in https://github.com/colmap/colmap/pull/4052 * Improve test coverage for image reader by @ahojnnes in https://github.com/colmap/colmap/pull/4053 * Abstract general bundle adjuster and solver summary. by @B1ueber2y in https://github.com/colmap/colmap/pull/4042 * Change test target names and file names by @ahojnnes in https://github.com/colmap/colmap/pull/4034 * Add nominal test and runtime benchmarking for global positioning. by @B1ueber2y in https://github.com/colmap/colmap/pull/4050 * Modularize cost functions by @ahojnnes in https://github.com/colmap/colmap/pull/4054 * Fix incorrect macOS install instruction by @sarlinpe in https://github.com/colmap/colmap/pull/4055 * Move glomap sources to colmap and replace glomap with colmap namespace by @ahojnnes in https://github.com/colmap/colmap/pull/4057 * Move motion averaging cost functions to sub-folder by @ahojnnes in https://github.com/colmap/colmap/pull/4058 * Move glomap Python bindings to colmap folder by @ahojnnes in https://github.com/colmap/colmap/pull/4059 * Add GLOMAP paper to list of citations by @ahojnnes in https://github.com/colmap/colmap/pull/4060 * Add test for reconstruction_clustering and sort cluster id by cluster size by @lpanaf in https://github.com/colmap/colmap/pull/4003 * Improve Fetzer cost implementation and tests by @ahojnnes in https://github.com/colmap/colmap/pull/4061 * Remove unused GetFileSize function by @ahojnnes in https://github.com/colmap/colmap/pull/4064 * Add function to read PLY mesh and add roundtrip tests by @ahojnnes in https://github.com/colmap/colmap/pull/4065 * Remove unused GLError function by @ahojnnes in https://github.com/colmap/colmap/pull/4066 * Modernize code using structured bindings by @ahojnnes in https://github.com/colmap/colmap/pull/4067 * Reuse sorting of initial images by @ahojnnes in https://github.com/colmap/colmap/pull/4069 * Clean up messy GPS conversion functionality by @ahojnnes in https://github.com/colmap/colmap/pull/4063 * Reuse code in camera by @ahojnnes in https://github.com/colmap/colmap/pull/4068 * Support static windows build with CUDA by @ahojnnes in https://github.com/colmap/colmap/pull/4072 * Use enum instead of #define in model viewer widget by @ahojnnes in https://github.com/colmap/colmap/pull/4073 * Extract some algorithmic logic from exe and add tests by @ahojnnes in https://github.com/colmap/colmap/pull/4074 * Cleanup outdated pycolmap deprecations by @ahojnnes in https://github.com/colmap/colmap/pull/4081 * Associate feature descriptors with type and propagate through pipeline and storage by @ahojnnes in https://github.com/colmap/colmap/pull/4080 * Fix pycolmap compiler warnings by @ahojnnes in https://github.com/colmap/colmap/pull/4082 * Update clang-format to 21.1.8 by @ahojnnes in https://github.com/colmap/colmap/pull/4085 * Update project dependencies by @ahojnnes in https://github.com/colmap/colmap/pull/4086 * Move instructions to build documentation to install.rst instead of root README by @ahojnnes in https://github.com/colmap/colmap/pull/4091 * Fix doc ordering for sphinx==0.9.1 by @sarlinpe in https://github.com/colmap/colmap/pull/4092 * Update to latest vcpkg by @ahojnnes in https://github.com/colmap/colmap/pull/4075 * Extract loading of random descriptors and sample randomly across images by @ahojnnes in https://github.com/colmap/colmap/pull/4096 * Fix duplicate word typos in observation manager and rig docstring. by @B1ueber2y in https://github.com/colmap/colmap/pull/4097 * Add LightGlue ONNX feature matching by @ahojnnes in https://github.com/colmap/colmap/pull/4098 * Improve incremental mapper/triangulator bindings by @ahojnnes in https://github.com/colmap/colmap/pull/4101 * Use native menu bar on Mac by @ahojnnes in https://github.com/colmap/colmap/pull/4102 * Avoid unnecessary copies by @ahojnnes in https://github.com/colmap/colmap/pull/4103 * Expose pycolmap.match_from_pairs to support custom pair matching on GPU by @samuelm2 in https://github.com/colmap/colmap/pull/4056 * Update onnxruntime FetchContent for ARM64 support by @johnnynunez in https://github.com/colmap/colmap/pull/4106 * Early exit incremental initialization if there are no points by @ahojnnes in https://github.com/colmap/colmap/pull/4108 * Fix incorrect propagation of incremental options in automatic reconstructor by @sarlinpe in https://github.com/colmap/colmap/pull/4111 * Fix triangulation angle computation in patch match sampling by @ahojnnes in https://github.com/colmap/colmap/pull/4110 * Correctly set the rpath in CMake by @sarlinpe in https://github.com/colmap/colmap/pull/4114 * Fix inverted units in ExifFocalLength metadata extraction by @MAX4ARCH in https://github.com/colmap/colmap/pull/4113 * Add vocab tree for ALIKED N16 rot feature by @ahojnnes in https://github.com/colmap/colmap/pull/4119 * Add track_length option in synthetic tooling and report ms/iter in BA benchmark. by @B1ueber2y in https://github.com/colmap/colmap/pull/4118 * Fix install destination for onnxruntime by @ahojnnes in https://github.com/colmap/colmap/pull/4120 * Read image orientation from EXIF and extract upright features by @sarlinpe in https://github.com/colmap/colmap/pull/4122 * Fix and improve documentation by @ahojnnes in https://github.com/colmap/colmap/pull/4123 * Optimize includes in MVS folder by @ahojnnes in https://github.com/colmap/colmap/pull/4125 * Follow-up fixes on EXIF auto-rotation by @sarlinpe in https://github.com/colmap/colmap/pull/4126 * Replace typedefs with using by @ahojnnes in https://github.com/colmap/colmap/pull/4128 * Add optional position prior support to RefineGeneralizedAbsolutePose by @ahojnnes in https://github.com/colmap/colmap/pull/4129 * Add Python bindings for feature extractor by @ahojnnes in https://github.com/colmap/colmap/pull/4130 * Add Python bindings for feature matcher by @ahojnnes in https://github.com/colmap/colmap/pull/4131 * Rotate LightGlue inputs based on gravity by @sarlinpe in https://github.com/colmap/colmap/pull/4132 * Fixing OpenGL profiles on Linux (Ubuntu 25.10) by @martin-pr in https://github.com/colmap/colmap/pull/4133 * Add pre-trained vocabulary tree for ALIKED N32 variant by @ahojnnes in https://github.com/colmap/colmap/pull/4134 * Untie circular dependency between feature and scene libraries by @ahojnnes in https://github.com/colmap/colmap/pull/4135 * Add tests for matcher cache by @ahojnnes in https://github.com/colmap/colmap/pull/4136 * Explicitly accept dragMoveEvent to fix broken drag-and-drop in Qt6 by @StonerLing in https://github.com/colmap/colmap/pull/4139 * Parse unregistered config options with warning instead of error by @StonerLing in https://github.com/colmap/colmap/pull/4140 * Revamp logging configuration and fix UI log sink formatting by @StonerLing in https://github.com/colmap/colmap/pull/4141 * Make view graph calibration a standalone module by @ahojnnes in https://github.com/colmap/colmap/pull/4143 * Refactor ONNX runtime configuration handling in CMake files by @whuaegeanse in https://github.com/colmap/colmap/pull/4144 * Relative pose decomposition operates in-memory on database cache by @ahojnnes in https://github.com/colmap/colmap/pull/4146 * Enforce compile-time type checking for option registration by @StonerLing in https://github.com/colmap/colmap/pull/4148 * Relax assertion for the global bundle adjustment by @lpanaf in https://github.com/colmap/colmap/pull/4149 * Expose more logging options in pycolmap by @ahojnnes in https://github.com/colmap/colmap/pull/4150 * Load keypoints for all images to database cache for triangulation by @B1ueber2y in https://github.com/colmap/colmap/pull/4151 * Add separate trial counter for structure-less registration by @B1ueber2y in https://github.com/colmap/colmap/pull/4152 * Fix the usage of filter_frames in FindNextImages by @B1ueber2y in https://github.com/colmap/colmap/pull/4153 * Read inputs in parallel in patch match by @ahojnnes in https://github.com/colmap/colmap/pull/4154 * Improved caching for faster and more reliable CI by @ahojnnes in https://github.com/colmap/colmap/pull/4156 * Add retries for more reliable file download by @ahojnnes in https://github.com/colmap/colmap/pull/4157 * Add support for building shared libraries by @ahojnnes in https://github.com/colmap/colmap/pull/4158 * Fix missing colmap namespace in file macro by @B1ueber2y in https://github.com/colmap/colmap/pull/4159 * Fix file THROW_CHECK_* macros from double evaluation of path expressions by @ahojnnes in https://github.com/colmap/colmap/pull/4160 * Fix empty patch match results on CUDA compute >= 100 (Blackwell GPUs) by @ahojnnes in https://github.com/colmap/colmap/pull/4161 * Add AGENTS.md file by @ahojnnes in https://github.com/colmap/colmap/pull/4162 * Fix docker action caching by @ahojnnes in https://github.com/colmap/colmap/pull/4163 * Add compiler caching for CUDA code by @ahojnnes in https://github.com/colmap/colmap/pull/4164 * Improve ONNX Runtime error handling and CUDA compatibility warnings by @StonerLing in https://github.com/colmap/colmap/pull/4167 * Add structureless fallback options to reconstruction widget by @ahojnnes in https://github.com/colmap/colmap/pull/4168 * Add multi-threading support for (LO)RANSAC loop by @ahojnnes in https://github.com/colmap/colmap/pull/4169 * Add missing estimate sub-folder sources to cmake project by @ahojnnes in https://github.com/colmap/colmap/pull/4171 * Improve bundle adjustment test coverage by @ahojnnes in https://github.com/colmap/colmap/pull/4172 * Improve test coverage for coordinate frame estimators by @ahojnnes in https://github.com/colmap/colmap/pull/4173 * Improve test coverage for MVS consistency graph by @ahojnnes in https://github.com/colmap/colmap/pull/4174 * Add tests for SPRT sampler by @ahojnnes in https://github.com/colmap/colmap/pull/4175 * Improve reconstruction test coverage by @ahojnnes in https://github.com/colmap/colmap/pull/4176 * Add tests for MVS model by @ahojnnes in https://github.com/colmap/colmap/pull/4177 * Add tests for incremental mapper by @ahojnnes in https://github.com/colmap/colmap/pull/4178 * Add tests for feature matching utils by @ahojnnes in https://github.com/colmap/colmap/pull/4179 * Expand meshing test coverage by @ahojnnes in https://github.com/colmap/colmap/pull/4180 * Expand pose_graph_test coverage by @ahojnnes in https://github.com/colmap/colmap/pull/4181 * Expand extractor_test coverage by @ahojnnes in https://github.com/colmap/colmap/pull/4182 * Expand matcher_test coverage by @ahojnnes in https://github.com/colmap/colmap/pull/4183 * Add more log options in GUI by @StonerLing in https://github.com/colmap/colmap/pull/4186 * Add test for EstimateManhattanWorldFrame by @ahojnnes in https://github.com/colmap/colmap/pull/4187 * Fix ONNX model path handling for Windows by converting to UTF-8 by @whuaegeanse in https://github.com/colmap/colmap/pull/4188 * Add tests for rotation averaging edge cases by @ahojnnes in https://github.com/colmap/colmap/pull/4190 * Add mesh texture mapping by @ahojnnes in https://github.com/colmap/colmap/pull/4202 * Update install instructions for conda to mention Mamba by @ahojnnes in https://github.com/colmap/colmap/pull/4204 * Homogenize undistorter options by @ahojnnes in https://github.com/colmap/colmap/pull/4205 * Add support for importing surface mesh in viewer by @ahojnnes in https://github.com/colmap/colmap/pull/4207 * Save memory by storing colors as uint8 instead of float32 by @ahojnnes in https://github.com/colmap/colmap/pull/4210 * Configure Delaunay meshing num_threads in automatic reconstruction, more logging by @ahojnnes in https://github.com/colmap/colmap/pull/4211 * Log image reader non-success status as warning by @ahojnnes in https://github.com/colmap/colmap/pull/4212 * Support drag-and-drop for PLY point cloud / surface mesh in viewer by @ahojnnes in https://github.com/colmap/colmap/pull/4214 * Fix broken panorama_sfm.py by @sarlinpe in https://github.com/colmap/colmap/pull/4215 * Add implementation of mesh simplification by @ahojnnes in https://github.com/colmap/colmap/pull/4216 * Tests for filtering logic for incremental mapping by @winterclincke in https://github.com/colmap/colmap/pull/4219 * Deduplicate SIFT tests by @ahojnnes in https://github.com/colmap/colmap/pull/4220 * Fix ONNX installation on Linux for lib64 target by @ahojnnes in https://github.com/colmap/colmap/pull/4221 * Misc safety improvements by @ahojnnes in https://github.com/colmap/colmap/pull/4222 * Improved test coverage for bitmap by @ahojnnes in https://github.com/colmap/colmap/pull/4224 * Fix 180-degree flipped solutions in gravity-aligned rotation averaging by @ahojnnes in https://github.com/colmap/colmap/pull/4225 * Improve docs regarding view graph calibrator in global pipeline by @B1ueber2y in https://github.com/colmap/colmap/pull/4226 * Remove legacy cluster_ids in the global pipeline by @B1ueber2y in https://github.com/colmap/colmap/pull/4227 * Fallback to BA CPU solver if GPU failed by @ahojnnes in https://github.com/colmap/colmap/pull/4230 * Log out BA failures as errors by @ahojnnes in https://github.com/colmap/colmap/pull/4231 * Fix dangling pointers in OptionManager after ResetOptions by @B1ueber2y in https://github.com/colmap/colmap/pull/4232 * Fix model_aligner crash by using new pose prior API by @B1ueber2y in https://github.com/colmap/colmap/pull/4233 * Prevent nested threading for ONNX by @ahojnnes in https://github.com/colmap/colmap/pull/4235 * Scale max_image_size proportionally by feature extractor type in quality presets by @ahojnnes in https://github.com/colmap/colmap/pull/4236 * Rename reconstruction clusterer command to model clusterer by @ahojnnes in https://github.com/colmap/colmap/pull/4237 * Fix CPU parallelization for ALIKED by @ahojnnes in https://github.com/colmap/colmap/pull/4238 * feat: Added global mapping bindings by @TannerGilbert in https://github.com/colmap/colmap/pull/4228 -------------------------- COLMAP 3.13.0 (11/07/2025) -------------------------- New Features ------------ * Improved human-readable consistency checks when configuring a reconstruction with rigs, cameras, frames, and images. * Improved multi-GPU feature extraction & matching performance by avoiding global mutex. * Improved robustness of pose prior mapper against outlier priors. * Improved the performance of reconstruction initialization through parallelization. * Added CUDA-enabled pycolmap package for Linux and automatic publishing to PyPI. * Make the reconstruction process fully deterministic with a new random_seed parameter. * Added support for filtering stationary points in two-view geometry estimation. * Added option to perform geometric verification with rig constraints. * Added option to skip matching for image pairs within the same frame. * Added option to keep specific cameras or rigs constant during the reconstruction. * Added option to clean two-view geometries in database_cleaner. * Added option to specify timeout for incremental mapper. * Added an abstract database interface for easier integration with other database backends. * Added experimental support for feature sub-selection for global BA. * Added testing tools for synthetic reconstruction noise and image generation. * Added official support for Python 3.14. * Added support for Qt6 while still fully supporting Qt5. Bug Fixes --------- * Fixed various issues with new rig support. * Fixed rare deadlocks in feature matching. * Support for UTF-8 database paths in Windows. * Fixed bundle adjustment performance regression due to changed Gauge behavior. * Removed custom manifold from colmap to avoid issues with pyceres. * Fixed rare crash of GUI when clearing the model viewer. * Fixed focal length extraction from 35mm equivalent EXIF information. * Fixed coordinate bug in ComputeEqualPartsBboxes. Breaking Changes ---------------- * Dropped official support for Python 3.8. * Dropped official support for MacOS x86. * Upgraded to pybind11 3.X. * Removed deprecated rig bundle adjuster command in favor of rig configurator and default bundle adjuster supporting the same functionality. * Allow one-to-many matches in correspondence graph. Previously, only one-to-one matches were added to the correspondence graph and therefore other matches were not used during reconstruction. Full Change List (sorted temporally) ------------------------------------ * Autoselect compatible nvidia for docker by @MasahiroOgawa in https://github.com/colmap/colmap/pull/3454 * Add 3.12.1 changelog to main by @ahojnnes in https://github.com/colmap/colmap/pull/3461 * Add min_num_neighbors constraint for spatial matching by @StonerLing in https://github.com/colmap/colmap/pull/3463 * Refactor feature extraction/matching to support other features by @ahojnnes in https://github.com/colmap/colmap/pull/3465 * Define VisualIndex::Read as static in python bindings by @ahojnnes in https://github.com/colmap/colmap/pull/3467 * Only find C/CXX OpenMP components by @ahojnnes in https://github.com/colmap/colmap/pull/3469 * Remove unnecessary GPU checks in pycolmap by @sarlinpe in https://github.com/colmap/colmap/pull/3472 * Fix a bug affecting feature matching on GPU by @sarlinpe in https://github.com/colmap/colmap/pull/3473 * Add command line option for `TwoViewGeometry.detect_watermark` by @gareth-cross in https://github.com/colmap/colmap/pull/3476 * Add and test GetRelativePath by @sarlinpe in https://github.com/colmap/colmap/pull/3475 * Update GetPathBaseName to rely on std::filesystem by @sarlinpe in https://github.com/colmap/colmap/pull/3481 * Fix potential deadlock in job queue by @huluoboge in https://github.com/colmap/colmap/pull/3480 * Update FindMetis.cmake to also link GK_LIBRARIES by @yeicor in https://github.com/colmap/colmap/pull/3470 * Remove check in database_cache.cc that breaks backwards compatibility with image_list_file_path by @pd-karoly-harsanyi in https://github.com/colmap/colmap/pull/3478 * Fix docker run script for GUI by @MasahiroOgawa in https://github.com/colmap/colmap/pull/3483 * Ensure UTF-8 encoding for database paths passed to SQLite open by @StonerLing in https://github.com/colmap/colmap/pull/3482 * Add changelog for 3.12.2 release by @ahojnnes in https://github.com/colmap/colmap/pull/3488 * Add changelog for 3.12.3 release by @ahojnnes in https://github.com/colmap/colmap/pull/3490 * Fix BundleAdjustmentConfig::SetConstantRigFromWorldPose by @whuaegeanse in https://github.com/colmap/colmap/pull/3501 * Add random_seed option to RANSACOptions for reproducible TwoViewGeometry estimation by @StonerLing in https://github.com/colmap/colmap/pull/3492 * Configure trivial rigs for unconfigured images by @ahojnnes in https://github.com/colmap/colmap/pull/3497 * Fix pycolmap test command by @ahojnnes in https://github.com/colmap/colmap/pull/3506 * Pano SFM example improvements and fixes by @ahojnnes in https://github.com/colmap/colmap/pull/3503 * Support Python 3.14 and retire 3.8 by @ahojnnes in https://github.com/colmap/colmap/pull/3518 * Upgrade ruff and automatically apply fixes by @ahojnnes in https://github.com/colmap/colmap/pull/3515 * Update point3D errors after bundle adjustment or SfM by @whuaegeanse in https://github.com/colmap/colmap/pull/3500 * Add option to filter stationary points in two-view geometry estimation by @ahojnnes in https://github.com/colmap/colmap/pull/3521 * Add unit test for watermark detection by @ahojnnes in https://github.com/colmap/colmap/pull/3524 * Fix comment for FilterStationaryMatches by @sarlinpe in https://github.com/colmap/colmap/pull/3525 * Ignore compile_commands.json in git by @ahojnnes in https://github.com/colmap/colmap/pull/3526 * Fix bundle adjustment performance regression due to changed Gauge by @ahojnnes in https://github.com/colmap/colmap/pull/3527 * Bind CUDA utils in pycolmap by @ahojnnes in https://github.com/colmap/colmap/pull/3532 * Add explicit tests for fixing gauge freedom and bugfix by @ahojnnes in https://github.com/colmap/colmap/pull/3533 * Fix format specifier overflow in WriteSnapshot() timestamp by @StonerLing in https://github.com/colmap/colmap/pull/3534 * Change input in cost function to log scale and remove custom manifold from colmap. by @B1ueber2y in https://github.com/colmap/colmap/pull/3538 * Throw exception when image.SetFramePtr() sets a frame without the image listed as its data. by @B1ueber2y in https://github.com/colmap/colmap/pull/3540 * Avoid frame having data from a sensor that does not exist in its associated rig. by @B1ueber2y in https://github.com/colmap/colmap/pull/3543 * Revert scale changes for metric reconstruction in rig sfm. by @B1ueber2y in https://github.com/colmap/colmap/pull/3530 * Fix comment for C++20 support. by @B1ueber2y in https://github.com/colmap/colmap/pull/3547 * Clean up unused includes in scene folder by @ahojnnes in https://github.com/colmap/colmap/pull/3548 * Add random_seed to IncrementalMapper to control all RANSAC seeds for improved SfM reproducibility by @StonerLing in https://github.com/colmap/colmap/pull/3544 * Throw exception at counting registered images when an image in the frame does not exist in the reconstruction. by @B1ueber2y in https://github.com/colmap/colmap/pull/3546 * Clean up unused includes in retrieval folder by @ahojnnes in https://github.com/colmap/colmap/pull/3549 * Clean up unused includes in optim folder by @ahojnnes in https://github.com/colmap/colmap/pull/3550 * Fix ModelViewerWidget::ClearReconstruction by @whuaegeanse in https://github.com/colmap/colmap/pull/3552 * Fix build with Boost 1.89.0 by @cho-m in https://github.com/colmap/colmap/pull/3553 * Clean up unused includes in util folder by @ahojnnes in https://github.com/colmap/colmap/pull/3551 * Retire Mac x86 support by @ahojnnes in https://github.com/colmap/colmap/pull/3555 * Update database bindings by @ahojnnes in https://github.com/colmap/colmap/pull/3556 * Throw exception for Frame::SetRigId when rig pointer is available. by @B1ueber2y in https://github.com/colmap/colmap/pull/3558 * Fix rig configuration with differing IDs between database and reconstruction by @ahojnnes in https://github.com/colmap/colmap/pull/3557 * Removing the Global Lock in SiftGPUFeatureMatcher for CUDA backend by @yimingc in https://github.com/colmap/colmap/pull/3561 * Add option to skip matching for image pairs in same frame by @ahojnnes in https://github.com/colmap/colmap/pull/3563 * Add option to keep specific cameras constant by @ahojnnes in https://github.com/colmap/colmap/pull/3565 * Throw exception if any camera in the rig does not exist in the reconstruction. by @B1ueber2y in https://github.com/colmap/colmap/pull/3564 * Clean up unused includes in controllers folder by @ahojnnes in https://github.com/colmap/colmap/pull/3566 * Extract image pair conversion functions into type utils by @ahojnnes in https://github.com/colmap/colmap/pull/3568 * Clean up unused includes in estimators folder by @ahojnnes in https://github.com/colmap/colmap/pull/3567 * Expect error threshold for generalized relative pose in pixel space by @ahojnnes in https://github.com/colmap/colmap/pull/3571 * Add function to read number of matches by @ahojnnes in https://github.com/colmap/colmap/pull/3572 * Add a test to ensure THROW_CHECK conditions are evaluated exactly once by @ahojnnes in https://github.com/colmap/colmap/pull/3573 * Simplify and reuse feature matcher thread creation by @ahojnnes in https://github.com/colmap/colmap/pull/3575 * Increase exhaustive matching cache size by @ahojnnes in https://github.com/colmap/colmap/pull/3576 * Add option to clean two-view geometries by @ahojnnes in https://github.com/colmap/colmap/pull/3577 * Decouple feature matching and two-view geometry options by @ahojnnes in https://github.com/colmap/colmap/pull/3578 * Display CLI floating-point defaults with 3 significant digits by @StonerLing in https://github.com/colmap/colmap/pull/3579 * Make use_log_scale an option for Point3DAlignmentCost. by @B1ueber2y in https://github.com/colmap/colmap/pull/3574 * Cache max number of keypoints by @ahojnnes in https://github.com/colmap/colmap/pull/3583 * Add tool for geometric verification by @ahojnnes in https://github.com/colmap/colmap/pull/3581 * Minor perf and code quality improvements for visual inverted index by @ahojnnes in https://github.com/colmap/colmap/pull/3584 * Upgrade pybind11 to 3.0.0 by @ahojnnes in https://github.com/colmap/colmap/pull/3523 * Fix missing colmap namespace in sqlite3 macro. by @B1ueber2y in https://github.com/colmap/colmap/pull/3587 * Use pybind smart holder for all classes by @ahojnnes in https://github.com/colmap/colmap/pull/3542 * Add option to perform geometric verification with rig constraints by @ahojnnes in https://github.com/colmap/colmap/pull/3498 * Use Qt QSettings to remember last-used paths across file dialogs by @MotivaCG in https://github.com/colmap/colmap/pull/3585 * Fix MaybeLoadImages by @whuaegeanse in https://github.com/colmap/colmap/pull/3586 * Sync 3.12 changelog changes by @ahojnnes in https://github.com/colmap/colmap/pull/3591 * Document rig-related feature matching options and change rig verification defaults by @ahojnnes in https://github.com/colmap/colmap/pull/3592 * Abstract database interface and sqlite implementation by @ahojnnes in https://github.com/colmap/colmap/pull/3541 * Share feature extraction max_image_size option by @ahojnnes in https://github.com/colmap/colmap/pull/3600 * Support for reading RGB or grayscale images for feature extraction by @ahojnnes in https://github.com/colmap/colmap/pull/3603 * Support both Qt5 and Qt6 by @zhouzq-thu in https://github.com/colmap/colmap/pull/3597 * Add option to fix individual rigs by @ahojnnes in https://github.com/colmap/colmap/pull/3604 * Ensure download support on Ubuntu by installing libssl-dev for crypto by @ahojnnes in https://github.com/colmap/colmap/pull/3607 * Update pybind11 to 3.0.1 by @ahojnnes in https://github.com/colmap/colmap/pull/3609 * Fix focal length extraction from 35mm equivalent by @ahojnnes in https://github.com/colmap/colmap/pull/3617 * Update to Windows 2025 runners and pin pycolmap image versions by @ahojnnes in https://github.com/colmap/colmap/pull/3618 * Update to latest vcpkg by @ahojnnes in https://github.com/colmap/colmap/pull/3619 * Add bindings for mvs::model for covisibility support in pycolmap by @B1ueber2y in https://github.com/colmap/colmap/pull/3621 * Fix inconsistent pycolmap naming for RegisterFrame and DeRegisterFrame. by @B1ueber2y in https://github.com/colmap/colmap/pull/3623 * Add Conda package installation instructions by @Tobias-Fischer in https://github.com/colmap/colmap/pull/3624 * Call Retriangulate irrespective of the logging level by @sarlinpe in https://github.com/colmap/colmap/pull/3626 * Add missing rig and frame interfaces for pycolmap database. by @B1ueber2y in https://github.com/colmap/colmap/pull/3629 * Throw exception when the rig configurations of the database and existing reconstruction are inconsistent. by @B1ueber2y in https://github.com/colmap/colmap/pull/3628 * Improve the Gauge logic for fixing two views. by @B1ueber2y in https://github.com/colmap/colmap/pull/3627 * Add changelog for 3.12.6 release. by @B1ueber2y in https://github.com/colmap/colmap/pull/3632 * Ensure min/max_focal_length_ratio and max_extra_param are piped to triangulation by @ahojnnes in https://github.com/colmap/colmap/pull/3637 * Fix broken logic of removing cameras at Reconstruction::TearDown(). by @B1ueber2y in https://github.com/colmap/colmap/pull/3634 * Minor: resolve -Wsign-compare warning for min_num_inliers. by @B1ueber2y in https://github.com/colmap/colmap/pull/3639 * Remove redundant binding of feature module in pycolmap by @ahojnnes in https://github.com/colmap/colmap/pull/3640 * Remove plyfile python package copy due to GNU license by @ahojnnes in https://github.com/colmap/colmap/pull/3644 * rig_from_world and sensor_from_rig in pycolmap should return reference rather than copy. by @B1ueber2y in https://github.com/colmap/colmap/pull/3645 * Also replace mask file extension to .png instead of appending by @Dawars in https://github.com/colmap/colmap/pull/3611 * Fix crash when built without GPU support by @sarlinpe in https://github.com/colmap/colmap/pull/3649 * Replace custom filtering with image filter view by @ahojnnes in https://github.com/colmap/colmap/pull/3651 * Expose image_ids filter view in frame in pycolmap by @ahojnnes in https://github.com/colmap/colmap/pull/3652 * Simplify and fix updating cameras with priors in benchmarking by @ahojnnes in https://github.com/colmap/colmap/pull/3653 * Fix typo in patch match options documentation by @ahojnnes in https://github.com/colmap/colmap/pull/3655 * Consistently name local BA options and deduplicate local_ba_num_images by @ahojnnes in https://github.com/colmap/colmap/pull/3657 * Adjust reconstruction consistency checks by @ahojnnes in https://github.com/colmap/colmap/pull/3658 * Improve the doc for loop_detection_period. by @sarlinpe in https://github.com/colmap/colmap/pull/3661 * Expose comparison operators in Python bindings by @jhacsonmeza in https://github.com/colmap/colmap/pull/3663 * Feature sub-selection for global BA by @ahojnnes in https://github.com/colmap/colmap/pull/3650 * Compute alignment RANSAC max_error from RMS stddev and chi-square by @StonerLing in https://github.com/colmap/colmap/pull/3664 * Address clang-tidy 21 errors by @ahojnnes in https://github.com/colmap/colmap/pull/3668 * Extract synthetic reconstruction noise functionality into separate utility by @ahojnnes in https://github.com/colmap/colmap/pull/3670 * Fix copy assignment of image by @ahojnnes in https://github.com/colmap/colmap/pull/3671 * Add gmock matcher for checking approximate reconstruction equality by @ahojnnes in https://github.com/colmap/colmap/pull/3672 * Add gmock matcher for checking reconstruction equality by @ahojnnes in https://github.com/colmap/colmap/pull/3673 * Include cassert by @FlexW in https://github.com/colmap/colmap/pull/3674 * Parallelize incremental mapper initialization by @ahojnnes in https://github.com/colmap/colmap/pull/3675 * Add timeout option for incremental mapper by @ahojnnes in https://github.com/colmap/colmap/pull/3676 * Implement equals operator for FeatureKeypoint/Match by @ahojnnes in https://github.com/colmap/colmap/pull/3678 * Add method to update keypoints in database by @ahojnnes in https://github.com/colmap/colmap/pull/3679 * Fix noise synthesis to consistently update database keypoints by @ahojnnes in https://github.com/colmap/colmap/pull/3680 * Add missing binding for Reconstruction::DeleteAllPoints2DAndPoints3D. by @B1ueber2y in https://github.com/colmap/colmap/pull/3682 * Fix BA convergence setting bug during initialization by @ahojnnes in https://github.com/colmap/colmap/pull/3677 * Add notice of the version of panorama_sfm.py by @sarlinpe in https://github.com/colmap/colmap/pull/3685 * Cuda-Enabled Pip Package (Linux x86-64 only) by @Tobias314 in https://github.com/colmap/colmap/pull/3608 * Update to CUDA 12.9.1 in CI by @ahojnnes in https://github.com/colmap/colmap/pull/3610 * Add test for BA controller by @ahojnnes in https://github.com/colmap/colmap/pull/3683 * Add tests for option manager by @ahojnnes in https://github.com/colmap/colmap/pull/3684 * Add tests for feature matching controllers by @ahojnnes in https://github.com/colmap/colmap/pull/3686 * Remove unused variable in reconstruction test by @ahojnnes in https://github.com/colmap/colmap/pull/3691 * Update the logic of duplicate correspondence to support one-to-many matches. by @B1ueber2y in https://github.com/colmap/colmap/pull/3681 * Some misc code improvements for ObservationManager by @ahojnnes in https://github.com/colmap/colmap/pull/3689 * Update PyPI publishing by @sarlinpe in https://github.com/colmap/colmap/pull/3692 * Update deprecated Eigen JacobiSVD usage by @ahojnnes in https://github.com/colmap/colmap/pull/3690 * Add unit test for feature extraction controller by @ahojnnes in https://github.com/colmap/colmap/pull/3693 * Update to latest clang-format 20.1.5 by @ahojnnes in https://github.com/colmap/colmap/pull/3694 * Add function to synthesize images by @ahojnnes in https://github.com/colmap/colmap/pull/3696 * Remove deprecated rig bundle adjuster by @B1ueber2y in https://github.com/colmap/colmap/pull/3698 * Fix coordinate bug in ComputeEqualPartsBboxes by @ahojnnes in https://github.com/colmap/colmap/pull/3700 * Create unit test for automatic reconstruction controller by @ahojnnes in https://github.com/colmap/colmap/pull/3697 * Add unit test for HammingDistanceWeightFunctor by @ahojnnes in https://github.com/colmap/colmap/pull/3702 * Add unit test for option manager parsing and move sys exit to call sites by @ahojnnes in https://github.com/colmap/colmap/pull/3703 * Add unit test for feature importer controller by @ahojnnes in https://github.com/colmap/colmap/pull/3704 -------------------------- COLMAP 3.12.6 (09/17/2025) -------------------------- Improvements ------------ * Upgrade to pybind 3.0.1 and use smart holder for all classes. * Support both Qt5 and Qt6. * Ensure download support on Ubuntu by installing libssl-dev for crypto. * Add bindings for mvs::model for covisibility support in pycolmap. * Add missing rig and frame interfaces for pycolmap database. * Throw exception when the rig configurations of the database and existing reconstruction are inconsistent. * Improve the Gauge logic for fixing two views. Bug Fixes --------- * Fix focal length extraction from 35mm equivalent. * Fix inconsistent pycolmap naming for RegisterFrame and DeRegisterFrame. * Call Retriangulate irrespective of the logging level. * Fix bundle adjustment with constant rig from world pose. -------------------------- COLMAP 3.12.5 (08/22/2025) -------------------------- Improvements ------------ * Add various safety checks with more understandable error messages when adding misconfigured rigs/cameras/frames/images to the reconstruction. * Recover original metric reconstruction scale in case of configured rigs. Bug Fixes --------- * Fix incompatibilities due to redundant symbol definition in pycolmap/pyceres. * Fix error threshold for generalized relative pose to be in pixel space. * Fix rig configuration in case of inconsistent IDs in database/reconstruction. * Fix missing colmap namespace in sqlite3 macro. * Fix CMake configuration with Boost 1.89 or newer. * Fix viewer crash when clearing the reconstruction. -------------------------- COLMAP 3.12.4 (08/04/2025) -------------------------- Bug Fixes --------- * Fix global bundle adjustment performance regression due to changing gauge fixing mechanism by fixing points vs. cameras. -------------------------- COLMAP 3.12.3 (07/16/2025) -------------------------- Bug Fixes --------- * Set correct version number -------------------------- COLMAP 3.12.2 (07/16/2025) -------------------------- Bug Fixes --------- * Define VisualIndex::Read as static in python bindings * Only find C/CXX OpenMP components to support new CMake versions * Fix a bug affecting feature matching on GPU * Fix potential deadlock in job queue * Update FindMetis.cmake to also link GK_LIBRARIES * Fix backwards compatibility in mapper with custom image list * Fix docker run script for GUI -------------------------- COLMAP 3.12.1 (07/05/2025) -------------------------- Bug Fixes --------- * Fix Docker runtime libraries * Fix spatial matcher bug * Minor fixes for documentation -------------------------- COLMAP 3.12.0 (06/30/2025) -------------------------- New Features ------------ * Support for modeling sensor rigs (and thus multi-camera rigs and panoramas). For more details and usage examples, see: https://colmap.github.io/rigs.html. * Automatic download and caching of vocabulary trees and other resources. * Support for converting between LLA and UTM coordinates. * Improved minimal solvers for affine transform and generalized absolute/relative pose. * Improved absolute pose estimation by minimizing pixel error in image space. * Replaced FLANN with faiss for fast approximate nearest neighbor search for improved speed in CPU-based feature matching and vocabulary tree-based image retrieval. * Support for propagating relative pose covariance. * Support visualization of models with arbitrary origin and scale (e.g., in GPS space). * Reconstruction benchmark for ETH3D, IMC, BlendedMVS datasets. * Measure and report code test coverage in CI. Bug Fixes --------- * Fixed RANSAC stopping criterion, see https://arxiv.org/pdf/2503.07829. * Fixed and improved two-view pose and triangulation angle estimation. * Fix rare deadlock during vocab tree feature matching. * For other bug fixes, see full list of changes below. Breaking Changes ---------------- * Serialization of reconstruction and database contains a new abstraction: rigs and frames. The reconstruction output contains two new files `rigs.{bin,txt}` and `frames.{bin,txt}`. The database contains new tables: `rigs`, `rig_sensors`, `frames`, `frames_data`. Reading from existing reconstructions and databases (without rigs/frames) is fully backwards compatible and vice versa reading new reconstructions (with rigs/frames) using old code is fully forwards compatible. * Sensor poses (and thus image poses) are now composed as: `sensor_from_world = sensor_from_rig * rig_from_world`. Previously, `image.cam_from_world` returned a reference to the pose parameters. Now it returns a copy of the pose composition: `image.cam_from_world() = image.frame.rig.sensor_from_rig(image.camera.sensor_id) * image.frame.rig_from_world` with the underlying pose parameters stored in the rig and frame objects. * Default bundle adjuster supports sensor rigs and thus rig bundle adjuster is deprecated. * FLANN-based vocabulary trees are incompatible with faiss. New trees automatically downloaded, if no vocab_tree_path is provided, otherwise manual download and update required. * Removed official support for Ubuntu 20.04, MacOS 13, and Visual Studio 2019. Full Change List (sorted temporally) ------------------------------------ * Cancel previous Github action runs upon push by @ahojnnes in https://github.com/colmap/colmap/pull/2998 * Fix ccache installation in pycolmap windows CI by @ahojnnes in https://github.com/colmap/colmap/pull/2997 * Use Azure blob storage as vcpkg binary cache by @ahojnnes in https://github.com/colmap/colmap/pull/2999 * Add missing openmp flags in retrieval for flann parallelization by @ahojnnes in https://github.com/colmap/colmap/pull/3018 * Separate read and write SAS tokens for vcpkg binary cache by @ahojnnes in https://github.com/colmap/colmap/pull/3027 * Define vcpkg binary cache source inline by @ahojnnes in https://github.com/colmap/colmap/pull/3028 * Fix conditional vcpkg binary cache config in bash by @ahojnnes in https://github.com/colmap/colmap/pull/3031 * Avoid absolute path for the include directory installation by @jhacsonmeza in https://github.com/colmap/colmap/pull/3024 * Add conversion between LLA and UTM coords. by @StonerLing in https://github.com/colmap/colmap/pull/3030 * Improve interface for ReadWriteBinaryBlob and add tests by @ahojnnes in https://github.com/colmap/colmap/pull/3033 * Add support for downloading files by @ahojnnes in https://github.com/colmap/colmap/pull/3022 * Add function to compute sha256 digest by @ahojnnes in https://github.com/colmap/colmap/pull/3035 * Automatically download and cache vocabulary tree by @ahojnnes in https://github.com/colmap/colmap/pull/3036 * Set vcpkg default features and synchronize to latest vcpkg by @ahojnnes in https://github.com/colmap/colmap/pull/3038 * Avoid unnecessary copy of input elements in Percentile/Median by @ahojnnes in https://github.com/colmap/colmap/pull/3039 * Abstract algorithm class IncrementalMapperImpl by @B1ueber2y in https://github.com/colmap/colmap/pull/3040 * Perform linear interpolation in percentile computation by @ahojnnes in https://github.com/colmap/colmap/pull/3041 * Avoid dependent inputs in IncrementalMapperImpl by @B1ueber2y in https://github.com/colmap/colmap/pull/3043 * Reorder destructors for better safety in EndReconstruction by @B1ueber2y in https://github.com/colmap/colmap/pull/3046 * Improvements for reconstruction normalization / bbox / centroid by @ahojnnes in https://github.com/colmap/colmap/pull/3047 * Speedup affine transform minimal solver, create python bindings by @ahojnnes in https://github.com/colmap/colmap/pull/3049 * Fix compilation with DOWNLOAD_ENABLED=OFF by @ahojnnes in https://github.com/colmap/colmap/pull/3053 * Consistent interface/tests for rigid3d/sim3d/affine2d, pycolmap bindings for rigid3d by @ahojnnes in https://github.com/colmap/colmap/pull/3051 * Improve logging for errors in masking during feature extraction by @Ambrosiussen in https://github.com/colmap/colmap/pull/3034 * Add copy constructor support for solver-related ceres bindings by @B1ueber2y in https://github.com/colmap/colmap/pull/3059 * Minor fix on using pycolmap bundle adjuster with pyceres by @B1ueber2y in https://github.com/colmap/colmap/pull/3060 * Re-enable interface support for covariance estimation from a Ceres::Problem instance by @B1ueber2y in https://github.com/colmap/colmap/pull/3061 * Only cancel CI runs in PRs and not in main/release branches by @ahojnnes in https://github.com/colmap/colmap/pull/3063 * Add binding support for invalid values in pycolmap id types by @B1ueber2y in https://github.com/colmap/colmap/pull/3072 * Fix custom quality level in ETH3D benchmark by @ahojnnes in https://github.com/colmap/colmap/pull/3076 * Set max_num_features automatically per quality level by @ahojnnes in https://github.com/colmap/colmap/pull/3077 * Make it possible to build the MVS doc even when CUDA is not installed by @sarlinpe in https://github.com/colmap/colmap/pull/3078 * Temporarily disable ccache in the pycolmap macOS CI by @sarlinpe in https://github.com/colmap/colmap/pull/3084 * Add option to specify image list in automatic reconstruction by @ahojnnes in https://github.com/colmap/colmap/pull/3074 * Only create OpenGL context in automatic reconstruction if necessary by @ahojnnes in https://github.com/colmap/colmap/pull/3075 * Remove unnecessary braces around initializer in pycolmap/covariance by @ahojnnes in https://github.com/colmap/colmap/pull/3080 * Remove temporary fixes for macOS CI by @sarlinpe in https://github.com/colmap/colmap/pull/2954 * Reconstruction benchmark by @ahojnnes in https://github.com/colmap/colmap/pull/2714 * Re-enable ccache in pycolmap Mac CI by @sarlinpe in https://github.com/colmap/colmap/pull/3085 * Fix transitive completion in incremental triangulator by @ahojnnes in https://github.com/colmap/colmap/pull/3094 * Fix image deletion, hide point viewer widget after deletion by @ahojnnes in https://github.com/colmap/colmap/pull/3098 * Fix download functionality under Windows by @ahojnnes in https://github.com/colmap/colmap/pull/3099 * Add back detailed logs for covariance estimation by @B1ueber2y in https://github.com/colmap/colmap/pull/3082 * Fix reprojection error in camera rig cost function by @binbin-xu in https://github.com/colmap/colmap/pull/3106 * Install missing libcurl4 runtime library in dockerfile by @ahojnnes in https://github.com/colmap/colmap/pull/3122 * Expose incremental mapper pose prior options in pycolmap by @ahojnnes in https://github.com/colmap/colmap/pull/3123 * Remove year from copyright by @ahojnnes in https://github.com/colmap/colmap/pull/3124 * Use poselib for generalized absolute pose minimal solver by @ahojnnes in https://github.com/colmap/colmap/pull/3125 * Add code coverage reporting by @ahojnnes in https://github.com/colmap/colmap/pull/3126 * Fix synthetic prior generation when stddev=0 by @ahojnnes in https://github.com/colmap/colmap/pull/3128 * Create temporary colmap test directy under system test directory by @ahojnnes in https://github.com/colmap/colmap/pull/3129 * Minor: pyceres is no longer a must for running pycolmap bundle adjuster by @B1ueber2y in https://github.com/colmap/colmap/pull/3130 * Fix cost functor convention for benchmarking by @B1ueber2y in https://github.com/colmap/colmap/pull/3131 * Support enum from string conversion by @ahojnnes in https://github.com/colmap/colmap/pull/3132 * More robustly handle degenerate triangulation angles by @ahojnnes in https://github.com/colmap/colmap/pull/3135 * Minor: add missing empty namespace in alignment testing script by @B1ueber2y in https://github.com/colmap/colmap/pull/3137 * Add frame impl for future rig support by @B1ueber2y in https://github.com/colmap/colmap/pull/2698 * Rename RigCalibration to RigCalib by @ahojnnes in https://github.com/colmap/colmap/pull/3142 * Fix and improve two-view pose and triangulation angle estimation by @ahojnnes in https://github.com/colmap/colmap/pull/3146 * Fix covariance propagation of pose inverse by @B1ueber2y in https://github.com/colmap/colmap/pull/3155 * [Spherical Camera Support] Change essential matrix estimation to use camera rays by @ahojnnes in https://github.com/colmap/colmap/pull/3159 * Improve incremental mapper initialization logic by @ahojnnes in https://github.com/colmap/colmap/pull/3161 * Improved RANSAC dependency injection by @ahojnnes in https://github.com/colmap/colmap/pull/3165 * Add docs on the left convention in COLMAP for covariance propagation. by @B1ueber2y in https://github.com/colmap/colmap/pull/3167 * Add docker instruction link to docs by @j3soon in https://github.com/colmap/colmap/pull/3169 * Compute absolute pose estimation error in image space by @ahojnnes in https://github.com/colmap/colmap/pull/3166 * Add support for propagating relative pose covariance. by @B1ueber2y in https://github.com/colmap/colmap/pull/3168 * Avoid using namespace in pycolmap headers by @ahojnnes in https://github.com/colmap/colmap/pull/3173 * Fix naming of cross covariance and add relative pose covariance interface by @B1ueber2y in https://github.com/colmap/colmap/pull/3170 * Camera models perform valid projection test by @ahojnnes in https://github.com/colmap/colmap/pull/3172 * Various improvements and extensions for pycolmap by @ahojnnes in https://github.com/colmap/colmap/pull/3176 * Fix pycolmap ci build for pull requests by @B1ueber2y in https://github.com/colmap/colmap/pull/3178 * Change CamFromImg to return optional ray by @ahojnnes in https://github.com/colmap/colmap/pull/3180 * Triangulation operates on camera rays by @ahojnnes in https://github.com/colmap/colmap/pull/3184 * Python bindings for visual index by @ahojnnes in https://github.com/colmap/colmap/pull/3185 * Define bindings in the correct order by @sarlinpe in https://github.com/colmap/colmap/pull/3189 * Restore CamFromImg to return normalized camera coordinates instead of… by @ahojnnes in https://github.com/colmap/colmap/pull/3193 * Add Rig serialization support to reconstruction+database by @ahojnnes in https://github.com/colmap/colmap/pull/3143 * Pull changes from main branch by @ahojnnes in https://github.com/colmap/colmap/pull/3194 * Fix maybe-uninitialized warnings by @papjuli in https://github.com/colmap/colmap/pull/3199 * Fix compilation errors with PoissonRecon by @theartful in https://github.com/colmap/colmap/pull/3200 * Remove Ubuntu 20.04 from the CI by @sarlinpe in https://github.com/colmap/colmap/pull/3203 * Add support for frame serialization by @ahojnnes in https://github.com/colmap/colmap/pull/3202 * Handle non-trivial frames in bundle adjustment by @ahojnnes in https://github.com/colmap/colmap/pull/3214 * Update email address by @sarlinpe in https://github.com/colmap/colmap/pull/3223 * Change the root of the Python package by @sarlinpe in https://github.com/colmap/colmap/pull/3217 * Fix bug when toggling rendering by @ahojnnes in https://github.com/colmap/colmap/pull/3230 * Add convenience iterator for frame image ids by @ahojnnes in https://github.com/colmap/colmap/pull/3231 * Update feature/rig with main by @ahojnnes in https://github.com/colmap/colmap/pull/3241 * Update to latest vcpkg by @ahojnnes in https://github.com/colmap/colmap/pull/3243 * Update feature/rig branch with latest changes in main by @ahojnnes in https://github.com/colmap/colmap/pull/3244 * Fix incremental pycolmap build script by @ahojnnes in https://github.com/colmap/colmap/pull/3245 * Logically group image reader options by @ahojnnes in https://github.com/colmap/colmap/pull/3246 * Fix chained match synthesis by @ahojnnes in https://github.com/colmap/colmap/pull/3248 * Retire Reconstruction::IsImageRegistered in favor of existing Image::HasPose by @ahojnnes in https://github.com/colmap/colmap/pull/3247 * Fix two-view geometry pose estimation for homography by @ahojnnes in https://github.com/colmap/colmap/pull/3250 * Fix uninitialized variable warnings by @ahojnnes in https://github.com/colmap/colmap/pull/3254 * Include Boost headers on build by @jonahjnewton in https://github.com/colmap/colmap/pull/3257 * Pull latest changes from main to feature/rig by @ahojnnes in https://github.com/colmap/colmap/pull/3262 * Support rigs/frames in incremental mapper by @ahojnnes in https://github.com/colmap/colmap/pull/3238 * Rename FrameFromWorld to RigFromWorld pose by @ahojnnes in https://github.com/colmap/colmap/pull/3263 * Add pytest on the e2e python pipeline into CI. by @B1ueber2y in https://github.com/colmap/colmap/pull/3266 * Fix broken python interfaces by @B1ueber2y in https://github.com/colmap/colmap/pull/3267 * Use generalized absolute pose estimation for non-trivial frames by @ahojnnes in https://github.com/colmap/colmap/pull/3265 * Fix color extraction for rig frames by @ahojnnes in https://github.com/colmap/colmap/pull/3268 * Sequential matcher expands rig images by @ahojnnes in https://github.com/colmap/colmap/pull/3270 * Fix usage of deprecated pycolmap interfaces in pycolmap README. by @B1ueber2y in https://github.com/colmap/colmap/pull/3272 * Improved code/docs and tests for rig configuration by @ahojnnes in https://github.com/colmap/colmap/pull/3275 * Update vcpkg to pull in fixes for ceres by @ahojnnes in https://github.com/colmap/colmap/pull/3276 * Rig bundle adjuster uses default bundle adjustment routine by @ahojnnes in https://github.com/colmap/colmap/pull/3281 * Cleanup legacy camera rig code by @ahojnnes in https://github.com/colmap/colmap/pull/3283 * Store rig sensors and frame data in separate database tables by @ahojnnes in https://github.com/colmap/colmap/pull/3285 * Configure trivial rigs and frames during feature extraction by @ahojnnes in https://github.com/colmap/colmap/pull/3287 * [Bugfix] Center 2D points by principal point for absolute pose estimation with unknown focal length by @xjiangan in https://github.com/colmap/colmap/pull/3289 * Add bindings for rig configuration by @ahojnnes in https://github.com/colmap/colmap/pull/3291 * Documentation for rig support by @ahojnnes in https://github.com/colmap/colmap/pull/3290 * Fix documentation of rigs.txt by @sarlinpe in https://github.com/colmap/colmap/pull/3292 * Update feature/rig with latest changes in main by @ahojnnes in https://github.com/colmap/colmap/pull/3293 * Merge feature/rig branch into main by @ahojnnes in https://github.com/colmap/colmap/pull/3295 * improve clarity of the rig example by @B1ueber2y in https://github.com/colmap/colmap/pull/3297 * Bind missing SequentialMatchingOptions.loop_detection_period by @sarlinpe in https://github.com/colmap/colmap/pull/3299 * cleanup legacy comments for base controller. by @B1ueber2y in https://github.com/colmap/colmap/pull/3300 * Fix bug in grayscale Bitmap.to_array by @sarlinpe in https://github.com/colmap/colmap/pull/3301 * Handle errors in Bitmap.read by @sarlinpe in https://github.com/colmap/colmap/pull/3302 * Add an example script for SfM with 360 spherical images by @sarlinpe in https://github.com/colmap/colmap/pull/3304 * Recognize URIs for vocab_tree_path in GUI feature matching by @ahojnnes in https://github.com/colmap/colmap/pull/3305 * Deterministic behavior for Python pipeline tests by @ahojnnes in https://github.com/colmap/colmap/pull/3306 * Move colmap/ui/main_window.h include to implementation by @ahojnnes in https://github.com/colmap/colmap/pull/3307 * Add Python 3.13 to pycolmap build matrix by @ahojnnes in https://github.com/colmap/colmap/pull/3308 * Add missing SiftMatchingOptions::cpu_brute_force_matcher to pycolmap bindings by @ahojnnes in https://github.com/colmap/colmap/pull/3309 * Augment pinhole renders with GPS EXIFs of the panos by @sarlinpe in https://github.com/colmap/colmap/pull/3310 * Add missing cpu_brute_force_matcher to option manager by @ahojnnes in https://github.com/colmap/colmap/pull/3315 * Bind GPSTransform and make GPSTransform::Ellipsoid an enum class by @sarlinpe in https://github.com/colmap/colmap/pull/3311 * Update pose prior bundle adjuster to handle rigs by @ahojnnes in https://github.com/colmap/colmap/pull/3312 * Add support for running pose prior mapper from GUI by @ahojnnes in https://github.com/colmap/colmap/pull/3313 * Enable different matcher types and default to sequential in pano example by @ahojnnes in https://github.com/colmap/colmap/pull/3314 * Modularize reconstruction I/O formats into different libraries by @ahojnnes in https://github.com/colmap/colmap/pull/3317 * Fall back to P3P solver for panoramic generalized absolute pose by @ahojnnes in https://github.com/colmap/colmap/pull/3318 * Fix FLANN-based CPU feature matcher crash in pycolmap by @ahojnnes in https://github.com/colmap/colmap/pull/3320 * Update cibuildwheel to 2.23.2 by @ahojnnes in https://github.com/colmap/colmap/pull/3081 * Assume prior focal length for explicitly defined rig camera models by @ahojnnes in https://github.com/colmap/colmap/pull/3321 * Fix rig configuration with partial input reconstruction by @ahojnnes in https://github.com/colmap/colmap/pull/3322 * Use reference for image.camera and image.frame in pycolmap. by @B1ueber2y in https://github.com/colmap/colmap/pull/3323 * Use reference for frame.rig in pycolmap. by @B1ueber2y in https://github.com/colmap/colmap/pull/3324 * Cosmetic improvement on some geometry python bindings by @B1ueber2y in https://github.com/colmap/colmap/pull/3325 * Add unit test for EstimateAbsolutePose by @ahojnnes in https://github.com/colmap/colmap/pull/3327 * Add gmock matchers for rigid3 and sim3 by @ahojnnes in https://github.com/colmap/colmap/pull/3328 * Add unit tests for absolute pose refinement by @ahojnnes in https://github.com/colmap/colmap/pull/3330 * Cosmetic cleanup for absolute pose tests by @ahojnnes in https://github.com/colmap/colmap/pull/3333 * Add generalized relative pose estimation and pose binding cleanups by @ahojnnes in https://github.com/colmap/colmap/pull/3334 * Turn camera parameter access debug checks into throwing checks by @ahojnnes in https://github.com/colmap/colmap/pull/3337 * Handle panoramic rigs in generalized relative pose estimation by @ahojnnes in https://github.com/colmap/colmap/pull/3338 * Cosmetic variable name improvements to match conventions by @ahojnnes in https://github.com/colmap/colmap/pull/3341 * Add unit test for relative pose estimation by @ahojnnes in https://github.com/colmap/colmap/pull/3342 * Avoid nested parallelization for vocab tree pairing by @ahojnnes in https://github.com/colmap/colmap/pull/3343 * Fix rigid3/sim3 matchers for older eigen versions by @ahojnnes in https://github.com/colmap/colmap/pull/3344 * Deterministic homography test by @ahojnnes in https://github.com/colmap/colmap/pull/3346 * Add missing return statement in PyEstimateGeneralizedRelativePose by @ahojnnes in https://github.com/colmap/colmap/pull/3349 * Fix runtime error in panorama_sfm.py with sequential matching by @samuelm2 in https://github.com/colmap/colmap/pull/3351 * Fix race conditions in feature matcher cache by @ahojnnes in https://github.com/colmap/colmap/pull/3354 * Use shared lock in thread safe LRU cache by @ahojnnes in https://github.com/colmap/colmap/pull/3355 * Upgrade Jimver/cuda-toolkit GH actions task to 0.2.23 by @ahojnnes in https://github.com/colmap/colmap/pull/3358 * Upgrade to Ubuntu 24.04 / clang-18 in CI for ASan and ClangTidy builds by @ahojnnes in https://github.com/colmap/colmap/pull/3357 * Use add_compile_definitions instead of deprecated add_definitions by @ahojnnes in https://github.com/colmap/colmap/pull/3348 * Update Mac Github runners and fix pycolmap deployment targets by @ahojnnes in https://github.com/colmap/colmap/pull/3361 * Suppress CUDA warnings related constexpr host/device calls by @ahojnnes in https://github.com/colmap/colmap/pull/3362 * Update docker image to ubuntu 24.04 by @ahojnnes in https://github.com/colmap/colmap/pull/3363 * Fix benchmarking for rigs by @ahojnnes in https://github.com/colmap/colmap/pull/3364 * Add option to overwrite matches in benchmarking by @ahojnnes in https://github.com/colmap/colmap/pull/3365 * Replace flann with faiss by @ahojnnes in https://github.com/colmap/colmap/pull/3350 * Update docker with all major CUDA archs and updated boost version by @ahojnnes in https://github.com/colmap/colmap/pull/3369 * Retire remaining flann components and remove as dependency by @ahojnnes in https://github.com/colmap/colmap/pull/3370 * Update feature index to use float descriptors and distances by @ahojnnes in https://github.com/colmap/colmap/pull/3371 * Fix deadlock during feature matching by @ahojnnes in https://github.com/colmap/colmap/pull/3373 * Warn user when reading legacy flann index by @ahojnnes in https://github.com/colmap/colmap/pull/3372 * expose loading database into database cache from DatabaseCache::Create. by @B1ueber2y in https://github.com/colmap/colmap/pull/3375 * minor: rename DatabaseCache::LoadDatabase to Load by @B1ueber2y in https://github.com/colmap/colmap/pull/3376 * Fix typo by @B1ueber2y in https://github.com/colmap/colmap/pull/3377 * Unit tests for image reader, remove redundant definition of database by @ahojnnes in https://github.com/colmap/colmap/pull/3383 * Fix trailing comma-separation when printing list contents by @ahojnnes in https://github.com/colmap/colmap/pull/3388 * Add missing VocabTreeMatching.num_threads in option manager by @ahojnnes in https://github.com/colmap/colmap/pull/3389 * Use OpenBLAS OpenMP version under Ubuntu to fix slow faiss by @ahojnnes in https://github.com/colmap/colmap/pull/3390 * Speedup database reads of rigs/frames with single SQL outer join query by @ahojnnes in https://github.com/colmap/colmap/pull/3387 * Introduce context manager to reset sqlite3 statements by @ahojnnes in https://github.com/colmap/colmap/pull/3392 * Add missing use_gpu options in pycolmap SIFT bindings by @ahojnnes in https://github.com/colmap/colmap/pull/3397 * Add FeatureMatch python bindings by @ahojnnes in https://github.com/colmap/colmap/pull/3398 * Add option to set log level in GUI by @ahojnnes in https://github.com/colmap/colmap/pull/3399 * Add docs to explain the concepts of rigs and frames. by @B1ueber2y in https://github.com/colmap/colmap/pull/3395 * Allow png mask without double extension by @MotivaCG in https://github.com/colmap/colmap/pull/3284 * Propagate macros to top-level CMakeLists.txt files by @jhacsonmeza in https://github.com/colmap/colmap/pull/3396 * Add a missing function implementation by @lpanaf in https://github.com/colmap/colmap/pull/3412 * Improved tests for reconstruction merging by @ahojnnes in https://github.com/colmap/colmap/pull/3413 * Use MKL as BLAS vendor for faiss by @ahojnnes in https://github.com/colmap/colmap/pull/3393 * Fix wrong doc for point covariance by @B1ueber2y in https://github.com/colmap/colmap/pull/3416 * Add legacy docs from 3.8 to 3.11. by @B1ueber2y in https://github.com/colmap/colmap/pull/3414 * Do not filter existing, fixed frames by @ahojnnes in https://github.com/colmap/colmap/pull/3403 * Tag commit id and date in the doc generation by @B1ueber2y in https://github.com/colmap/colmap/pull/3417 * Return bad initial pair when number of triangulation is less than abs_pose_min_num_inliers by @B1ueber2y in https://github.com/colmap/colmap/pull/3418 * Add option to build with thread sanitizer flags by @ahojnnes in https://github.com/colmap/colmap/pull/3420 * Add option to build with undefined behavior sanitizer flags by @ahojnnes in https://github.com/colmap/colmap/pull/3421 * Fix the RANSAC stopping criterion by @ahojnnes in https://github.com/colmap/colmap/pull/3425 * Replace incorrect call to nonZeros by @sarlinpe in https://github.com/colmap/colmap/pull/3426 * Add deprecation warning for rig_bundle_adjuster by @sarlinpe in https://github.com/colmap/colmap/pull/3427 * Fix incorrect include in euclidean_transform.h by @sarlinpe in https://github.com/colmap/colmap/pull/3428 * Add Frame::SetCamFromWorld in pycolmap and fix comment. by @B1ueber2y in https://github.com/colmap/colmap/pull/3429 * Estimate essential matrix using camera rays instead of points by @ahojnnes in https://github.com/colmap/colmap/pull/3423 * Fix FilterPoints3DWithSmallTriangulationAngle to return number of filtered observations by @whuaegeanse in https://github.com/colmap/colmap/pull/3424 * Update to latest vcpkg commit by @ahojnnes in https://github.com/colmap/colmap/pull/3430 * Initialize from non-trivial frame pairs using generalized relative pose by @ahojnnes in https://github.com/colmap/colmap/pull/3419 * Fix setup_ubuntu.sh for docker by @MasahiroOgawa in https://github.com/colmap/colmap/pull/3432 * Support visualization of models with arbitrary origin and scale by @ahojnnes in https://github.com/colmap/colmap/pull/3044 * Fix ReadPositionPriorData to return valid and numerically more stable Position prior data by @whuaegeanse https://github.com/colmap/colmap/pull/3438 -------------------------- COLMAP 3.11.1 (12/06/2024) -------------------------- Bug Fixes --------- * Fix typo in pycolmap function align_reconstruction_to_locations interface by @B1ueber2y in https://github.com/colmap/colmap/pull/2961 * Add back some ceres bindings to use pycolmap bundle adjustment without pyceres by @B1ueber2y in https://github.com/colmap/colmap/pull/2985 * Fix setting of RANSAC max error in pose prior BA alignment by @ahojnnes in https://github.com/colmap/colmap/pull/2993 -------------------------- COLMAP 3.11.0 (11/28/2024) -------------------------- New Features ------------ * New pose prior based incremental mapper that can leverage absolute pose priors from e.g. GPS measurements. * New bundle adjustment covariance estimation functionality. Significantly faster and more robust than Ceres. * API documentation with auto-generated stubs for pycolmap. * Use PoseLib's minimal solvers for faster performance and improved robustness. * Experimental support for CUDA-based bundle adjustment through Ceres (disabled by default). * Support for reading 16-bit PNG grayscale images. * New RAD_TAN_THIN_PRISM_FISHEYE camera model in support of Meta's Project Aria devices. * Replace numerical with analytical Jacobian in image undistortion for better convergence. * Many more performance optimizations and other improvements. See full list of changes below. Bug Fixes --------- * Fixed non-deterministic behavior of CUDA SIFT feature extractor. Broken since 3.10 release. * Fixed orientation detection of covariant/affine SIFT feature extractor. Broken since initial release. * Fixed point triangulator crashing due to bug in observation manager. Broken since 3.10 release. * Fixed sequential feature matcher overlap missing the farthest image. Broken since initial release. * Fixed rare deadlock during matching due to concurrent database access. Broken since 3.10 release. * Fixed little/big endian detection. Broken since 3.1 release. * For other bug fixes, see full list of changes below. Breaking Changes ---------------- * Dropped official support for Ubuntu 18.04, Visual Studio 2019. * Upgrade to C++17 standard in C++ and C++14 in CUDA source code. * New ``pose_priors`` table in database in support of pose prior based mapper. * PyCOLMAP API: * ``align_reconstrution_to_locations`` is renamed to ``align_reconstruction_to_locations`` (typo). * ``pycomap.cost_functions`` becomes a module and should be explicitly imported as ``import pycolmap.cost_functions``. * Replaced ``Image.registered`` by ``Image.{has_pose,reset_pose}``. * Replaced ``Image.{get_valid_point2D_ids,get_valid_points2D}`` by ``Image.{get_observation_point2D_idxs,get_observation_points2D}``. * Replaced ``Track.{append,remove}`` by ``Track.{add_element,delete_element}``. * ``AbsolutePoseErrorCost`` becomes ``AbsolutePosePriorCost``. * ``MetricRelativePoseErrorCost`` becomes ``RelativePosePriorCost``. * The signature of ``ReprojErrorCost`` and related cost functions was changed: arguments are reordered, the detection uncertainty is now a 2x2 covariance matrix. * ``BundleAdjuster`` becomes virtual and should be created with ``pycolmap.create_default_bundle_adjuster()``. * ``absolute_pose_estimation`` becomes ``estimate_and_refine_absolute_pose``. * ``pose_refinement`` becomes ``refine_absolute_pose``. * ``essential_matrix_estimation`` becomes ``estimate_essential_matrix``. * ``fundamental_matrix_estimation`` becomes ``estimate_fundamental_matrix``. * ``rig_absolute_pose_estimation`` becomes ``estimate_and_refine_generalized_absolute_pose``. * ``homography_matrix_estimation`` becomes ``estimate_homography_matrix``. * ``squared_sampson_error`` becomes ``compute_squared_sampson_error``. * ``homography_decomposition`` becomes ``pose_from_homography_matrix``. * ``Rigid3d.essential_matrix`` becomes ``pycolmap.essential_matrix_from_pose``. Full Change List (sorted temporally) ------------------------------------ * Updates for pycolmap by @ahojnnes in https://github.com/colmap/colmap/pull/2672 * Trigger CI on release/* branches by @ahojnnes in https://github.com/colmap/colmap/pull/2673 * Use consistent versioning scheme between C++/Python by @ahojnnes in https://github.com/colmap/colmap/pull/2674 * Add cost function for 3D alignment (with covariance) by @B1ueber2y in https://github.com/colmap/colmap/pull/2621 * Numpy 2 compatibility by @sarlinpe in https://github.com/colmap/colmap/pull/2682 * Add fix for specifying the correct pycolmap CMake python development … by @fulkast in https://github.com/colmap/colmap/pull/2683 * Remove non existant flags of model_aligner from docs by @TamirCohen in https://github.com/colmap/colmap/pull/2696 * Reset CMAKE_MODULE_PATH to previous value by @mvieth in https://github.com/colmap/colmap/pull/2699 * Robustify nchoosek against overflow by @ahojnnes in https://github.com/colmap/colmap/pull/2706 * Observation manager needs to check if image_id exists before query operations by @bo-rc in https://github.com/colmap/colmap/pull/2704 * Remove pose prior from database.py:add_image by @sarlinpe in https://github.com/colmap/colmap/pull/2707 * Fix: sequential matcher overlap number should be inclusive by @flm8620 in https://github.com/colmap/colmap/pull/2701 * Fix table mangled by clang-format by @sweber1 in https://github.com/colmap/colmap/pull/2710 * Write out options to ini in full precision, relax bundle adjuster convergence by @ahojnnes in https://github.com/colmap/colmap/pull/2713 * Tests for pairing library in feature matching by @ahojnnes in https://github.com/colmap/colmap/pull/2711 * Rename IncrementalMapperOptions to IncrementalPipelineOptions by @B1ueber2y in https://github.com/colmap/colmap/pull/2708 * Add support for CUDA sparse BA solver by @ahojnnes in https://github.com/colmap/colmap/pull/2717 * Rename HierarchicalMapperController to HierarchicalPipeline by @ahojnnes in https://github.com/colmap/colmap/pull/2718 * Make VisualIndex::Quantize const to improve readability by @IshitaTakeshi in https://github.com/colmap/colmap/pull/2723 * Fix CUDA_ENABLED macro in new bundle adjustment code by @drkoller in https://github.com/colmap/colmap/pull/2725 * Automatically generate stub files by @sarlinpe in https://github.com/colmap/colmap/pull/2721 * Add CUDA-based dense BA solver by @ahojnnes in https://github.com/colmap/colmap/pull/2732 * Improved and simplified caching in feature matching by @ahojnnes in https://github.com/colmap/colmap/pull/2731 * Fix colmap namespace in the macro support of logging. by @B1ueber2y in https://github.com/colmap/colmap/pull/2733 * Add callbacks by move by @ahojnnes in https://github.com/colmap/colmap/pull/2734 * Implement transitive matcher with pair generator + tests by @ahojnnes in https://github.com/colmap/colmap/pull/2735 * Provide reasonable defaults for some estimator options by @sarlinpe in https://github.com/colmap/colmap/pull/2745 * Fix mismatched Delaunay meshing options by @sarlinpe in https://github.com/colmap/colmap/pull/2748 * PyCOLMAP API documentation by @sarlinpe in https://github.com/colmap/colmap/pull/2749 * Improved pycolmap coverage and docs by @sarlinpe in https://github.com/colmap/colmap/pull/2752 * Follow-up fixes in pycolmap by @sarlinpe in https://github.com/colmap/colmap/pull/2755 * Report errors in import_images by @sarlinpe in https://github.com/colmap/colmap/pull/2750 * Further simplification of feature matcher code by @ahojnnes in https://github.com/colmap/colmap/pull/2744 * Add missing ClearModifiedPoints3D by @sarlinpe in https://github.com/colmap/colmap/pull/2761 * Store shared camera ptr for reconstruction images by @ahojnnes in https://github.com/colmap/colmap/pull/2762 * Avoid unnecessary copy of queue in IncrementalTriangulator::Complete() by @ahojnnes in https://github.com/colmap/colmap/pull/2764 * Branch prediction for THROW_CHECK_NOTNULL by @ahojnnes in https://github.com/colmap/colmap/pull/2765 * Use shared camera pointer in more places by @ahojnnes in https://github.com/colmap/colmap/pull/2763 * Support switching camera directly with camera pointer by @B1ueber2y in https://github.com/colmap/colmap/pull/2767 * Add test for MergeReconstructions by @B1ueber2y in https://github.com/colmap/colmap/pull/2766 * Fix little/big endian detection by @ahojnnes in https://github.com/colmap/colmap/pull/2768 * Fix options for CUDA sparse BA solver by @whuaegeanse in https://github.com/colmap/colmap/pull/2758 * Rename SupperMeasurer::Compare for improved readability by @ahojnnes in https://github.com/colmap/colmap/pull/2774 * Improvements for install docs by @ahojnnes in https://github.com/colmap/colmap/pull/2773 * fixed typo of align_reconstrution_to_locations to align_reconstructio… by @TamirCohen in https://github.com/colmap/colmap/pull/2776 * Fix missing camera ptr for Reconstruction.DeleteAllPoints2DAndPoints3D() by @B1ueber2y in https://github.com/colmap/colmap/pull/2779 * Rename remaining proj_matrix instances to cam_from_world by @ahojnnes in https://github.com/colmap/colmap/pull/2780 * Relative pose decomposition uses Rigid3d by @ahojnnes in https://github.com/colmap/colmap/pull/2781 * Minor renaming on pycolmap point2d and point3d filenames by @B1ueber2y in https://github.com/colmap/colmap/pull/2784 * Add validity check for pixel coordinate in the Fisheye camera. Fix tests. by @B1ueber2y in https://github.com/colmap/colmap/pull/2790 * Use branch prediction in PRNG functions by @ahojnnes in https://github.com/colmap/colmap/pull/2796 * Implementation of Aria Fisheye camera model by @nushakrishnan in https://github.com/colmap/colmap/pull/2786 * Upgrade to C++ 17 by @B1ueber2y in https://github.com/colmap/colmap/pull/2801 * Pose Prior based Incremental Mapper by @ferreram in https://github.com/colmap/colmap/pull/2660 * Expose UpdatePoint3DErrors to pycolmap by @theartful in https://github.com/colmap/colmap/pull/2805 * Switch to the Ruff Python formatter by @sarlinpe in https://github.com/colmap/colmap/pull/2803 * Add mixed Python-C++ PyCOLMAP package by @sarlinpe in https://github.com/colmap/colmap/pull/2747 * Enable Ruff linter for Python by @sarlinpe in https://github.com/colmap/colmap/pull/2806 * Use C++17 structured bindings in some places by @ahojnnes in https://github.com/colmap/colmap/pull/2808 * Add RAD_TAN_THIN_PRISM_FISHEYE to camera docs by @ahojnnes in https://github.com/colmap/colmap/pull/2810 * Customized cost functions should be functors instead by @B1ueber2y in https://github.com/colmap/colmap/pull/2811 * Install and use newer clang-format from pypi by @ahojnnes in https://github.com/colmap/colmap/pull/2812 * Return a reference in Reconstruction.image/camera/point3D by @sarlinpe in https://github.com/colmap/colmap/pull/2814 * Add test for PositionPriorErrorCostFunctor. by @ferreram in https://github.com/colmap/colmap/pull/2815 * Replace boost/filesystem with standard library by @ahojnnes in https://github.com/colmap/colmap/pull/2809 * Fix selection of BA solver type when there is no cuda by @ahojnnes in https://github.com/colmap/colmap/pull/2822 * More informative exception if invalid access of image/camera/point3D by @sarlinpe in https://github.com/colmap/colmap/pull/2825 * Use minimal solvers from poselib by @ahojnnes in https://github.com/colmap/colmap/pull/2288 * Disable -march=native flags in poselib by @ahojnnes in https://github.com/colmap/colmap/pull/2828 * Make ``Image::cam_from_world_`` optional by @sarlinpe in https://github.com/colmap/colmap/pull/2824 * Remove warning in configure step by @sarlinpe in https://github.com/colmap/colmap/pull/2830 * Fix coordinate notation in EstimateAbsolutePose by @ahojnnes in https://github.com/colmap/colmap/pull/2833 * Return success status in low-level triangulation functions by @ahojnnes in https://github.com/colmap/colmap/pull/2834 * Pin mypy version for tests by @ahojnnes in https://github.com/colmap/colmap/pull/2849 * Suppress CMP0167 warning for FindBoost under CMake 3.30 or newer by @ahojnnes in https://github.com/colmap/colmap/pull/2853 * Reconstruction reader/writer tests and scene class repr by @ahojnnes in https://github.com/colmap/colmap/pull/2842 * Select CUDA device when bundle adjustment uses GPU by @ahojnnes in https://github.com/colmap/colmap/pull/2846 * Fix copying behaviors of Reconstruction regarding camera pointers by @B1ueber2y in https://github.com/colmap/colmap/pull/2841 * Use the C++ string representation for Python dataclass objects by @sarlinpe in https://github.com/colmap/colmap/pull/2855 * Various improvements for pycolmap bindings by @ahojnnes in https://github.com/colmap/colmap/pull/2854 * Use analytical Jacobian in IterativeUndistortion. Add trust region by @B1ueber2y in https://github.com/colmap/colmap/pull/2857 * Improve the conditioning of covariance estimation by @B1ueber2y in https://github.com/colmap/colmap/pull/2860 * Avoid unnecessary copy of RANSAC inlier masks by @ahojnnes in https://github.com/colmap/colmap/pull/2863 * Various improvements for cost functors by @ahojnnes in https://github.com/colmap/colmap/pull/2867 * Rename ``*_mapper`` to ``*_pipeline`` files by @ahojnnes in https://github.com/colmap/colmap/pull/2870 * Update the manylinux CI to GCC 10 by @sarlinpe in https://github.com/colmap/colmap/pull/2873 * Fix rare deadlock during matching due to concurrent database access by @ahojnnes in https://github.com/colmap/colmap/pull/2876 * Add new and missing options to automatic reconstructor by @ahojnnes in https://github.com/colmap/colmap/pull/2877 * Shared auto diff cost function creation by @ahojnnes in https://github.com/colmap/colmap/pull/2878 * Enable model alignment to reference model by @ahojnnes in https://github.com/colmap/colmap/pull/2879 * Add covariance weighted cost functor by @ahojnnes in https://github.com/colmap/colmap/pull/2880 * Fix unused variable warnings under MSVC by @ahojnnes in https://github.com/colmap/colmap/pull/2884 * Skip all but latest Python version in PR builds by @ahojnnes in https://github.com/colmap/colmap/pull/2881 * [doc] Fix path to example in README.md by @kielnino in https://github.com/colmap/colmap/pull/2886 * Update Github actions versions by @ahojnnes in https://github.com/colmap/colmap/pull/2887 * [doc] Fix typo for gui menu item by @kielnino in https://github.com/colmap/colmap/pull/2885 * Fix input type for automatic stereo fusion on extreme quality setting by @ahojnnes in https://github.com/colmap/colmap/pull/2893 * Make target with all sources optional by @HernandoR in https://github.com/colmap/colmap/pull/2889 * Gracefully handle missing image pose in viewer by @ahojnnes in https://github.com/colmap/colmap/pull/2894 * Update to latest vcpkg release 2024.10.21 by @ahojnnes in https://github.com/colmap/colmap/pull/2908 * Fix conversion from CUDA texture references to objects in SIFT feature extraction by @ahojnnes in https://github.com/colmap/colmap/pull/2911 * Modernized bundle adjustment interface by @ahojnnes in https://github.com/colmap/colmap/pull/2896 * Add missing unit tests for reconstruction alignment functions by @ahojnnes in https://github.com/colmap/colmap/pull/2913 * Do not test EstimateManhattanWorldFrame if LSD is disabled by @sarlinpe in https://github.com/colmap/colmap/pull/2920 * Custom macro for enum to string support by @B1ueber2y in https://github.com/colmap/colmap/pull/2918 * Bind the estimation of Sim3d by @sarlinpe in https://github.com/colmap/colmap/pull/2903 * Initialize glog in custom gmock main function by @ahojnnes in https://github.com/colmap/colmap/pull/2916 * Update ccache for faster windows CI builds by @ahojnnes in https://github.com/colmap/colmap/pull/2922 * Fixes for Windows ARM64 support by @ahojnnes in https://github.com/colmap/colmap/pull/2921 * Move geometry implementation of ``__repr__``, ``__eq__`` overloads to C++ side by @ahojnnes in https://github.com/colmap/colmap/pull/2915 * Consistent interface and various improvements for pycolmap/estimators by @ahojnnes in https://github.com/colmap/colmap/pull/2923 * Exclude DetectLineSegments if LSD is disabled by @sarlinpe in https://github.com/colmap/colmap/pull/2927 * Enable reading 16bit/channel (png) images to grayscale by @Ediolot in https://github.com/colmap/colmap/pull/2924 * Cleanup of remaining pycolmap interfaces by @ahojnnes in https://github.com/colmap/colmap/pull/2925 * Fix affine SIFT feature orientation detection by @ahojnnes in https://github.com/colmap/colmap/pull/2929 * Improvements to deprecated pycolmap members by @sarlinpe in https://github.com/colmap/colmap/pull/2932 * Fix pkgconf installation in Mac CI by @ahojnnes in https://github.com/colmap/colmap/pull/2936 * Make sphinx show the pycolmap constructors by @sarlinpe in https://github.com/colmap/colmap/pull/2935 * Bind synthetic dataset functionality in pycolmap by @ahojnnes in https://github.com/colmap/colmap/pull/2938 * Cleaner import of C++ symbols by @sarlinpe in https://github.com/colmap/colmap/pull/2933 * Fix pycolmap breakage for Python 3.8 by @sarlinpe in https://github.com/colmap/colmap/pull/2941 * Remove legacy boost test macro by @ahojnnes in https://github.com/colmap/colmap/pull/2940 * Drop support for VS 2019 CI checks by @ahojnnes in https://github.com/colmap/colmap/pull/2943 * Fix CI cache thrashing by inconsistent vcpkg binary caching by @ahojnnes in https://github.com/colmap/colmap/pull/2942 * Introduce gmock Eigen matrix matchers by @ahojnnes in https://github.com/colmap/colmap/pull/2939 * Prevent double initialization of glog for <=0.5 by @sarlinpe in https://github.com/colmap/colmap/pull/2945 * Fixes and refactoring for bundle adjustment covariance estimation by @ahojnnes in https://github.com/colmap/colmap/pull/2788 * Fix duplicate library warnings in linking stage by @ahojnnes in https://github.com/colmap/colmap/pull/2871 * Add test for Python mapping pipeline by @ahojnnes in https://github.com/colmap/colmap/pull/2946 * Add helper script for incremental pycolmap build by @ahojnnes in https://github.com/colmap/colmap/pull/2947 * Fix and consistently define Qt window flags by @ahojnnes in https://github.com/colmap/colmap/pull/2949 * Cross platform usage of monospace font by @ahojnnes in https://github.com/colmap/colmap/pull/2950 * Update to latest pybind11 version by @ahojnnes in https://github.com/colmap/colmap/pull/2952 * Update install instructions for Mac using homebrew by @ahojnnes in https://github.com/colmap/colmap/pull/2953 ------------------------ COLMAP 3.10 (07/23/2024) ------------------------ * Add missing "include " needed for unique_ptr by @Tobias-Fischer in https://github.com/colmap/colmap/pull/2338 * Support decoding multi-byte characters in Python script by @jot-jt in https://github.com/colmap/colmap/pull/2344 * Split Dockerfile in two stages: builder and runtime. by @pablospe in https://github.com/colmap/colmap/pull/2347 * Dockerfile improvements by @pablospe in https://github.com/colmap/colmap/pull/2356 * Update VCPKG commit in Windows CI by @sarlinpe in https://github.com/colmap/colmap/pull/2365 * Simplify the creation of reprojection error cost functions by @sarlinpe in https://github.com/colmap/colmap/pull/2364 * Migrate pycolmap by @sarlinpe in https://github.com/colmap/colmap/pull/2367 * Rename master -> main in pycolmap CI by @sarlinpe in https://github.com/colmap/colmap/pull/2370 * Bind SetPRNGSeed by @sarlinpe in https://github.com/colmap/colmap/pull/2369 * Encapsulate freeimage usage from pycolmap in colmap bitmap by @ahojnnes in https://github.com/colmap/colmap/pull/2372 * Re-generate version info on git changes by @ahojnnes in https://github.com/colmap/colmap/pull/2373 * Consolidate colmap/pycolmap readmes, updated acknowledgements, etc. by @ahojnnes in https://github.com/colmap/colmap/pull/2374 * Fix crashing pycolmap CI on Windows by @sarlinpe in https://github.com/colmap/colmap/pull/2383 * Add costs for pose graph optimization by @sarlinpe in https://github.com/colmap/colmap/pull/2378 * Switch to exception checks - v2 by @sarlinpe in https://github.com/colmap/colmap/pull/2376 * Cleanup checks in pycolmap by @sarlinpe in https://github.com/colmap/colmap/pull/2388 * Add RigReprojErrorConstantRigCostFunction by @sarlinpe in https://github.com/colmap/colmap/pull/2377 * Add cost functions to pycolmap by @sarlinpe in https://github.com/colmap/colmap/pull/2393 * Fix warning C4722 by @whuaegeanse in https://github.com/colmap/colmap/pull/2391 * Move reconstruction IO utils to a new file by @sarlinpe in https://github.com/colmap/colmap/pull/2399 * Acquire the GIL before returning None by @sarlinpe in https://github.com/colmap/colmap/pull/2400 * Disentangle the controller from threading and integrate the new logic into IncrementalMapperController by @B1ueber2y in https://github.com/colmap/colmap/pull/2392 * Simplify the low-level triangulation API by @sarlinpe in https://github.com/colmap/colmap/pull/2402 * Initialize glog in pycolmap only if not already done by @sarlinpe in https://github.com/colmap/colmap/pull/2405 * Adapt all the controllers to inherit from BaseController rather than Thread (except for feature extraction and matching) by @B1ueber2y in https://github.com/colmap/colmap/pull/2406 * Update path to models.h in database docs by @diffner in https://github.com/colmap/colmap/pull/2412 * Migrate Ubuntu CI pipelines from ADO to Github by @ahojnnes in https://github.com/colmap/colmap/pull/2411 * Build wheels for Python 3.12 by @sarlinpe in https://github.com/colmap/colmap/pull/2416 * Migrate MacOS CI pipeline from ADO to Github by @ahojnnes in https://github.com/colmap/colmap/pull/2418 * Improve bindings of Database by @sarlinpe in https://github.com/colmap/colmap/pull/2413 * Migrate Windows CI pipeline from ADO to Github by @ahojnnes in https://github.com/colmap/colmap/pull/2419 * Reduce logging during incremental mapping by @sarlinpe in https://github.com/colmap/colmap/pull/2420 * Migrate Docker CI from ADO to Github, remove ADO pipelines by @ahojnnes in https://github.com/colmap/colmap/pull/2422 * Simplify IncrementalMapperController by @sarlinpe in https://github.com/colmap/colmap/pull/2421 * Fix for glog 0.7.0 by @sarlinpe in https://github.com/colmap/colmap/pull/2428 * Fix typo by @whuaegeanse in https://github.com/colmap/colmap/pull/2430 * Fix RunMapper by @whuaegeanse in https://github.com/colmap/colmap/pull/2431 * Do triangulation in the IncrementalMapperController by @sarlinpe in https://github.com/colmap/colmap/pull/2429 * Only push a new Docker image on release by @sarlinpe in https://github.com/colmap/colmap/pull/2436 * model aligner with type "custom" does not update reconstruction by @lpanaf in https://github.com/colmap/colmap/pull/2433 * Define vcpkg manifest by @ahojnnes in https://github.com/colmap/colmap/pull/2426 * Fix ordering of keyword arguments in pycolmap.rig_absolute_pose_estimation by @sarlinpe in https://github.com/colmap/colmap/pull/2440 * Reduce the build time of pycolmap by @sarlinpe in https://github.com/colmap/colmap/pull/2443 * Improve bindings of CorrespondenceGraph by @sarlinpe in https://github.com/colmap/colmap/pull/2476 * Bind Reconstruction::{SetUp,ImagePairStats} by @sarlinpe in https://github.com/colmap/colmap/pull/2477 * Add bindings for substeps of incremental mapper with a python example by @B1ueber2y in https://github.com/colmap/colmap/pull/2478 * Debug crashing VCPKG-based CI builds by @sarlinpe in https://github.com/colmap/colmap/pull/2508 * Upgrade to pybind11 v2.12. Fix bind_map and reconstruction.points3D by @B1ueber2y in https://github.com/colmap/colmap/pull/2502 * Minor fix on logging for the pycolmap customized runner by @B1ueber2y in https://github.com/colmap/colmap/pull/2503 * Fix missing public link deps, break circular feature-scene dependency by @ahojnnes in https://github.com/colmap/colmap/pull/2497 * Avoid duplicate image allocation during undistortion by @fseegraeber in https://github.com/colmap/colmap/pull/2520 * Fix reconstruction.points3D by @B1ueber2y in https://github.com/colmap/colmap/pull/2523 * Fix 'std::out_of_range' error when using hierarchical_mapper by @GrayMask in https://github.com/colmap/colmap/pull/2526 * Fix binding for std::vector by @sarlinpe in https://github.com/colmap/colmap/pull/2533 * Include pybind eigen header by @tmnku in https://github.com/colmap/colmap/pull/2510 * Fix pycolmap python pipeline for multiple models by @B1ueber2y in https://github.com/colmap/colmap/pull/2531 * make two view geometry writable by @tmnku in https://github.com/colmap/colmap/pull/2540 * Customized python interface for bundle adjustment by @B1ueber2y in https://github.com/colmap/colmap/pull/2509 * Fix typos by @MaximSmolskiy in https://github.com/colmap/colmap/pull/2553 * Implicitly convert iterator to ListPoint2D by @sarlinpe in https://github.com/colmap/colmap/pull/2558 * Fix model_cropper not resetting image.num_points3D of cropped_rec by @ArneSchulzTUBS in https://github.com/colmap/colmap/pull/2557 * Split pair generation and matching by @sarlinpe in https://github.com/colmap/colmap/pull/2573 * Add ObservationManager by @sarlinpe in https://github.com/colmap/colmap/pull/2575 * Log info about created feature extractor/matcher types by @ahojnnes in https://github.com/colmap/colmap/pull/2579 * LSD: making the AGPL dependency optional by @zap150 in https://github.com/colmap/colmap/pull/2578 * Disable LSD when building pycolmap wheels by @sarlinpe in https://github.com/colmap/colmap/pull/2580 * Synthesize full two-view geometry and raw matches by @ahojnnes in https://github.com/colmap/colmap/pull/2595 * Support Adjoint matrix computation for Rigid3d by @B1ueber2y in https://github.com/colmap/colmap/pull/2598 * Fix cost functions for pose graph optimization by @B1ueber2y in https://github.com/colmap/colmap/pull/2601 * Fix python bundle adjustment example with pyceres by @B1ueber2y in https://github.com/colmap/colmap/pull/2606 * Faster homography estimator by @ahojnnes in https://github.com/colmap/colmap/pull/2603 * Add function to find real cubic polynomial roots by @ahojnnes in https://github.com/colmap/colmap/pull/2609 * Align with the convention of ceres doc on SqrtInformation. by @B1ueber2y in https://github.com/colmap/colmap/pull/2611 * Faster 7-point fundamental matrix estimator by @ahojnnes in https://github.com/colmap/colmap/pull/2612 * Faster 8-point fundamental matrix estimator by @ahojnnes in https://github.com/colmap/colmap/pull/2613 * Covariance estimation for bundle adjustment with Schur elimination by @B1ueber2y in https://github.com/colmap/colmap/pull/2610 * Mac OS improvements by @BSVogler in https://github.com/colmap/colmap/pull/2622 * Update cibuildwheel to 2.19.2 by @ahojnnes in https://github.com/colmap/colmap/pull/2632 * Faster essential matrix estimators by @ahojnnes in https://github.com/colmap/colmap/pull/2618 * Remove CamFromWorldPrior and create LocationPrior by @sarlinpe in https://github.com/colmap/colmap/pull/2620 * Add option to disable uninstall target, restore CI pipeline by @ahojnnes in https://github.com/colmap/colmap/pull/2634 * Faster covariance computation for small blocks by @B1ueber2y in https://github.com/colmap/colmap/pull/2633 * Fix optimal point algorithm by @morrishelle in https://github.com/colmap/colmap/pull/2640 * Add shell script helper for profiling by @ahojnnes in https://github.com/colmap/colmap/pull/2635 * Declare PosePrior::IsValid as const by @ahojnnes in https://github.com/colmap/colmap/pull/2653 * Add CI build for Windows CUDA by @ahojnnes in https://github.com/colmap/colmap/pull/2651 * Publish windows binaries from CI by @ahojnnes in https://github.com/colmap/colmap/pull/2663 ------------------------- COLMAP 3.9.1 (01/08/2024) ------------------------- * Version 3.9 changelog by @ahojnnes in https://github.com/colmap/colmap/pull/2325 * Fully encapsulate freeimage in bitmap library (#2332) by @ahojnnes in https://github.com/colmap/colmap/pull/2334 ----------------------- COLMAP 3.9 (01/06/2024) ----------------------- * clang format all code and require clang-format-14 by @ahojnnes in https://github.com/colmap/colmap/pull/1785 * Fix compilation for vcpkg windows build by @ahojnnes in https://github.com/colmap/colmap/pull/1791 * Increment version number to 3.9 by @ahojnnes in https://github.com/colmap/colmap/pull/1794 * Remove unnecessary /arch:sse2 flag for MSVC by @ahojnnes in https://github.com/colmap/colmap/pull/1798 * Updated faq.rst by @CGCooke in https://github.com/colmap/colmap/pull/1801 * Fixed mistake in code comment for OpenCV Fisheye camera by @CGCooke in https://github.com/colmap/colmap/pull/1802 * Replace deprecated cudaThreadSynchronize with cudaDeviceSynchronize by @ahojnnes in https://github.com/colmap/colmap/pull/1806 * Replace deprecated Cuda texture references with texture objects by @ahojnnes in https://github.com/colmap/colmap/pull/1809 * Remove unused SIFT GPU cuda texture reference by @ahojnnes in https://github.com/colmap/colmap/pull/1823 * Upgrade SiftGPU to use CUDA texture objects by @ahojnnes in https://github.com/colmap/colmap/pull/1838 * Remove PBA as bundle adjustment backend to support CUDA 12+ by @ahojnnes in https://github.com/colmap/colmap/pull/1840 * Replace deprecated CUDA sature function call by @ahojnnes in https://github.com/colmap/colmap/pull/1841 * Avoid unnecessary mallocs during sampling by @ahojnnes in https://github.com/colmap/colmap/pull/1842 * Cleaned up docker readme and scripts by @ahojnnes in https://github.com/colmap/colmap/pull/1852 * add "Shared intrinsics per sub-folder" checkbox to automatic reconstruction window by @kenshi84 in https://github.com/colmap/colmap/pull/1853 * Update vcpkg by @ahojnnes in https://github.com/colmap/colmap/pull/1925 * Log the name of the file that causes Mat::Read() to checkfail by @SomeAlphabetGuy in https://github.com/colmap/colmap/pull/1923 * check Z_index correctly in ReadPly by @countywest in https://github.com/colmap/colmap/pull/1896 * Don't re-open files when reading and writing matrices by @SomeAlphabetGuy in https://github.com/colmap/colmap/pull/1926 * Update vcpkg to latest commit by @ahojnnes in https://github.com/colmap/colmap/pull/1948 * Remove unnecessary custom Eigen aligned allocator macros by @ahojnnes in https://github.com/colmap/colmap/pull/1947 * Prefix internal sources/includes with colmap by @ahojnnes in https://github.com/colmap/colmap/pull/1949 * Simplify clang-format config and sort includes by @ahojnnes in https://github.com/colmap/colmap/pull/1950 * Handle possible overflow in median function by @ahojnnes in https://github.com/colmap/colmap/pull/1951 * Run ASan pipeline under Ubuntu 22.04 by @ahojnnes in https://github.com/colmap/colmap/pull/1952 * Fix Ceres version test by @drkoller in https://github.com/colmap/colmap/pull/1954 * Fix deprecation warning for Qt font metrics width by @ahojnnes in https://github.com/colmap/colmap/pull/1958 * Setup clang-tidy and enable perf warnings by @ahojnnes in https://github.com/colmap/colmap/pull/1959 * VCPKG binary caching for windows CI by @ahojnnes in https://github.com/colmap/colmap/pull/1957 * Cosmetics for VS dev shell script by @ahojnnes in https://github.com/colmap/colmap/pull/1965 * Enable clang-tidy concurrency checks by @ahojnnes in https://github.com/colmap/colmap/pull/1967 * [Bug] fix finding shared points3D in FindLocalBundle by @wesleyliwei in https://github.com/colmap/colmap/pull/1963 * Enable compiler caching in CI by @ahojnnes in https://github.com/colmap/colmap/pull/1972 * Set number of features for different quality levels by @ahojnnes in https://github.com/colmap/colmap/pull/1975 * Specify parameter name using inline comment by @ahojnnes in https://github.com/colmap/colmap/pull/1976 * Fix Windows CCache by @ahojnnes in https://github.com/colmap/colmap/pull/1977 * Add e2e tests in CI pipeline using ETH3D datasets by @ahojnnes in https://github.com/colmap/colmap/pull/1397 * [feature] print verbose information for model analyzer by @wesleyliwei in https://github.com/colmap/colmap/pull/1978 * Add a missing include to compile with gcc13 by @EstebanDugueperoux2 in https://github.com/colmap/colmap/pull/1984 * Speed up snapshot construct in RigBundleAdjuster by @wesleyliwei in https://github.com/colmap/colmap/pull/1988 * Update outdated docker cuda image tag by @ahojnnes in https://github.com/colmap/colmap/pull/1992 * Add boulders ETH3D dataset to CI E2E tests by @ahojnnes in https://github.com/colmap/colmap/pull/1991 * Update executable paths in documentation by @ahojnnes in https://github.com/colmap/colmap/pull/1993 * Avoid unnecessary copy in ExtractTopScaleFeatures by @ahojnnes in https://github.com/colmap/colmap/pull/1994 * Move related code under new image library folder by @ahojnnes in https://github.com/colmap/colmap/pull/1995 * Move related code under new camera folder by @ahojnnes in https://github.com/colmap/colmap/pull/1996 * Added a virtual destructor to Sampler by @SomeAlphabetGuy in https://github.com/colmap/colmap/pull/2000 * Add a few more clang-tidy checks by @ahojnnes in https://github.com/colmap/colmap/pull/2001 * Move related code to new geometry module by @ahojnnes in https://github.com/colmap/colmap/pull/2006 * Use #pragma once as include guard by @ahojnnes in https://github.com/colmap/colmap/pull/2007 * Add bugprone-* clang-tidy checks by @ahojnnes in https://github.com/colmap/colmap/pull/2010 * Avoid const params in declarations by @ahojnnes in https://github.com/colmap/colmap/pull/2011 * Set and require C++14 by @ahojnnes in https://github.com/colmap/colmap/pull/2012 * Cleanup math functions that are now part of eigen/stdlib by @ahojnnes in https://github.com/colmap/colmap/pull/2013 * Add clang-analyzer checks by @ahojnnes in https://github.com/colmap/colmap/pull/2014 * Replace CMake provided find_package scripts and modern CMake targets by @ahojnnes in https://github.com/colmap/colmap/pull/2016 * Switch from Boost unit tests to Gtest by @ahojnnes in https://github.com/colmap/colmap/pull/2017 * Fix ccache restore keys in pipeline caching by @ahojnnes in https://github.com/colmap/colmap/pull/2018 * Add missing cacheHitVar to fix ccache by @ahojnnes in https://github.com/colmap/colmap/pull/2020 * Add missing Boost::graph import by @sarlinpe in https://github.com/colmap/colmap/pull/2021 * Compressed/flattened correspondence graph for faster triangulation / less memory by @ahojnnes in https://github.com/colmap/colmap/pull/2019 * Fix window ccache key by @ahojnnes in https://github.com/colmap/colmap/pull/2024 * Consistently use shared_ptr for shared pointers for SFM objects by @ahojnnes in https://github.com/colmap/colmap/pull/2023 * Remove check on Qt version by @sarlinpe in https://github.com/colmap/colmap/pull/2022 * Synthetics for E2E incremental mapper tests by @ahojnnes in https://github.com/colmap/colmap/pull/2025 * New math module by @ahojnnes in https://github.com/colmap/colmap/pull/2028 * Simplify similarity transform and more tests by @ahojnnes in https://github.com/colmap/colmap/pull/2030 * Extract reconstruction alignment functions into new file by @ahojnnes in https://github.com/colmap/colmap/pull/2032 * Add E2E hierarchical mapper tests by @ahojnnes in https://github.com/colmap/colmap/pull/2033 * Rename SimilarityTransform3 to Sim3d by @ahojnnes in https://github.com/colmap/colmap/pull/2034 * Add Rigid3d transform class by @ahojnnes in https://github.com/colmap/colmap/pull/2035 * Consolidate and simplify Rigid3d and Sim3d by @ahojnnes in https://github.com/colmap/colmap/pull/2037 * Some small improvements/cleanup for rigid3d/sim3d usage by @ahojnnes in https://github.com/colmap/colmap/pull/2041 * CamFromWorld replaces qvec/tvec by @ahojnnes in https://github.com/colmap/colmap/pull/2039 * Retry download of ETH3D datasets by @ahojnnes in https://github.com/colmap/colmap/pull/2043 * WorldToImage becomes CamToImg by @ahojnnes in https://github.com/colmap/colmap/pull/2044 * Camera models operate on camera rays by @ahojnnes in https://github.com/colmap/colmap/pull/2045 * Ignore directory .vs by @whuaegeanse in https://github.com/colmap/colmap/pull/2046 * Use the reference of Rigid3d to reduce memory consumption by @whuaegeanse in https://github.com/colmap/colmap/pull/2047 * Inline point to image projection by @ahojnnes in https://github.com/colmap/colmap/pull/2050 * Point2D becomes simpler pure data struct by @ahojnnes in https://github.com/colmap/colmap/pull/2051 * Use Eigen math for estimator utils by @ahojnnes in https://github.com/colmap/colmap/pull/2052 * Move cost functions under geometry module and rename by @ahojnnes in https://github.com/colmap/colmap/pull/2053 * Bundle adjuster is an estimator by @ahojnnes in https://github.com/colmap/colmap/pull/2054 * Remaining base targets move to new scene module by @ahojnnes in https://github.com/colmap/colmap/pull/2055 * Vote and verify improvements/speedup by @ahojnnes in https://github.com/colmap/colmap/pull/2056 * Generate version info in .cc file to reduce number of recompilations by @ahojnnes in https://github.com/colmap/colmap/pull/2057 * Option manager moves to controllers to disentangle circular deps by @ahojnnes in https://github.com/colmap/colmap/pull/2058 * Granular CMake modules and build targets by @ahojnnes in https://github.com/colmap/colmap/pull/2059 * Fix docker build by @ahojnnes in https://github.com/colmap/colmap/pull/2069 * Remove warnings about duplicated marco NOMINMAX by @whuaegeanse in https://github.com/colmap/colmap/pull/2067 * lib folder becomes thirdparty folder by @ahojnnes in https://github.com/colmap/colmap/pull/2068 * Remove unnecessary checks in image pair conversion by @ahojnnes in https://github.com/colmap/colmap/pull/2074 * Replace flaky ETH3D terrace with courtyard dataset by @ahojnnes in https://github.com/colmap/colmap/pull/2075 * Synthesize chained match graph for more mapper tests by @ahojnnes in https://github.com/colmap/colmap/pull/2076 * Introduce abstract feature extractor by @ahojnnes in https://github.com/colmap/colmap/pull/2077 * Avoid unnecessary data copies in feature conversion utils by @ahojnnes in https://github.com/colmap/colmap/pull/2078 * Abstract feature matcher by @ahojnnes in https://github.com/colmap/colmap/pull/2082 * Encapsulate feature matching controller/worker implementations by @ahojnnes in https://github.com/colmap/colmap/pull/2085 * Some cosmetics for util/feature types by @ahojnnes in https://github.com/colmap/colmap/pull/2084 * Use std:: when cmath included by @whuaegeanse in https://github.com/colmap/colmap/pull/2081 * Encapsulate feature extraction controller/worker implementations by @ahojnnes in https://github.com/colmap/colmap/pull/2086 * Reenable VS2022 CI pipeline by @ahojnnes in https://github.com/colmap/colmap/pull/1689 * Consistent transform convention for CenterAndNormalizeImagePoints by @ahojnnes in https://github.com/colmap/colmap/pull/2092 * Retire Mac 11 CI build by @ahojnnes in https://github.com/colmap/colmap/pull/2094 * Add ReprojErrorConstantPoint3DCostFunction to speed up the RefineAbsolutePose function by @whuaegeanse in https://github.com/colmap/colmap/pull/2089 * Numeric differentiation of camera model using partial piv LU by @ahojnnes in https://github.com/colmap/colmap/pull/2100 * cmake: add testing.cc to colmap_util only if TESTS_ENABLED=ON by @NeroBurner in https://github.com/colmap/colmap/pull/2102 * Set CUDA_STANDARD to 14 by @ahojnnes in https://github.com/colmap/colmap/pull/2108 * Transform back to existing images positions after mapper processing if set fixed by @ferreram in https://github.com/colmap/colmap/pull/2095 * Update documentation with new branch policy by @ahojnnes in https://github.com/colmap/colmap/pull/2110 * Update CMake find dependencies for vcpkg by @ahojnnes in https://github.com/colmap/colmap/pull/2116 * Decouple SIFT match from two view geometry options by @ahojnnes in https://github.com/colmap/colmap/pull/2118 * Fix docker build by @vnmsklnk in https://github.com/colmap/colmap/pull/2122 * Trigger build pipeline on main branch by @ahojnnes in https://github.com/colmap/colmap/pull/2123 * Update Linux install documentation with new branch policy by @joshuaoreilly in https://github.com/colmap/colmap/pull/2126 * Fix link in camera model documentation by @CFretter in https://github.com/colmap/colmap/pull/2152 * [Bugfix] Fix GUI_ENABLED=OFF and skip SiftGPU if no GUI and no CUDA by @sarlinpe in https://github.com/colmap/colmap/pull/2151 * [Bugfix] Properly handle CGAL_ENABLED by @sarlinpe in https://github.com/colmap/colmap/pull/2149 * Refinement of intrinsics in the point_triangulator by @tsattler in https://github.com/colmap/colmap/pull/2144 * Bugfix in handling COLMAP_GPU_ENABLED by @sarlinpe in https://github.com/colmap/colmap/pull/2163 * Expose exe as libs by @sarlinpe in https://github.com/colmap/colmap/pull/2165 * Add Sim3d::FromMatrix by @sarlinpe in https://github.com/colmap/colmap/pull/2147 * Check code format in CI by @ahojnnes in https://github.com/colmap/colmap/pull/2171 * Clean up dependencies by @sarlinpe in https://github.com/colmap/colmap/pull/2173 * Move tests into anonymous namespaces by @ahojnnes in https://github.com/colmap/colmap/pull/2175 * Fix glew/qopengl conflict warning by @ahojnnes in https://github.com/colmap/colmap/pull/2176 * Update documentation with new link to GitHub discussions by @ahojnnes in https://github.com/colmap/colmap/pull/2177 * Restore GLEW include by @sarlinpe in https://github.com/colmap/colmap/pull/2178 * Align reconstructions via shared 3D points by @sarlinpe in https://github.com/colmap/colmap/pull/2169 * Add clang-tidy-cachein CI by @ahojnnes in https://github.com/colmap/colmap/pull/2182 * Disable GUI build in one CI config by @ahojnnes in https://github.com/colmap/colmap/pull/2181 * Show verbose ccache stats by @ahojnnes in https://github.com/colmap/colmap/pull/2183 * Add EstimateGeneralizedAbsolutePose by @sarlinpe in https://github.com/colmap/colmap/pull/2174 * Fix bug in ReconstructionManagerWidget::Update by @whuaegeanse in https://github.com/colmap/colmap/pull/2186 * Fix missing retrieval dependency by @ahojnnes in https://github.com/colmap/colmap/pull/2189 * Removing clustering_options and mapper_options in Hierarchical Mapper Controller by @Serenitysmk in https://github.com/colmap/colmap/pull/2193 * Publish docker image to docker hub by @ahojnnes in https://github.com/colmap/colmap/pull/2195 * Fix Cuda architecture in docker build by @ahojnnes in https://github.com/colmap/colmap/pull/2196 * Fix all-major cuda arch missing in CMake < 3.23 by @ahojnnes in https://github.com/colmap/colmap/pull/2197 * Update triangulation.cc by @RayShark0605 in https://github.com/colmap/colmap/pull/2205 * Update author and acknowledgements by @ahojnnes in https://github.com/colmap/colmap/pull/2207 * Code formatting for Python by @ahojnnes in https://github.com/colmap/colmap/pull/2208 * Retire outdated build script by @ahojnnes in https://github.com/colmap/colmap/pull/2217 * Remove mention of deprecated build script by @sarlinpe in https://github.com/colmap/colmap/pull/2220 * Improve word spelling by @zchrissirhcz in https://github.com/colmap/colmap/pull/2235 * Stack allocate camera param idx arrays by @ahojnnes in https://github.com/colmap/colmap/pull/2234 * fix: typo in colmap/src/colmap/ui/project_widget.cc by @varundhand in https://github.com/colmap/colmap/pull/2241 * Update reconstruction.cc by @RayShark0605 in https://github.com/colmap/colmap/pull/2238 * Update to Docker CUDA 12.2.2 by @ahojnnes in https://github.com/colmap/colmap/pull/2244 * Stop setting C++ standard flags manually by @AdrianBunk in https://github.com/colmap/colmap/pull/2251 * Setting clear_points to true per default in point_triangulator by @tsattler in https://github.com/colmap/colmap/pull/2252 * Update cameras.rst to fix link to code by @tsattler in https://github.com/colmap/colmap/pull/2246 * Fix matching of imported features without descriptors by @ahojnnes in https://github.com/colmap/colmap/pull/2269 * Consistent versioning between documentation and code by @ahojnnes in https://github.com/colmap/colmap/pull/2275 * Reduce mallocs for RANSAC estimator models by @ahojnnes in https://github.com/colmap/colmap/pull/2283 * Migrate to glog logging by @ahojnnes in https://github.com/colmap/colmap/pull/2172 * Turn Point3D into simple data struct by @ahojnnes in https://github.com/colmap/colmap/pull/2285 * Camera becomes simple data struct by @ahojnnes in https://github.com/colmap/colmap/pull/2286 * Recover custom Eigen std::vector allocator for Eigen <3.4 support by @ahojnnes in https://github.com/colmap/colmap/pull/2293 * Replace result_of with invoke_result_t by @sarlinpe in https://github.com/colmap/colmap/pull/2300 * Allow getters FocalLength{X,Y} for isotropic models by @sarlinpe in https://github.com/colmap/colmap/pull/2301 * Add missing Boost targets and cleanup includes by @sarlinpe in https://github.com/colmap/colmap/pull/2304 * Expose IncrementalMapperOptions::{mapper,triangulation} by @sarlinpe in https://github.com/colmap/colmap/pull/2308 * Update install instructions for Mac by @Dawars in https://github.com/colmap/colmap/pull/2310 * Remove unused ceres reference in doc by @ahojnnes in https://github.com/colmap/colmap/pull/2315 * Fix typo by @whuaegeanse in https://github.com/colmap/colmap/pull/2317 * Stable version 3.9 release by @ahojnnes in https://github.com/colmap/colmap/pull/2319 ----------------------- COLMAP 3.8 (01/31/2023) ----------------------- * Updating geo-registration doc. by @ferreram in https://github.com/colmap/colmap/pull/1410 * Adding user-specified option for reconstructing purely planar scene. … by @ferreram in https://github.com/colmap/colmap/pull/1408 * Only apply sqlite vacuum command when elements are deleted from the database. by @ferreram in https://github.com/colmap/colmap/pull/1414 * Replace Graclus with Metis dependency by @ahojnnes in https://github.com/colmap/colmap/pull/1422 * Update ceres download URL in build script by @whuaegeanse in https://github.com/colmap/colmap/pull/1430 * Fix type errors when building colmap with build.py in windows by @whuaegeanse in https://github.com/colmap/colmap/pull/1440 * Fix bug in the computation of the statistics Global/Local BA by @whuaegeanse in https://github.com/colmap/colmap/pull/1449 * Add RefineGeneralizedAbsolutePose and covariance estimation by @Skydes in https://github.com/colmap/colmap/pull/1464 * Update docker image definition by @ahojnnes in https://github.com/colmap/colmap/pull/1478 * Upgrade deprecated ceres parameterizations to manifolds by @ahojnnes in https://github.com/colmap/colmap/pull/1477 * Use masks for stereo fusion on automatic reconstruction by @ibrarmalik in https://github.com/colmap/colmap/pull/1488 * fix random seed set failed from external interface by @WZG3661 in https://github.com/colmap/colmap/pull/1498 * Replace deprecated Eigen nonZeros() call for most recent Eigen versions. by @nackjaylor in https://github.com/colmap/colmap/pull/1494 * Fix ceres-solver folder name by @f-fl0 in https://github.com/colmap/colmap/pull/1501 * Improved convergence criterion for XYZ to ELL conversion by @ahojnnes in https://github.com/colmap/colmap/pull/1505 * Fix bug in the function SetPtr of Bitmap by @whuaegeanse in https://github.com/colmap/colmap/pull/1525 * Avoid the calling of copy constructor/assignment by @whuaegeanse in https://github.com/colmap/colmap/pull/1524 * Avoid calling copy constructors of FeatureKeypoints and FeatureDescriptors by @whuaegeanse in https://github.com/colmap/colmap/pull/1540 * Initialize freeimage if statically linked by @ahojnnes in https://github.com/colmap/colmap/pull/1549 * Avoid hard crash if Jacobian matrix is rank deficient by @mihaidusmanu in https://github.com/colmap/colmap/pull/1557 * visualize_model.py: added FULL_OPENCV model by @soeroesg in https://github.com/colmap/colmap/pull/1552 * Update vcpkg version to fix CI pipeline by @ahojnnes in https://github.com/colmap/colmap/pull/1568 * Replace deprecated Mac OS 10.15 with Mac OS 12 build in CI by @ahojnnes in https://github.com/colmap/colmap/pull/1569 * Fix inconsistent between the actual executed image reader option and the saved project.ini file by @XuChengHUST in https://github.com/colmap/colmap/pull/1564 * checkout the expected version of ceres solver by @scott-vsi in https://github.com/colmap/colmap/pull/1576 * use default qt5 brew install directory #1573 by @catapulta in https://github.com/colmap/colmap/pull/1574 * Fix image undistortion with nested image folders by @ahojnnes in https://github.com/colmap/colmap/pull/1606 * Fix source file permissions by @ahojnnes in https://github.com/colmap/colmap/pull/1607 * Fixed the collection of arguments in colmap.bat by @tdegraaff in https://github.com/colmap/colmap/pull/1121 * Add OpenMP to COLMAP_EXTERNAL_LIBRARIES if enabled by @logchan in https://github.com/colmap/colmap/pull/1632 * Fix output tile reconstructions are the same as the input reconstruction in `RunModelSplitter` (#1513) by @Serenitysmk in https://github.com/colmap/colmap/pull/1531 * add `libmetis-dev` to solve `METIS_INCLUDE_DIRS`. by @FavorMylikes in https://github.com/colmap/colmap/pull/1672 * Update install.rst by @tomer-grin in https://github.com/colmap/colmap/pull/1671 * Update freeimage links. by @Yulv-git in https://github.com/colmap/colmap/pull/1675 * fix small typo by @skal65535 in https://github.com/colmap/colmap/pull/1668 * Update build.py with new glew link by @aghand0ur in https://github.com/colmap/colmap/pull/1658 * Add use_cache in fusion options GUI by @hrflr in https://github.com/colmap/colmap/pull/1655 * Add CI pipeline for Ubuntu 22.04 by @ahojnnes in https://github.com/colmap/colmap/pull/1688 * Avoid unnecessary copies of data by @ahojnnes in https://github.com/colmap/colmap/pull/1691 * Reduce memory allocations in correspondence graph search by @ahojnnes in https://github.com/colmap/colmap/pull/1692 * Use FindCUDAToolkit when available. by @hanseuljun in https://github.com/colmap/colmap/pull/1693 * Fixed a crash due to inconsistent undistortion by @SomeAlphabetGuy in https://github.com/colmap/colmap/pull/1698 * Add CUDA Ubuntu 22.04 CI build by @ahojnnes in https://github.com/colmap/colmap/pull/1705 * Delete the redundancy install of libmetis-dev by @thomas-graphopti in https://github.com/colmap/colmap/pull/1721 * Fix broken loading of image masks on macOS by @buesma in https://github.com/colmap/colmap/pull/1639 * Update install instructions with latest hints and known issues by @ahojnnes in https://github.com/colmap/colmap/pull/1736 * Modernize smart pointer initialization, fix alloc/dealloc mismatch by @ahojnnes in https://github.com/colmap/colmap/pull/1737 * Fix typo in cli.rst by @ojhernandez in https://github.com/colmap/colmap/pull/1747 * Fix inconsistent image resizing between CPU/GPU implementations of SIFT by @Yzhbuaa in https://github.com/colmap/colmap/pull/1642 * Reduce number of SIFT test features to make tests run under WSL by @ahojnnes in https://github.com/colmap/colmap/pull/1748 * Tag documentation version with dev by @ahojnnes in https://github.com/colmap/colmap/pull/1749 * Update copyright to 2023 by @ahojnnes in https://github.com/colmap/colmap/pull/1750 * Fix max image dimension for positive first_octave by @ahojnnes in https://github.com/colmap/colmap/pull/1751 * Fix SIFT GPU match creation by @ahojnnes in https://github.com/colmap/colmap/pull/1757 * Fix SIFT tests for OpenGL by @ahojnnes in https://github.com/colmap/colmap/pull/1762 * Suppress CUDA stack size warning for ptxas by @ahojnnes in https://github.com/colmap/colmap/pull/1770 * Simplify CUDA CMake configuration by @ahojnnes in https://github.com/colmap/colmap/pull/1776 * Fixes for CUDA compilation by @ahojnnes in https://github.com/colmap/colmap/pull/1777 * Improvements to dockerfile and build pipeline by @ahojnnes in https://github.com/colmap/colmap/pull/1778 * Explicitly require CMAKE_CUDA_ARCHITECTURES to be defined by @ahojnnes in https://github.com/colmap/colmap/pull/1781 * Depend on system installed FLANN by @ahojnnes in https://github.com/colmap/colmap/pull/1782 * Option to store relative pose between two cameras in database by @yanxke in https://github.com/colmap/colmap/pull/1774 * Depend on system installed SQLite3 by @ahojnnes in https://github.com/colmap/colmap/pull/1783 ----------------------- COLMAP 3.7 (01/26/2022) ----------------------- * Allow to save fused point cloud in colmap format when using command line by @boitumeloruf in https://github.com/colmap/colmap/pull/799 * Fix typos in image.h by @Pascal-So in https://github.com/colmap/colmap/pull/936 * Fix for EPnP estimator by @vlarsson in https://github.com/colmap/colmap/pull/943 * Visualize models using Python in Open3D by @ahojnnes in https://github.com/colmap/colmap/pull/948 * Update tutorial.rst by @ignacio-rocco in https://github.com/colmap/colmap/pull/953 * 8 point algorithm internal contraint fix by @mihaidusmanu in https://github.com/colmap/colmap/pull/982 * Python script for writing depth/normal arrays by @SBCV in https://github.com/colmap/colmap/pull/957 * BuildImageModel: use std::vector instead of numbered arguments by @Pascal-So in https://github.com/colmap/colmap/pull/949 * Fix bugs of sift feature matching by @whuaegeanse in https://github.com/colmap/colmap/pull/985 * script for modifying fused results by @SBCV in https://github.com/colmap/colmap/pull/984 * fix camera model query by @Pascal-So in https://github.com/colmap/colmap/pull/997 * fixed small bug in visualize_model.py by @sniklaus in https://github.com/colmap/colmap/pull/1007 * Update .travis.yml by @srinivas32 in https://github.com/colmap/colmap/pull/989 * Ensure DecomposeHomographyMatrix() always returns rotations by @daithimaco in https://github.com/colmap/colmap/pull/1040 * Remove deprecated qt foreach by @UncleGene in https://github.com/colmap/colmap/pull/1039 * Fix AMD/Windows GUI visualization bug by @drkoller in https://github.com/colmap/colmap/pull/1079 * include colmap_cuda in COLMAP_LIBRARIES when compiled with cuda by @ClementPinard in https://github.com/colmap/colmap/pull/1084 * Fix runtime crash when sparsesuite is missing from ceres by @anmatako in https://github.com/colmap/colmap/pull/1115 * Store relative poses in two_view_geometry table by @Ahmed-Salama in https://github.com/colmap/colmap/pull/1103 * search src images for patch_match from all set, not only referenced subset by @DaniilSNikulin in https://github.com/colmap/colmap/pull/1038 * Replace Travis CI with Azure Pipelines for Linux/Mac builds by @ahojnnes in https://github.com/colmap/colmap/pull/1119 * Allow ReadPly to handle double precision files by @anmatako in https://github.com/colmap/colmap/pull/1131 * Update GPSTransform calculations to improve accuracy by @anmatako in https://github.com/colmap/colmap/pull/1132 * Add scale template flag in SimilarityTransform3::Estimate by @anmatako in https://github.com/colmap/colmap/pull/1133 * Add CopyFile utility that can copy or hard/soft-link files by @anmatako in https://github.com/colmap/colmap/pull/1134 * Expose BA options in IncrementalMapper by @anmatako in https://github.com/colmap/colmap/pull/1139 * Allow configurable paths for mvs::Model by @anmatako in https://github.com/colmap/colmap/pull/1141 * Change ReconstructionMaanger to write larger recons first by @anmatako in https://github.com/colmap/colmap/pull/1137 * Setup Azure pipelines for Windows build by @ahojnnes in https://github.com/colmap/colmap/pull/1150 * Add fixed extrinsics in rig config by @anmatako in https://github.com/colmap/colmap/pull/1144 * Allow custom config and missing dependencies for patch-match by @anmatako in https://github.com/colmap/colmap/pull/1142 * Update print statements for Python 3 compatibility by @UncleGene in https://github.com/colmap/colmap/pull/1126 * Allow cleanup of SQLite tables using new database_cleaner command by @anmatako in https://github.com/colmap/colmap/pull/1136 * Extend SceneClustering to support non-hierarchical (flat) clusters by @anmatako in https://github.com/colmap/colmap/pull/1140 * Support more formats in model_converter by @anmatako in https://github.com/colmap/colmap/pull/1147 * Fix Mac 10.15 build due to changed Qt5 path by @ahojnnes in https://github.com/colmap/colmap/pull/1157 * Fix bug in ReadCameraRigConfig when reading extrinsics by @anmatako in https://github.com/colmap/colmap/pull/1158 * Add utility to compare poses between two sparse models by @ahojnnes in https://github.com/colmap/colmap/pull/1159 * Modularize executable main functions into separate sources by @ahojnnes in https://github.com/colmap/colmap/pull/1160 * Fix unnecessary copies in for range loops by @ahojnnes in https://github.com/colmap/colmap/pull/1162 * Add script to clang-format all source code by @ahojnnes in https://github.com/colmap/colmap/pull/1163 * Add back new options and formats for model_converter by @anmatako in https://github.com/colmap/colmap/pull/1164 * ImageReder new option and bug fix in GPS priors by @anmatako in https://github.com/colmap/colmap/pull/1146 * Parallelize stereo fusion; needs pre-loading of entire workspace by @anmatako in https://github.com/colmap/colmap/pull/1148 * Refactoring and new functionality in Reconstruction class by @anmatako in https://github.com/colmap/colmap/pull/1169 * Add new functionality in image_undistorter by @anmatako in https://github.com/colmap/colmap/pull/1168 * Add new CMake option to disable GUI by @anmatako in https://github.com/colmap/colmap/pull/1165 * Fix the memory leak caused by not releasing the memory of the PRNG at the end of the thread by @whuaegeanse in https://github.com/colmap/colmap/pull/1170 * Fix fusion segfault bug by @anmatako in https://github.com/colmap/colmap/pull/1176 * Update SiftGPU to use floorf for floats by @anmatako in https://github.com/colmap/colmap/pull/1182 * fix typo in extraction.cc by @iuk in https://github.com/colmap/colmap/pull/1191 * Improvements to NVM, Cam, Recon3D, and Bundler exporters by @drkoller in https://github.com/colmap/colmap/pull/1187 * Update model_aligner functionality by @anmatako in https://github.com/colmap/colmap/pull/1177 * Add new model_cropper and model_splitter commands by @anmatako in https://github.com/colmap/colmap/pull/1179 * use type point2D_t instead of image_t by @iuk in https://github.com/colmap/colmap/pull/1199 * Fix radial distortion in Cam format exporter by @drkoller in https://github.com/colmap/colmap/pull/1196 * Add new model_transformer command by @anmatako in https://github.com/colmap/colmap/pull/1178 * Fix error of using urllib to download eigen from gitlab by @whuaegeanse in https://github.com/colmap/colmap/pull/1194 * Multi-line string fix in Python model script by @mihaidusmanu in https://github.com/colmap/colmap/pull/1217 * added visibility_sigma to CLI input options for delaunay_mesher. by @Matstah in https://github.com/colmap/colmap/pull/1236 * Backwards compatibility of model_aligner by @tsattler in https://github.com/colmap/colmap/pull/1240 * [update undistortion] update dumped commands by @hiakru in https://github.com/colmap/colmap/pull/1276 * Compute reprojection error in generalized absolute solver by @Skydes in https://github.com/colmap/colmap/pull/1257 * Modifying scripts/python/flickr_downloader.py to create files with correct extensions by @snavely in https://github.com/colmap/colmap/pull/1275 * revise Dockerfile and readme. by @MasahiroOgawa in https://github.com/colmap/colmap/pull/1281 * Update to latest vcpkg version by @ahojnnes in https://github.com/colmap/colmap/pull/1319 * Fix compiler warnings reported by GCC by @ahojnnes in https://github.com/colmap/colmap/pull/1317 * Auto-rotate JPEG images based on EXIF orientation by @ahojnnes in https://github.com/colmap/colmap/pull/1318 * Upgrade vcpkg to fix CI build issues by @ahojnnes in https://github.com/colmap/colmap/pull/1331 * Added descriptor normalization argument to feature_extractor. by @mihaidusmanu in https://github.com/colmap/colmap/pull/1332 * Fix memory leak in the function of StringAppendV by @whuaegeanse in https://github.com/colmap/colmap/pull/1337 * Add CUDA_SAFE_CALL to cudaGetDeviceCount. by @chpatrick in https://github.com/colmap/colmap/pull/1334 * Add missing include in case CUDA/GUI is not available by @ahojnnes in https://github.com/colmap/colmap/pull/1329 * Fix wrong WGS84 model and test cases in GPSTransform by @Freeverc in https://github.com/colmap/colmap/pull/1333 * Fixes bug in sprt.cc: num_inliers was not set. by @rmbrualla in https://github.com/colmap/colmap/pull/1360 * Prevent a divide by zero corner case. by @rmbrualla in https://github.com/colmap/colmap/pull/1361 * Adds missing header. by @rmbrualla in https://github.com/colmap/colmap/pull/1362 * Require Qt in COLMAPConfig only if GUI is enabled by @Skydes in https://github.com/colmap/colmap/pull/1365 * Keep precision in the process of storing in text. by @whuaegeanse in https://github.com/colmap/colmap/pull/1363 * Expose exe internals by @Skydes in https://github.com/colmap/colmap/pull/1366 * Fix inliers matches extraction in EstimateUncalibrated function. by @ferreram in https://github.com/colmap/colmap/pull/1369 * Expose exe internals - fix by @Skydes in https://github.com/colmap/colmap/pull/1368 * Remove deprecated Mac OSX 10.14 image in ADO pipeline by @ahojnnes in https://github.com/colmap/colmap/pull/1383 * Add Mac OSX 11 ADO pipeline job by @ahojnnes in https://github.com/colmap/colmap/pull/1384 * Fix warnings for latest compiler/libraries by @ahojnnes in https://github.com/colmap/colmap/pull/1382 * Fix clang compiler warnings by @ahojnnes in https://github.com/colmap/colmap/pull/1387 * Add Address Sanitizer options and fix reported issues by @ahojnnes in https://github.com/colmap/colmap/pull/1390 * User/joschonb/asan cleanup by @ahojnnes in https://github.com/colmap/colmap/pull/1391 * Add ADO pipeline for Visual Studio 2022 by @ahojnnes in https://github.com/colmap/colmap/pull/1392 * Add ccache option by @ahojnnes in https://github.com/colmap/colmap/pull/1395 * Update ModelAligner to handle GPS and custom coords. and more by @ferreram in https://github.com/colmap/colmap/pull/1371 ----------------------- COLMAP 3.6 (07/24/2020) ----------------------- * Improved robustness and faster incremental reconstruction process * Add ``image_deleter`` command to remove images from sparse model * Add ``image_filter`` command to filter bad registrations from sparse model * Add ``point_filtering`` command to filter sparse model point clouds * Add ``database_merger`` command to merge two databases, which is useful to parallelize matching across different machines * Add ``image_undistorter_standalone`` to enable undistorting images without a pre-existing full sparse model * Improved undistortion for fisheye cameras and FOV camera model * Support for masking input images in feature extraction stage * Improved HiDPI support in GUI for high-resolution monitors * Import sparse model when launching GUI from CLI * Faster CPU-based matching using approximate NN search * Support for bundle adjustment with fixed extrinsics * Support for fixing existing images when continuing reconstruction * Camera model colors in viewer can be customized * Support for latest GPU architectures in CUDA build * Support for writing sparse models in Python scripts * Scripts for building and running COLMAP in Docker * Many more bug fixes and improvements to code and documentation ----------------------- COLMAP 3.5 (08/22/2018) ----------------------- * COLMAP is now released under the BSD license instead of the GPL * COLMAP is now installed as a library, whose headers can be included and libraries linked against from other C/C++ code * Add hierarchical mapper for parallelized reconstruction or large scenes * Add sparse and dense Delaunay meshing algorithms, which reconstruct a watertight surface using a graph cut on the Delaunay triangulation of the reconstructed sparse or dense point cloud * Improved robustness when merging different models * Improved pre-trained vocabulary trees available for download * Add COLMAP as a software entry under Linux desktop systems * Add support to compile COLMAP on ARM platforms * Add example Python script to read/write COLMAP database * Add region of interest (ROI) cropping in image undistortion * Several import bug fixes for spatial verification in image retrieval * Add more extensive continuous integration across more compilation scenarios * Many more bug fixes and improvements to code and documentation ----------------------- COLMAP 3.4 (01/29/2018) ----------------------- * Unified command-line interface: The functionality of previous executables have been merged into the ``src/exe/colmap.cc`` executable. The GUI can now be started using the command ``colmap gui`` and other commands are available as ``colmap [command]``. For example, the feature extractor is now available as ``colmap feature_extractor [args]`` while all command-line arguments stay the same as before. This should result in much faster project compile times and smaller disk space usage of the program. More details about the new interface are documented at https://colmap.github.io/cli.html * More complete depth and normal maps with larger patch sizes * Faster dense stereo computation by skipping rows/columns in patch match, improved random sampling in patch match, and faster bilateral NCC * Better high DPI screen support for the graphical user interface * Improved model viewer under Windows, which now requires Qt 5.4 * Save computed two-view geometries in database * Images (keypoint/matches visualization, depth and normal maps) can now be saved from the graphical user interface * Support for PMVS format without sparse bundler file * Faster covariant feature detection * Many more bug fixes and improvements ----------------------- COLMAP 3.3 (11/21/2017) ----------------------- * Add DSP (Domain Size Pooling) SIFT implementation. DSP-SIFT outperforms standard SIFT in most cases, as shown in "Comparative Evaluation of Hand-Crafted and Learned Local Features", Schoenberger et al., CVPR 2017 * Improved parameters dense reconstruction of smaller models * Improved compile times due to various code optimizations * Add option to specify camera model in automatic reconstruction * Add new model orientation alignment based on upright image assumption * Improved numerical stability for generalized absolute pose solver * Support for image range specification in PMVS dense reconstruction format * Support for older Python versions in automatic build script * Fix OpenCV Fisheye camera model to exactly match OpenCV specifications --------------------- COLMAP 3.2 (9/2/2017) --------------------- * Fully automatic cross-platform build script (Windows, Mac, Linux) * Add multi-GPU feature extraction if multiple CUDA devices are available * Configurable dimension and data type for vocabulary tree implementation * Add new sequential matching mode for image sequences with high frame-rate * Add generalized relative pose solver for multi-camera systems * Add sparse least absolute deviation solver * Add CPU/GPU options to automatic reconstruction tool * Add continuous integration system under Windows, Mac, Linux through Github * Many more bug fixes and improvements ---------------------- COLMAP 3.1 (6/15/2017) ---------------------- * Add fast spatial verification to image retrieval module * Add binary file format for sparse models by default. Old text format still fully compatible and possible conversion in GUI and CLI * Add cross-platform little endian binary file reading and writing * Faster and less memory hungry stereo fusion by computing consistency on demand and possible limitation of image size in fusion * Simpler geometric stereo processing interface. Now geometric stereo output can be computed using a single pass * Faster and multi-architecture CUDA compilation * Add medium quality option in automatic reconstructor * Many more bug fixes and improvements ---------------------- COLMAP 3.0 (5/22/2017) ---------------------- * Add automatic end-to-end reconstruction tool that automatically performs sparse and dense reconstruction on a given set of images * Add multi-GPU dense stereo if multiple CUDA devices are available * Add multi-GPU feature matching if multiple CUDA devices are available * Add Manhattan-world / gravity alignment using line detection * Add CUDA-based feature extraction useful for usage on clusters * Add CPU-based feature matching for machines without GPU * Add new THIN_PRISM_FISHEYE camera model with tangential/radial correction * Add binary to triangulate existing/empty sparse reconstruction * Add binary to print summary statistics about sparse reconstruction * Add transitive feature matching to transitively complete match graph * Improved scalability of dense reconstruction by using caching * More stable GPU-based feature matching with informative warnings * Faster vocabulary tree matching using dynamic scheduling in FLANN * Faster spatial feature matching using linear index instead of kd-tree * More stable camera undistortion using numerical Newton iteration * Improved option parsing with some backwards incompatible option renaming * Faster compile times by optimizing includes and CUDA flags * More stable view selection for small baseline scenario in dense reconstruction * Many more bug fixes and improvements ---------------------- COLMAP 2.1 (12/7/2016) ---------------------- * Support to only index and match specific images in vocabulary tree matching * Support to perform image retrieval using vocabulary tree * Several bug fixes and improvements for multi-view stereo module * Improved Structure-from-Motion initialization strategy * Support to only reconstruct the scene using specific images in the database * Add support to merge two models using overlapping registered images * Add support to geo-register/align models using known camera locations * Support to only extract specific images in feature extraction module * Support for snapshot model export during reconstruction * Skip already undistorted images if they exist in output directory * Support to limit the number of features in image retrieval for improved speed * Miscellaneous bug fixes and improvements --------------------- COLMAP 2.0 (9/8/2016) --------------------- * Implementation of dense reconstruction pipeline * Improved feature matching performance * New bundle adjuster for rigidly mounted multi-camera systems * New generalized absolute pose solver for multi-camera systems * New executable to extract colors from all images * Boost can now be linked in shared and static mode * Various bug fixes and performance improvements ---------------------- COLMAP 1.1 (5/19/2016) ---------------------- * Implementation of state-of-the-art image retrieval system using Hamming embedding for vocabulary tree matching. This should lead to much improved matching results as compared to the previous implementation. * Guided matching as an optional functionality. * New demo datasets for download. * Automatically switch to PBA if supported by the project. * Implementation of EPNP solver for local pose optimization in RANSAC. * Add option to extract upright SIFT features. * Saving JPEGs in superb quality by default in export. * Add option to clear matches and inlier matches in the project. * New fisheye camera models, including the FOV camera model used by Google Project Tango (Thomas Schoeps). * Extended documentation based on user feedback. * Fixed typo in documentation (Thomas Schoeps). --------------------- COLMAP 1.0 (4/4/2016) --------------------- * Initial release of COLMAP. colmap-4.2.0/CMakeLists.txt000066400000000000000000000471431524536416500155540ustar00rootroot00000000000000# Copyright (c), ETH Zurich and UNC Chapel Hill. # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are met: # # * Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer. # # * Redistributions in binary form must reproduce the above copyright # notice, this list of conditions and the following disclaimer in the # documentation and/or other materials provided with the distribution. # # * Neither the name of ETH Zurich and UNC Chapel Hill nor the names of # its contributors may be used to endorse or promote products derived # from this software without specific prior written permission. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. cmake_minimum_required(VERSION 3.12) ################################################################################ # Options ################################################################################ option(SIMD_ENABLED "Whether to enable SIMD optimizations" ON) option(OPENMP_ENABLED "Whether to enable OpenMP parallelization" ON) option(IPO_ENABLED "Whether to enable interprocedural optimization" ON) option(CUDA_ENABLED "Whether to enable CUDA, if available" ON) option(HIP_ENABLED "Whether to enable AMD GPU support via HIP/ROCm" OFF) if(CUDA_ENABLED AND HIP_ENABLED) message(FATAL_ERROR "CUDA and HIP cannot be enabled at the same time") endif() # enable_language(HIP) and set_source_files_properties(LANGUAGE HIP) require # CMake 3.21+. Bump the minimum only when HIP is requested so CUDA-only and # CPU-only builds continue to work on older CMake versions. if(HIP_ENABLED) cmake_minimum_required(VERSION 3.21) endif() option(ONNX_ENABLED "Whether to enable ONNX, if available" ON) option(GUI_ENABLED "Whether to enable the graphical UI" ON) option(MVS_ENABLED "Whether to enable the Multi-View Stereo module" ON) option(OPENGL_ENABLED "Whether to enable OpenGL, if available" ON) option(TESTS_ENABLED "Whether to build test binaries" OFF) option(COVERAGE_ENABLED "Whether to enable code coverage" OFF) option(ASAN_ENABLED "Whether to enable AddressSanitizer flags" OFF) option(TSAN_ENABLED "Whether to enable ThreadSanitizer flags" OFF) option(UBSAN_ENABLED "Whether to enable UndefinedBehaviorSanitizer flags" OFF) option(PROFILING_ENABLED "Whether to enable google-perftools linker flags" OFF) option(WERROR_ENABLED "Whether to treat compiler warnings as errors" OFF) option(CCACHE_ENABLED "Whether to enable compiler caching, if available" ON) option(CGAL_ENABLED "Whether to enable the CGAL library" ON) option(LSD_ENABLED "Whether to enable the LSD library" ON) option(DOWNLOAD_ENABLED "Whether to enable (automatic) download of resources (requires Curl/OpenSSL)" ON) option(UNINSTALL_ENABLED "Whether to create a target to 'uninstall' colmap" ON) option(BENCHMARK_ENABLED "Whether to enable runtime benchmarking support" OFF) option(FETCH_POSELIB "Whether to consume PoseLib using FetchContent or find_package" ON) option(FETCH_FAISS "Whether to consume faiss using FetchContent or find_package" ON) option(FETCH_ONNX "Whether to consume ONNX using FetchContent or find_package" ON) option(BUILD_SHARED_LIBS "Whether to build shared libraries (faster linktime, slower runtime)" OFF) option(ALL_SOURCE_TARGET "Whether to create a target for all source files (for Visual Studio / XCode development)" OFF) # Experimental option(CASPAR_ENABLED "Whether to enable CASPAR-accelerated bundle adjustment" OFF) option(CASPAR_USE_DOUBLE "Use double precision in Caspar solver" OFF) # Hash map backend used by the performance-critical scene/SfM containers (see # src/colmap/util/hash_containers.h). One of: STD, BOOST, or empty for auto. # Auto selects BOOST (faster boost::unordered_flat/node maps) when the available # Boost is new enough (>= 1.84, for boost::unordered_node_map) and STD otherwise. set(COLMAP_HASH_MAP_BACKEND "" CACHE STRING "Hash map backend for scene/SfM containers: STD, BOOST, or empty for auto") set_property(CACHE COLMAP_HASH_MAP_BACKEND PROPERTY STRINGS "" STD BOOST) if(CASPAR_ENABLED) add_compile_definitions(CASPAR_ENABLED) if(CASPAR_USE_DOUBLE) add_compile_definitions(CASPAR_USE_DOUBLE) endif() endif() # Disables default features, as we specify each required feature manually below. list(APPEND VCPKG_MANIFEST_FEATURES "core") # Propagate options to vcpkg manifest. if(TESTS_ENABLED) list(APPEND VCPKG_MANIFEST_FEATURES "tests") endif() if(CUDA_ENABLED) list(APPEND VCPKG_MANIFEST_FEATURES "cuda") endif() if(ONNX_ENABLED AND NOT FETCH_ONNX) list(APPEND VCPKG_MANIFEST_FEATURES "onnx") endif() if(GUI_ENABLED) list(APPEND VCPKG_MANIFEST_FEATURES "gui") endif() if(CGAL_ENABLED) list(APPEND VCPKG_MANIFEST_FEATURES "cgal") endif() if(DOWNLOAD_ENABLED) list(APPEND VCPKG_MANIFEST_FEATURES "download") endif() project(COLMAP LANGUAGES C CXX) set(COLMAP_VERSION "4.2.0") set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD_REQUIRED ON) set(CMAKE_CUDA_STANDARD 17) set(CMAKE_CUDA_STANDARD_REQUIRED ON) set_property(GLOBAL PROPERTY GLOBAL_DEPENDS_NO_CYCLES ON) ################################################################################ # Include CMake dependencies ################################################################################ set(CMAKE_MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/cmake) include(CheckCXXCompilerFlag) include(GNUInstallDirs) # Include helper macros and commands, and allow the included file to override # the CMake policies in this file include(${CMAKE_CURRENT_SOURCE_DIR}/cmake/CMakeHelper.cmake NO_POLICY_SCOPE) # Build position-independent code, so that shared libraries can link against # COLMAP's static libraries. set(CMAKE_POSITION_INDEPENDENT_CODE ON) ################################################################################ # Dependency configuration ################################################################################ set(COLMAP_FIND_QUIETLY FALSE) include(cmake/FindDependencies.cmake) ################################################################################ # Compiler specific configuration ################################################################################ if(CMAKE_BUILD_TYPE) message(STATUS "Build type specified as ${CMAKE_BUILD_TYPE}") else() message(STATUS "Build type not specified, using Release") set(CMAKE_BUILD_TYPE Release) set(IS_DEBUG OFF) endif() if("${CMAKE_BUILD_TYPE}" STREQUAL "ClangTidy") find_program(CLANG_TIDY_EXE NAMES clang-tidy) if(NOT CLANG_TIDY_EXE) message(FATAL_ERROR "Could not find the clang-tidy executable, please set CLANG_TIDY_EXE") endif() else() unset(CLANG_TIDY_EXE) endif() if(IS_MSVC) # Some fixes for the Glog library. add_compile_definitions(GLOG_USE_GLOG_EXPORT) add_compile_definitions(GLOG_NO_ABBREVIATED_SEVERITIES) add_compile_definitions(GL_GLEXT_PROTOTYPES) add_compile_definitions(NOMINMAX) set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /EHsc") # Disable warning: 'initializing': conversion from 'X' to 'Y', possible loss of data set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /wd4244 /wd4267 /wd4305") # Enable object level parallel builds in Visual Studio. set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} /MP") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /MP") if("${CMAKE_BUILD_TYPE}" STREQUAL "Debug" OR "${CMAKE_BUILD_TYPE}" STREQUAL "RelWithDebInfo") set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} /bigobj") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /bigobj") endif() endif() if(IS_GNU) if(CMAKE_CXX_COMPILER_VERSION VERSION_LESS 4.9) message(FATAL_ERROR "GCC version 4.8 or older not supported") endif() endif() if(IS_MACOS) # Mitigate CMake limitation, see: https://discourse.cmake.org/t/avoid-duplicate-linking-to-avoid-xcode-15-warnings/9084/10 add_link_options(LINKER:-no_warn_duplicate_libraries) endif() # Correctly set RPATH to look up dependencies. if(NOT IS_MSVC) if(IS_MACOS) set(RPATH_BASE "@executable_path") else() set(RPATH_BASE "$ORIGIN") endif() file(RELATIVE_PATH INSTALL_LIB_DIR "${CMAKE_CURRENT_BINARY_DIR}/${CMAKE_INSTALL_BINDIR}" "${CMAKE_CURRENT_BINARY_DIR}/${CMAKE_INSTALL_LIBDIR}") list(APPEND CMAKE_INSTALL_RPATH "${RPATH_BASE}/${INSTALL_LIB_DIR}") endif() if(IS_DEBUG) add_compile_definitions(EIGEN_INITIALIZE_MATRICES_BY_NAN) endif() if(SIMD_ENABLED) message(STATUS "Enabling SIMD support") else() message(STATUS "Disabling SIMD support") endif() if(IPO_ENABLED AND NOT IS_DEBUG AND NOT IS_GNU) message(STATUS "Enabling interprocedural optimization") set_property(DIRECTORY PROPERTY INTERPROCEDURAL_OPTIMIZATION 1) else() message(STATUS "Disabling interprocedural optimization") endif() if(ASAN_ENABLED) message(STATUS "Enabling ASan support") if(IS_CLANG OR IS_GNU) add_compile_options(-fsanitize=address -fno-omit-frame-pointer -fsanitize-address-use-after-scope) add_link_options(-fsanitize=address) else() message(FATAL_ERROR "Unsupported compiler for ASan mode") endif() endif() if(TSAN_ENABLED) message(STATUS "Enabling TSan support") if(IS_CLANG OR IS_GNU) add_compile_options(-fsanitize=thread) add_link_options(-fsanitize=thread) else() message(FATAL_ERROR "Unsupported compiler for TSan mode") endif() endif() if(UBSAN_ENABLED) message(STATUS "Enabling UBsan support") if(IS_CLANG OR IS_GNU) add_compile_options(-fsanitize=undefined) add_link_options(-fsanitize=undefined) else() message(FATAL_ERROR "Unsupported compiler for UBsan mode") endif() endif() if(CCACHE_ENABLED) find_program(CCACHE ccache) if(CCACHE) message(STATUS "Enabling ccache support") set(CMAKE_C_COMPILER_LAUNCHER ${CCACHE}) set(CMAKE_CXX_COMPILER_LAUNCHER ${CCACHE}) if(CUDA_ENABLED) set(CMAKE_CUDA_COMPILER_LAUNCHER ${CCACHE}) endif() else() message(STATUS "Disabling ccache support") endif() else() message(STATUS "Disabling ccache support") endif() if(PROFILING_ENABLED) message(STATUS "Enabling profiling support") set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -lprofiler -ltcmalloc") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -lprofiler -ltcmalloc") else() message(STATUS "Disabling profiling support") endif() if(TESTS_ENABLED) message(STATUS "Enabling tests") enable_testing() include(CTest) else() message(STATUS "Disabling tests") endif() if(COVERAGE_ENABLED) message(STATUS "Enabling coverage support") else() message(STATUS "Disabling coverage support") endif() ################################################################################ # Add sources ################################################################################ # Generate source file with version definitions. include(GenerateVersionDefinitions) include_directories(src) link_directories(${COLMAP_LINK_DIRS}) add_subdirectory(src/thirdparty) add_subdirectory(src/colmap) if(BENCHMARK_ENABLED) add_subdirectory(benchmark/runtime) endif() ################################################################################ # Generate source groups for Visual Studio, XCode, etc. ################################################################################ COLMAP_ADD_SOURCE_DIR(src/colmap/controllers CONTROLLERS_SRCS *.h *.cc) COLMAP_ADD_SOURCE_DIR(src/colmap/estimators ESTIMATORS_SRCS *.h *.cc) COLMAP_ADD_SOURCE_DIR(src/colmap/estimators/cost_functions ESTIMATORS_COST_FUNCTIONS_SRCS *.h *.cc) COLMAP_ADD_SOURCE_DIR(src/colmap/estimators/solvers ESTIMATORS_SOLVERS_SRCS *.h *.cc) COLMAP_ADD_SOURCE_DIR(src/colmap/exe EXE_SRCS *.h *.cc) COLMAP_ADD_SOURCE_DIR(src/colmap/feature FEATURE_SRCS *.h *.cc) COLMAP_ADD_SOURCE_DIR(src/colmap/geometry GEOMETRY_SRCS *.h *.cc) COLMAP_ADD_SOURCE_DIR(src/colmap/image IMAGE_SRCS *.h *.cc) COLMAP_ADD_SOURCE_DIR(src/colmap/math MATH_SRCS *.h *.cc) if(MVS_ENABLED) COLMAP_ADD_SOURCE_DIR(src/colmap/mvs MVS_SRCS *.h *.cc *.cu) endif() COLMAP_ADD_SOURCE_DIR(src/colmap/optim OPTIM_SRCS *.h *.cc) COLMAP_ADD_SOURCE_DIR(src/colmap/retrieval RETRIEVAL_SRCS *.h *.cc) COLMAP_ADD_SOURCE_DIR(src/colmap/scene SCENE_SRCS *.h *.cc) COLMAP_ADD_SOURCE_DIR(src/colmap/sensor SENSOR_SRCS *.h *.cc) COLMAP_ADD_SOURCE_DIR(src/colmap/sfm SFM_SRCS *.h *.cc) COLMAP_ADD_SOURCE_DIR(src/colmap/tools TOOLS_SRCS *.h *.cc) COLMAP_ADD_SOURCE_DIR(src/colmap/ui UI_SRCS *.h *.cc) COLMAP_ADD_SOURCE_DIR(src/colmap/util UTIL_SRCS *.h *.cc) if(LSD_ENABLED) COLMAP_ADD_SOURCE_DIR(src/thirdparty/LSD THIRDPARTY_LSD_SRCS *.h *.c) endif() COLMAP_ADD_SOURCE_DIR(src/thirdparty/PoissonRecon THIRDPARTY_POISSON_RECON_SRCS *.h *.cpp *.inl) COLMAP_ADD_SOURCE_DIR(src/thirdparty/SiftGPU THIRDPARTY_SIFT_GPU_SRCS *.h *.cpp *.cu) COLMAP_ADD_SOURCE_DIR(src/thirdparty/VLFeat THIRDPARTY_VLFEAT_SRCS *.h *.c *.tc) # Add all of the source files to a regular library target, as using a custom # target does not allow us to set its C++ include directories (and thus # intellisense can't find any of the included files). if(ALL_SOURCE_TARGET) set(ALL_SRCS ${CONTROLLERS_SRCS} ${ESTIMATORS_SRCS} ${ESTIMATORS_COST_FUNCTIONS_SRCS} ${ESTIMATORS_SOLVERS_SRCS} ${EXE_SRCS} ${FEATURE_SRCS} ${GEOMETRY_SRCS} ${IMAGE_SRCS} ${MATH_SRCS} ${OPTIM_SRCS} ${RETRIEVAL_SRCS} ${SCENE_SRCS} ${SENSOR_SRCS} ${SFM_SRCS} ${TOOLS_SRCS} ${UI_SRCS} ${UTIL_SRCS} ${THIRDPARTY_POISSON_RECON_SRCS} ${THIRDPARTY_SIFT_GPU_SRCS} ${THIRDPARTY_VLFEAT_SRCS} ) if(MVS_ENABLED) list(APPEND ALL_SRCS ${MVS_SRCS} ) endif() if(LSD_ENABLED) list(APPEND ALL_SRCS ${THIRDPARTY_LSD_SRCS} ) endif() add_library( ${COLMAP_SRC_ROOT_FOLDER} ${ALL_SRCS} ) # Prevent the library from being compiled automatically. set_target_properties( ${COLMAP_SRC_ROOT_FOLDER} PROPERTIES EXCLUDE_FROM_ALL 1 EXCLUDE_FROM_DEFAULT_BUILD 1) endif() ################################################################################ # Install and uninstall scripts ################################################################################ # Install batch scripts under Windows. if(IS_MSVC) install(FILES "scripts/shell/COLMAP.bat" "scripts/shell/RUN_TESTS.bat" PERMISSIONS OWNER_READ OWNER_WRITE OWNER_EXECUTE GROUP_READ GROUP_EXECUTE WORLD_READ WORLD_EXECUTE DESTINATION "/") endif() # Install application meny entry and icon under Linux/Unix. if(UNIX AND NOT APPLE) install(FILES "doc/COLMAP.desktop" DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/applications") # Install the app icon so the desktop entry's "Icon=colmap" resolves. install(FILES "src/colmap/ui/media/colmap-logo.svg" DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/icons/hicolor/scalable/apps" RENAME "colmap.svg") endif() # Configure the uninstallation script. if(UNINSTALL_ENABLED) configure_file("${CMAKE_CURRENT_SOURCE_DIR}/cmake/CMakeUninstall.cmake.in" "${CMAKE_CURRENT_BINARY_DIR}/CMakeUninstall.cmake" IMMEDIATE @ONLY) add_custom_target(uninstall COMMAND ${CMAKE_COMMAND} -P ${CMAKE_CURRENT_BINARY_DIR}/CMakeUninstall.cmake) set_target_properties(uninstall PROPERTIES FOLDER ${CMAKE_TARGETS_ROOT_FOLDER}) endif() set(COLMAP_EXPORT_LIBS # Internal. colmap_controllers colmap_estimators colmap_estimators_cost_functions colmap_estimators_solvers colmap_exe colmap_feature_types colmap_feature colmap_geometry colmap_image colmap_math colmap_optim colmap_retrieval colmap_scene colmap_scene_types colmap_sensor colmap_sfm colmap_util # Third-party. colmap_poisson_recon colmap_vlfeat ) if(MVS_ENABLED) list(APPEND COLMAP_EXPORT_LIBS colmap_mvs) endif() if(LSD_ENABLED) list(APPEND COLMAP_EXPORT_LIBS # Third-party. colmap_lsd ) endif() if(GUI_ENABLED) list(APPEND COLMAP_EXPORT_LIBS colmap_ui ) endif() if(CUDA_ENABLED OR HIP_ENABLED) list(APPEND COLMAP_EXPORT_LIBS colmap_util_cuda ) if(MVS_ENABLED) list(APPEND COLMAP_EXPORT_LIBS colmap_mvs_cuda) endif() endif() if(GPU_ENABLED) list(APPEND COLMAP_EXPORT_LIBS colmap_sift_gpu ) endif() if(FETCH_POSELIB) list(APPEND COLMAP_EXPORT_LIBS PoseLib) endif() if(FETCH_FAISS) list(APPEND COLMAP_EXPORT_LIBS faiss) endif() if(CASPAR_ENABLED) list(APPEND COLMAP_EXPORT_LIBS caspar_lib_core) endif() # Add unified interface library target to export. add_library(colmap INTERFACE) target_link_libraries(colmap INTERFACE ${COLMAP_EXPORT_LIBS}) target_include_directories( colmap INTERFACE $ $) install( TARGETS colmap ${COLMAP_EXPORT_LIBS} EXPORT colmap-targets LIBRARY DESTINATION thirdparty/) # Generate config and version. include(CMakePackageConfigHelpers) set(PACKAGE_CONFIG_FILE "${CMAKE_CURRENT_BINARY_DIR}/colmap-config.cmake") set(INSTALL_CONFIG_DIR "${CMAKE_INSTALL_DATAROOTDIR}/colmap") configure_package_config_file( ${CMAKE_CURRENT_SOURCE_DIR}/cmake/colmap-config.cmake.in ${PACKAGE_CONFIG_FILE} INSTALL_DESTINATION ${INSTALL_CONFIG_DIR}) install(FILES ${PACKAGE_CONFIG_FILE} DESTINATION ${INSTALL_CONFIG_DIR}) configure_file("${CMAKE_CURRENT_SOURCE_DIR}/cmake/colmap-config-version.cmake.in" "${CMAKE_CURRENT_BINARY_DIR}/colmap-config-version.cmake" @ONLY) install(FILES "${CMAKE_CURRENT_BINARY_DIR}/colmap-config-version.cmake" DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/colmap") # Install targets. install( EXPORT colmap-targets FILE colmap-targets.cmake NAMESPACE colmap:: DESTINATION ${INSTALL_CONFIG_DIR}) # Install header files. install( DIRECTORY src/colmap DESTINATION ${CMAKE_INSTALL_INCLUDEDIR} FILES_MATCHING PATTERN "*.h") install( DIRECTORY src/thirdparty DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/colmap FILES_MATCHING REGEX ".*[.]h|.*[.]hpp|.*[.]inl" PATTERN "Symforce-Caspar" EXCLUDE) # Only ship the active Caspar precision tree (avoids bundling unused kernel # headers and the inactive precision when CASPAR_ENABLED is off). if(CASPAR_ENABLED) if(CASPAR_USE_DOUBLE) set(_caspar_install_precision "f64") else() set(_caspar_install_precision "f32") endif() install( DIRECTORY src/thirdparty/Symforce-Caspar/generated/${_caspar_install_precision} DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/colmap/thirdparty/Symforce-Caspar/generated FILES_MATCHING REGEX ".*[.]h|.*[.]hpp|.*[.]inl") endif() # Install find_package scripts for dependencies. install( DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/cmake DESTINATION ${CMAKE_INSTALL_DATAROOTDIR}/colmap FILES_MATCHING PATTERN "Find*.cmake") colmap-4.2.0/CONTRIBUTING.md000066400000000000000000000010721524536416500152340ustar00rootroot00000000000000Contributing ------------ Contributions (bug reports, bug fixes, improvements, etc.) are very welcome and should be submitted in the form of new issues and/or pull requests on GitHub. Please, adhere to the Google coding style guide: https://google.github.io/styleguide/cppguide.html by using the provided ".clang-format" file. Document code, functions, methods, classes, etc. Make sure to add unit tests for all newly added code and make sure that algorithmic "improvements" generalize and actually improve the results of the pipeline on a variety of datasets. colmap-4.2.0/COPYING.txt000066400000000000000000000035651524536416500146650ustar00rootroot00000000000000The COLMAP library is licensed under the new BSD license. Note that this text refers only to the license for COLMAP itself, independent of its dependencies, which are separately licensed. Building COLMAP with these dependencies may affect the resulting COLMAP license. Copyright (c), ETH Zurich and UNC Chapel Hill. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * Neither the name of ETH Zurich and UNC Chapel Hill nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. colmap-4.2.0/README.md000066400000000000000000000150011524536416500142570ustar00rootroot00000000000000COLMAP ====== About ----- COLMAP is a general-purpose Structure-from-Motion (SfM) and Multi-View Stereo (MVS) pipeline with a graphical and command-line interface. It offers a wide range of features for reconstruction of ordered and unordered image collections. The software is licensed under the new BSD license. The latest source code is available at https://github.com/colmap/colmap. COLMAP builds on top of existing works and when using specific algorithms within COLMAP, please also cite the original authors, as specified in the source code, and consider citing relevant third-party dependencies (most notably ceres-solver, poselib, sift-gpu, vlfeat). Download -------- * Binaries for **Windows** and other resources can be downloaded from https://github.com/colmap/colmap/releases. * Binaries for **Linux/Unix/BSD** are available at https://repology.org/metapackage/colmap/versions. * Pre-built **Docker** images are available at https://hub.docker.com/r/colmap/colmap. * Conda packages are available at https://anaconda.org/conda-forge/colmap and can be installed with `conda install colmap` * **Python bindings** are available at https://pypi.org/project/pycolmap. CUDA-enabled wheels are available at https://pypi.org/project/pycolmap-cuda12. AMD GPUs are supported via HIP/ROCm when building from source (see install docs). * To **build from source**, please see https://colmap.github.io/install.html. Getting Started --------------- 1. Download pre-built binaries or build from source. 2. Download one of the provided [sample datasets](https://demuc.de/colmap/datasets/) or use your own images. 3. Use the **automatic reconstruction** to easily build models with a single click or command. Documentation ------------- The documentation is available [here](https://colmap.github.io/). To build and update the documentation at the documentation website, follow [these steps](https://colmap.github.io/install.html#documentation). Support ------- Please, use [GitHub Discussions](https://github.com/colmap/colmap/discussions) for questions and the [GitHub issue tracker](https://github.com/colmap/colmap) for bug reports, feature requests/additions, etc. Acknowledgments --------------- COLMAP was originally written by [Johannes Schönberger](https://demuc.de/) with funding provided by his PhD advisors Jan-Michael Frahm and Marc Pollefeys. The team of core project maintainers currently includes [Johannes Schönberger](https://github.com/ahojnnes), [Paul-Edouard Sarlin](https://github.com/sarlinpe), [Shaohui Liu](https://github.com/B1ueber2y), and [Linfei Pan](https://lpanaf.github.io/). The Python bindings in PyCOLMAP were originally added by [Mihai Dusmanu](https://github.com/mihaidusmanu), [Philipp Lindenberger](https://github.com/Phil26AT), and [Paul-Edouard Sarlin](https://github.com/sarlinpe). The project has also benefitted from countless community contributions, including bug fixes, improvements, new features, third-party tooling, and community support (special credits to [Torsten Sattler](https://tsattler.github.io)). Citation -------- If you use this project for your research, please cite: @inproceedings{schoenberger2016sfm, author={Sch\"{o}nberger, Johannes Lutz and Frahm, Jan-Michael}, title={Structure-from-Motion Revisited}, booktitle={Conference on Computer Vision and Pattern Recognition (CVPR)}, year={2016}, } @inproceedings{schoenberger2016mvs, author={Sch\"{o}nberger, Johannes Lutz and Zheng, Enliang and Pollefeys, Marc and Frahm, Jan-Michael}, title={Pixelwise View Selection for Unstructured Multi-View Stereo}, booktitle={European Conference on Computer Vision (ECCV)}, year={2016}, } If you use the global SfM pipeline (GLOMAP), please cite: @inproceedings{pan2024glomap, author={Pan, Linfei and Barath, Daniel and Pollefeys, Marc and Sch\"{o}nberger, Johannes Lutz}, title={{Global Structure-from-Motion Revisited}}, booktitle={European Conference on Computer Vision (ECCV)}, year={2024}, } If you use the image retrieval / vocabulary tree engine, please cite: @inproceedings{schoenberger2016vote, author={Sch\"{o}nberger, Johannes Lutz and Price, True and Sattler, Torsten and Frahm, Jan-Michael and Pollefeys, Marc}, title={A Vote-and-Verify Strategy for Fast Spatial Verification in Image Retrieval}, booktitle={Asian Conference on Computer Vision (ACCV)}, year={2016}, } Contribution ------------ Contributions (bug reports, bug fixes, improvements, etc.) are very welcome and should be submitted in the form of new issues and/or pull requests on GitHub. License ------- The COLMAP library is licensed under the new BSD license. Note that this text refers only to the license for COLMAP itself, independent of its thirdparty dependencies, which are separately licensed. Building COLMAP with these dependencies may affect the resulting COLMAP license. Copyright (c), ETH Zurich and UNC Chapel Hill. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * Neither the name of ETH Zurich and UNC Chapel Hill nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. colmap-4.2.0/benchmark/000077500000000000000000000000001524536416500147355ustar00rootroot00000000000000colmap-4.2.0/benchmark/reconstruction/000077500000000000000000000000001524536416500200165ustar00rootroot00000000000000colmap-4.2.0/benchmark/reconstruction/compare.py000066400000000000000000000161001524536416500220140ustar00rootroot00000000000000# Copyright (c), ETH Zurich and UNC Chapel Hill. # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are met: # # * Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer. # # * Redistributions in binary form must reproduce the above copyright # notice, this list of conditions and the following disclaimer in the # documentation and/or other materials provided with the distribution. # # * Neither the name of ETH Zurich and UNC Chapel Hill nor the names of # its contributors may be used to endorse or promote products derived # from this software without specific prior written permission. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. """Compare the reports of two evaluate.py runs (A vs B). With one report per variant, this prints the result tables for A, B and A - B. With several reports per variant -- one per random seed, produced by an evaluate.py --seeds/--num_seeds run -- it instead prints mean +/- std over the seeds. The std is the run-to-run spread; use enough seeds that it is small relative to the A-B effect you care about. Seeds are shared between the variants, so A - B is computed per seed before averaging (a paired difference). Reports should be generated with --threads_per_scene 1 so that a fixed seed is deterministic. To produce such reports for two colmap binaries, run evaluate.py once per binary with the same seeds and --run_name -- so that they share the scene workspaces -- and a different --report_name, which becomes the report path prefix passed here. Pass --overwrite_two_view_geometries to both, otherwise they reuse the two-view geometries cached in the shared database and a change to geometric verification has no effect on A - B. Each variant is given either as a single report, or as a report path prefix, in which case its _s.pkl reports are discovered and matched by seed against the other variant's. A prefix resolves to .pkl when the runs were made without seeds. Prefixed variants may live in different run directories. Examples: # Two individual reports: python compare.py --report_a_path runs/X/base.pkl \\ --report_b_path runs/X/msac.pkl --labels base msac # Every seed shared by two multi-seed runs: python compare.py --report_a_path_prefix runs/X/base \\ --report_b_path_prefix runs/Y/msac # ... restricted to a subset of the seeds: python compare.py --report_a_path_prefix runs/X/base \\ --report_b_path_prefix runs/Y/msac --seeds 0 1 2 7 9 The same prefix invocation also covers unseeded runs, where it resolves to runs/X/base.pkl and runs/Y/msac.pkl. """ import argparse from pathlib import Path from evaluation.utils import collect_reports, compare_reports, pair_reports def parse_args() -> argparse.Namespace: parser = argparse.ArgumentParser( description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter, ) parser.add_argument( "--report_a_path", type=Path, default=None, metavar="PKL", help="Variant A report.", ) parser.add_argument( "--report_b_path", type=Path, default=None, metavar="PKL", help="Variant B report, compared against --report_a_path.", ) parser.add_argument( "--report_a_path_prefix", type=Path, default=None, metavar="PREFIX", help="Variant A given as a report path without the _s.pkl " "suffix, e.g. runs/X/base. Its seeds are discovered from the " "filenames and matched against variant B's, which may live in a " "different run directory.", ) parser.add_argument( "--report_b_path_prefix", type=Path, default=None, metavar="PREFIX", help="Variant B report path prefix, paired by seed with " "--report_a_path_prefix.", ) parser.add_argument( "--labels", nargs=2, default=None, metavar=("A", "B"), help="Variant labels used in the table titles. Defaults to the " "prefix names, or to A and B for explicit report paths.", ) seed_group = parser.add_mutually_exclusive_group() seed_group.add_argument( "--seeds", type=int, nargs="+", default=None, help="Compare only these seeds of the prefixed reports, e.g. --seeds " "0 1 2 7 9. Defaults to every seed the two variants share.", ) seed_group.add_argument( "--num_seeds", type=int, default=None, help="Shorthand for --seeds 0 1 ... N-1.", ) args = parser.parse_args() # Each variant is given as a report or as a prefix, independently: a # single report may be compared against every seed of a prefixed one. for side in ("a", "b"): path = getattr(args, f"report_{side}_path") prefix = getattr(args, f"report_{side}_path_prefix") if (path is None) == (prefix is None): parser.error( f"provide exactly one of --report_{side}_path and " f"--report_{side}_path_prefix" ) args.use_prefixes = ( args.report_a_path_prefix is not None or args.report_b_path_prefix is not None ) if args.num_seeds is not None: if args.num_seeds <= 0: parser.error("--num_seeds must be > 0") args.seeds = list(range(args.num_seeds)) if args.seeds is not None and not args.use_prefixes: parser.error( "--seeds/--num_seeds select among prefixed reports; they do not " "apply to two individual reports" ) return args def main() -> None: args = parse_args() labels = args.labels or [ (path or prefix).name.removesuffix(".pkl") for path, prefix in ( (args.report_a_path, args.report_a_path_prefix), (args.report_b_path, args.report_b_path_prefix), ) ] reports_a = collect_reports(args.report_a_path, args.report_a_path_prefix) reports_b = collect_reports(args.report_b_path, args.report_b_path_prefix) report_a_paths, report_b_paths, seeds = pair_reports( reports_a, reports_b, labels, args.seeds ) compare_reports(report_a_paths, report_b_paths, labels, seeds) if __name__ == "__main__": main() colmap-4.2.0/benchmark/reconstruction/download.py000066400000000000000000000255151524536416500222070ustar00rootroot00000000000000# Copyright (c), ETH Zurich and UNC Chapel Hill. # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are met: # # * Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer. # # * Redistributions in binary form must reproduce the above copyright # notice, this list of conditions and the following disclaimer in the # documentation and/or other materials provided with the distribution. # # * Neither the name of ETH Zurich and UNC Chapel Hill nor the names of # its contributors may be used to endorse or promote products derived # from this software without specific prior written permission. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. import argparse import hashlib import inspect import json import shutil import subprocess import tarfile import zipfile from pathlib import Path import py7zr import requests from evaluation.tartanair.tartanair_v2 import ( MANIFEST_PATH, load_manifest, scene_shards, shard_name, ) import pycolmap def download_file(url: str, target_folder: Path) -> str: filename = url.split("/")[-1] with requests.get(url, stream=True) as req: req.raise_for_status() with open(target_folder / filename, "wb") as f: for chunk in req.iter_content(chunk_size=8192): f.write(chunk) return filename def download_eth3d(data_path: Path) -> None: for filename, category in [ ("multi_view_training_dslr_undistorted.7z", "dslr"), ("multi_view_test_dslr_undistorted.7z", "dslr"), ("multi_view_training_rig_undistorted.7z", "rig"), ("multi_view_test_rig_undistorted.7z", "rig"), ]: target_folder = data_path / category target_folder.mkdir(parents=True, exist_ok=True) pycolmap.logging.info( f"Downloading ETH3D category={category}, filename={filename}" ) download_file("https://www.eth3d.net/data/" + filename, target_folder) pycolmap.logging.info( f"Extracting ETH3D category={category}, filename={filename}" ) with py7zr.SevenZipFile(target_folder / filename, mode="r") as archive: archive.extractall(path=target_folder) def download_imc2023(data_path: Path) -> None: data_path.mkdir(parents=True, exist_ok=True) pycolmap.logging.info("Downloading IMC2023") subprocess.check_call( [ "kaggle", "competitions", "download", "-c", "image-matching-challenge-2023", "-p", str(data_path), ], ) pycolmap.logging.info("Extracting IMC2023") with zipfile.ZipFile( data_path / "image-matching-challenge-2023.zip", mode="r" ) as archive: archive.extractall(path=data_path) def download_imc2024(data_path: Path) -> None: data_path.mkdir(parents=True, exist_ok=True) pycolmap.logging.info("Downloading IMC2024") subprocess.check_call( [ "kaggle", "competitions", "download", "-c", "image-matching-challenge-2024", "-p", str(data_path), ], ) pycolmap.logging.info("Extracting IMC2024") with zipfile.ZipFile( data_path / "image-matching-challenge-2024.zip", mode="r" ) as archive: archive.extractall(path=data_path) # Move all scenes to the "all" category sub-folder. category_path = data_path / "train/all" category_path.mkdir(parents=True, exist_ok=True) for scene in (data_path / "train").iterdir(): if str(scene).endswith("/all"): continue shutil.move(scene, data_path / category_path) def download_imc2025(data_path: Path) -> None: data_path.mkdir(parents=True, exist_ok=True) pycolmap.logging.info("Downloading IMC2025") subprocess.check_call( [ "kaggle", "competitions", "download", "-c", "image-matching-challenge-2025", "-p", str(data_path), ], ) pycolmap.logging.info("Extracting IMC2025") with zipfile.ZipFile( data_path / "image-matching-challenge-2025.zip", mode="r" ) as archive: archive.extractall(path=data_path) # Move all scenes to the "all" category sub-folder. category_path = data_path / "train/all" category_path.mkdir(parents=True, exist_ok=True) for scene in (data_path / "train").iterdir(): if scene.name == "all": continue shutil.move(scene, category_path) # TODO: BlendedMVS+ and BlendedMVS++. def download_blended_mvs(data_path: Path) -> None: target_folder = data_path / "BlendedMVS" target_folder.mkdir(parents=True, exist_ok=True) pycolmap.logging.info("Downloading BlendedMVS") for filename in [ "BlendedMVS.zip", ] + [f"BlendedMVS.z{i:02d}" for i in range(1, 16)]: download_file( "https://github.com/YoYo000/BlendedMVS/releases/download/v1.0.0/" + filename, target_folder, ) pycolmap.logging.info("Merging BlendedMVS split archive") combined_zip = target_folder / "BlendedMVS_combined.zip" subprocess.check_call( [ "zip", "-q", "-s", "0", str(target_folder / "BlendedMVS.zip"), "--out", str(combined_zip), ] ) pycolmap.logging.info("Extracting BlendedMVS") try: with zipfile.ZipFile(combined_zip, mode="r") as archive: archive.extractall(path=data_path) finally: if combined_zip.exists(): combined_zip.unlink() def _sha256(path: Path) -> str: digest = hashlib.sha256() with open(path, "rb") as fid: while chunk := fid.read(1024 * 1024): digest.update(chunk) return digest.hexdigest() def _extract_tar_safely(archive_path: Path, output_path: Path) -> None: output_root = output_path.resolve() with tarfile.open(archive_path) as archive: for member in archive.getmembers(): if member.issym() or member.islnk(): raise RuntimeError( f"Refusing link in release archive: {member.name}" ) target = (output_path / member.name).resolve() if not target.is_relative_to(output_root): raise RuntimeError( f"Refusing unsafe release archive path: {member.name}" ) if "filter" in inspect.signature(archive.extractall).parameters: archive.extractall(output_path, filter="fully_trusted") else: archive.extractall(output_path) def download_tartanair_v2( data_path: Path, categories: list[str] | None = None, scenes: list[str] | None = None, ) -> None: manifest = load_manifest() categories = categories or [] scenes = scenes or [] shards = scene_shards(manifest) selected_shards = [] for index, shard_scenes in enumerate(shards): if any( (not categories or scene.category in categories) and (not scenes or scene.name in scenes) for scene in shard_scenes ): selected_shards.append(index) if not selected_shards: pycolmap.logging.warning("No TartanAir V2 scenes matched the filters") return data_path.mkdir(parents=True, exist_ok=True) archive_path = data_path / ".archives" archive_path.mkdir(exist_ok=True) release = manifest["release"] base_url = ( f"https://github.com/{release['repository']}/releases/download/" f"{release['tag']}" ) checksum_path = MANIFEST_PATH.with_name("tartanair_v2_checksums.json") checksums = json.loads(checksum_path.read_text()) for index in selected_shards: filename = shard_name(manifest, index) target = archive_path / filename expected = checksums.get(filename) if expected is None: raise RuntimeError(f"Missing release checksum for {filename}") if not target.exists() or (expected and _sha256(target) != expected): temporary = target.with_suffix(target.suffix + ".part") if temporary.exists(): temporary.unlink() pycolmap.logging.info(f"Downloading TartanAir V2 {filename}") download_file(f"{base_url}/{filename}", archive_path) downloaded = archive_path / filename if downloaded != temporary: downloaded.replace(temporary) if expected and _sha256(temporary) != expected: temporary.unlink() raise RuntimeError(f"Checksum mismatch for {filename}") temporary.replace(target) pycolmap.logging.info(f"Extracting TartanAir V2 {filename}") _extract_tar_safely(target, data_path) DOWNLOADERS = { "eth3d": download_eth3d, "imc2023": download_imc2023, "imc2024": download_imc2024, "imc2025": download_imc2025, "blended-mvs": download_blended_mvs, "tartanair-v2": download_tartanair_v2, } def parse_args() -> argparse.Namespace: parser = argparse.ArgumentParser() parser.add_argument( "--data_path", type=Path, default=Path(__file__).parent / "data" ) parser.add_argument( "--datasets", nargs="+", default=DOWNLOADERS.keys(), choices=DOWNLOADERS.keys(), ) parser.add_argument( "--categories", nargs="+", default=[], help="TartanAir categories to download; empty downloads all.", ) parser.add_argument( "--scenes", nargs="+", default=[], help="TartanAir scenes to download; empty downloads all.", ) return parser.parse_args() def main() -> None: args = parse_args() for dataset in args.datasets: if dataset == "tartanair-v2": download_tartanair_v2( args.data_path / dataset, args.categories, args.scenes ) else: DOWNLOADERS[dataset](args.data_path / dataset) if __name__ == "__main__": main() colmap-4.2.0/benchmark/reconstruction/evaluate.py000066400000000000000000000163471524536416500222110ustar00rootroot00000000000000# Copyright (c), ETH Zurich and UNC Chapel Hill. # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are met: # # * Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer. # # * Redistributions in binary form must reproduce the above copyright # notice, this list of conditions and the following disclaimer in the # documentation and/or other materials provided with the distribution. # # * Neither the name of ETH Zurich and UNC Chapel Hill nor the names of # its contributors may be used to endorse or promote products derived # from this software without specific prior written permission. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. """Run the reconstruction benchmark and report pose accuracy metrics. A plain run evaluates one colmap binary once and writes a single report. --seeds/--num_seeds instead runs it once per random seed, writing one _s.pkl report per seed, to measure run-to-run spread. The scene workspaces are shared across those runs, so features and raw matches are computed once and reused. Reports are compared with compare.py. Reproducibility caveat: RANSAC seeds per thread as random_seed + omp_get_thread_num(), so only single-threaded scenes are deterministic at a fixed seed. Pass --threads_per_scene 1 for reproducible runs, and use --num_parallel_scenes for throughput instead -- parallelism across scenes does not touch any one scene's random number stream. Note that a seed reseeds both geometric verification and the mapper, but only --overwrite_reconstruction is forced between the runs; pass --overwrite_two_view_geometries as well to also redo verification. Example: # One binary across 5 seeds (run-to-run variance): python evaluate.py --colmap_path /path/colmap --num_seeds 5 \\ --threads_per_scene 1 --overwrite_two_view_geometries \\ --data_path data --datasets eth3d --categories dslr --scenes meadow \\ --run_path runs --run_name variance-meadow """ import argparse import json import pickle from evaluation.blended_mvs import DatasetBlendedMVS from evaluation.eth3d import DatasetETH3D from evaluation.imc import DatasetIMC2023, DatasetIMC2024, DatasetIMC2025 from evaluation.tartanair import ( DatasetTartanAirPerspective, DatasetTartanAirSpherical, ) from evaluation.tartanair.tartanair_v2 import load_manifest from evaluation.utils import ( Dataset, MetricsByDatasetByCatByScene, create_result_table, filter_smallest_scenes_per_category, parse_args, process_scenes, ) import pycolmap def run_once(args: argparse.Namespace) -> MetricsByDatasetByCatByScene | None: """Evaluates all datasets once and writes args.report_name. Returns None if a dataset is unknown or no scenes matched. """ datasets: dict[str, type[Dataset]] = { "eth3d": DatasetETH3D, "blended-mvs": DatasetBlendedMVS, "imc2023": DatasetIMC2023, "imc2024": DatasetIMC2024, "imc2025": DatasetIMC2025, "tartanair-v2-perspective": DatasetTartanAirPerspective, "tartanair-v2-spherical": DatasetTartanAirSpherical, } metrics: MetricsByDatasetByCatByScene = {} for dataset_name in args.datasets: if dataset_name not in datasets: pycolmap.logging.error(f"Unknown dataset: {dataset_name}") return None pycolmap.logging.info(f"Evaluating dataset: {dataset_name}") dataset = datasets[dataset_name]( data_path=args.data_path, categories=args.categories, scenes=args.scenes, run_path=args.run_path, run_name=args.run_name, ) scene_infos = dataset.list_scenes() if args.fast: scene_infos = filter_smallest_scenes_per_category( scene_infos, args.fast_num_scenes ) if not scene_infos: pycolmap.logging.warning("No scenes found") return None metrics[dataset_name] = process_scenes( args=args, scene_infos=scene_infos, dataset=dataset, ) pycolmap.logging.info("Results:\n" + create_result_table(metrics)) report_path = args.run_path / args.run_name / (args.report_name + ".pkl") pycolmap.logging.info(f"Saving report to: {report_path}") with open(report_path, "wb") as report_file: pickle.dump(metrics, report_file) metadata_path = report_path.with_suffix(".json") metadata = { "datasets": args.datasets, "pycolmap_version": pycolmap.__version__, "random_seed": args.random_seed, "feature": args.feature, "mapper": args.mapper, "use_gpu": args.use_gpu, } if any(name.startswith("tartanair-v2-") for name in args.datasets): metadata["tartanair_manifest_version"] = load_manifest()["version"] metadata_path.write_text(json.dumps(metadata, indent=2) + "\n") return metrics def run_seeds(args: argparse.Namespace) -> None: """Evaluates once per seed, writing _s.pkl each time. A failing seed is reported and skipped, so the remaining ones still run. """ # The seeds share one workspace per scene, so without this every run after # the first would silently re-evaluate the first one's reconstruction. args.overwrite_reconstruction = True args.overwrite_alignment = True report_name = args.report_name pycolmap.logging.info( f"{report_name}: {len(args.seeds)} seeds {args.seeds} " f"-> {args.run_path / args.run_name}" ) failures = [] for seed in args.seeds: pycolmap.logging.info(f"---- seed={seed} ----") args.random_seed = seed args.report_name = f"{report_name}_s{seed}" try: reason = "" if run_once(args) is not None else ": no scenes" except Exception as error: reason = f": {error!r}" if reason: failures.append(seed) pycolmap.logging.warning( f"seed={seed} FAILED{reason} (its report will be missing and " "a comparison skips it)" ) if failures: pycolmap.logging.warning( f"{len(failures)} of {len(args.seeds)} run(s) failed: {failures}" ) raise SystemExit(1) def main() -> None: args = parse_args(__doc__) if args.seeds is not None: run_seeds(args) else: run_once(args) if __name__ == "__main__": main() colmap-4.2.0/benchmark/reconstruction/evaluation/000077500000000000000000000000001524536416500221655ustar00rootroot00000000000000colmap-4.2.0/benchmark/reconstruction/evaluation/__init__.py000066400000000000000000000000001524536416500242640ustar00rootroot00000000000000colmap-4.2.0/benchmark/reconstruction/evaluation/blended_mvs.py000066400000000000000000000135651524536416500250330ustar00rootroot00000000000000# Copyright (c), ETH Zurich and UNC Chapel Hill. # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are met: # # * Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer. # # * Redistributions in binary form must reproduce the above copyright # notice, this list of conditions and the following disclaimer in the # documentation and/or other materials provided with the distribution. # # * Neither the name of ETH Zurich and UNC Chapel Hill nor the names of # its contributors may be used to endorse or promote products derived # from this software without specific prior written permission. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. import numpy as np from PIL import Image import pycolmap from .utils import Dataset, SceneInfo class DatasetBlendedMVS(Dataset): @property def position_accuracy_gt(self): return 0.001 @property def supports_covisibility_filtering(self) -> bool: return True def list_scenes(self): scene_infos = [] for category_path in (self.data_path / "blended-mvs").iterdir(): if not category_path.is_dir() or ( self.categories and category_path.name not in self.categories ): continue category = category_path.name for scene_path in sorted(category_path.iterdir()): if not scene_path.is_dir(): continue scene = scene_path.name if self.scenes and scene not in self.scenes: continue workspace_path = ( self.run_path / self.run_name / "blended-mvs" / category / scene ) image_path = scene_path / "blended_images" image_list_path = scene_path / "images.txt" num_images = 0 with open(image_list_path, "w") as fid: for filepath in sorted(image_path.iterdir()): image_name = str(filepath.name) if ( image_name.endswith(".jpg") and "masked" not in image_name ): fid.write(image_name + "\n") num_images += 1 sparse_gt_path = scene_path / "sparse_gt" colmap_extra_args = ["--image_list_path", image_list_path] scene_info = SceneInfo( dataset="blended-mvs", category=category, scene=scene, num_images=num_images, workspace_path=workspace_path, image_path=image_path, sparse_gt_path=sparse_gt_path, has_camera_priors=True, colmap_extra_args=colmap_extra_args, ) scene_infos.append(scene_info) return scene_infos def prepare_scene(self, scene_info): if scene_info.sparse_gt_path.exists(): return scene_path = scene_info.image_path.parent sparse_gt = pycolmap.Reconstruction() for i, filepath in enumerate(sorted((scene_path / "cams").iterdir())): filename = str(filepath.name) if not filename.endswith("_cam.txt"): continue image_name = filename[:-8] + ".jpg" width, height = Image.open( scene_path / "blended_images" / image_name ).size[:2] with open(filepath, encoding="ascii") as fid: lines = list(map(lambda b: b.strip(), fid.readlines())) extrinsic = np.fromstring( " ".join(lines[1:4]), count=12, sep=" ", ).reshape(3, 4) intrinsic = np.fromstring( " ".join(lines[7:10]), count=9, sep=" ", ).reshape(3, 3) camera = pycolmap.Camera( camera_id=i, model=pycolmap.CameraModelId.PINHOLE, width=width, height=height, params=intrinsic[(0, 1, 0, 1), (0, 1, 2, 2)], ) rig = pycolmap.Rig(rig_id=i) rig.add_ref_sensor(camera.sensor_id) image = pycolmap.Image( image_id=i, camera_id=i, name=image_name, ) image.frame_id = i frame = pycolmap.Frame(frame_id=i) frame.rig_id = i frame.add_data_id(image.data_id) frame.rig_from_world = pycolmap.Rigid3d(extrinsic) sparse_gt.add_camera(camera) sparse_gt.add_rig(rig) sparse_gt.add_frame(frame) sparse_gt.add_image(image) scene_info.sparse_gt_path.mkdir(exist_ok=True) sparse_gt.write(scene_info.sparse_gt_path) colmap-4.2.0/benchmark/reconstruction/evaluation/covisibility.py000066400000000000000000000275061524536416500252620ustar00rootroot00000000000000# Copyright (c), ETH Zurich and UNC Chapel Hill. # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are met: # # * Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer. # # * Redistributions in binary form must reproduce the above copyright # notice, this list of conditions and the following disclaimer in the # documentation and/or other materials provided with the distribution. # # * Neither the name of ETH Zurich and UNC Chapel Hill nor the names of # its contributors may be used to endorse or promote products derived # from this software without specific prior written permission. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. import dataclasses import functools from pathlib import Path import numpy as np import numpy.typing as npt import pycolmap from .geometry import vec_angular_dist_deg @dataclasses.dataclass class Frustum: # Sampled 3D points on the frustum in world coordinates. points: npt.NDArray[np.floating] # Near/far depth bounds (in camera coordinates) for the frustum. depth_range: tuple[float, float] def _estimate_depth_ranges( sparse_gt: pycolmap.Reconstruction, percentile_near: float = 2.0, percentile_far: float = 98.0, min_num_points: int = 10, ) -> dict[int, tuple[float, float]]: """Estimate per-image near/far depth range from GT 3D points. For each image, computes depths of the 3D points visible in that image and returns percentile-based near/far bounds. Falls back to (0.1, 100.0) for images with insufficient depth data. """ default_range = (0.1, 100.0) depth_ranges: dict[int, tuple[float, float]] = {} for image_id, image in sparse_gt.images.items(): valid = [ p.point3D_id for p in image.points2D if p.point3D_id != pycolmap.INVALID_POINT3D_ID ] if len(valid) < min_num_points: depth_ranges[image_id] = default_range continue points_xyz = np.array([sparse_gt.points3D[pid].xyz for pid in valid]) cam_from_world = image.cam_from_world() points_in_cam = ( cam_from_world.rotation.matrix() @ points_xyz.T + cam_from_world.translation[:, np.newaxis] ) depths = points_in_cam[2, :] pos_depths = depths[depths > 0] if len(pos_depths) < min_num_points: depth_ranges[image_id] = default_range continue near, far = np.percentile(pos_depths, [percentile_near, percentile_far]) depth_ranges[image_id] = (float(near), float(far)) return depth_ranges def _sample_frustum_points( image: pycolmap.Image, camera: pycolmap.Camera, near: float, far: float, num_steps: int = 5, ) -> npt.NDArray[np.floating]: """Sample points on a camera viewing frustum in world coordinates. Samples a grid of points on the image plane (corners, edges, and interior) at multiple depths between near and far, then transforms them to world space for more accurate overlap checks. """ w, h = camera.width, camera.height us = np.linspace(0, w, num_steps) vs = np.linspace(0, h, num_steps) grid_x, grid_y = np.meshgrid(us, vs) pixels = np.stack([grid_x.ravel(), grid_y.ravel()], axis=1) cam_points = camera.cam_from_img(pixels) assert cam_points is not None cam_rays = np.column_stack([cam_points, np.ones(len(cam_points))]) depths = np.linspace(near, far, num_steps) points_in_cam = np.vstack([cam_rays * d for d in depths]) world_from_cam = image.cam_from_world().inverse() points_in_world = ( world_from_cam.rotation.matrix() @ points_in_cam.T + world_from_cam.translation[:, np.newaxis] ).T return points_in_world def _build_image_point3D_sets( sparse_gt: pycolmap.Reconstruction, ) -> dict[int, set[int]]: """Build a mapping from image ID to the set of observed 3D point IDs.""" image_points: dict[int, set[int]] = {} for image_id, image in sparse_gt.images.items(): image_points[image_id] = { p.point3D_id for p in image.points2D if p.point3D_id != pycolmap.INVALID_POINT3D_ID } return image_points def _compute_frustums_for_all_images( sparse_gt: pycolmap.Reconstruction, frustum_near: float | None, frustum_far: float | None, ) -> dict[int, Frustum]: depth_ranges: dict[int, tuple[float, float]] = {} if frustum_near is None or frustum_far is None: depth_ranges = _estimate_depth_ranges(sparse_gt) frustums: dict[int, Frustum] = {} for image_id, image_gt in sparse_gt.images.items(): camera_gt = sparse_gt.cameras[image_gt.camera_id] est_near, est_far = depth_ranges.get(image_id, (0.1, 100.0)) near = frustum_near if frustum_near is not None else est_near far = frustum_far if frustum_far is not None else est_far frustums[image_id] = Frustum( points=_sample_frustum_points(image_gt, camera_gt, near, far), depth_range=(near, far), ) return frustums def _is_pair_covisible_by_tracks( image1: pycolmap.Image, image2: pycolmap.Image, image_point3D_sets: dict[int, set[int]], min_shared_points: int, ) -> bool: shared = len( image_point3D_sets[image1.image_id] & image_point3D_sets[image2.image_id] ) return shared >= min_shared_points def _is_pair_covisible_by_frustum( image1: pycolmap.Image, image2: pycolmap.Image, sparse_gt: pycolmap.Reconstruction, frustums: dict[int, Frustum], max_viewing_angle_deg: float, ) -> bool: """Check whether two cameras have overlapping viewing frustums. Uses a three-stage test that short-circuits on the first definitive result: 1. Viewing angle: reject if angle between viewing directions exceeds the threshold. 2. Vertex projection: accept if any of A's frustum vertices projects into B's image (or vice versa) with positive depth within the target image's expected GT depth range. """ if ( vec_angular_dist_deg( image1.viewing_direction(), image2.viewing_direction() ) > max_viewing_angle_deg ): return False camera1 = sparse_gt.cameras[image1.camera_id] camera2 = sparse_gt.cameras[image2.camera_id] def project_and_check( frustum: Frustum, image: pycolmap.Image, camera: pycolmap.Camera, ) -> bool: near, far = frustums[image.image_id].depth_range cam_from_world = image.cam_from_world() R = cam_from_world.rotation.matrix() t = cam_from_world.translation points_in_cam = (R @ frustum.points.T + t[:, np.newaxis]).T depths = points_in_cam[:, 2] mask = (depths >= near) & (depths <= far) if not np.any(mask): return False img_points = camera.img_from_cam(points_in_cam[mask]) if img_points is None: return False in_bounds = ( (img_points[:, 0] >= 0) & (img_points[:, 0] <= camera.width) & (img_points[:, 1] >= 0) & (img_points[:, 1] <= camera.height) ) return bool(np.any(in_bounds)) return project_and_check( frustums[image1.image_id], image2, camera2 ) or project_and_check(frustums[image2.image_id], image1, camera1) def filter_covisibility( database_path: Path, sparse_gt: pycolmap.Reconstruction, covisibility_frustum_near: float | None, covisibility_frustum_far: float | None, max_viewing_angle_deg: float, min_shared_points: int = 0, ) -> None: """Filter non-covisible image pairs from the database. If the GT reconstruction contains 3D point tracks and min_shared_points > 0, uses track-based covisibility: two images are covisible if they share at least min_shared_points common 3D points. Otherwise, falls back to frustum-based overlap checking using GT camera poses and intrinsics. """ pycolmap.logging.info("Filtering non-covisible image pairs") use_tracks = min_shared_points > 0 and sparse_gt.num_points3D() > 0 if use_tracks: pycolmap.logging.info( f"Using track-based covisibility " f"(min_shared_points={min_shared_points})" ) image_point3D_sets = _build_image_point3D_sets(sparse_gt) is_covisible = functools.partial( _is_pair_covisible_by_tracks, image_point3D_sets=image_point3D_sets, min_shared_points=min_shared_points, ) else: if min_shared_points > 0: pycolmap.logging.warning( "No GT 3D points available for track-based covisibility, " "falling back to frustum-based check" ) frustums = _compute_frustums_for_all_images( sparse_gt, covisibility_frustum_near, covisibility_frustum_far ) is_covisible = functools.partial( _is_pair_covisible_by_frustum, sparse_gt=sparse_gt, frustums=frustums, max_viewing_angle_deg=max_viewing_angle_deg, ) images_gt_by_name: dict[str, pycolmap.Image] = { image_gt.name: image_gt for image_gt in sparse_gt.images.values() } with pycolmap.Database.open(str(database_path)) as database: db_id_to_name: dict[int, str] = { db_image.image_id: db_image.name for db_image in database.read_all_images() } pair_ids, _ = database.read_two_view_geometry_num_inliers() total_pairs = len(pair_ids) filtered_count = 0 missing_gt_image_names: set[str] = set() for pair_id in pair_ids: image_id1, image_id2 = pycolmap.pair_id_to_image_pair(pair_id) name1 = db_id_to_name.get(image_id1) name2 = db_id_to_name.get(image_id2) if name1 is None or name2 is None: continue image_gt1 = images_gt_by_name.get(name1) image_gt2 = images_gt_by_name.get(name2) if image_gt1 is None: missing_gt_image_names.add(name1) if image_gt2 is None: missing_gt_image_names.add(name2) if image_gt1 is None or image_gt2 is None: continue if not is_covisible(image_gt1, image_gt2): # Only delete the two-view geometry, so it will be ignored # during reconstruction but keep the raw matches, so upon # re-running the pipeline, the matches will be re-computed, # if not filtering by covisibility. database.delete_two_view_geometry(image_id1, image_id2) filtered_count += 1 pycolmap.logging.info( f"Co-visibility filtering: {filtered_count}/{total_pairs} pairs " f"removed, {total_pairs - filtered_count} kept" ) if missing_gt_image_names: pycolmap.logging.warning( f"Skipped pairs involving {len(missing_gt_image_names)} " f"database image(s) without a GT counterpart" ) colmap-4.2.0/benchmark/reconstruction/evaluation/covisibility_test.py000066400000000000000000000333431524536416500263150ustar00rootroot00000000000000# Copyright (c), ETH Zurich and UNC Chapel Hill. # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are met: # # * Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer. # # * Redistributions in binary form must reproduce the above copyright # notice, this list of conditions and the following disclaimer in the # documentation and/or other materials provided with the distribution. # # * Neither the name of ETH Zurich and UNC Chapel Hill nor the names of # its contributors may be used to endorse or promote products derived # from this software without specific prior written permission. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. import numpy as np import pycolmap from .covisibility import ( Frustum, _build_image_point3D_sets, _estimate_depth_ranges, _is_pair_covisible_by_frustum, _sample_frustum_points, ) def _make_camera() -> pycolmap.Camera: cam = pycolmap.Camera() cam.model = pycolmap.CameraModelId.PINHOLE cam.width = 640 cam.height = 480 cam.params = [500, 500, 320, 240] cam.camera_id = 1 return cam def _add_image( recon: pycolmap.Reconstruction, image_id: int, name: str, position: np.ndarray, rotation: pycolmap.Rotation3d | None = None, num_points2D: int = 0, ) -> None: if rotation is None: rotation = pycolmap.Rotation3d() translation = -rotation.matrix() @ position points2d = [pycolmap.Point2D() for _ in range(num_points2D)] for i, p in enumerate(points2d): p.xy = np.array([100.0 + i * 10, 200.0]) frame = pycolmap.Frame() frame.rig_id = 1 frame.frame_id = image_id img = pycolmap.Image() img.name = name img.camera_id = 1 img.frame_id = image_id img.image_id = image_id if num_points2D > 0: img.points2D = pycolmap.Point2DList(points2d) frame.add_data_id(img.data_id) frame.rig_from_world = pycolmap.Rigid3d(rotation, translation) recon.add_frame(frame) recon.add_image(img) class TestEstimateDepthRanges: def test_single_image_with_points(self): recon = pycolmap.Reconstruction() cam = _make_camera() recon.add_camera_with_trivial_rig(cam) _add_image(recon, 1, "img1.jpg", np.array([0, 0, 0]), num_points2D=20) depths = [] for i in range(20): depth = 2.0 + i * 0.5 depths.append(depth) recon.add_point3D( np.array([0.0, 0.0, depth]), pycolmap.Track([pycolmap.TrackElement(1, i)]), ) ranges = _estimate_depth_ranges(recon) assert 1 in ranges near, far = ranges[1] np.testing.assert_almost_equal(near, np.percentile(depths, 2.0)) np.testing.assert_almost_equal(far, np.percentile(depths, 98.0)) def test_insufficient_points_returns_default(self): recon = pycolmap.Reconstruction() cam = _make_camera() recon.add_camera_with_trivial_rig(cam) _add_image(recon, 1, "img1.jpg", np.array([0, 0, 0]), num_points2D=5) for i in range(5): recon.add_point3D( np.array([0.0, 0.0, float(i + 1)]), pycolmap.Track([pycolmap.TrackElement(1, i)]), ) ranges = _estimate_depth_ranges(recon) assert ranges[1] == (0.1, 100.0) def test_per_image_ranges_differ(self): recon = pycolmap.Reconstruction() cam = _make_camera() recon.add_camera_with_trivial_rig(cam) _add_image(recon, 1, "img1.jpg", np.array([0, 0, 0]), num_points2D=20) _add_image(recon, 2, "img2.jpg", np.array([0, 0, 100]), num_points2D=20) # Points near image 1 (depths 1-10 from image 1) for i in range(20): depth = 1.0 + i * 0.5 recon.add_point3D( np.array([0.0, 0.0, depth]), pycolmap.Track([pycolmap.TrackElement(1, i)]), ) # Points near image 2 (depths 1-10 from image 2, so z ~ 90-99) for i in range(20): depth = 1.0 + i * 0.5 recon.add_point3D( np.array([0.0, 0.0, 100.0 + depth]), pycolmap.Track([pycolmap.TrackElement(2, i)]), ) ranges = _estimate_depth_ranges(recon) near1, far1 = ranges[1] near2, far2 = ranges[2] # Image 1 sees nearby points, image 2 sees distant points assert near1 < 15 assert near2 < 15 class TestSampleFrustumPoints: def test_identity_camera_shape(self): recon = pycolmap.Reconstruction() cam = _make_camera() recon.add_camera_with_trivial_rig(cam) _add_image(recon, 1, "img.jpg", np.array([0, 0, 0])) verts = _sample_frustum_points(recon.images[1], cam, 1.0, 10.0) # num_steps=5 default: 5*5 grid at 5 depths = 125 points assert verts.shape == (125, 3) def test_custom_num_steps(self): recon = pycolmap.Reconstruction() cam = _make_camera() recon.add_camera_with_trivial_rig(cam) _add_image(recon, 1, "img.jpg", np.array([0, 0, 0])) verts = _sample_frustum_points( recon.images[1], cam, 1.0, 10.0, num_steps=3 ) assert verts.shape == (27, 3) def test_vertices_depth_range(self): recon = pycolmap.Reconstruction() cam = _make_camera() recon.add_camera_with_trivial_rig(cam) # Identity pose: camera at origin looking +z _add_image(recon, 1, "img.jpg", np.array([0, 0, 0])) near, far = 2.0, 20.0 verts = _sample_frustum_points(recon.images[1], cam, near, far) # In world coords, z should span [near, far] for identity pose np.testing.assert_almost_equal(verts[:, 2].min(), near) np.testing.assert_almost_equal(verts[:, 2].max(), far) def test_translated_camera(self): recon = pycolmap.Reconstruction() cam = _make_camera() recon.add_camera_with_trivial_rig(cam) offset = np.array([10.0, 20.0, 30.0]) _add_image(recon, 1, "img.jpg", offset) verts = _sample_frustum_points( recon.images[1], cam, 1.0, 10.0, num_steps=2 ) # Center of near plane should be at camera position + [0,0,near] center = verts.mean(axis=0) np.testing.assert_allclose(center[:2], offset[:2], atol=1e-10) assert center[2] > offset[2] class TestCheckFrustumCovisibility: def _build_two_camera_recon( self, pos1, pos2, rot1=None, rot2=None ) -> pycolmap.Reconstruction: recon = pycolmap.Reconstruction() cam = _make_camera() recon.add_camera_with_trivial_rig(cam) _add_image(recon, 1, "img1.jpg", np.array(pos1), rot1) _add_image(recon, 2, "img2.jpg", np.array(pos2), rot2) return recon def _check(self, recon, max_angle=90.0, near=1.0, far=100.0): cam = recon.cameras[1] imgs = [recon.images[1], recon.images[2]] frustums = { img.image_id: Frustum( points=_sample_frustum_points(img, cam, near, far), depth_range=(near, far), ) for img in imgs } return _is_pair_covisible_by_frustum( imgs[0], imgs[1], recon, frustums, max_angle ) def test_covisible_side_by_side(self): recon = self._build_two_camera_recon([-0.5, 0, -5], [0.5, 0, -5]) assert self._check(recon) def test_not_covisible_opposite_directions(self): rot_180_y = pycolmap.Rotation3d(np.array([0, 1, 0, 0])) recon = self._build_two_camera_recon( [0, 0, -5], [0, 0, 5], rot2=rot_180_y ) assert not self._check(recon) def test_rejected_by_viewing_angle(self): # 90 deg rotation around y-axis rot = pycolmap.Rotation3d( np.array([0, 1 / np.sqrt(2), 0, 1 / np.sqrt(2)]) ) recon = self._build_two_camera_recon([0, 0, -5], [5, 0, -5], rot2=rot) # Viewing angle is 90 deg, threshold is 45 -> should be rejected assert not self._check(recon, max_angle=45.0) def test_covisible_converging_cameras(self): # Two cameras angled inward looking at the same point # Camera 1 at (-2,0,0) looking at +x (toward origin) rot1 = pycolmap.Rotation3d( np.array([0, -1 / np.sqrt(2), 0, 1 / np.sqrt(2)]) ) # Camera 2 at (2,0,0) looking at -x (toward origin) rot2 = pycolmap.Rotation3d( np.array([0, 1 / np.sqrt(2), 0, 1 / np.sqrt(2)]) ) recon = self._build_two_camera_recon( [-5, 0, 0], [5, 0, 0], rot1=rot1, rot2=rot2 ) assert self._check(recon, max_angle=180.0) def test_not_covisible_far_apart_narrow_fov(self): # Two cameras far apart, both looking +z, with small frustum depth recon = self._build_two_camera_recon([0, 0, 0], [1000, 0, 0]) assert self._check(recon, near=1.0, far=2.0) is False def test_not_covisible_depth_out_of_range(self): # Two cameras side by side looking +z. Their frustums project into # each other's images, but the depth ranges don't overlap: camera 1 # expects depths 1-5 while camera 2 expects 50-100. Each camera's # frustum points land outside the other's depth range. recon = self._build_two_camera_recon([-0.5, 0, 0], [0.5, 0, 0]) cam = recon.cameras[1] img1, img2 = recon.images[1], recon.images[2] frustums = { img1.image_id: Frustum( points=_sample_frustum_points(img1, cam, 1.0, 5.0), depth_range=(1.0, 5.0), ), img2.image_id: Frustum( points=_sample_frustum_points(img2, cam, 50.0, 100.0), depth_range=(50.0, 100.0), ), } assert not _is_pair_covisible_by_frustum( img1, img2, recon, frustums, 90.0 ) # Sanity: with matching depth ranges, these cameras are covisible. assert self._check(recon, near=1.0, far=100.0) def test_covisible_identical_cameras(self): recon = self._build_two_camera_recon([0, 0, 0], [0, 0, 0]) assert self._check(recon) def _make_recon_with_tracks( num_images: int, num_points2D_per_image: int, tracks: list[list[tuple[int, int]]], ) -> pycolmap.Reconstruction: """Build a reconstruction with images and 3D point tracks. Args: num_images: Number of images to create. num_points2D_per_image: Number of 2D points per image. tracks: List of tracks, where each track is a list of (image_id, point2D_idx) tuples. """ recon = pycolmap.Reconstruction() cam = _make_camera() recon.add_camera_with_trivial_rig(cam) for i in range(1, num_images + 1): _add_image( recon, i, f"img{i}.jpg", np.array([i * 2.0, 0, 0]), num_points2D=num_points2D_per_image, ) for track in tracks: xyz = np.array([0.0, 0.0, 5.0]) elements = [pycolmap.TrackElement(img_id, idx) for img_id, idx in track] recon.add_point3D(xyz, pycolmap.Track(elements)) return recon class TestBuildImagePoint3DSets: def test_basic_tracks(self): # 2 images, 5 points2D each, 3 shared tracks recon = _make_recon_with_tracks( num_images=2, num_points2D_per_image=5, tracks=[ [(1, 0), (2, 0)], [(1, 1), (2, 1)], [(1, 2), (2, 2)], ], ) sets = _build_image_point3D_sets(recon) assert len(sets[1]) == 3 assert len(sets[2]) == 3 assert len(sets[1] & sets[2]) == 3 def test_no_shared_tracks(self): recon = _make_recon_with_tracks( num_images=2, num_points2D_per_image=5, tracks=[ [(1, 0)], [(1, 1)], [(2, 0)], [(2, 1)], ], ) sets = _build_image_point3D_sets(recon) assert len(sets[1] & sets[2]) == 0 def test_no_tracks(self): recon = _make_recon_with_tracks( num_images=2, num_points2D_per_image=5, tracks=[], ) sets = _build_image_point3D_sets(recon) assert len(sets[1]) == 0 assert len(sets[2]) == 0 def test_partial_overlap(self): # 3 images: img1-img2 share 5 points # img2-img3 share 2, img1-img3 share 0 recon = _make_recon_with_tracks( num_images=3, num_points2D_per_image=10, tracks=[ [(1, 0), (2, 0)], [(1, 1), (2, 1)], [(1, 2), (2, 2)], [(1, 3), (2, 3)], [(1, 4), (2, 4)], [(2, 5), (3, 0)], [(2, 6), (3, 1)], ], ) sets = _build_image_point3D_sets(recon) assert len(sets[1] & sets[2]) == 5 assert len(sets[2] & sets[3]) == 2 assert len(sets[1] & sets[3]) == 0 colmap-4.2.0/benchmark/reconstruction/evaluation/eth3d.py000066400000000000000000000074241524536416500235550ustar00rootroot00000000000000# Copyright (c), ETH Zurich and UNC Chapel Hill. # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are met: # # * Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer. # # * Redistributions in binary form must reproduce the above copyright # notice, this list of conditions and the following disclaimer in the # documentation and/or other materials provided with the distribution. # # * Neither the name of ETH Zurich and UNC Chapel Hill nor the names of # its contributors may be used to endorse or promote products derived # from this software without specific prior written permission. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. from .utils import Dataset, SceneInfo class DatasetETH3D(Dataset): @property def position_accuracy_gt(self): return 0.001 @property def supports_covisibility_filtering(self) -> bool: return True def list_scenes(self): scene_infos = [] for category_path in (self.data_path / "eth3d").iterdir(): if not category_path.is_dir() or ( self.categories and category_path.name not in self.categories ): continue category = category_path.name for scene_path in sorted(category_path.iterdir()): if not scene_path.is_dir(): continue scene = scene_path.name if self.scenes and scene not in self.scenes: continue workspace_path = ( self.run_path / self.run_name / "eth3d" / category / scene ) image_path = scene_path / "images" sparse_gt_path = list( scene_path.glob("*_calibration_undistorted") )[0] colmap_extra_args = [] if category == "dslr": colmap_extra_args.extend(["--data_type", "individual"]) elif category == "rig": colmap_extra_args.extend(["--data_type", "video"]) num_images = sum( 1 for p in image_path.rglob("*") if p.is_file() and p.suffix.lower() in {".jpg", ".jpeg", ".png"} ) scene_info = SceneInfo( dataset="eth3d", category=category, scene=scene, num_images=num_images, workspace_path=workspace_path, image_path=image_path, sparse_gt_path=sparse_gt_path, has_camera_priors=True, colmap_extra_args=colmap_extra_args, ) scene_infos.append(scene_info) return scene_infos def prepare_scene(self, scene_info): # Nothing to prepare for ETH3D. pass colmap-4.2.0/benchmark/reconstruction/evaluation/geometry.py000066400000000000000000000037611524536416500244010ustar00rootroot00000000000000# Copyright (c), ETH Zurich and UNC Chapel Hill. # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are met: # # * Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer. # # * Redistributions in binary form must reproduce the above copyright # notice, this list of conditions and the following disclaimer in the # documentation and/or other materials provided with the distribution. # # * Neither the name of ETH Zurich and UNC Chapel Hill nor the names of # its contributors may be used to endorse or promote products derived # from this software without specific prior written permission. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. import numpy as np import numpy.typing as npt def normalize_vec( vec: npt.NDArray[np.floating], eps: float = 1e-10 ) -> npt.NDArray[np.floating]: return vec / max(eps, float(np.linalg.norm(vec))) def vec_angular_dist_deg( vec1: npt.NDArray[np.floating], vec2: npt.NDArray[np.floating] ) -> float: cos_dist = np.clip(np.dot(normalize_vec(vec1), normalize_vec(vec2)), -1, 1) return np.rad2deg(np.acos(cos_dist)) colmap-4.2.0/benchmark/reconstruction/evaluation/geometry_test.py000066400000000000000000000067531524536416500254440ustar00rootroot00000000000000# Copyright (c), ETH Zurich and UNC Chapel Hill. # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are met: # # * Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer. # # * Redistributions in binary form must reproduce the above copyright # notice, this list of conditions and the following disclaimer in the # documentation and/or other materials provided with the distribution. # # * Neither the name of ETH Zurich and UNC Chapel Hill nor the names of # its contributors may be used to endorse or promote products derived # from this software without specific prior written permission. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. import numpy as np from .geometry import normalize_vec, vec_angular_dist_deg class TestNormalizeVec: def test_unit_vector(self): vec = np.array([1.0, 0.0, 0.0]) normalized = normalize_vec(vec) np.testing.assert_allclose(normalized, vec) np.testing.assert_almost_equal(np.linalg.norm(normalized), 1.0) def test_non_unit_vector(self): vec = np.array([3.0, 4.0, 0.0]) normalized = normalize_vec(vec) expected = np.array([0.6, 0.8, 0.0]) np.testing.assert_allclose(normalized, expected) np.testing.assert_almost_equal(np.linalg.norm(normalized), 1.0) def test_zero_vector(self): vec = np.array([0.0, 0.0, 0.0]) normalized = normalize_vec(vec) assert np.linalg.norm(normalized) < 1e-8 class TestVecAngularDistDeg: def test_identical_vectors(self): vec1 = np.array([1.0, 0.0, 0.0]) vec2 = np.array([1.0, 0.0, 0.0]) dist = vec_angular_dist_deg(vec1, vec2) np.testing.assert_almost_equal(dist, 0.0) def test_opposite_vectors(self): vec1 = np.array([1.0, 0.0, 0.0]) vec2 = np.array([-1.0, 0.0, 0.0]) dist = vec_angular_dist_deg(vec1, vec2) np.testing.assert_almost_equal(dist, 180.0) def test_perpendicular_vectors(self): vec1 = np.array([1.0, 0.0, 0.0]) vec2 = np.array([0.0, 1.0, 0.0]) dist = vec_angular_dist_deg(vec1, vec2) np.testing.assert_almost_equal(dist, 90.0) def test_45_degree_vectors(self): vec1 = np.array([1.0, 0.0, 0.0]) vec2 = np.array([1.0, 1.0, 0.0]) dist = vec_angular_dist_deg(vec1, vec2) np.testing.assert_almost_equal(dist, 45.0) def test_non_unit_vectors(self): vec1 = np.array([2.0, 0.0, 0.0]) vec2 = np.array([0.0, 3.0, 0.0]) dist = vec_angular_dist_deg(vec1, vec2) # Should normalize internally np.testing.assert_almost_equal(dist, 90.0) colmap-4.2.0/benchmark/reconstruction/evaluation/imc.py000066400000000000000000000505351524536416500233170ustar00rootroot00000000000000# Copyright (c), ETH Zurich and UNC Chapel Hill. # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are met: # # * Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer. # # * Redistributions in binary form must reproduce the above copyright # notice, this list of conditions and the following disclaimer in the # documentation and/or other materials provided with the distribution. # # * Neither the name of ETH Zurich and UNC Chapel Hill nor the names of # its contributors may be used to endorse or promote products derived # from this software without specific prior written permission. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. import csv import tempfile from collections import defaultdict from pathlib import Path import numpy as np from PIL import Image as PilImage import pycolmap from .utils import Dataset, SceneInfo _POINTS3D_FILENAME = "points3D.txt" _TRAIN_LABELS_FILENAME = "train_labels.csv" # Scene label used by IMC2025 to mark images that do not belong to any scene. _OUTLIER_SCENE = "outliers" # Fallback image dimensions if an image file cannot be opened. Only used to # build placeholder GT cameras, which do not affect relative pose evaluation. _DEFAULT_IMAGE_WIDTH = 1024 _DEFAULT_IMAGE_HEIGHT = 768 def _read_points3D_lenient( path: Path, valid_image_ids: set[int] ) -> dict[int, pycolmap.Point3D]: """Parse points3D.txt, dropping track elements whose image_id is not in valid_image_ids and skipping any point3D whose track becomes empty.""" points3D: dict[int, pycolmap.Point3D] = {} with open(path) as f: for line in f: line = line.strip() if not line or line.startswith("#"): continue tokens = line.split() point3D_id = int(tokens[0]) xyz = [float(v) for v in tokens[1:4]] rgb = [int(v) for v in tokens[4:7]] error = float(tokens[7]) elements = [] track_tokens = tokens[8:] for i in range(0, len(track_tokens), 2): image_id = int(track_tokens[i]) point2D_idx = int(track_tokens[i + 1]) if image_id in valid_image_ids: elements.append( pycolmap.TrackElement(image_id, point2D_idx) ) if not elements: continue point3D = pycolmap.Point3D() point3D.xyz = np.array(xyz) point3D.color = np.array(rgb, dtype=np.uint8) point3D.error = error point3D.track = pycolmap.Track(elements) points3D[point3D_id] = point3D return points3D def _read_sparse_sfm_without_points3D( sfm_path: Path, ) -> pycolmap.Reconstruction: """Read an SfM reconstruction's cameras/rigs/frames/images, skipping points3D entirely. Some IMC scenes ship with points3D.txt referencing image_ids absent from images.txt, which makes the strict pycolmap reader abort. Callers can parse points3D separately via _read_points3D_lenient if needed. """ with tempfile.TemporaryDirectory() as tmp: tmp_path = Path(tmp) for src_file in sfm_path.iterdir(): if src_file.name == _POINTS3D_FILENAME: continue (tmp_path / src_file.name).symlink_to(src_file.resolve()) (tmp_path / _POINTS3D_FILENAME).touch() return pycolmap.Reconstruction(tmp_path) class _DatasetIMC(Dataset): @property def position_accuracy_gt(self): return 0.02 @property def supports_covisibility_filtering(self) -> bool: # IMC2023/2024 ship per-scene COLMAP reconstructions with real # intrinsics and 3D points, so covisibility filtering is supported. return True def _has_ground_truth(self, scene_path: Path) -> bool: """Whether ground truth is available for a scene. The default IMC layout ships a per-scene COLMAP reconstruction under sfm/; scenes without it are skipped. """ return (scene_path / "sfm").exists() def _image_path(self, scene_path: Path) -> Path: """Folder holding a scene's images (a dedicated images/ subfolder).""" return scene_path / "images" def _count_images(self, scene: str, image_path: Path) -> int: """Number of images for a scene (image files present on disk).""" return sum( 1 for p in image_path.iterdir() if p.is_file() and p.suffix.lower() in {".jpg", ".jpeg", ".png"} ) def list_scenes(self): folder_name = f"imc{self.year}" scene_infos = [] for category_path in Path( self.data_path / f"{folder_name}/train" ).iterdir(): if not category_path.is_dir() or ( self.categories and category_path.name not in self.categories ): continue category = category_path.name for scene_path in category_path.iterdir(): if not scene_path.is_dir(): continue scene = scene_path.name if self.scenes and scene not in self.scenes: continue if not self._has_ground_truth(scene_path): pycolmap.logging.warning( f"Skipping dataset=IMC{self.year}, " f"category={category}, scene={scene}, " "because the GT reconstruction is missing" ) continue image_path = self._image_path(scene_path) sparse_gt_path = scene_path / "sparse_gt" workspace_path = ( self.run_path / self.run_name / folder_name / category / scene ) scene_info = SceneInfo( dataset=f"IMC{self.year}", category=category, scene=scene, num_images=self._count_images(scene, image_path), workspace_path=workspace_path, image_path=image_path, sparse_gt_path=sparse_gt_path, has_camera_priors=False, colmap_extra_args=None, ) scene_infos.append(scene_info) return scene_infos def prepare_scene(self, scene_info): if scene_info.sparse_gt_path.exists(): return scene_path = scene_info.image_path.parent sfm_path = scene_path / "sfm" sparse_sfm = _read_sparse_sfm_without_points3D(sfm_path) sparse_gt = pycolmap.Reconstruction() train_image_ids = set() train_image_names = set( image.name for image in scene_info.image_path.iterdir() ) for image in sparse_sfm.images.values(): if image.name in train_image_names: train_image_ids.add(image.image_id) for camera in sparse_sfm.cameras.values(): sparse_gt.add_camera(camera) for rig in sparse_sfm.rigs.values(): sparse_gt.add_rig(rig) for frame in sparse_sfm.frames.values(): has_train_image = False for data_id in frame.image_ids: if data_id.id in train_image_ids: has_train_image = True break if has_train_image: frame.reset_rig_ptr() sparse_gt.add_frame(frame) for image in sparse_sfm.images.values(): if image.image_id not in train_image_ids: continue if image.camera_id not in sparse_gt.cameras: sparse_gt.add_camera(image.camera) image.reset_camera_ptr() image.reset_frame_ptr() sparse_gt.add_image(image) points3D = _read_points3D_lenient( sfm_path / _POINTS3D_FILENAME, train_image_ids ) for point3D_id, point3D in points3D.items(): sparse_gt.add_point3D_with_id(point3D_id, point3D) scene_info.sparse_gt_path.mkdir(exist_ok=True) sparse_gt.write(scene_info.sparse_gt_path) class DatasetIMC2023(_DatasetIMC): def __init__( self, data_path: Path, categories: list[str], scenes: list[Path], run_path: Path, run_name: str, ): super().__init__( data_path=data_path, categories=categories, scenes=scenes, run_path=run_path, run_name=run_name, ) self.year = 2023 class DatasetIMC2024(_DatasetIMC): def __init__( self, data_path: Path, categories: list[str], scenes: list[Path], run_path: Path, run_name: str, ): super().__init__( data_path=data_path, categories=categories, scenes=scenes, run_path=run_path, run_name=run_name, ) self.year = 2024 class DatasetIMC2025(_DatasetIMC): """IMC2025 benchmark dataset. Unlike IMC2023/IMC2024, the ground truth is provided as poses in a single train_labels.csv rather than per-scene COLMAP reconstructions, and each IMC "dataset" folder mixes images from multiple scenes plus outliers that belong to no scene. Here each IMC dataset is a single benchmark scene: all of its images (every scene plus outliers) are fed into one reconstruction problem, which typically yields several sub-models. Evaluation uses the base-class set-based, GT-component-aware pose metric (relative by default; absolute is also supported), driven by a per-GT-scene grouping supplied via scene_info.image_name_to_component, which jointly penalizes wrong merges, registered outliers, and failed/fragmented registrations. Ground-truth 3D points and intrinsics are unavailable, so the GT reconstruction (used only for stats and covisibility heuristics) uses placeholder pinhole cameras built from the actual image dimensions. All scenes are stored in one GT reconstruction, each with its own gauge; the metric only ever compares poses within the same scene, so the (arbitrary) relative placement of different scenes does not matter. IMC2025 has no coarse category grouping: the downloaded layout places every dataset under train/all/, so the base iteration reports "all" as the (single) category and each dataset as its own scene. """ def __init__( self, data_path: Path, categories: list[str], scenes: list[Path], run_path: Path, run_name: str, ): super().__init__( data_path=data_path, categories=categories, scenes=scenes, run_path=run_path, run_name=run_name, ) self.year = 2025 # Lazily-parsed {dataset: [image, ...]} from train_labels.csv. self._images_by_dataset_cache: dict[str, list[str]] | None = None @staticmethod def _parse_floats(text: str) -> list[float] | None: """Parse a list of floats from an IMC label field. IMC stores matrices/vectors as ';'-separated values inside a single CSV field (falls back to whitespace separation). Returns None if the field is empty or cannot be fully parsed as floats. """ if text is None: return None text = text.strip() if not text: return None tokens = text.split(";") if ";" in text else text.split() try: return [float(t) for t in tokens if t.strip() != ""] except ValueError: return None @staticmethod def _read_imc2025_labels( path: Path, ) -> tuple[dict[tuple[str, str], list[dict]], dict[str, list[str]]]: """Parse IMC2025 train_labels.csv. Expected columns: dataset, scene, image, rotation_matrix (row-major 3x3, cam_from_world), translation_vector (cam_from_world). Returns a tuple of: - gt_rows_by_scene: {(dataset, scene): [{image, R, t}, ...]} for all non-outlier images with a parseable pose (the ground truth). - images_by_dataset: {dataset: [image, ...]} for every image, including outliers (the full input to the reconstruction problem). """ gt_rows_by_scene: dict[tuple[str, str], list[dict]] = defaultdict(list) images_by_dataset: dict[str, list[str]] = defaultdict(list) with open(path, newline="") as f: reader = csv.DictReader(f) for row in reader: dataset = (row.get("dataset") or "").strip() scene = (row.get("scene") or "").strip() image = (row.get("image") or "").strip() if not dataset or not image: continue images_by_dataset[dataset].append(image) if not scene or scene == _OUTLIER_SCENE: continue rotation = DatasetIMC2025._parse_floats( row.get("rotation_matrix", "") ) translation = DatasetIMC2025._parse_floats( row.get("translation_vector", "") ) if ( rotation is None or translation is None or len(rotation) != 9 or len(translation) != 3 ): continue gt_rows_by_scene[(dataset, scene)].append( { "image": image, "R": np.array(rotation, dtype=np.float64).reshape(3, 3), "t": np.array(translation, dtype=np.float64), } ) return gt_rows_by_scene, images_by_dataset @staticmethod def _build_imc2025_gt_reconstruction( rows: list[dict], image_dir: Path ) -> pycolmap.Reconstruction: """Build a placeholder GT reconstruction from IMC2025 label rows. Each row provides {image, R, t} with cam_from_world extrinsics. IMC2025 ships neither intrinsics nor 3D points, so we attach a rough pinhole camera sized from each image (falling back to default dimensions if the file cannot be opened) and add no points3D. The result has one rig/frame/camera/image per row and is only used for pose-based evaluation and covisibility heuristics, not for anything that depends on intrinsics. """ reconstruction = pycolmap.Reconstruction() for idx, row in enumerate(rows, start=1): image_name = row["image"] image_file = image_dir / image_name try: width, height = PilImage.open(image_file).size except (OSError, ValueError): pycolmap.logging.warning( f"Could not read dimensions for {image_file}, " "using placeholder camera size" ) width = _DEFAULT_IMAGE_WIDTH height = _DEFAULT_IMAGE_HEIGHT # Intrinsics are not provided by IMC; use a rough pinhole guess. # This only affects covisibility/alignment heuristics, not the # relative pose error, which depends solely on poses. focal = 1.2 * max(width, height) camera = pycolmap.Camera( camera_id=idx, model=pycolmap.CameraModelId.PINHOLE, width=width, height=height, params=[focal, focal, width / 2.0, height / 2.0], ) reconstruction.add_camera_with_trivial_rig(camera) image = pycolmap.Image( image_id=idx, camera_id=idx, name=image_name, ) # IMC stores cam_from_world (x_cam = R * x_world + t). With a # trivial rig, sensor_from_rig is identity, so cam_from_world # equals rig_from_world. cam_from_world = pycolmap.Rigid3d( np.hstack([row["R"], row["t"].reshape(3, 1)]) ) reconstruction.add_image_with_trivial_frame(image, cam_from_world) return reconstruction @property def supports_covisibility_filtering(self) -> bool: # IMC2025 ships neither intrinsics nor 3D points, and the placeholder GT # stores every scene in its own arbitrary gauge. Feeding that to the # frustum filter would delete valid verified pairs based on guessed # focal/depth values, so covisibility filtering is disabled here. return False def _labels_path(self) -> Path: """Path to train_labels.csv (sibling of the train/ folder).""" return self.data_path / "imc2025" / _TRAIN_LABELS_FILENAME def _images_by_dataset(self) -> dict[str, list[str]]: """Parse (and cache) the {dataset: [image, ...]} map from the labels.""" if self._images_by_dataset_cache is None: _, self._images_by_dataset_cache = self._read_imc2025_labels( self._labels_path() ) return self._images_by_dataset_cache def _has_ground_truth(self, scene_path: Path) -> bool: """IMC2025 ships GT as poses in train_labels.csv rather than a per-scene sfm/ reconstruction, so availability is determined by the existence of the labels file.""" return self._labels_path().exists() def _image_path(self, scene_path: Path) -> Path: # All of a dataset's images live directly in the dataset folder. return scene_path def _count_images(self, scene: str, image_path: Path) -> int: # The folder contains exactly the labeled images; count them from the # labels and warn about any listed image missing on disk. num_images = 0 num_missing = 0 for name in self._images_by_dataset().get(scene, []): if (image_path / name).exists(): num_images += 1 else: num_missing += 1 if num_missing: pycolmap.logging.warning( f"IMC2025 dataset={scene}: {num_missing} listed " "image(s) not found on disk" ) return num_images def prepare_scene(self, scene_info): gt_rows_by_scene, _ = self._read_imc2025_labels(self._labels_path()) dataset = scene_info.scene # Each GT scene within this IMC dataset is its own reconstruction; map # every GT image name to a distinct integer id so the base-class # set-based metric only ever compares poses within the same scene. # Outliers are already excluded from gt_rows_by_scene, so they are # absent from the mapping and penalized when registered. scene_to_component: dict[str, int] = {} image_name_to_component: dict[str, int] = {} rows = [] for (ds, scene), scene_rows in sorted(gt_rows_by_scene.items()): if ds != dataset: continue component = scene_to_component.setdefault( scene, len(scene_to_component) ) for row in scene_rows: image_name_to_component[row["image"]] = component rows.extend(scene_rows) scene_info.image_name_to_component = image_name_to_component if scene_info.sparse_gt_path.exists(): return sparse_gt = self._build_imc2025_gt_reconstruction( rows, scene_info.image_path ) scene_info.sparse_gt_path.mkdir(parents=True, exist_ok=True) sparse_gt.write(scene_info.sparse_gt_path) colmap-4.2.0/benchmark/reconstruction/evaluation/tartanair/000077500000000000000000000000001524536416500241525ustar00rootroot00000000000000colmap-4.2.0/benchmark/reconstruction/evaluation/tartanair/README.md000066400000000000000000000036461524536416500254420ustar00rootroot00000000000000# TartanAir V2 COLMAP benchmark subset This package contains a modified subset of the TartanAir V2 dataset for evaluating panoramic structure-from-motion in COLMAP. Selected RGB images are encoded as quality-97 progressive JPEG with 4:4:4 chroma sampling. Ground-truth depth maps are min-pooled from 2048x1024 to 512x256 and stored as lossless 16-bit PNGs. A depth value `d` represents `d / 8` meters, and zero is invalid. During evaluation, the depth maps are sampled every four pixels and projected between ground-truth camera poses. A sample is considered shared when its projected range agrees with the target depth within `max(0.25 m, 1%)`, up to a maximum range of 256 m. Verified image pairs with no shared samples are removed before mapping. Derived overlap counts are cached locally in `covisibility.npz` and can be regenerated from the packaged depths. Dataset: https://tartanair.org/ Source files: https://huggingface.co/datasets/theairlabcmu/tartanair2 TartanAir V2 is distributed under the Creative Commons Attribution 4.0 International license (CC BY 4.0). This benchmark subset modifies the source RGB and depth images as described above. See `TARTANAIR_LICENSE` for the full license terms and cite the original dataset when publishing results based on this subset: > Wenshan Wang et al., "TartanAir: A Dataset to Push the Limits of Visual > SLAM," IROS 2020. The exact source revision, trajectories, frames, and packaging checksums are recorded in `tartanair_v2_manifest.json`. From the COLMAP checkout, download and evaluate both pipeline variants with: ```bash python benchmark/reconstruction/download.py --datasets tartanair-v2 python benchmark/reconstruction/evaluate.py \ --datasets tartanair-v2-perspective tartanair-v2-spherical \ --colmap_path /path/to/colmap ``` The panorama reconstruction itself uses the pycolmap package imported by the current Python interpreter. The COLMAP executable is used for model alignment. colmap-4.2.0/benchmark/reconstruction/evaluation/tartanair/__init__.py000066400000000000000000000166061524536416500262740ustar00rootroot00000000000000# Copyright (c), ETH Zurich and UNC Chapel Hill. # All rights reserved. import json import numpy as np import pycolmap from ..utils import Dataset, SceneInfo from .depth_covisibility import covisibility_is_current, write_covisibility # Precomputed TartanAir panoramas have a -90 degree longitude shift: panorama # +X is NED forward, +Y is down, and +Z is left. NED_FROM_COLMAP = np.array([[1.0, 0.0, 0.0], [0.0, 0.0, -1.0], [0.0, 1.0, 0.0]]) def tartanair_world_from_camera( translation: np.ndarray, quaternion_xyzw: np.ndarray ) -> pycolmap.Rigid3d: """Convert a TartanAir NED camera pose to COLMAP camera coordinates.""" world_from_ned = pycolmap.Rigid3d( pycolmap.Rotation3d(quaternion_xyzw), translation[:, np.newaxis] ) # The translation defaults to zero. Passing np.zeros((3, 1)) explicitly # fails mypy with numpy 2.4, the latest version available on Python 3.11. ned_from_colmap = pycolmap.Rigid3d( rotation=pycolmap.Rotation3d(NED_FROM_COLMAP) ) return world_from_ned * ned_from_colmap class DatasetTartanAir(Dataset): dataset_name = "" render_type = "" @property def position_accuracy_gt(self) -> float: return 0.001 @property def supports_covisibility_filtering(self) -> bool: return False def list_scenes(self) -> list[SceneInfo]: scene_infos: list[SceneInfo] = [] dataset_path = self.data_path / "tartanair-v2" if not dataset_path.exists(): return scene_infos for category_path in sorted(dataset_path.iterdir()): if not category_path.is_dir() or ( self.categories and category_path.name not in self.categories ): continue category = category_path.name for scene_path in sorted(category_path.iterdir()): if not scene_path.is_dir(): continue scene = scene_path.name if self.scenes and scene not in self.scenes: continue metadata_path = scene_path / "scene.json" if not metadata_path.exists(): continue metadata = json.loads(metadata_path.read_text()) workspace_path = ( self.run_path / self.run_name / self.dataset_name / category / scene ) reconstruction_subdir = ( "sparse_equirectangular" if self.render_type == "perspective_overlapping" else "sparse" ) scene_infos.append( SceneInfo( dataset=self.dataset_name, category=category, scene=scene, num_images=len(metadata["frames"]), workspace_path=workspace_path, image_path=scene_path / "images", sparse_gt_path=scene_path / "sparse_gt", has_camera_priors=False, colmap_extra_args=[], reconstruction_backend=f"panorama-{self.render_type}", reconstruction_subdir=reconstruction_subdir, covisibility_path=scene_path / "covisibility.npz", covisibility_min_shared_points=1, ) ) return scene_infos def prepare_scene(self, scene_info: SceneInfo) -> None: scene_path = scene_info.image_path.parent metadata = json.loads((scene_path / "scene.json").read_text()) image_names = [frame["image_name"] for frame in metadata["frames"]] depth_path = scene_path / "depth" covisibility_path = scene_info.covisibility_path if covisibility_path is None or not depth_path.exists(): needs_covisibility = False covisibility_current = True else: needs_covisibility = True covisibility_current = covisibility_is_current( covisibility_path, image_names ) if scene_info.sparse_gt_path.exists() and covisibility_current: return pose_by_frame = {} with open(scene_path / "poses.txt", encoding="ascii") as fid: for line in fid: values = line.split() if not values: continue frame_id = int(values[0]) pose_by_frame[frame_id] = np.array(values[1:8], dtype=float) world_from_cameras = [] for frame_info in metadata["frames"]: pose = pose_by_frame[frame_info["source_frame"]] world_from_camera_pose = tartanair_world_from_camera( pose[:3], pose[3:] ) matrix = np.eye(4) matrix[:3, :3] = world_from_camera_pose.rotation.matrix() matrix[:3, 3] = world_from_camera_pose.translation world_from_cameras.append(matrix) if scene_info.sparse_gt_path.exists(): reconstruction = None else: width, height = metadata["image_size"] reconstruction = pycolmap.Reconstruction() camera = pycolmap.Camera( camera_id=1, model=pycolmap.CameraModelId.EQUIRECTANGULAR, width=width, height=height, params=[width, height], ) rig = pycolmap.Rig(rig_id=1) rig.add_ref_sensor(camera.sensor_id) reconstruction.add_camera(camera) reconstruction.add_rig(rig) if reconstruction is not None: for image_id, (frame_info, world_from_camera_matrix) in enumerate( zip(metadata["frames"], world_from_cameras, strict=True), start=1, ): image = pycolmap.Image( image_id=image_id, camera_id=camera.camera_id, name=frame_info["image_name"], ) image.frame_id = image_id frame = pycolmap.Frame(frame_id=image_id) frame.rig_id = rig.rig_id frame.add_data_id(image.data_id) frame.rig_from_world = pycolmap.Rigid3d( pycolmap.Rotation3d(world_from_camera_matrix[:3, :3]), world_from_camera_matrix[:3, 3], ).inverse() reconstruction.add_frame(frame) reconstruction.add_image(image) scene_info.sparse_gt_path.mkdir(parents=True) reconstruction.write(scene_info.sparse_gt_path) if ( covisibility_path is not None and needs_covisibility and not covisibility_current ): write_covisibility( covisibility_path, [ depth_path / frame["depth_name"] for frame in metadata["frames"] ], image_names, np.stack(world_from_cameras), metadata["depth_scale"], ) class DatasetTartanAirPerspective(DatasetTartanAir): dataset_name = "tartanair-v2-perspective" render_type = "perspective_overlapping" class DatasetTartanAirSpherical(DatasetTartanAir): dataset_name = "tartanair-v2-spherical" render_type = "spherical" colmap-4.2.0/benchmark/reconstruction/evaluation/tartanair/depth_covisibility.py000066400000000000000000000123701524536416500304240ustar00rootroot00000000000000"""Depth-based covisibility for TartanAir equirectangular images.""" from pathlib import Path import numpy as np import numpy.typing as npt SAMPLE_STRIDE = 4 MAX_DEPTH = 256.0 ABSOLUTE_TOLERANCE = 0.25 RELATIVE_TOLERANCE = 0.01 def equirectangular_rays( width: int, height: int, stride: int ) -> npt.NDArray[np.float64]: x = np.arange(0, width, stride, dtype=np.float64) + 0.5 y = np.arange(0, height, stride, dtype=np.float64) + 0.5 u: npt.NDArray[np.float64] v: npt.NDArray[np.float64] u, v = np.meshgrid(x / width, y / height) yaw = (2.0 * u - 1.0) * np.pi pitch = (1.0 - 2.0 * v) * np.pi / 2.0 cos_pitch = np.cos(pitch) return np.column_stack( [ np.sin(yaw).ravel() * cos_pitch.ravel(), -np.sin(pitch).ravel(), np.cos(yaw).ravel() * cos_pitch.ravel(), ] ) def _project_equirectangular( points_in_camera: npt.NDArray[np.float64], width: int, height: int ) -> tuple[npt.NDArray[np.int64], npt.NDArray[np.int64]]: yaw = np.arctan2(points_in_camera[:, 0], points_in_camera[:, 2]) pitch = -np.arctan2( points_in_camera[:, 1], np.linalg.norm(points_in_camera[:, [0, 2]], axis=1), ) x = np.floor((1.0 + yaw / np.pi) * width / 2.0).astype(np.int64) y = np.floor((1.0 - pitch * 2.0 / np.pi) * height / 2.0).astype(np.int64) return np.mod(x, width), np.clip(y, 0, height - 1) def compute_covisibility_counts( depths: npt.NDArray[np.uint16], world_from_cameras: npt.NDArray[np.float64], depth_scale: float, *, stride: int = SAMPLE_STRIDE, max_depth: float = MAX_DEPTH, absolute_tolerance: float = ABSOLUTE_TOLERANCE, relative_tolerance: float = RELATIVE_TOLERANCE, ) -> npt.NDArray[np.uint32]: if depths.ndim != 3: raise ValueError(f"Expected depth array (N,H,W), got {depths.shape}") if world_from_cameras.shape != (len(depths), 4, 4): raise ValueError( "Expected one 4x4 world_from_camera matrix per depth image" ) if depth_scale <= 0: raise ValueError("depth_scale must be positive") _, height, width = depths.shape rays = equirectangular_rays(width, height, stride) sampled_depths = ( depths[:, ::stride, ::stride] .reshape(len(depths), -1) .astype(np.float64) / depth_scale ) counts: npt.NDArray[np.uint32] = np.zeros( (len(depths), len(depths)), dtype=np.uint32 ) for source_idx in range(len(depths)): source_depth = sampled_depths[source_idx] valid = (source_depth > 0) & (source_depth <= max_depth) if not np.any(valid): continue source_points = rays[valid] * source_depth[valid, np.newaxis] source_pose = world_from_cameras[source_idx] points_in_world = ( source_pose[:3, :3] @ source_points.T + source_pose[:3, 3, np.newaxis] ).T for target_idx in range(len(depths)): if source_idx == target_idx: counts[source_idx, target_idx] = np.count_nonzero(valid) continue target_pose = world_from_cameras[target_idx] points_in_target = ( target_pose[:3, :3].T @ (points_in_world - target_pose[:3, 3]).T ).T projected_depth = np.linalg.norm(points_in_target, axis=1) x, y = _project_equirectangular(points_in_target, width, height) target_depth = depths[target_idx, y, x].astype(np.float64) target_depth /= depth_scale tolerance = np.maximum( absolute_tolerance, relative_tolerance * projected_depth ) visible = ( (target_depth > 0) & (target_depth <= max_depth) & (np.abs(target_depth - projected_depth) <= tolerance) ) counts[source_idx, target_idx] = np.count_nonzero(visible) return counts def covisibility_is_current(path: Path, image_names: list[str]) -> bool: if not path.exists(): return False try: with np.load(path) as covisibility: return ( covisibility["image_names"].tolist() == image_names and covisibility["sample_stride"].item() == SAMPLE_STRIDE and covisibility["max_depth"].item() == MAX_DEPTH and covisibility["absolute_tolerance"].item() == ABSOLUTE_TOLERANCE and covisibility["relative_tolerance"].item() == RELATIVE_TOLERANCE ) except (KeyError, OSError, ValueError): return False def write_covisibility( output_path: Path, depth_paths: list[Path], image_names: list[str], world_from_cameras: npt.NDArray[np.float64], depth_scale: float, ) -> None: from PIL import Image depths = np.stack( [np.asarray(Image.open(path), dtype=np.uint16) for path in depth_paths] ) counts = compute_covisibility_counts( depths, world_from_cameras, depth_scale ) np.savez_compressed( output_path, image_names=np.asarray(image_names), directed_overlap_counts=counts, sample_stride=SAMPLE_STRIDE, max_depth=MAX_DEPTH, absolute_tolerance=ABSOLUTE_TOLERANCE, relative_tolerance=RELATIVE_TOLERANCE, ) colmap-4.2.0/benchmark/reconstruction/evaluation/tartanair/depth_covisibility_test.py000066400000000000000000000014741524536416500314660ustar00rootroot00000000000000import numpy as np from .depth_covisibility import compute_covisibility_counts def test_compute_covisibility_counts_identical_views(): depths = np.full((2, 4, 8), 80, dtype=np.uint16) world_from_cameras = np.repeat(np.eye(4)[np.newaxis], 2, axis=0) counts = compute_covisibility_counts( depths, world_from_cameras, depth_scale=8.0, stride=1 ) np.testing.assert_array_equal(counts, np.full((2, 2), 32)) def test_compute_covisibility_counts_rejects_disjoint_views(): depths = np.full((2, 4, 8), 80, dtype=np.uint16) world_from_cameras = np.repeat(np.eye(4)[np.newaxis], 2, axis=0) world_from_cameras[1, 0, 3] = 100.0 counts = compute_covisibility_counts( depths, world_from_cameras, depth_scale=8.0, stride=1 ) assert counts[0, 1] == 0 assert counts[1, 0] == 0 colmap-4.2.0/benchmark/reconstruction/evaluation/tartanair/package_tartanair_v2.py000066400000000000000000000420101524536416500305700ustar00rootroot00000000000000"""Build deterministic COLMAP benchmark shards from TartanAir V2.""" import argparse import concurrent.futures import dataclasses import hashlib import io import json import struct import tarfile import time import zlib from pathlib import Path from typing import TYPE_CHECKING from urllib.parse import quote import numpy as np import PIL.Image import requests if TYPE_CHECKING: from .tartanair_v2 import ( MANIFEST_PATH, SceneSelection, load_manifest, scene_shards, select_frame_window, shard_name, ) else: try: from .tartanair_v2 import ( MANIFEST_PATH, SceneSelection, load_manifest, scene_shards, select_frame_window, shard_name, ) except ImportError: # Support direct execution as a packaging script. from tartanair_v2 import ( MANIFEST_PATH, SceneSelection, load_manifest, scene_shards, select_frame_window, shard_name, ) @dataclasses.dataclass(frozen=True) class ZipMember: name: str method: int crc32: int compressed_size: int uncompressed_size: int local_offset: int class RemoteZip: """Read selected members of a remote ZIP using HTTP range requests.""" def __init__(self, url: str): self.url = url self.session = requests.Session() self.members = self._read_central_directory() def _range(self, byte_range: str) -> tuple[bytes, requests.Response]: for attempt in range(5): try: response = self.session.get( self.url, headers={"Range": f"bytes={byte_range}"}, timeout=180, ) response.raise_for_status() if response.status_code != 206: raise RuntimeError( f"Server ignored byte range for {self.url}" ) return response.content, response except requests.RequestException: if attempt == 4: raise time.sleep(2**attempt) raise AssertionError("unreachable") @staticmethod def _zip64_values( extra: bytes, uncompressed: int, compressed: int, offset: int ) -> tuple[int, int, int]: position = 0 while position + 4 <= len(extra): tag, size = struct.unpack_from(" dict[str, ZipMember]: tail, response = self._range("-262144") total_size = int(response.headers["Content-Range"].split("/")[-1]) eocd_offset = tail.rfind(b"PK\x05\x06") if eocd_offset < 0 or eocd_offset + 22 > len(tail): raise RuntimeError(f"Cannot locate ZIP directory for {self.url}") eocd = struct.unpack_from("<4s4H2LH", tail, eocd_offset) num_entries, directory_size, directory_offset = eocd[4:7] if ( num_entries == 0xFFFF or directory_size == 0xFFFFFFFF or directory_offset == 0xFFFFFFFF ): locator_offset = tail.rfind( b"PK\x06\x07", max(0, eocd_offset - 64), eocd_offset ) if locator_offset < 0: raise RuntimeError(f"Cannot locate ZIP64 directory: {self.url}") zip64_offset = struct.unpack_from("<4sLQL", tail, locator_offset)[2] zip64, _ = self._range(f"{zip64_offset}-{zip64_offset + 55}") values = struct.unpack_from("<4sQ2H2L4Q", zip64) num_entries, directory_size, directory_offset = values[7:10] if directory_offset + directory_size > total_size: raise RuntimeError(f"Invalid ZIP directory bounds: {self.url}") directory, _ = self._range( f"{directory_offset}-{directory_offset + directory_size - 1}" ) members = {} position = 0 while ( position + 46 <= len(directory) and directory[position : position + 4] == b"PK\x01\x02" ): values = struct.unpack_from("<4s6H3L5H2L", directory, position) method = values[4] crc32 = values[7] compressed = values[8] uncompressed = values[9] name_size, extra_size, comment_size = values[10:13] local_offset = values[16] name_start = position + 46 name = directory[name_start : name_start + name_size].decode() extra = directory[ name_start + name_size : name_start + name_size + extra_size ] uncompressed, compressed, local_offset = self._zip64_values( extra, uncompressed, compressed, local_offset ) members[name] = ZipMember( name=name, method=method, crc32=crc32, compressed_size=compressed, uncompressed_size=uncompressed, local_offset=local_offset, ) position += 46 + name_size + extra_size + comment_size if len(members) != num_entries: raise RuntimeError( f"Expected {num_entries} ZIP members, parsed {len(members)}" ) return members def read(self, name: str) -> tuple[bytes, ZipMember]: member = self.members[name] header, _ = self._range( f"{member.local_offset}-{member.local_offset + 29}" ) values = struct.unpack_from("<4s5H3L2H", header) name_size, extra_size = values[9:11] data_offset = member.local_offset + 30 + name_size + extra_size compressed, _ = self._range( f"{data_offset}-{data_offset + member.compressed_size - 1}" ) if member.method == 0: data = compressed elif member.method == 8: data = zlib.decompress(compressed, -15) else: raise RuntimeError(f"Unsupported ZIP method {member.method}") if len(data) != member.uncompressed_size: raise RuntimeError(f"Invalid size for ZIP member {name}") if zlib.crc32(data) != member.crc32: raise RuntimeError(f"Invalid CRC for ZIP member {name}") return data, member def add_bytes(archive: tarfile.TarFile, name: str, data: bytes) -> None: info = tarfile.TarInfo(name) info.size = len(data) info.mode = 0o644 info.mtime = 0 info.uid = 0 info.gid = 0 info.uname = "" info.gname = "" archive.addfile(info, io.BytesIO(data)) DEPTH_SIZE = (512, 256) DEPTH_SCALE = 8.0 DEPTH_INVALID_VALUE = 0 IMAGE_QUALITY = 97 IMAGE_SUBSAMPLING = 0 TARTANAIR_LICENSE_URL = ( "https://creativecommons.org/licenses/by/4.0/legalcode.txt" ) TARTANAIR_LICENSE_SHA256 = ( "9ba9550ad48438d0836ddab3da480b3b69ffa0aac7b7878b5a0039e7ab429411" ) def download_tartanair_license() -> bytes: response = requests.get(TARTANAIR_LICENSE_URL, timeout=60) response.raise_for_status() license_data = response.content digest = hashlib.sha256(license_data).hexdigest() if digest != TARTANAIR_LICENSE_SHA256: raise RuntimeError(f"Unexpected TartanAir license SHA-256: {digest}") return license_data def encode_image_jpeg(source_png: bytes) -> bytes: output = io.BytesIO() PIL.Image.open(io.BytesIO(source_png)).convert("RGB").save( output, format="JPEG", quality=IMAGE_QUALITY, subsampling=IMAGE_SUBSAMPLING, optimize=True, progressive=True, ) return output.getvalue() def encode_depth_png(source_png: bytes) -> bytes: """Convert packed float32 RGBA depth to min-pooled uint16 depth.""" rgba = np.asarray(PIL.Image.open(io.BytesIO(source_png))).copy() if rgba.dtype != np.uint8 or rgba.ndim != 3 or rgba.shape[2] != 4: raise ValueError("Expected an 8-bit RGBA depth image") source_height = int(rgba.shape[0]) source_width = int(rgba.shape[1]) depth = rgba.view(" 0) depth = np.where(valid, depth, np.inf) depth = depth.reshape( output_height, block_height, output_width, block_width ).min(axis=(1, 3)) quantized = np.zeros(depth.shape, dtype=np.uint16) valid = np.isfinite(depth) quantized[valid] = np.clip( np.rint(depth[valid] * DEPTH_SCALE), 1, np.iinfo(np.uint16).max ).astype(np.uint16) output = io.BytesIO() PIL.Image.fromarray(quantized).save( output, format="PNG", compress_level=9, optimize=True ) return output.getvalue() def source_url(manifest: dict, scene: SceneSelection) -> str: source = manifest["source"] path = quote(scene.source_archive) return ( f"https://huggingface.co/datasets/{source['repository']}/resolve/" f"{source['revision']}/{path}" ) def package_scene( archive: tarfile.TarFile, manifest: dict, scene: SceneSelection, remote_zips: dict[str, RemoteZip], num_workers: int, ) -> None: image_remote_zip = remote_zips.get(scene.source_archive) if image_remote_zip is None: image_remote_zip = RemoteZip(source_url(manifest, scene)) remote_zips[scene.source_archive] = image_remote_zip depth_remote_zip = remote_zips.get(scene.depth_source_archive) if depth_remote_zip is None: depth_url = source_url(manifest, scene).replace( "image_lcam_equirect.zip", "depth_lcam_equirect.zip" ) depth_remote_zip = RemoteZip(depth_url) remote_zips[scene.depth_source_archive] = depth_remote_zip source_prefix = ( f"{scene.environment}/Data_{scene.difficulty}/{scene.trajectory}" ) pose_name = f"{source_prefix}/pose_lcam_front.txt" pose_data, pose_member = image_remote_zip.read(pose_name) pose_lines = [line for line in pose_data.decode().splitlines() if line] poses = np.loadtxt(io.StringIO("\n".join(pose_lines))) options = manifest["frame_selection"] selected_window = select_frame_window( poses, num_frames=options["num_frames"], max_adjacent_translation_m=options["max_adjacent_translation_m"], max_adjacent_rotation_deg=options["max_adjacent_rotation_deg"], ) selection_key = f"{scene.environment}:{scene.difficulty}:{scene.trajectory}" canonical_start = manifest["frame_starts"][selection_key] frame_ids = range(canonical_start, canonical_start + options["num_frames"]) if frame_ids != selected_window: raise RuntimeError( f"Canonical frame window changed for {selection_key}: " f"expected {frame_ids.start}, selected {selected_window.start}" ) scene_root = f"{scene.category}/{scene.name}" selected_pose_lines = [] frames = [] source_names = [ ( f"{source_prefix}/image_lcam_equirect/" f"{frame_id:06d}_lcam_equirect_image.png" ) for frame_id in frame_ids ] depth_source_names = [ ( f"{source_prefix}/depth_lcam_equirect/" f"{frame_id:06d}_lcam_equirect_depth.png" ) for frame_id in frame_ids ] with concurrent.futures.ThreadPoolExecutor( max_workers=num_workers ) as executor: image_members = list(executor.map(image_remote_zip.read, source_names)) depth_members = list( executor.map(depth_remote_zip.read, depth_source_names) ) for frame_id, (image_data, image_member), (depth_data, depth_member) in zip( frame_ids, image_members, depth_members, strict=True ): image_name = f"{frame_id:06d}.jpg" depth_name = f"{frame_id:06d}.png" add_bytes( archive, f"{scene_root}/images/{image_name}", encode_image_jpeg(image_data), ) add_bytes( archive, f"{scene_root}/depth/{depth_name}", encode_depth_png(depth_data), ) selected_pose_lines.append(f"{frame_id} {pose_lines[frame_id]}") frames.append( { "source_frame": frame_id, "image_name": image_name, "depth_name": depth_name, "source_crc32": f"{image_member.crc32:08x}", "depth_source_crc32": f"{depth_member.crc32:08x}", } ) metadata = { "manifest_version": manifest["version"], "category": scene.category, "environment": scene.environment, "difficulty": scene.difficulty, "trajectory": scene.trajectory, "source_archive": scene.source_archive, "depth_source_archive": scene.depth_source_archive, "pose_crc32": f"{pose_member.crc32:08x}", "image_size": manifest["source"]["image_size"], "image_encoding": { "format": "jpeg", "quality": IMAGE_QUALITY, "subsampling": "4:4:4", "progressive": True, }, "depth_size": list(DEPTH_SIZE), "depth_scale": DEPTH_SCALE, "depth_invalid_value": DEPTH_INVALID_VALUE, "depth_downsampling": "min_pool", "frames": frames, } add_bytes( archive, f"{scene_root}/poses.txt", ("\n".join(selected_pose_lines) + "\n").encode(), ) add_bytes( archive, f"{scene_root}/scene.json", (json.dumps(metadata, indent=2) + "\n").encode(), ) def build_shard( output_path: Path, manifest: dict, shard_index: int, scenes: list[SceneSelection], overwrite: bool, num_workers: int, ) -> tuple[str, str]: filename = shard_name(manifest, shard_index) target = output_path / filename if target.exists() and not overwrite: digest = sha256_file(target) return filename, digest temporary = target.with_suffix(target.suffix + ".part") remote_zips: dict[str, RemoteZip] = {} license_data = download_tartanair_license() with tarfile.open(temporary, "w", format=tarfile.PAX_FORMAT) as archive: root = Path(__file__).parent add_bytes(archive, "TARTANAIR_LICENSE", license_data) add_bytes( archive, "TARTANAIR_README.md", (root / "README.md").read_bytes(), ) add_bytes(archive, MANIFEST_PATH.name, MANIFEST_PATH.read_bytes()) for scene in scenes: print(f"Packaging {scene.category}/{scene.name}") package_scene(archive, manifest, scene, remote_zips, num_workers) if temporary.stat().st_size >= 2 * 1024**3: raise RuntimeError(f"Release asset exceeds 2 GiB: {temporary}") temporary.replace(target) digest = sha256_file(target) return filename, digest def sha256_file(path: Path) -> str: digest = hashlib.sha256() with open(path, "rb") as fid: while chunk := fid.read(1024 * 1024): digest.update(chunk) return digest.hexdigest() def main() -> None: parser = argparse.ArgumentParser(description=__doc__) parser.add_argument("--output_path", type=Path, required=True) parser.add_argument("--shards", type=int, nargs="+", default=[]) parser.add_argument("--overwrite", action="store_true") parser.add_argument("--num_workers", type=int, default=8) args = parser.parse_args() manifest = load_manifest() shards = scene_shards(manifest) selected = args.shards or list(range(len(shards))) if any(index < 0 or index >= len(shards) for index in selected): raise ValueError(f"Shard index must be in [0, {len(shards) - 1}]") args.output_path.mkdir(parents=True, exist_ok=True) checksums = {} for index in selected: filename, digest = build_shard( args.output_path, manifest, index, shards[index], args.overwrite, args.num_workers, ) checksums[filename] = digest checksum_path = args.output_path / "tartanair_v2_checksums.json" existing = ( json.loads(checksum_path.read_text()) if checksum_path.exists() else {} ) existing.update(checksums) checksum_path.write_text(json.dumps(existing, indent=2) + "\n") print(f"Wrote {checksum_path}") if __name__ == "__main__": main() colmap-4.2.0/benchmark/reconstruction/evaluation/tartanair/package_tartanair_v2_test.py000066400000000000000000000041171524536416500316350ustar00rootroot00000000000000import hashlib import io import numpy as np import pytest PILImage = pytest.importorskip("PIL.Image") from . import package_tartanair_v2 # noqa: E402 from .package_tartanair_v2 import ( # noqa: E402 DEPTH_SCALE, DEPTH_SIZE, encode_depth_png, encode_image_jpeg, ) class FakeResponse: def __init__(self, content: bytes): self.content = content def raise_for_status(self): pass def test_download_tartanair_license(monkeypatch): license_data = b"license text" monkeypatch.setattr( package_tartanair_v2, "TARTANAIR_LICENSE_SHA256", hashlib.sha256(license_data).hexdigest(), ) monkeypatch.setattr( package_tartanair_v2.requests, "get", lambda *args, **kwargs: FakeResponse(license_data), ) assert package_tartanair_v2.download_tartanair_license() == license_data def test_download_tartanair_license_rejects_unexpected_content(monkeypatch): monkeypatch.setattr( package_tartanair_v2.requests, "get", lambda *args, **kwargs: FakeResponse(b"unexpected"), ) with pytest.raises(RuntimeError, match="Unexpected TartanAir license"): package_tartanair_v2.download_tartanair_license() def test_encode_depth_png_min_pools_and_quantizes(): depth = np.full((1024, 2048), 10.0, dtype=" str: return f"{self.environment}-{self.difficulty}-{self.trajectory}" @property def source_archive(self) -> str: return ( f"{self.environment}/Data_{self.difficulty}/image_lcam_equirect.zip" ) @property def depth_source_archive(self) -> str: return ( f"{self.environment}/Data_{self.difficulty}/depth_lcam_equirect.zip" ) def load_manifest() -> dict: return json.loads(MANIFEST_PATH.read_text()) def list_scenes(manifest: dict | None = None) -> list[SceneSelection]: manifest = manifest or load_manifest() scenes = [] for category, selections in manifest["selections"].items(): for selection in selections: environment, difficulty, trajectory = selection.split(":") scenes.append( SceneSelection( category=category, environment=environment, difficulty=difficulty, trajectory=trajectory, ) ) return scenes def shard_name(manifest: dict, shard_index: int) -> str: version = manifest["version"] return f"tartanair-v2-v{version}-shard-{shard_index:03d}.tar" def scene_shards(manifest: dict | None = None) -> list[list[SceneSelection]]: manifest = manifest or load_manifest() scenes = list_scenes(manifest) size = manifest["release"]["scenes_per_shard"] return [scenes[i : i + size] for i in range(0, len(scenes), size)] def quaternion_angular_distance_deg( quaternion1: np.ndarray, quaternion2: np.ndarray ) -> float: dot = abs(float(np.dot(quaternion1, quaternion2))) return math.degrees(2.0 * math.acos(np.clip(dot, -1.0, 1.0))) def select_frame_window( poses: np.ndarray, num_frames: int, max_adjacent_translation_m: float, max_adjacent_rotation_deg: float, ) -> range: """Select a contiguous, overlapping window with maximum spatial extent.""" if len(poses) < num_frames: raise ValueError( f"Trajectory has {len(poses)} poses, fewer than {num_frames}" ) best_score = None best_start = None for start in range(len(poses) - num_frames + 1): window = poses[start : start + num_frames] translations = np.linalg.norm(np.diff(window[:, :3], axis=0), axis=1) rotations = np.array( [ quaternion_angular_distance_deg(q1, q2) for q1, q2 in zip( window[:-1, 3:7], window[1:, 3:7], strict=True ) ] ) if ( translations.max() > max_adjacent_translation_m or rotations.max() > max_adjacent_rotation_deg ): continue bbox_diagonal = np.linalg.norm( window[:, :3].max(axis=0) - window[:, :3].min(axis=0) ) path_length = translations.sum() score = (bbox_diagonal, path_length, -start) if best_score is None or score > best_score: best_score = score best_start = start if best_start is None: raise ValueError( "Trajectory has no frame window satisfying motion limits" ) return range(best_start, best_start + num_frames) colmap-4.2.0/benchmark/reconstruction/evaluation/tartanair/tartanair_v2_checksums.json000066400000000000000000000024761524536416500315170ustar00rootroot00000000000000{ "tartanair-v2-v1-shard-000.tar": "8b568dc7a500aef3db264c25429a18d480ee6096649500e57d26781908becd15", "tartanair-v2-v1-shard-001.tar": "6f3e3afe8a165d4ac0d1659cd3b914e2ef1d3ac743979fc2be0cc352afbe5e93", "tartanair-v2-v1-shard-002.tar": "dcd2cb7827380739453d9b5a1c1fde87129af730fb06fa7b39c6862fa5ee68d7", "tartanair-v2-v1-shard-003.tar": "9ec41dbdebb71f39cbc6371cdff9e030d69b46e9c0b0b3b0606ee0592dee5c84", "tartanair-v2-v1-shard-004.tar": "2016368a85bf13e0081f6f7897326b3a93bb5a71f598c61624904d02a5f834e8", "tartanair-v2-v1-shard-005.tar": "48ac3a98b4aa92f3697928b2ce70fad9f978662410cb1fd0fbc6b8bc612d5082", "tartanair-v2-v1-shard-006.tar": "90252bb06847dd804684bcc9588d2590685d3ff0cadd0fa17eda4573fa86fe21", "tartanair-v2-v1-shard-007.tar": "488cb15992b71f8dd1ed52f93e9ccb88d3aae3081e6de12ef755ec6b9710aa29", "tartanair-v2-v1-shard-008.tar": "9828a49bb69a99bbe1aadbb8543ebdabf6faac32ed46d5160149a47857e860ee", "tartanair-v2-v1-shard-009.tar": "9ace09456249a6f53fbbfe5baab39a3190c230b33b52a9094f746dd14204f217", "tartanair-v2-v1-shard-010.tar": "0658434a25c9ff22fb4624c63562fa896d7ec81dd66bfe99dd8b31ee8d8fb5fd", "tartanair-v2-v1-shard-011.tar": "caadbd918a6a9d6d72f9d28297748c8b72c56416b02d14ac10ec4eaa1d72dc27", "tartanair-v2-v1-shard-012.tar": "c7fe035a0257b74e71297b2571ba0f9028b8b81e47978a9dc4cc8790af9ccce0" } colmap-4.2.0/benchmark/reconstruction/evaluation/tartanair/tartanair_v2_manifest.json000066400000000000000000000166331524536416500313400ustar00rootroot00000000000000{ "version": 1, "source": { "repository": "theairlabcmu/tartanair2", "revision": "0d2d145e973832742a2aaa04b7d2ebffc8d82817", "license": "CC-BY-4.0", "image_size": [2048, 1024] }, "release": { "repository": "colmap/colmap", "tag": "datasets-tartanair-v2-v1", "scenes_per_shard": 8 }, "frame_selection": { "num_frames": 50, "max_adjacent_translation_m": 1.0, "max_adjacent_rotation_deg": 30.0 }, "image_encoding": { "format": "jpeg", "quality": 97, "subsampling": "4:4:4", "progressive": true }, "depth_processing": { "size": [512, 256], "scale": 8.0, "invalid_value": 0, "downsampling": "min_pool" }, "covisibility": { "sample_stride": 4, "max_depth_m": 256.0, "absolute_tolerance_m": 0.25, "relative_tolerance": 0.01 }, "frame_starts": { "AbandonedCable:easy:P002": 2524, "AbandonedCable:hard:P003": 2073, "AbandonedFactory:easy:P000": 180, "AbandonedFactory2:easy:P000": 476, "AbandonedSchool:easy:P001": 50, "CarWelding:easy:P005": 0, "CarWelding:hard:P000": 557, "CoalMine:hard:P001": 402, "ConstructionSite:easy:P009": 62, "FactoryWeather:easy:P011": 3017, "FactoryWeather:hard:P012": 1060, "Hospital:easy:P008": 977, "Hospital:hard:P000": 1251, "IndustrialHangar:hard:P005": 499, "OldIndustrialCity:easy:P008": 1831, "Prison:hard:P006": 795, "Sewerage:easy:P008": 0, "Sewerage:hard:P010": 608, "AmericanDiner:hard:P002": 0, "ArchVizTinyHouseDay:easy:P006": 0, "ArchVizTinyHouseDay:hard:P003": 21, "ArchVizTinyHouseNight:easy:P006": 0, "CountryHouse:hard:P004": 0, "House:easy:P000": 625, "Office:easy:P005": 772, "Office:hard:P006": 30, "OldBrickHouseDay:hard:P003": 195, "OldBrickHouseNight:easy:P004": 705, "Restaurant:easy:P008": 782, "Restaurant:hard:P005": 183, "RetroOffice:hard:P005": 0, "Supermarket:easy:P000": 751, "Supermarket:hard:P004": 81, "AmusementPark:easy:P003": 1307, "AncientTowns:easy:P000": 1055, "AncientTowns:hard:P005": 226, "CastleFortress:easy:P007": 288, "CastleFortress:hard:P008": 1270, "DesertGasStation:hard:P001": 287, "EndofTheWorld:easy:P004": 392, "Fantasy:hard:P004": 86, "GothicIsland:easy:P006": 731, "GreatMarsh:easy:P001": 1852, "GreatMarsh:hard:P003": 4379, "HQWesternSaloon:hard:P004": 542, "SeasideTown:easy:P006": 748, "SoulCity:easy:P007": 1124, "SoulCity:hard:P003": 496, "WaterMillDay:easy:P006": 1157, "WaterMillDay:hard:P004": 182, "WaterMillNight:hard:P006": 136, "WesternDesertTown:easy:P004": 1216, "WesternDesertTown:hard:P006": 789, "Antiquity3D:hard:P006": 515, "Apocalyptic:easy:P002": 399, "CyberPunkDowntown:easy:P006": 1783, "CyberPunkDowntown:hard:P000": 864, "Cyberpunk:hard:P006": 1569, "PolarSciFi:easy:P006": 543, "PolarSciFi:hard:P005": 313, "Rome:easy:P005": 807, "Slaughter:hard:P001": 227, "BrushifyMoon:hard:P004": 1645, "ForestEnv:easy:P002": 2617, "ForestEnv:hard:P000": 804, "Gascola:easy:P003": 170, "Ocean:easy:P003": 222, "Ocean:hard:P001": 24, "OldScandinavia:easy:P005": 1234, "OldScandinavia:hard:P004": 1130, "Ruins:easy:P008": 1976, "Ruins:hard:P000": 1187, "SeasonalForestAutumn:hard:P000": 2504, "SeasonalForestSpring:easy:P001": 2198, "SeasonalForestSummerNight:hard:P001": 0, "SeasonalForestWinter:easy:P002": 1225, "SeasonalForestWinterNight:easy:P001": 3972, "SeasonalForestWinterNight:hard:P002": 687, "ShoreCaves:hard:P003": 231, "TerrainBlending:easy:P003": 1874, "Downtown:easy:P008": 317, "HongKong:easy:P001": 230, "HongKong:hard:P004": 14, "JapaneseAlley:easy:P000": 219, "JapaneseAlley:hard:P005": 342, "JapaneseCity:hard:P005": 90, "MiddleEast:easy:P005": 1425, "ModUrbanCity:hard:P005": 349, "ModernCityDowntown:easy:P006": 156, "ModularNeighborhood:hard:P010": 1446, "ModularNeighborhoodIntExt:easy:P007": 1391, "ModularNeighborhoodIntExt:hard:P001": 172, "NordicHarbor:easy:P006": 3245, "OldTownFall:hard:P001": 127, "OldTownNight:easy:P002": 25, "OldTownNight:hard:P000": 0, "OldTownSummer:easy:P000": 115, "OldTownWinter:hard:P001": 315, "UrbanConstruction:easy:P000": 1455, "VictorianStreet:hard:P000": 189 }, "selections": { "infrastructure": [ "AbandonedCable:easy:P002", "AbandonedCable:hard:P003", "AbandonedFactory:easy:P000", "AbandonedFactory2:easy:P000", "AbandonedSchool:easy:P001", "CarWelding:easy:P005", "CarWelding:hard:P000", "CoalMine:hard:P001", "ConstructionSite:easy:P009", "FactoryWeather:easy:P011", "FactoryWeather:hard:P012", "Hospital:easy:P008", "Hospital:hard:P000", "IndustrialHangar:hard:P005", "OldIndustrialCity:easy:P008", "Prison:hard:P006", "Sewerage:easy:P008", "Sewerage:hard:P010" ], "domestic": [ "AmericanDiner:hard:P002", "ArchVizTinyHouseDay:easy:P006", "ArchVizTinyHouseDay:hard:P003", "ArchVizTinyHouseNight:easy:P006", "CountryHouse:hard:P004", "House:easy:P000", "Office:easy:P005", "Office:hard:P006", "OldBrickHouseDay:hard:P003", "OldBrickHouseNight:easy:P004", "Restaurant:easy:P008", "Restaurant:hard:P005", "RetroOffice:hard:P005", "Supermarket:easy:P000", "Supermarket:hard:P004" ], "rural": [ "AmusementPark:easy:P003", "AncientTowns:easy:P000", "AncientTowns:hard:P005", "CastleFortress:easy:P007", "CastleFortress:hard:P008", "DesertGasStation:hard:P001", "EndofTheWorld:easy:P004", "Fantasy:hard:P004", "GothicIsland:easy:P006", "GreatMarsh:easy:P001", "GreatMarsh:hard:P003", "HQWesternSaloon:hard:P004", "SeasideTown:easy:P006", "SoulCity:easy:P007", "SoulCity:hard:P003", "WaterMillDay:easy:P006", "WaterMillDay:hard:P004", "WaterMillNight:hard:P006", "WesternDesertTown:easy:P004", "WesternDesertTown:hard:P006" ], "thematic": [ "Antiquity3D:hard:P006", "Apocalyptic:easy:P002", "CyberPunkDowntown:easy:P006", "CyberPunkDowntown:hard:P000", "Cyberpunk:hard:P006", "PolarSciFi:easy:P006", "PolarSciFi:hard:P005", "Rome:easy:P005", "Slaughter:hard:P001" ], "nature": [ "BrushifyMoon:hard:P004", "ForestEnv:easy:P002", "ForestEnv:hard:P000", "Gascola:easy:P003", "Ocean:easy:P003", "Ocean:hard:P001", "OldScandinavia:easy:P005", "OldScandinavia:hard:P004", "Ruins:easy:P008", "Ruins:hard:P000", "SeasonalForestAutumn:hard:P000", "SeasonalForestSpring:easy:P001", "SeasonalForestSummerNight:hard:P001", "SeasonalForestWinter:easy:P002", "SeasonalForestWinterNight:easy:P001", "SeasonalForestWinterNight:hard:P002", "ShoreCaves:hard:P003", "TerrainBlending:easy:P003" ], "urban": [ "Downtown:easy:P008", "HongKong:easy:P001", "HongKong:hard:P004", "JapaneseAlley:easy:P000", "JapaneseAlley:hard:P005", "JapaneseCity:hard:P005", "MiddleEast:easy:P005", "ModUrbanCity:hard:P005", "ModernCityDowntown:easy:P006", "ModularNeighborhood:hard:P010", "ModularNeighborhoodIntExt:easy:P007", "ModularNeighborhoodIntExt:hard:P001", "NordicHarbor:easy:P006", "OldTownFall:hard:P001", "OldTownNight:easy:P002", "OldTownNight:hard:P000", "OldTownSummer:easy:P000", "OldTownWinter:hard:P001", "UrbanConstruction:easy:P000", "VictorianStreet:hard:P000" ] } } colmap-4.2.0/benchmark/reconstruction/evaluation/tartanair/tartanair_v2_test.py000066400000000000000000000036151524536416500301640ustar00rootroot00000000000000import json import numpy as np import pytest from .tartanair_v2 import ( MANIFEST_PATH, list_scenes, load_manifest, scene_shards, select_frame_window, shard_name, ) def test_manifest_invariants(): manifest = load_manifest() scenes = list_scenes(manifest) assert len(scenes) == 100 assert len({scene.name for scene in scenes}) == 100 assert len({scene.environment for scene in scenes}) == 74 assert sum(scene.difficulty == "easy" for scene in scenes) == 50 assert sum(scene.difficulty == "hard" for scene in scenes) == 50 assert set(manifest["selections"]) == { "domestic", "infrastructure", "nature", "rural", "thematic", "urban", } assert set(manifest["frame_starts"]) == { f"{scene.environment}:{scene.difficulty}:{scene.trajectory}" for scene in scenes } assert len(scene_shards(manifest)) == 13 checksum_path = MANIFEST_PATH.with_name("tartanair_v2_checksums.json") checksums = json.loads(checksum_path.read_text()) assert set(checksums) == { shard_name(manifest, index) for index in range(len(scene_shards(manifest))) } def test_select_frame_window_maximizes_extent(): poses = np.zeros((8, 7)) poses[:, 6] = 1.0 poses[:, 0] = [0.0, 0.1, 0.2, 0.3, 0.4, 1.3, 2.2, 3.1] selected = select_frame_window( poses, num_frames=4, max_adjacent_translation_m=1.0, max_adjacent_rotation_deg=30.0, ) assert selected == range(4, 8) def test_select_frame_window_rejects_motion_gaps(): poses = np.zeros((4, 7)) poses[:, 6] = 1.0 poses[:, 0] = [0.0, 0.1, 2.0, 2.1] with pytest.raises(ValueError, match="no frame window"): select_frame_window( poses, num_frames=4, max_adjacent_translation_m=1.0, max_adjacent_rotation_deg=30.0, ) colmap-4.2.0/benchmark/reconstruction/evaluation/utils.py000066400000000000000000002254641524536416500237140ustar00rootroot00000000000000# Copyright (c), ETH Zurich and UNC Chapel Hill. # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are met: # # * Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer. # # * Redistributions in binary form must reproduce the above copyright # notice, this list of conditions and the following disclaimer in the # documentation and/or other materials provided with the distribution. # # * Neither the name of ETH Zurich and UNC Chapel Hill nor the names of # its contributors may be used to endorse or promote products derived # from this software without specific prior written permission. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. import argparse import collections import copy import ctypes import dataclasses import datetime import functools import itertools import multiprocessing import pickle import platform import shutil import signal import subprocess import sys import threading from abc import ABC, abstractmethod from collections.abc import Iterable, Sequence from pathlib import Path import numpy as np import numpy.typing as npt import pycolmap from pycolmap import panorama from .covisibility import filter_covisibility # noqa: F401 from .geometry import normalize_vec, vec_angular_dist_deg # noqa: F401 # Sentinel GT component id in image_name_to_component marking an outlier image # that does not belong to any GT reconstruction. Outliers are never part of a # GT edge (relative metric) or a GT component (absolute metric). OUTLIER_COMPONENT_ID = -1 _PR_SET_PDEATHSIG = 1 _LIBC = ( ctypes.CDLL("libc.so.6", use_errno=True) if platform.system() == "Linux" else None ) def _set_pdeathsig() -> None: """preexec_fn: ensure child process is killed if parent dies.""" if _LIBC is not None: _LIBC.prctl(_PR_SET_PDEATHSIG, signal.SIGTERM) def _init_pool_worker() -> None: """Pool initializer: ignore SIGINT in workers so the main process handles KeyboardInterrupt and terminates the pool cleanly.""" signal.signal(signal.SIGINT, signal.SIG_IGN) def _run_with_log( cmd: list, log_path: Path, check: bool = True, **kwargs ) -> int: """Run a subprocess, redirecting stdout+stderr to log_path (overwrite). Uses preexec_fn=_set_pdeathsig on Linux so children die with their parent. preexec_fn is unavailable on Windows, and PR_SET_PDEATHSIG is Linux-specific. Raises CalledProcessError on non-zero exit when check=True. """ log_path.parent.mkdir(parents=True, exist_ok=True) with open(log_path, "wb") as fh: runner = subprocess.check_call if check else subprocess.call popen_kwargs = dict(stdout=fh, stderr=subprocess.STDOUT, **kwargs) if platform.system() != "Windows": popen_kwargs["preexec_fn"] = _set_pdeathsig return runner(cmd, **popen_kwargs) @dataclasses.dataclass(kw_only=True) class SceneInfo: # Dataset name. dataset: str # Category name. category: str # Scene name. scene: str # Number of input images in the scene. num_images: int # Path to the workspace directory in the run directory. workspace_path: Path # Path to the input images. image_path: Path # Path to the ground-truth sparse reconstruction. sparse_gt_path: Path # Whether the dataset has camera priors. has_camera_priors: bool # Additional arguments for the COLMAP reconstruction command. colmap_extra_args: list[str] # Reconstruction backend. The default uses automatic_reconstructor. reconstruction_backend: str = "automatic" # Subdirectory below workspace_path containing models to evaluate. reconstruction_subdir: str = "sparse" covisibility_path: Path | None = None covisibility_min_shared_points: int | None = None # Maps image name -> ground-truth component id. Images sharing an id # belong to the same GT reconstruction (this defines the GT edge set for # the relative metric and the component for the absolute metric). Empty # means the scene ships a single GT reconstruction; process_scene then # materializes a default {name: 0 for every sparse_gt image}. This is the # case for all current datasets that ship one GT model per scene. image_name_to_component: dict[str, int] = dataclasses.field( default_factory=dict ) @dataclasses.dataclass(kw_only=True) class SceneResult: # Scene information for which the result was computed. scene_info: SceneInfo # Flat list of errors. errors: npt.NDArray[np.floating] # Number of images in the scene. num_images: int # Number of registered images in the scene (over all components). num_reg_images: int # Number of components in the scene. num_components: int # Number of images in the largest component. largest_component: int @dataclasses.dataclass(kw_only=True) class Metrics: # Recall at specified error thresholds. recalls: npt.NDArray[np.floating] # Area under the curve (AUC) scores at specified error thresholds. aucs: npt.NDArray[np.floating] error_thresholds: npt.NDArray[np.floating] error_type: str # Number of images in the scene. num_images: int # Number of registered images in the scene (over all components). num_reg_images: int # Number of components in the scene. num_components: int # Number of images in the largest component. largest_component: int # Raw errors that produced aucs/recalls. Empty for entries (like __avg__) # where no underlying error pool exists. Retained so higher-level summaries # (per-dataset, overall) can recompute pooled __all__ statistics. errors: npt.NDArray[np.floating] = dataclasses.field( default_factory=lambda: np.array([]) ) # Ground-truth position accuracy (used as min_error when computing AUC). # Carried so cross-dataset aggregations can pick a sensible value. position_accuracy_gt: float = 0.0 MetricsByScene = dict[str, Metrics] MetricsByCatByScene = dict[str, MetricsByScene] MetricsByDatasetByCatByScene = dict[str, MetricsByCatByScene] # Identifies one scene across reports: (dataset, category, scene). SceneKey = tuple[str, str, str] class Dataset(ABC): def __init__( self, data_path: Path, categories: list[str], scenes: list[Path], run_path: Path, run_name: str, ): self.data_path = data_path self.categories = categories self.scenes = scenes self.run_path = run_path self.run_name = run_name @property @abstractmethod def position_accuracy_gt(self) -> float: """Ground-truth position accuracy in meters.""" pass @property @abstractmethod def supports_covisibility_filtering(self) -> bool: """Whether the GT reconstruction can drive covisibility filtering. The frustum/track-based filter needs a GT reconstruction with real intrinsics (and ideally 3D points) in a shared gauge. Datasets whose GT lacks these (e.g. IMC2025 with placeholder cameras) must return False so process_scene does not pass their GT to the filter. """ pass @abstractmethod def list_scenes(self) -> list[SceneInfo]: """List all scenes to evaluate.""" pass @abstractmethod def prepare_scene(self, scene_info: SceneInfo) -> None: """Prepare the scene for reconstruction.""" pass class _PhaseTracker: """Worker-side helper that publishes the current phase for a scene to a shared dict. No-op when status_dict is None.""" def __init__(self, status_dict=None, scene_key: str = "") -> None: self._dict = status_dict self._key = scene_key def set(self, phase: str) -> None: if self._dict is not None: self._dict[self._key] = phase def _scene_key(scene_info: SceneInfo) -> str: return f"{scene_info.dataset}/{scene_info.category}/{scene_info.scene}" def _run_progress_monitor( status_dict, total: int, stop_event: threading.Event ) -> None: """Render a live progress display of in-flight scenes until stop_event is set. Counts entries marked "done" toward overall completion; everything else is shown as an in-progress task with a spinner and elapsed time.""" from rich.console import Group from rich.live import Live from rich.progress import ( BarColumn, MofNCompleteColumn, Progress, SpinnerColumn, TextColumn, TimeElapsedColumn, ) overall = Progress( TextColumn("[bold]Scenes[/bold]"), BarColumn(), MofNCompleteColumn(), TimeElapsedColumn(), ) scenes = Progress( SpinnerColumn(), TextColumn("{task.description}"), TextColumn("[cyan]{task.fields[phase]:>14}[/cyan]"), TimeElapsedColumn(), ) overall_task = overall.add_task("scenes", total=total) scene_tasks: dict[str, int] = {} def refresh() -> None: snapshot = dict(status_dict) done = sum(1 for v in snapshot.values() if v == "finished") overall.update(overall_task, completed=done) in_progress = {k: v for k, v in snapshot.items() if v != "finished"} for key in list(scene_tasks): if key not in in_progress: scenes.remove_task(scene_tasks.pop(key)) for key, phase in in_progress.items(): if key in scene_tasks: scenes.update(scene_tasks[key], phase=phase) else: scene_tasks[key] = scenes.add_task(key, total=None, phase=phase) with Live(Group(overall, scenes), refresh_per_second=4): while not stop_event.is_set(): refresh() stop_event.wait(0.5) refresh() def filter_smallest_scenes_per_category( scene_infos: list[SceneInfo], num_scenes: int ) -> list[SceneInfo]: """Keep only the `num_scenes` smallest scenes (by num_images) per category, preserving the original order.""" indices_by_category: dict[str, list[int]] = collections.defaultdict(list) for i, scene_info in enumerate(scene_infos): indices_by_category[scene_info.category].append(i) keep: set[int] = set() for indices in indices_by_category.values(): smallest = sorted(indices, key=lambda i: scene_infos[i].num_images)[ :num_scenes ] keep.update(smallest) return [scene_infos[i] for i in sorted(keep)] def parse_args(description: str | None = None) -> argparse.Namespace: datetime_str = datetime.datetime.now().strftime("%Y-%m-%d_%H-%M-%S") parser = argparse.ArgumentParser( description=description, formatter_class=argparse.RawDescriptionHelpFormatter, ) parser.add_argument( "--data_path", default=Path(__file__).parent.parent / "data", type=Path ) parser.add_argument( "--datasets", nargs="+", default=["eth3d", "blended-mvs", "imc2023", "imc2024"], ) parser.add_argument( "--categories", nargs="+", default=[], help="Categories to evaluate, if empty all categories are evaluated.", ) parser.add_argument( "--scenes", nargs="+", default=[], help="Scenes to evaluate, if empty all scenes are evaluated.", ) parser.add_argument( "--progress", default=None, action=argparse.BooleanOptionalAction, help="Show a live progress display of in-flight scenes " "(default: enabled when stdout is a TTY).", ) parser.add_argument( "--fast", default=False, action="store_true", help="Fast mode: only evaluate the N smallest scenes per category, " "where N is set by --fast_num_scenes.", ) parser.add_argument( "--fast_num_scenes", type=int, default=1, help="Number of smallest scenes per category to evaluate in --fast " "mode.", ) parser.add_argument( "--run_path", default=Path(__file__).parent.parent / "runs", type=Path ) parser.add_argument("--run_name", default=datetime_str) parser.add_argument( "--report_name", default=f"report-{datetime_str}", help="Report file stem, without the .pkl suffix. With " "--seeds/--num_seeds it is the stem of every run, which are written " "as _s.pkl; compare.py reads that naming back via " "--report_a_path_prefix/--report_b_path_prefix.", ) parser.add_argument( "--overwrite_database", default=False, action="store_true" ) parser.add_argument( "--overwrite_matches", default=False, action="store_true" ) parser.add_argument( "--overwrite_two_view_geometries", default=False, action="store_true", help="Clear two-view geometries (inlier matches) but keep raw matches, " "so geometric verification is recomputed from the cached matches. " "Useful for re-tuning geometric verification without re-matching.", ) parser.add_argument( "--overwrite_reconstruction", default=False, action="store_true" ) parser.add_argument( "--overwrite_alignment", default=False, action="store_true" ) parser.add_argument("--colmap_path", required=True) parser.add_argument("--use_gpu", default=True, action="store_true") parser.add_argument("--use_cpu", dest="use_gpu", action="store_false") parser.add_argument( "--num_threads", type=int, default=-1, help=( "Total number of threads to use across all parallel scenes. " "Defaults to 2x the number of logical CPU cores (-1)." ), ) parser.add_argument( "--num_parallel_scenes", type=int, default=-1, help=( "Number of scenes to reconstruct in parallel. " "Defaults to max(1, num_threads // 4) (-1)." ), ) parser.add_argument( "--threads_per_scene", type=int, default=-1, help=( "Override the number of threads used within each scene. " "Defaults to num_threads // num_parallel_scenes (-1). Set to 1 " "for reproducible runs: RANSAC seeds per thread as " "random_seed + omp_get_thread_num(), so with a fixed --random_seed " "only single-threaded scenes are deterministic (required for " "paired common-random-number A/B comparison)." ), ) parser.add_argument( "--gpu_index", type=str, default="-1", help="GPU indices to use for reconstruction. " "Use '-1' to auto-detect and use all available GPUs. " "Use comma-separated indices like '0,1,2' to specify exact GPUs.", ) parser.add_argument( "--random_seed", type=int, default=-1, help="Random seed forwarded to colmap's automatic_reconstructor " "(two-view RANSAC + mapper). -1 (default) uses a nondeterministic " "random device, matching prior harness behavior. Fix it to a " "non-negative value for reproducible / paired A-B runs.", ) seed_group = parser.add_mutually_exclusive_group() seed_group.add_argument( "--seeds", type=int, nargs="+", default=None, help="Run the evaluation once per seed, writing " "_s.pkl each time, to measure run-to-run spread. " "Mutually exclusive with --random_seed.", ) seed_group.add_argument( "--num_seeds", type=int, default=None, help="Shorthand for --seeds 0 1 ... N-1.", ) parser.add_argument( "--feature", default="sift", choices=["sift", "aliked", "loma", "loma128"], ) parser.add_argument( "--mapper", default="incremental", choices=["incremental", "hierarchical", "global"], ) parser.add_argument( "--quality", default="high", choices=["low", "medium", "high"] ) parser.add_argument( "--uncalibrated", default=False, action="store_true", help="Whether to evaluate the setting of uncalibrated input cameras, " "even if normal setting for the dataset contains calibrated inputs. " "This is useful for evaluating the performance of self-calibration.", ) parser.add_argument( "--filter_covisibility", default=True, action=argparse.BooleanOptionalAction, help="Filter out non-covisible image pairs based on GT camera poses. " "Use --no-filter_covisibility to disable.", ) parser.add_argument( "--covisibility_frustum_near", type=float, default=None, help="Near plane for frustum co-visibility check. " "Auto-detected from GT points if not specified.", ) parser.add_argument( "--covisibility_frustum_far", type=float, default=None, help="Far plane for frustum co-visibility check. " "Auto-detected from GT points if not specified.", ) parser.add_argument( "--covisibility_max_viewing_angle", type=float, default=120.0, help="Maximum viewing angle in degrees for co-visibility check.", ) parser.add_argument( "--covisibility_min_shared_points", type=int, default=5, help="Minimum number of shared GT 3D points for two images to be " "considered covisible. If GT tracks are available and this is > 0, " "track-based covisibility is preferred over frustum-based checking.", ) parser.add_argument( "--error_type", default="relative_auc", choices=[ "relative_auc", "absolute_auc", "relative_recall", "absolute_recall", ], help="Whether to evaluate relative pairwise pose errors in angular " "distance or absolute pose errors through GT alignment.", ) parser.add_argument( "--rel_error_thresholds", type=float, nargs="+", default=[0.5, 1, 5, 10], help="Evaluation thresholds in degrees.", ) parser.add_argument( "--abs_error_thresholds", type=float, nargs="+", default=[0.02, 0.05, 0.2, 0.5], help="Evaluation thresholds in meters.", ) args = parser.parse_args() args.colmap_path = Path(args.colmap_path).resolve() if args.num_seeds is not None: if args.num_seeds <= 0: parser.error("--num_seeds must be > 0") args.seeds = list(range(args.num_seeds)) if args.seeds is not None: if args.random_seed >= 0: parser.error( "--random_seed sets the seed of a single run; use " "--seeds/--num_seeds to run once per seed" ) if any(seed < 0 for seed in args.seeds): parser.error("--seeds must be non-negative") # Names the flag the user actually passed, so the non-determinism warning # does not point at --random_seed during a --seeds/--num_seeds run. args.seed_flag = "--seeds" if args.seeds is not None else "--random_seed" if args.fast and args.fast_num_scenes <= 0: parser.error("--fast_num_scenes must be > 0 when --fast is set") if args.progress is None: args.progress = sys.stdout.isatty() if args.num_threads <= 0: args.num_threads = 2 * multiprocessing.cpu_count() if args.num_parallel_scenes <= 0: args.num_parallel_scenes = max(1, args.num_threads // 4) if args.overwrite_database: pycolmap.logging.info( "Overwriting database also overwrites reconstruction" ) args.overwrite_reconstruction = True if args.overwrite_matches: pycolmap.logging.info( "Overwriting matches also overwrites reconstruction" ) args.overwrite_reconstruction = True if args.overwrite_two_view_geometries: pycolmap.logging.info( "Overwriting two-view geometries also overwrites reconstruction" ) args.overwrite_reconstruction = True if args.overwrite_reconstruction: pycolmap.logging.info( "Overwriting reconstruction also overwrites alignment" ) args.overwrite_alignment = True return args def set_camera_priors( database_path: Path, camera_priors_sparse_gt: pycolmap.Reconstruction ) -> None: pycolmap.logging.info("Setting prior cameras from GT") with pycolmap.Database.open(str(database_path)) as database: images_gt_by_name = {} for image_gt in camera_priors_sparse_gt.images.values(): images_gt_by_name[image_gt.name] = image_gt updated_camera_ids = set() for image in database.read_all_images(): if image.name not in images_gt_by_name: pycolmap.logging.warning( f"Not setting prior camera for image {image.name}, " "because it does not exist in GT" ) continue image_gt = images_gt_by_name[image.name] if image.camera_id in updated_camera_ids: continue camera_gt = camera_priors_sparse_gt.cameras[image_gt.camera_id] camera_gt.camera_id = image.camera_id camera_gt.has_prior_focal_length = True database.update_camera(camera_gt) updated_camera_ids.add(image.camera_id) def colmap_reconstruction( args: argparse.Namespace, workspace_path: Path, image_path: Path, camera_priors_sparse_gt: pycolmap.Reconstruction | None = None, covisibility_sparse_gt: pycolmap.Reconstruction | None = None, colmap_extra_args: list | None = None, num_threads: int = 1, gpu_index: str = "-1", phase_tracker: _PhaseTracker | None = None, ) -> None: phase_tracker = phase_tracker or _PhaseTracker() workspace_path.mkdir(parents=True, exist_ok=True) database_path = workspace_path / "database.db" if args.overwrite_database and database_path.exists(): database_path.unlink() sparse_path = workspace_path / "sparse" if args.overwrite_reconstruction and sparse_path.exists(): shutil.rmtree(sparse_path) if sparse_path.exists(): pycolmap.logging.info("Skipping reconstruction, as it already exists") return # Clearing matches also clears two-view geometries, so there is no need to # clear the latter separately when both flags are set. if args.overwrite_matches: cleaner_type = "matches" elif args.overwrite_two_view_geometries: cleaner_type = "two_view_geometries" else: cleaner_type = None if cleaner_type is not None: subprocess.check_call( [ args.colmap_path, "database_cleaner", "--database_path", database_path, "--type", cleaner_type, ], cwd=workspace_path, preexec_fn=( _set_pdeathsig if platform.system() != "Windows" else None ), ) # TODO: Expose automatic reconstruction through pycolmap bindings instead # of using the command line interface. One blocker for this is that we # currently do not produce CUDA enabled pycolmap packages. colmap_args = [ args.colmap_path, "automatic_reconstructor", "--image_path", image_path, "--workspace_path", workspace_path, "--use_gpu", "1" if args.use_gpu else "0", "--gpu_index", gpu_index, "--num_threads", str(num_threads), "--random_seed", str(args.random_seed), "--feature", args.feature, "--mapper", args.mapper, "--quality", args.quality, ] phase_tracker.set("extraction") _run_with_log( colmap_args + (colmap_extra_args or []) + [ "--extraction", "1", "--matching", "0", "--sparse", "0", "--dense", "0", ], workspace_path / "extraction.log", cwd=workspace_path, ) if camera_priors_sparse_gt is not None: set_camera_priors(database_path, camera_priors_sparse_gt) phase_tracker.set("matching") _run_with_log( colmap_args + (colmap_extra_args or []) + [ "--extraction", "0", "--matching", "1", "--sparse", "0", "--dense", "0", ], workspace_path / "matching.log", cwd=workspace_path, ) if covisibility_sparse_gt is not None: filter_covisibility( database_path, covisibility_sparse_gt, args.covisibility_frustum_near, args.covisibility_frustum_far, args.covisibility_max_viewing_angle, args.covisibility_min_shared_points, ) # Decouple matching from sparse reconstruction, because matching will # initialize an OpenGL context and Mac on Apple silicon tends to assign GUI # applications to the low efficiency cores but we want to use the # performance cores. phase_tracker.set("reconstruction") _run_with_log( colmap_args + (colmap_extra_args or []) + [ "--extraction", "0", "--matching", "0", "--sparse", "1", "--dense", "0", ], workspace_path / "reconstruction.log", cwd=workspace_path, ) def panorama_reconstruction( args: argparse.Namespace, scene_info: SceneInfo, num_threads: int, gpu_index: str, phase_tracker: _PhaseTracker | None = None, ) -> None: """Run the reusable pycolmap panorama pipeline for a benchmark scene.""" phase_tracker = phase_tracker or _PhaseTracker() workspace_path = scene_info.workspace_path sparse_path = workspace_path / scene_info.reconstruction_subdir if args.overwrite_reconstruction and workspace_path.exists(): shutil.rmtree(workspace_path) if sparse_path.exists(): pycolmap.logging.info("Skipping reconstruction, as it already exists") return if args.feature != "sift": raise ValueError("Panorama reconstruction currently supports SIFT only") if args.mapper == "hierarchical": raise ValueError( "Panorama reconstruction does not support hierarchical mapping" ) if args.uncalibrated: raise ValueError( "Equirectangular panorama reconstruction has fixed calibration" ) render_type = scene_info.reconstruction_backend.removeprefix("panorama-") if render_type not in {"perspective_overlapping", "spherical"}: raise ValueError( f"Unknown panorama reconstruction backend: " f"{scene_info.reconstruction_backend}" ) covisibility_path = None min_shared_points = args.covisibility_min_shared_points if ( args.filter_covisibility and scene_info.covisibility_path is not None and scene_info.covisibility_path.exists() ): covisibility_path = scene_info.covisibility_path min_shared_points = ( scene_info.covisibility_min_shared_points if scene_info.covisibility_min_shared_points is not None else args.covisibility_min_shared_points ) workspace_path.mkdir(parents=True, exist_ok=True) phase_tracker.set("reconstruction") panorama.reconstruct( scene_info.image_path, workspace_path, panorama.PanoramaReconstructionOptions( matcher=panorama.Matcher.SEQUENTIAL, mapper=panorama.Mapper(args.mapper), render_type=panorama.PanoRenderType(render_type), random_seed=args.random_seed, num_threads=num_threads, gpu_index=gpu_index, use_gpu=args.use_gpu, covisibility_path=covisibility_path, covisibility_min_shared_points=min_shared_points, show_progress=False, ), ) sparse_path.mkdir(parents=True, exist_ok=True) def colmap_alignment( args: argparse.Namespace, sparse_path: Path, sparse_gt_path: Path, sparse_aligned_path: Path, max_ref_model_error: float, ) -> None: if args.overwrite_alignment and sparse_aligned_path.exists(): shutil.rmtree(sparse_aligned_path) if sparse_aligned_path.exists(): pycolmap.logging.info("Skipping alignment, as it already exists") return if sparse_path.exists(): sparse_aligned_path.mkdir(parents=True, exist_ok=True) _run_with_log( [ args.colmap_path, "model_aligner", "--input_path", sparse_path, "--ref_model_path", sparse_gt_path, "--output_path", sparse_aligned_path, "--alignment_max_error", str(max_ref_model_error), ], sparse_aligned_path.parent / "alignment.log", check=False, ) def merge_sub_models( sub_models: list[pycolmap.Reconstruction], ) -> pycolmap.Reconstruction: """Merge estimated sub-models into a single reconstruction. Each sub-model keeps its own (independent) gauge, so sub-models are "randomly" aligned to each other. With this simple approach there is a small chance that images from different sub-models happen to be correctly aligned and the error is therefore underestimated, but this is very unlikely to happen. """ sparse_merged = pycolmap.Reconstruction() for sparse in sub_models: for image in sparse.images.values(): if image.image_id in sparse_merged.images: continue if image.camera_id not in sparse_merged.cameras: sparse_merged.add_camera(image.camera) if image.frame_id not in sparse_merged.frames: if image.frame.rig_id not in sparse_merged.rigs: sparse_merged.add_rig(image.frame.rig) image.frame.reset_rig_ptr() sparse_merged.add_frame(image.frame) image.reset_camera_ptr() image.reset_frame_ptr() sparse_merged.add_image(image) return sparse_merged def process_scene( args: argparse.Namespace, scene_info: SceneInfo, dataset: Dataset, num_threads: int, gpu_index: str = "-1", progress_status=None, ) -> SceneResult: pycolmap.logging.info( f"Processing dataset={scene_info.dataset}, " f"category={scene_info.category}, " f"scene={scene_info.scene}" ) position_accuracy_gt = dataset.position_accuracy_gt tracker = _PhaseTracker(progress_status, _scene_key(scene_info)) tracker.set("setup") dataset.prepare_scene(scene_info) sparse_gt = pycolmap.Reconstruction(str(scene_info.sparse_gt_path)) if scene_info.reconstruction_backend == "automatic": colmap_reconstruction( args=args, workspace_path=scene_info.workspace_path, image_path=scene_info.image_path, camera_priors_sparse_gt=( sparse_gt if not args.uncalibrated and scene_info.has_camera_priors else None ), covisibility_sparse_gt=( sparse_gt if args.filter_covisibility and dataset.supports_covisibility_filtering else None ), num_threads=num_threads, colmap_extra_args=scene_info.colmap_extra_args, gpu_index=gpu_index, phase_tracker=tracker, ) else: panorama_reconstruction( args=args, scene_info=scene_info, num_threads=num_threads, gpu_index=gpu_index, phase_tracker=tracker, ) tracker.set("evaluation") # Load all estimated sub-models. Both metrics keep the sub-models separate: # the relative set-based metric forms edges within a sub-model, and the # absolute metric scores each GT image against the best-aligned sub-model. # For absolute errors each sub-model is aligned to the GT independently. # These sub-models are turned into a flat error array via # compute_scene_errors. sub_models: list[pycolmap.Reconstruction] = [] num_components = 0 largest_component = 0 reconstruction_path = ( scene_info.workspace_path / scene_info.reconstruction_subdir ) for sparse_path in reconstruction_path.iterdir(): if not sparse_path.is_dir(): continue num_components += 1 sparse = None if args.error_type.startswith("relative"): sparse = pycolmap.Reconstruction(str(sparse_path)) elif args.error_type.startswith("absolute"): sparse_aligned_path = ( scene_info.workspace_path / "sparse_aligned" / sparse_path.name ) colmap_alignment( args=args, sparse_path=sparse_path, sparse_gt_path=scene_info.sparse_gt_path, sparse_aligned_path=sparse_aligned_path, max_ref_model_error=position_accuracy_gt, ) if (sparse_aligned_path / "images.bin").exists(): sparse = pycolmap.Reconstruction(str(sparse_aligned_path)) else: raise ValueError(f"Invalid error type: {args.error_type}") if sparse is None: continue largest_component = max(largest_component, sparse.num_images()) sub_models.append(sparse) # Registered images counted as the union of names across all sub-models, # restricted to GT images so registered outliers (e.g. IMC2025) are not # counted and num_reg_images stays consistent with num_images. gt_image_names = {image.name for image in sparse_gt.images.values()} num_reg_images = len( { image.name for sub_model in sub_models for image in sub_model.images.values() if image.name in gt_image_names } ) # The dataset turns the estimated sub-models into a flat error array. The # default implementation uses the set-based grouped metrics; datasets (e.g. # IMC2025) can customize the grouping or override the computation entirely. errors = compute_scene_errors( args=args, scene_info=scene_info, sub_models=sub_models, sparse_gt=sparse_gt, position_accuracy_gt=position_accuracy_gt, ) tracker.set("finished") return SceneResult( scene_info=scene_info, errors=errors, num_images=sparse_gt.num_images(), num_reg_images=num_reg_images, num_components=num_components, largest_component=largest_component, ) def _parse_gpu_index(args: argparse.Namespace) -> list[int]: if args.gpu_index == "-1": if not pycolmap.has_cuda: return [-1] num_devices = pycolmap.get_num_cuda_devices() # type: ignore[attr-defined] if num_devices <= 0: return [-1] return list(range(num_devices)) indices = [int(idx) for idx in args.gpu_index.split(",") if idx.strip()] return indices if indices else [-1] def _process_scene_with_gpu( scene_info_and_gpu: tuple[SceneInfo, str], args: argparse.Namespace, dataset: Dataset, num_threads: int, progress_status=None, ) -> SceneResult: scene_info, gpu_index = scene_info_and_gpu return process_scene( args=args, scene_info=scene_info, dataset=dataset, num_threads=num_threads, gpu_index=gpu_index, progress_status=progress_status, ) @functools.cache def _warn_nondeterministic(seed_flag: str, num_threads_per_scene: int) -> None: """Warns once per process; process_scenes() runs per dataset and seed.""" pycolmap.logging.warning( f"{seed_flag} is set but " f"num_threads_per_scene={num_threads_per_scene} > 1: RANSAC seeds " "per thread, so results are NOT deterministic. Pass " "--threads_per_scene 1 for reproducible / paired A-B runs." ) def process_scenes( args: argparse.Namespace, scene_infos: list[SceneInfo], dataset: Dataset, ) -> MetricsByCatByScene: position_accuracy_gt = dataset.position_accuracy_gt error_thresholds = get_error_thresholds(args) gpu_index = _parse_gpu_index(args) scene_gpu_pairs = [ (scene_info, str(gpu_index[i % len(gpu_index)])) for i, scene_info in enumerate(scene_infos) ] num_parallel_scenes = min(args.num_parallel_scenes, len(scene_infos)) if args.threads_per_scene > 0: num_threads_per_scene = args.threads_per_scene else: num_threads_per_scene = max(1, args.num_threads // num_parallel_scenes) if args.random_seed >= 0 and num_threads_per_scene != 1: _warn_nondeterministic(args.seed_flag, num_threads_per_scene) manager = None progress_status = None monitor_thread = None stop_event = threading.Event() if args.progress: manager = multiprocessing.Manager() progress_status = manager.dict() monitor_thread = threading.Thread( target=_run_progress_monitor, args=(progress_status, len(scene_infos), stop_event), daemon=True, ) monitor_thread.start() try: p = multiprocessing.Pool( processes=num_parallel_scenes, initializer=_init_pool_worker ) try: results = list( p.imap_unordered( functools.partial( _process_scene_with_gpu, args=args, dataset=dataset, num_threads=num_threads_per_scene, progress_status=progress_status, ), scene_gpu_pairs, chunksize=1, ) ) except KeyboardInterrupt: pycolmap.logging.warning( "Interrupted, terminating workers and child processes..." ) p.terminate() raise except BaseException: p.terminate() raise else: p.close() finally: p.join() finally: stop_event.set() if monitor_thread is not None: monitor_thread.join() if manager is not None: manager.shutdown() metrics: MetricsByCatByScene = collections.defaultdict(dict) for result in results: metrics[result.scene_info.category][result.scene_info.scene] = Metrics( aucs=compute_auc( result.errors, error_thresholds, min_error=position_accuracy_gt, ), recalls=compute_recall(result.errors, error_thresholds), error_thresholds=error_thresholds, error_type=args.error_type, num_images=result.num_images, num_reg_images=result.num_reg_images, num_components=result.num_components, largest_component=result.largest_component, errors=np.asarray(result.errors), position_accuracy_gt=position_accuracy_gt, ) for category in metrics: metrics[category].update( aggregate_scene_metrics( metrics[category].items(), error_thresholds=error_thresholds, error_type=args.error_type, ) ) return metrics def aggregate_scene_metrics( scene_metrics: Iterable[tuple[str, Metrics]], error_thresholds: npt.NDArray[np.floating], error_type: str, ) -> dict[str, Metrics]: """Compute __avg__ (mean of per-scene metrics) and __all__ (recomputed from the pool of raw errors) summary entries from per-scene Metrics. Skips entries whose key starts and ends with "__" so this can be applied iteratively at higher levels (category -> dataset -> overall) without double-counting previously-emitted summaries. """ real = [ m for k, m in scene_metrics if not (k.startswith("__") and k.endswith("__")) ] if not real: return {} n = len(real) sum_num_images = sum(m.num_images for m in real) sum_num_reg_images = sum(m.num_reg_images for m in real) sum_num_components = sum(m.num_components for m in real) sum_largest_component = sum(m.largest_component for m in real) min_pos_acc = min(m.position_accuracy_gt for m in real) pooled_errors = np.concatenate([m.errors for m in real]) summary = { "__avg__": Metrics( aucs=np.mean([m.aucs for m in real], axis=0), recalls=np.mean([m.recalls for m in real], axis=0), error_thresholds=error_thresholds, error_type=error_type, num_images=int(round(sum_num_images / n)), num_reg_images=int(round(sum_num_reg_images / n)), num_components=int(round(sum_num_components / n)), largest_component=int(round(sum_largest_component / n)), position_accuracy_gt=min_pos_acc, ), } if pooled_errors.size: summary["__all__"] = Metrics( aucs=compute_auc( pooled_errors, error_thresholds, min_error=min_pos_acc ), recalls=compute_recall(pooled_errors, error_thresholds), error_thresholds=error_thresholds, error_type=error_type, num_images=sum_num_images, num_reg_images=sum_num_reg_images, num_components=sum_num_components, largest_component=sum_largest_component, errors=pooled_errors, position_accuracy_gt=min_pos_acc, ) return summary def get_error_thresholds(args: argparse.Namespace) -> npt.NDArray[np.floating]: if args.error_type.startswith("relative"): return np.array(args.rel_error_thresholds) elif args.error_type.startswith("absolute"): return np.array(args.abs_error_thresholds) else: raise ValueError(f"Invalid error type: {args.error_type}") def get_scores(error_type: str, metrics: Metrics) -> npt.NDArray[np.floating]: if error_type.endswith("auc"): return metrics.aucs elif error_type.endswith("recall"): return metrics.recalls else: raise ValueError(f"Invalid error type: {error_type}") def compute_rel_pose_error( tgt_from_src_est: pycolmap.Rigid3d, tgt_from_src_gt: pycolmap.Rigid3d, min_proj_center_dist: float, ) -> tuple[float, float]: """Angular relative pose errors (dt, dR) in degrees. dR is the geodesic rotation error and dt is the angular distance between the relative translation directions. If the GT baseline is shorter than min_proj_center_dist, the translation direction is unstable and dt is set to zero, so only the rotation error is measured. """ estimated_from_gt = tgt_from_src_est.inverse() * tgt_from_src_gt if np.linalg.norm(tgt_from_src_gt.translation) < min_proj_center_dist: # If the cameras almost coincide, then the angular direction distance # is unstable, because a small position change can cause a large # rotational error. In this case, we only measure rotational error. dt = 0.0 else: dt = vec_angular_dist_deg( tgt_from_src_est.translation, tgt_from_src_gt.translation ) dR = np.rad2deg(estimated_from_gt.rotation.angle()) return dt, dR def compute_scene_errors( args: argparse.Namespace, scene_info: SceneInfo, sub_models: list[pycolmap.Reconstruction], sparse_gt: pycolmap.Reconstruction, position_accuracy_gt: float, ) -> npt.NDArray[np.floating]: """Compute the flat error array for a reconstructed scene. Keeps the estimated sub-models separate and computes the set-based, GT-component-aware relative or absolute pose errors against the ground truth. The GT components come from scene_info.image_name_to_component, defaulting to a single reconstruction (all GT images in component 0) when the dataset does not provide one. Datasets can populate that mapping (via list_scenes) to control the grouping. """ image_name_to_component = scene_info.image_name_to_component or { image.name: 0 for image in sparse_gt.images.values() } if args.error_type.startswith("relative"): return compute_grouped_rel_errors( sparse_gt=sparse_gt, sub_models=sub_models, image_name_to_component=image_name_to_component, min_proj_center_dist=position_accuracy_gt, ) elif args.error_type.startswith("absolute"): return compute_grouped_abs_errors( sparse_gt=sparse_gt, sub_models=sub_models, image_name_to_component=image_name_to_component, ) else: raise ValueError(f"Invalid error type: {args.error_type}") def compute_grouped_rel_errors( sparse_gt: pycolmap.Reconstruction, sub_models: list[pycolmap.Reconstruction], image_name_to_component: dict[str, int], min_proj_center_dist: float, ) -> npt.NDArray[np.floating]: """Set-based relative pose errors over the graph of image pairs. Let A be the set of ordered image pairs (i, j) that share an estimated sub-model and B the set of ordered pairs that share a GT component (as defined by image_name_to_component). For pairs in A n B we measure the relative pose error; for pairs in the symmetric difference (grouped in only one of the two, i.e. wrong merges / registered outliers or failed / fragmented registrations) we assign the maximum error of 180 degrees. The returned error array covers all pairs in A u B. A and B are materialized as flat collections and scored in a single pass. A is a multiset (tgt_from_src_est_edges): an image may appear in several sub-models, so the same edge can carry several estimated relative poses, each contributing one error entry. """ gt_cam_from_world = { image.name: image.cam_from_world() for image in sparse_gt.images.values() } # A: ordered estimated edges -> list of relative poses (one per sub-model # containing both endpoints). Keeping a list makes A a multiset. tgt_from_src_est_edges: dict[tuple[str, str], list[pycolmap.Rigid3d]] = ( collections.defaultdict(list) ) for sub_model in sub_models: cam_from_world = { image.name: image.cam_from_world() for image in sub_model.images.values() } for src_name, tgt_name in itertools.permutations(cam_from_world, 2): tgt_from_src_est = ( cam_from_world[tgt_name] * cam_from_world[src_name].inverse() ) tgt_from_src_est_edges[(src_name, tgt_name)].append( tgt_from_src_est ) # B: ordered GT edges grouped by GT component. Outliers never belong to a # GT component, and names absent from sparse_gt cannot form a measurable GT # edge, so both are excluded here. names_by_component: dict[int, list[str]] = collections.defaultdict(list) for name, component in image_name_to_component.items(): if component == OUTLIER_COMPONENT_ID or name not in gt_cam_from_world: continue names_by_component[component].append(name) gt_edges: set[tuple[str, str]] = set() for group_names in names_by_component.values(): gt_edges.update(itertools.permutations(group_names, 2)) errors: list[float] = [] for edge in set(tgt_from_src_est_edges) | gt_edges: src_name, tgt_name = edge tgt_from_src_ests = tgt_from_src_est_edges.get(edge, []) if edge in gt_edges: tgt_from_src_gt = ( gt_cam_from_world[tgt_name] * gt_cam_from_world[src_name].inverse() ) if not tgt_from_src_ests: # Edge in B - A: failed / fragmented registration. errors.append(180.0) for tgt_from_src_est in tgt_from_src_ests: # Edge in A n B: measure the relative pose error. dt, dR = compute_rel_pose_error( tgt_from_src_est, tgt_from_src_gt, min_proj_center_dist ) errors.append(max(dt, dR)) else: # Edge in A - B: wrong merge or registered outlier. errors.extend(180.0 for _ in tgt_from_src_ests) if not errors: # No evaluable pairs (e.g. only singleton GT reconstructions). Report # the worst case rather than raising downstream on an empty array. return np.array([180.0]) return np.array(errors) def compute_abs_errors( sparse_gt: pycolmap.Reconstruction, sparse: pycolmap.Reconstruction, image_name_to_component: dict[str, int] | None = None, ) -> tuple[npt.NDArray[np.floating], npt.NDArray[np.floating]]: """Computes rotational and translational absolute pose errors. Assumes that the input reconstructions are aligned in the same coordinate system. Iterates over the estimated reconstruction (sparse) and computes one error per sparse image that also exists in the ground truth, in sparse.images.values() order; sparse images absent from the GT are skipped. When image_name_to_component is given, the reconstruction is treated as covering a single GT component: the component with the smallest mean finite translational error is kept intact and every other image (other components, outliers, or images missing from the mapping) is set to the maximum error (inf translation, 180 rotation). Without the mapping the raw per-image errors are returned. """ if sparse is None: pycolmap.logging.error("Reconstruction or alignment failed") return ( np.array([], dtype=np.float64), np.array([], dtype=np.float64), ) gt_images = {image.name: image for image in sparse_gt.images.values()} names: list[str] = [] dt_list: list[float] = [] dR_list: list[float] = [] for image in sparse.images.values(): image_gt = gt_images.get(image.name) if image_gt is None: continue estimated_from_gt = ( image.cam_from_world() * image_gt.cam_from_world().inverse() ) names.append(image.name) dt_list.append(float(np.linalg.norm(estimated_from_gt.translation))) dR_list.append(float(np.rad2deg(estimated_from_gt.rotation.angle()))) dts = np.array(dt_list, dtype=np.float64) dRs = np.array(dR_list, dtype=np.float64) if image_name_to_component is None: return dts, dRs best_component = _best_component(names, dts, image_name_to_component) # Keep only the best component intact; max out every other image (other # components, outliers, or names missing from the mapping). When no # component is selectable (best_component is None) every image is maxed out. for i, name in enumerate(names): in_best_component = ( best_component is not None and image_name_to_component.get(name) == best_component ) if not in_best_component: dts[i] = np.inf dRs[i] = 180 return dts, dRs def _best_component( names: list[str], dts: npt.NDArray[np.floating], image_name_to_component: dict[str, int], ) -> int | None: """GT component id with the smallest mean finite translational error. Returns None when no image maps to a selectable component. Outliers and names missing from the mapping never form a selectable component. """ finite_dts_by_component: dict[int, list[float]] = collections.defaultdict( list ) for name, dt in zip(names, dts, strict=True): component = image_name_to_component.get(name) if ( component is not None and component != OUTLIER_COMPONENT_ID and np.isfinite(dt) ): finite_dts_by_component[component].append(float(dt)) if not finite_dts_by_component: return None return min( finite_dts_by_component, key=lambda component: float( np.mean(finite_dts_by_component[component]) ), ) def compute_grouped_abs_errors( sparse_gt: pycolmap.Reconstruction, sub_models: list[pycolmap.Reconstruction], image_name_to_component: dict[str, int], ) -> npt.NDArray[np.floating]: """GT-component-aware absolute pose errors. Each estimated sub-model is assumed to be aligned to the GT. A GT image that is registered in n sub-models contributes n (translational) errors, one per reconstruction; a GT image registered in no sub-model contributes a single infinite error (a failure). Within each sub-model only its best-matching GT component (smallest mean finite error) is kept intact and every other GT image is maxed out (see compute_abs_errors). Because the selection is per sub-model, a scene with several GT components can credit several of them, one per sub-model. With a single component this reduces to the standard absolute metric. """ gt_names = [image.name for image in sparse_gt.images.values()] gt_name_set = set(gt_names) # Flat node multiset keyed by GT image name: one translational error per # (GT image, sub-model) registration. compute_abs_errors keeps each # sub-model's best GT component intact and maxes out (inf) every other # registered image, which still contributes an error. Its returned errors # are aligned to the sub-model's images that also exist in the GT, in # sub_model.images.values() order, so we rebuild the names with the same # filter/order. errors_by_name: dict[str, list[float]] = collections.defaultdict(list) for sub_model in sub_models: sub_dts, _ = compute_abs_errors( sparse_gt=sparse_gt, sparse=sub_model, image_name_to_component=image_name_to_component, ) sub_names = [ image.name for image in sub_model.images.values() if image.name in gt_name_set ] for name, dt in zip(sub_names, sub_dts, strict=True): errors_by_name[name].append(float(dt)) # A GT image registered in no sub-model counts as a single failure. for name in gt_names: if not errors_by_name[name]: errors_by_name[name].append(np.inf) # Flatten the node dict in GT image order. return np.array([dt for name in gt_names for dt in errors_by_name[name]]) def compute_auc( errors: npt.NDArray[np.floating], thresholds: npt.NDArray[np.floating], min_error: float = 0, ) -> npt.NDArray[np.floating]: num_elems = len(errors) if len(errors) == 0: raise ValueError("No errors to evaluate") errors = np.sort(errors) recalls = (np.arange(num_elems) + 1) / num_elems if min_error > 0: min_index = np.searchsorted(errors, min_error, side="right") min_recall = min_index / num_elems recalls = np.r_[min_recall, min_recall, recalls[min_index:]] errors = np.r_[0, min_error, errors[min_index:]] else: recalls = np.r_[0, recalls] errors = np.r_[0, errors] aucs = np.zeros(len(thresholds), dtype=np.float64) for i, t in enumerate(thresholds): last_index = np.searchsorted(errors, t, side="right") r = np.r_[recalls[:last_index], recalls[last_index - 1]] e = np.r_[errors[:last_index], t] auc = np.trapezoid(r, x=e) / t aucs[i] = auc * 100 return aucs def compute_recall( errors: npt.NDArray[np.floating], thresholds: npt.NDArray[np.floating], min_error: float = 0, ) -> npt.NDArray[np.floating]: num_elems = len(errors) if num_elems == 0: raise ValueError("No errors to evaluate") recalls = np.zeros(len(thresholds), dtype=np.float64) for i, t in enumerate(thresholds): recalls[i] = 100 * np.sum(errors <= t) / num_elems return recalls def compute_avg_metrics( scene_metrics: MetricsByScene, ) -> tuple[npt.NDArray[np.floating], npt.NDArray[np.floating]]: auc_sum = None recall_sum = None num_scenes = 0 for scene, metrics in scene_metrics.items(): if scene.startswith("__") and scene.endswith("__"): continue num_scenes += 1 if auc_sum is None: auc_sum = copy.copy(metrics.aucs) if recall_sum is None: recall_sum = copy.copy(metrics.recalls) else: for i in range(len(auc_sum)): auc_sum[i] += metrics.aucs[i] for i in range(len(recall_sum)): recall_sum[i] += metrics.recalls[i] return np.array(auc_sum) / num_scenes, np.array(recall_sum) / num_scenes def diff_metrics( metrics_a: MetricsByDatasetByCatByScene, metrics_b: MetricsByDatasetByCatByScene, ): """Computes difference between two sets of metrics. Raises exception if the metrics are inconsistent. """ metrics_diff = copy.deepcopy(metrics_a) for dataset, category_metrics_a in metrics_a.items(): if dataset not in metrics_b: raise ValueError(f"Dataset {dataset} not found in metrics_b") category_metrics_b = metrics_b[dataset] for category, scene_metrics_a in category_metrics_a.items(): if category not in category_metrics_b: raise ValueError(f"Category {category} not found in metrics_b") scene_metrics_b = category_metrics_b[category] for scene, metrics_a_item in scene_metrics_a.items(): if scene not in scene_metrics_b: raise ValueError(f"Scene {scene} not found in metrics_b") metrics_b_item = scene_metrics_b[scene] if ( metrics_a_item.error_type != metrics_b_item.error_type or not np.all( metrics_a_item.error_thresholds == metrics_b_item.error_thresholds ) ): raise ValueError("Inconsistent error thresholds or types") metrics_diff[dataset][category][scene] = Metrics( aucs=metrics_a_item.aucs - metrics_b_item.aucs, recalls=metrics_a_item.recalls - metrics_b_item.recalls, error_thresholds=metrics_a_item.error_thresholds, error_type=metrics_a_item.error_type, num_images=metrics_a_item.num_images - metrics_b_item.num_images, num_reg_images=metrics_a_item.num_reg_images - metrics_b_item.num_reg_images, num_components=metrics_a_item.num_components - metrics_b_item.num_components, largest_component=metrics_a_item.largest_component - metrics_b_item.largest_component, ) return metrics_diff def _is_summary_scene(scene: str) -> bool: """Whether a scene name is an aggregate row (e.g. __avg__, __all__).""" return scene.startswith("__") and scene.endswith("__") def create_result_table( dataset_metrics: MetricsByDatasetByCatByScene, ) -> str: first_metrics = next( iter(next(iter(next(iter(dataset_metrics.values())).values())).values()) ) is_auc = first_metrics.error_type.endswith("auc") is_relative = first_metrics.error_type.startswith("relative") score_type = "AUC" if is_auc else "Recall" score_unit = "deg" if is_relative else "cm" label = f"{score_type} @ X {score_unit} (%)" if is_relative: thresholds = first_metrics.error_thresholds else: thresholds = 100 * first_metrics.error_thresholds # cm column = "scenes" size_scenes = max( len(column) + 2, max( len(s) for d in dataset_metrics.values() for c in d.values() for s in c ), ) size_aucs = max(len(label) + 2, len(thresholds) * 7 - 1) size_imgs = 12 size_comps = 12 size_sep = size_scenes + size_aucs + size_imgs + size_comps + 3 header = ( f"{column:=^{size_scenes}} {label:=^{size_aucs}} " f"{'images':=^{size_imgs}} {'components':=^{size_comps}}" ) header += "\n" + " " * (size_scenes + 1) header += " ".join(f"{str(t).rstrip('.'):^6}" for t in thresholds) header += " reg all num largest" text = [header] def render_block( header_text: str, scene_metrics: MetricsByScene, header_fill: str = "=", ) -> None: text.append(f"\n{header_text:{header_fill}^{size_sep}}") any_scene_row = False summary_separator_drawn = False for scene, metrics in sorted( scene_metrics.items(), key=lambda x: ( x[0].startswith("__"), x[0], ), ): scores = get_scores(first_metrics.error_type, metrics) assert len(scores) == len(thresholds) row = "" is_summary = _is_summary_scene(scene) if is_summary and any_scene_row and not summary_separator_drawn: row += "-" * size_sep + "\n" summary_separator_drawn = True if not is_summary: any_scene_row = True if scene == "__avg__": scene = "average" if scene == "__all__": scene = "overall" row += f"{scene:<{size_scenes}} " row += " ".join(f"{score:>6.2f}" for score in scores) row += f" {metrics.num_reg_images:6d}" row += f"{metrics.num_images:6d}" row += f" {metrics.num_components:4d}" row += f"{metrics.largest_component:8d}" text.append(row) overall_scene_metrics: list[tuple[str, Metrics]] = [] for dataset, category_metrics in dataset_metrics.items(): dataset_scene_metrics: list[tuple[str, Metrics]] = [] for category, scene_metrics in category_metrics.items(): render_block(f"{dataset}={category}", scene_metrics) dataset_scene_metrics.extend(scene_metrics.items()) if len(category_metrics) > 1: render_block( dataset, aggregate_scene_metrics( dataset_scene_metrics, error_thresholds=first_metrics.error_thresholds, error_type=first_metrics.error_type, ), header_fill="#", ) overall_scene_metrics.extend(dataset_scene_metrics) if len(dataset_metrics) > 1: render_block( "overall", aggregate_scene_metrics( overall_scene_metrics, error_thresholds=first_metrics.error_thresholds, error_type=first_metrics.error_type, ), header_fill="#", ) return "\n".join(text) def load_report(path: Path) -> MetricsByDatasetByCatByScene: """Loads a report pickled by evaluate.py.""" with open(path, "rb") as report_file: return pickle.load(report_file) def _collect_reports(prefix: Path) -> dict[int, Path] | Path: """Collects the reports of one variant given as a report path prefix. Returns seed -> path for the _s.pkl reports of a seeded run, or the single .pkl path of an unseeded one. """ reports: dict[int, Path] = {} for path in prefix.parent.glob(f"{prefix.name}_s*.pkl"): seed = path.stem[len(prefix.name) + 2 :] if seed.isdigit(): reports[int(seed)] = path if reports: return reports path = prefix.with_name(f"{prefix.name}.pkl") if not path.exists(): raise SystemExit( f"found neither {prefix.name}_s.pkl reports nor {path}" ) return path def collect_reports( path: Path | None, prefix: Path | None ) -> dict[int, Path] | Path: """Collects one variant's reports from a report path or a path prefix. Returns the single report path, or seed -> path for the _s.pkl reports of a seeded run. """ if path is not None: if not path.exists(): raise SystemExit(f"no such report: {path}") return path assert prefix is not None return _collect_reports(prefix) def pair_reports( reports_a: dict[int, Path] | Path, reports_b: dict[int, Path] | Path, labels: Sequence[str] = ("A", "B"), seeds: list[int] | None = None, ) -> tuple[list[Path], list[Path], list[int] | None]: """Pairs up the collected reports of two variants. Seeded variants are matched on the seeds they have in common; pass seeds to restrict the comparison to a subset. A variant that is a single report is compared against every seed of the other. """ label_a, label_b = labels if isinstance(reports_a, dict) and isinstance(reports_b, dict): # Both seeded: pair them up on the seeds they have in common. shared = sorted(set(reports_a) & set(reports_b)) if not shared: raise SystemExit( f"{label_a} and {label_b} share no seed, but the " "comparison is paired per seed " f"({label_a}: {sorted(reports_a)}, " f"{label_b}: {sorted(reports_b)})" ) for label, reports in ((label_a, reports_a), (label_b, reports_b)): dropped = sorted(set(reports) - set(shared)) if dropped: pycolmap.logging.warning( f"ignoring {len(dropped)} of {label}'s " f"{len(reports)} reports, seed(s) {dropped}: the other " "variant has no report for them" ) elif isinstance(reports_a, dict): # Only A seeded: B is a single run, compared against each of A's seeds. shared = sorted(reports_a) elif isinstance(reports_b, dict): shared = sorted(reports_b) else: # Neither seeded: a plain one-to-one comparison. return [reports_a], [reports_b], None if seeds is not None: unknown = sorted(set(seeds) - set(shared)) if unknown: pycolmap.logging.warning( f"skipping seed(s) {unknown}: not present for both variants" ) shared = [seed for seed in shared if seed in set(seeds)] if not shared: raise SystemExit(f"none of the seeds {seeds} is available") def paths(reports: dict[int, Path] | Path) -> list[Path]: if isinstance(reports, Path): return [reports] * len(shared) return [reports[seed] for seed in shared] return paths(reports_a), paths(reports_b), shared def _first_metrics(report: MetricsByDatasetByCatByScene) -> Metrics: return next(iter(next(iter(next(iter(report.values())).values())).values())) def _common_scene_keys( reports: list[MetricsByDatasetByCatByScene], ) -> list[SceneKey]: """Scene keys present in every report, ordered as in the first report.""" key_sets: list[set[SceneKey]] = [] for report in reports: keys: set[SceneKey] = set() for dataset, cat_metrics in report.items(): for category, scene_metrics in cat_metrics.items(): for scene in scene_metrics: keys.add((dataset, category, scene)) key_sets.append(keys) shared: set[SceneKey] = set() if key_sets: shared = key_sets[0].intersection(*key_sets[1:]) ordered: list[SceneKey] = [] for dataset, cat_metrics in reports[0].items(): for category, scene_metrics in cat_metrics.items(): for scene in scene_metrics: if (dataset, category, scene) in shared: ordered.append((dataset, category, scene)) return ordered def _stack_scores( reports: list[MetricsByDatasetByCatByScene], key: SceneKey, error_type: str, ) -> npt.NDArray[np.floating]: """Scores for one scene across reports, shaped (num_reports, num_thr).""" dataset, category, scene = key return np.array( [ get_scores(error_type, report[dataset][category][scene]) for report in reports ] ) def _render_meanstd( title: str, per_key_stack: dict[SceneKey, npt.NDArray[np.floating]], keys: list[SceneKey], error_type: str, thresholds: npt.NDArray[np.floating], num_runs: int, signed: bool = False, num_reported: int | None = None, ) -> str: """Renders one mean +/- std table over runs, per scene x threshold. per_key_stack maps each key to a (num_runs, num_thresholds) score array. num_reported is shown in the trailing N column as the number of distinct runs behind the statistics, which is 1 for a single run compared against every seed of the other variant, and whose std is then zero. """ is_relative = error_type.startswith("relative") thresholds_disp = thresholds if is_relative else 100 * thresholds size_scene = max(8, max(len(s) for _, _, s in keys)) size_cell = 12 # "+dd.dd±dd.dd" fmt = "{:+6.2f}±{:5.2f}" if signed else "{:6.2f}±{:5.2f}" header = f"{'scene':<{size_scene}} {'N':>3} " + " ".join( f"{'@' + str(t).rstrip('.'):^{size_cell}}" for t in thresholds_disp ) num_reported = num_runs if num_reported is None else num_reported lines: list[str] = [title, header, "-" * len(header)] prev_summary = False for key in sorted(keys, key=lambda k: (_is_summary_scene(k[2]), k)): scene = key[2] if _is_summary_scene(scene) and not prev_summary: lines.append("-" * len(header)) prev_summary = True scores = per_key_stack[key] # (num_runs, num_thresholds) mean = scores.mean(axis=0) std = ( scores.std(axis=0, ddof=1) if num_runs > 1 else np.zeros_like(mean) ) label = {"__avg__": "average", "__all__": "overall"}.get(scene, scene) cells = " ".join( fmt.format(m, s) for m, s in zip(mean, std, strict=True) ) lines.append(f"{label:<{size_scene}} {num_reported:>3} {cells}") return "\n".join(lines) def compare_reports( report_a_paths: list[Path], report_b_paths: list[Path], labels: Sequence[str] = ("A", "B"), seeds: list[int] | None = None, ) -> None: """Logs an A vs B comparison of two sets of paired reports. With one report per variant this prints the usual result tables for A, B and A - B. With several reports per variant (one per seed, paired by position) it instead prints mean +/- std over the runs, with A - B computed per seed before averaging. """ if len(report_a_paths) != len(report_b_paths): raise SystemExit( "A and B must have the same number of reports (paired by seed): " f"{len(report_a_paths)} vs {len(report_b_paths)}" ) num_runs = len(report_a_paths) if num_runs == 0: raise SystemExit("no reports to compare") if seeds is not None and len(seeds) != num_runs: raise SystemExit( f"seed count ({len(seeds)}) != report count ({num_runs})" ) label_a, label_b = labels reports_a = [load_report(path) for path in report_a_paths] reports_b = [load_report(path) for path in report_b_paths] if num_runs == 1: metrics_a, metrics_b = reports_a[0], reports_b[0] metrics_diff = diff_metrics(metrics_a, metrics_b) pycolmap.logging.info( f"Results {label_a}:\n" + create_result_table(metrics_a) ) pycolmap.logging.info( f"Results {label_b}:\n" + create_result_table(metrics_b) ) pycolmap.logging.info( f"Results {label_a} - {label_b}:\n" + create_result_table(metrics_diff) ) return keys = _common_scene_keys(reports_a + reports_b) if not keys: raise SystemExit("No scenes shared across all reports.") first_metrics = _first_metrics(reports_a[0]) error_type = first_metrics.error_type thresholds = np.asarray(first_metrics.error_thresholds) score = "AUC" if error_type.endswith("auc") else "Recall" # A variant may be a single run compared against every seed of the other, # in which case it has no spread of its own and is labelled as such. single_a = len(set(report_a_paths)) == 1 and num_runs > 1 single_b = len(set(report_b_paths)) == 1 and num_runs > 1 # Both variants ran on the same seeds, so the difference is paired; a # single run broadcast against the other's seeds is not. shared = not (single_a or single_b) over_seeds = f"{score} mean ± std over {num_runs} seeds" seeds_label = " ".join(map(str, seeds)) if seeds is not None else "-" num_scenes = len([k for k in keys if not _is_summary_scene(k[2])]) num_reconstructions = num_runs * (2 - single_a - single_b) pycolmap.logging.info( f"{label_a} vs {label_b}: {num_runs} seeds; " f"{num_reconstructions} reconstruction runs over {num_scenes} scenes; " f"seeds: [{seeds_label}]" ) if single_a or single_b: single, seeded = (label_a, label_b) if single_a else (label_b, label_a) pycolmap.logging.warning( f"{single} is a single run compared against every seed of " f"{seeded}: the difference therefore carries only {seeded}'s " f"spread, and is not a paired (common random number) comparison. " f"Run {single} on the same seeds for that." ) stacks_a = {k: _stack_scores(reports_a, k, error_type) for k in keys} stacks_b = {k: _stack_scores(reports_b, k, error_type) for k in keys} stacks_diff = {k: stacks_a[k] - stacks_b[k] for k in keys} common = (keys, error_type, thresholds, num_runs) pycolmap.logging.info( "\n" + _render_meanstd( f"A = {label_a} " + (f"({score}, single run)" if single_a else f"({over_seeds})"), stacks_a, *common, num_reported=1 if single_a else num_runs, ) ) pycolmap.logging.info( "\n" + _render_meanstd( f"B = {label_b} " + (f"({score}, single run)" if single_b else f"({over_seeds})"), stacks_b, *common, num_reported=1 if single_b else num_runs, ) ) pycolmap.logging.info( "\n" + _render_meanstd( f"A - B = {label_a} - {label_b} ({over_seeds}, " + ( "shared seeds -> paired)" if shared else f"seeds NOT shared -> {label_b if single_a else label_a}" "'s spread only)" ), stacks_diff, *common, signed=True, ) ) colmap-4.2.0/benchmark/reconstruction/evaluation/utils_test.py000066400000000000000000001331361524536416500247450ustar00rootroot00000000000000# Copyright (c), ETH Zurich and UNC Chapel Hill. # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are met: # # * Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer. # # * Redistributions in binary form must reproduce the above copyright # notice, this list of conditions and the following disclaimer in the # documentation and/or other materials provided with the distribution. # # * Neither the name of ETH Zurich and UNC Chapel Hill nor the names of # its contributors may be used to endorse or promote products derived # from this software without specific prior written permission. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. import argparse from pathlib import Path import numpy as np import pytest import pycolmap from . import utils from .utils import ( OUTLIER_COMPONENT_ID, Metrics, SceneInfo, _parse_gpu_index, aggregate_scene_metrics, compute_abs_errors, compute_auc, compute_avg_metrics, compute_grouped_abs_errors, compute_grouped_rel_errors, compute_recall, diff_metrics, filter_smallest_scenes_per_category, get_scores, panorama_reconstruction, ) def _make_scene_info(category: str, scene: str, num_images: int) -> SceneInfo: return SceneInfo( dataset="dummy", category=category, scene=scene, num_images=num_images, workspace_path=Path("/tmp/workspace"), image_path=Path("/tmp/images"), sparse_gt_path=Path("/tmp/sparse_gt"), has_camera_priors=False, colmap_extra_args=[], ) def test_panorama_reconstruction_uses_library_api(tmp_path, monkeypatch): covisibility_path = tmp_path / "covisibility.npz" covisibility_path.touch() scene_info = SceneInfo( dataset="tartanair-v2-spherical", category="test", scene="scene", num_images=2, workspace_path=tmp_path / "workspace", image_path=tmp_path / "images", sparse_gt_path=tmp_path / "sparse_gt", has_camera_priors=False, colmap_extra_args=[], reconstruction_backend="panorama-spherical", covisibility_path=covisibility_path, covisibility_min_shared_points=1, ) args = argparse.Namespace( overwrite_reconstruction=False, feature="sift", mapper="global", uncalibrated=False, filter_covisibility=True, covisibility_min_shared_points=5, random_seed=7, use_gpu=False, ) call = {} def reconstruct(input_image_path, output_path, options): call.update( input_image_path=input_image_path, output_path=output_path, options=options, ) return {} monkeypatch.setattr(utils.panorama, "reconstruct", reconstruct) panorama_reconstruction(args, scene_info, num_threads=3, gpu_index="2") assert call["input_image_path"] == scene_info.image_path assert call["output_path"] == scene_info.workspace_path options = call["options"] assert options.mapper.value == "global" assert options.render_type.value == "spherical" assert options.random_seed == 7 assert options.num_threads == 3 assert options.gpu_index == "2" assert not options.use_gpu assert options.covisibility_path == covisibility_path assert options.covisibility_min_shared_points == 1 assert not options.show_progress class TestFilterSmallestScenesPerCategory: def test_picks_smallest_per_category(self): scenes = [ _make_scene_info("a", "a3", 30), _make_scene_info("a", "a1", 10), _make_scene_info("a", "a2", 20), _make_scene_info("b", "b2", 5), _make_scene_info("b", "b1", 1), ] result = filter_smallest_scenes_per_category(scenes, num_scenes=2) names = [(s.category, s.scene) for s in result] assert names == [("a", "a1"), ("a", "a2"), ("b", "b2"), ("b", "b1")] def test_preserves_input_order(self): scenes = [ _make_scene_info("a", "a3", 30), _make_scene_info("a", "a1", 10), _make_scene_info("a", "a2", 20), ] result = filter_smallest_scenes_per_category(scenes, num_scenes=2) # Smallest are a1 and a2, but the original order (a3, a1, a2) must # be preserved among the kept scenes. assert [s.scene for s in result] == ["a1", "a2"] def test_num_scenes_larger_than_category_size(self): scenes = [ _make_scene_info("a", "a1", 10), _make_scene_info("a", "a2", 20), _make_scene_info("b", "b1", 5), ] result = filter_smallest_scenes_per_category(scenes, num_scenes=10) # All scenes are kept since each category has fewer than num_scenes. assert [s.scene for s in result] == ["a1", "a2", "b1"] def test_num_scenes_one(self): scenes = [ _make_scene_info("a", "a1", 10), _make_scene_info("a", "a2", 5), _make_scene_info("b", "b1", 100), _make_scene_info("b", "b2", 50), ] result = filter_smallest_scenes_per_category(scenes, num_scenes=1) assert sorted((s.category, s.scene) for s in result) == [ ("a", "a2"), ("b", "b2"), ] def test_empty_input(self): assert filter_smallest_scenes_per_category([], num_scenes=3) == [] def test_ties_broken_stably(self): # When several scenes share the same num_images, sorting must be # stable so we keep the ones that appeared first in the input. scenes = [ _make_scene_info("a", "a1", 10), _make_scene_info("a", "a2", 10), _make_scene_info("a", "a3", 10), ] result = filter_smallest_scenes_per_category(scenes, num_scenes=2) assert [s.scene for s in result] == ["a1", "a2"] class TestParseGpuIndex: @staticmethod def _make_args(gpu_index: str) -> argparse.Namespace: return argparse.Namespace(gpu_index=gpu_index) def test_single_gpu(self): assert _parse_gpu_index(self._make_args("0")) == [0] def test_multiple_gpus(self): assert _parse_gpu_index(self._make_args("0,1,2")) == [0, 1, 2] def test_trailing_comma(self): assert _parse_gpu_index(self._make_args("1,")) == [1] def test_empty_string(self): assert _parse_gpu_index(self._make_args("")) == [-1] def test_only_commas(self): assert _parse_gpu_index(self._make_args(",")) == [-1] def test_auto_detect(self, monkeypatch): monkeypatch.setattr(pycolmap, "has_cuda", True) monkeypatch.setattr( pycolmap, "get_num_cuda_devices", lambda: 3, raising=False ) assert _parse_gpu_index(self._make_args("-1")) == [0, 1, 2] def test_auto_detect_no_devices(self, monkeypatch): monkeypatch.setattr(pycolmap, "has_cuda", True) monkeypatch.setattr( pycolmap, "get_num_cuda_devices", lambda: 0, raising=False ) assert _parse_gpu_index(self._make_args("-1")) == [-1] class TestComputeAuc: def test_simple_uniform_errors(self): errors = np.array([0.1, 0.2, 0.3, 0.4, 0.5]) thresholds = np.array([0.25, 0.5, 1.0]) aucs = compute_auc(errors, thresholds) np.testing.assert_almost_equal(aucs[0], 24.0, decimal=5) np.testing.assert_almost_equal(aucs[1], 50.0, decimal=5) np.testing.assert_almost_equal(aucs[2], 75.0, decimal=5) def test_all_errors_zero(self): errors = np.array([0.0, 0.0, 0.0]) thresholds = np.array([0.5, 1.0]) aucs = compute_auc(errors, thresholds) np.testing.assert_array_almost_equal(aucs, [100.0, 100.0]) def test_empty_errors(self): errors = np.array([]) thresholds = np.array([0.5, 1.0]) with pytest.raises(ValueError, match="No errors to evaluate"): compute_auc(errors, thresholds) def test_all_errors_above_threshold(self): errors = np.array([10.0, 20.0, 30.0]) thresholds = np.array([5.0]) aucs = compute_auc(errors, thresholds) np.testing.assert_almost_equal(aucs[0], 0.0) def test_all_errors_below_threshold(self): errors = np.array([0.1, 0.2, 0.3]) thresholds = np.array([1.0]) aucs = compute_auc(errors, thresholds) np.testing.assert_almost_equal(aucs[0], 85.0, decimal=5) def test_inf_errors(self): errors = np.array([0.1, 0.2, np.inf, np.inf]) thresholds = np.array([0.5, 1.0]) aucs = compute_auc(errors, thresholds) assert np.all(aucs >= 0) assert np.all(aucs <= 100) def test_single_error(self): errors = np.array([0.5]) thresholds = np.array([0.3, 1.0]) aucs = compute_auc(errors, thresholds) assert len(aucs) == 2 np.testing.assert_almost_equal(aucs[0], 0.0) np.testing.assert_almost_equal(aucs[1], 75.0) class TestComputeRecall: def test_basic_recall(self): errors = np.array([0.1, 0.2, 0.3, 0.4, 0.5]) thresholds = np.array([0.05, 0.25, 0.5, 1.0]) recalls = compute_recall(errors, thresholds) assert len(recalls) == 4 assert recalls[3] >= recalls[2] >= recalls[1] >= recalls[0] assert np.all(recalls >= 0) assert np.all(recalls <= 100) def test_empty_errors(self): errors = np.array([]) thresholds = np.array([0.5, 1.0]) with pytest.raises(ValueError, match="No errors to evaluate"): compute_recall(errors, thresholds) def test_all_errors_above_threshold(self): errors = np.array([10.0, 20.0, 30.0]) thresholds = np.array([5.0]) recalls = compute_recall(errors, thresholds) np.testing.assert_almost_equal(recalls[0], 0.0) def test_all_errors_below_threshold(self): errors = np.array([0.1, 0.2, 0.3]) thresholds = np.array([1.0]) recalls = compute_recall(errors, thresholds) np.testing.assert_almost_equal(recalls[0], 100.0) def test_exact_threshold(self): errors = np.array([0.1, 0.5, 0.9]) thresholds = np.array([0.5]) recalls = compute_recall(errors, thresholds) np.testing.assert_almost_equal(recalls[0], 200.0 / 3.0) def test_multiple_thresholds(self): errors = np.array([1.0, 2.0, 3.0, 4.0, 5.0]) thresholds = np.array([2.0, 3.0, 4.0]) recalls = compute_recall(errors, thresholds) np.testing.assert_almost_equal(recalls[0], 40.0) np.testing.assert_almost_equal(recalls[1], 60.0) np.testing.assert_almost_equal(recalls[2], 80.0) class TestComputeAvgMetrics: def test_single_scene(self): scene_metrics = { "scene1": Metrics( aucs=np.array([10.0, 20.0, 30.0]), recalls=np.array([15.0, 25.0, 35.0]), error_thresholds=np.array([0.5, 1.0, 2.0]), error_type="relative_auc", num_images=100, num_reg_images=90, num_components=1, largest_component=90, ) } aucs, recalls = compute_avg_metrics(scene_metrics) np.testing.assert_array_equal(aucs, [10.0, 20.0, 30.0]) np.testing.assert_array_equal(recalls, [15.0, 25.0, 35.0]) def test_multiple_scenes(self): scene_metrics = { "scene1": Metrics( aucs=np.array([10.0, 20.0, 30.0]), recalls=np.array([15.0, 25.0, 35.0]), error_thresholds=np.array([0.5, 1.0, 2.0]), error_type="relative_auc", num_images=100, num_reg_images=90, num_components=1, largest_component=90, ), "scene2": Metrics( aucs=np.array([20.0, 30.0, 40.0]), recalls=np.array([25.0, 35.0, 45.0]), error_thresholds=np.array([0.5, 1.0, 2.0]), error_type="relative_auc", num_images=100, num_reg_images=90, num_components=1, largest_component=90, ), } aucs, recalls = compute_avg_metrics(scene_metrics) np.testing.assert_array_equal(aucs, [15.0, 25.0, 35.0]) np.testing.assert_array_equal(recalls, [20.0, 30.0, 40.0]) def test_skip_special_scenes(self): scene_metrics = { "scene1": Metrics( aucs=np.array([10.0, 20.0, 30.0]), recalls=np.array([15.0, 25.0, 35.0]), error_thresholds=np.array([0.5, 1.0, 2.0]), error_type="relative_auc", num_images=100, num_reg_images=90, num_components=1, largest_component=90, ), "__avg__": Metrics( aucs=np.array([50.0, 60.0, 70.0]), recalls=np.array([55.0, 65.0, 75.0]), error_thresholds=np.array([0.5, 1.0, 2.0]), error_type="relative_auc", num_images=100, num_reg_images=90, num_components=1, largest_component=90, ), "__all__": Metrics( aucs=np.array([80.0, 90.0, 100.0]), recalls=np.array([85.0, 95.0, 105.0]), error_thresholds=np.array([0.5, 1.0, 2.0]), error_type="relative_auc", num_images=100, num_reg_images=90, num_components=1, largest_component=90, ), } aucs, recalls = compute_avg_metrics(scene_metrics) # Should only average scene1, not __avg__ or __all__ np.testing.assert_array_equal(aucs, [10.0, 20.0, 30.0]) np.testing.assert_array_equal(recalls, [15.0, 25.0, 35.0]) class TestAggregateSceneMetrics: @staticmethod def _make_metrics(aucs, recalls, errors, num_images=100, num_reg_images=90): return Metrics( aucs=np.array(aucs, dtype=float), recalls=np.array(recalls, dtype=float), error_thresholds=np.array([0.5, 1.0, 2.0]), error_type="relative_auc", num_images=num_images, num_reg_images=num_reg_images, num_components=1, largest_component=num_reg_images, errors=np.array(errors, dtype=float), position_accuracy_gt=0.01, ) def test_avg_and_all(self): scene_metrics = [ ( "scene1", self._make_metrics([10, 20, 30], [15, 25, 35], [0.1, 0.5]), ), ( "scene2", self._make_metrics([20, 30, 40], [25, 35, 45], [0.2, 1.5]), ), ] summary = aggregate_scene_metrics( scene_metrics, error_thresholds=np.array([0.5, 1.0, 2.0]), error_type="relative_auc", ) np.testing.assert_array_equal( summary["__avg__"].aucs, [15.0, 25.0, 35.0] ) np.testing.assert_array_equal( summary["__avg__"].recalls, [20.0, 30.0, 40.0] ) assert summary["__avg__"].num_images == 100 assert summary["__avg__"].num_reg_images == 90 np.testing.assert_array_equal( summary["__all__"].errors, [0.1, 0.5, 0.2, 1.5] ) # __all__ aggregates totals (not means). assert summary["__all__"].num_images == 200 assert summary["__all__"].num_reg_images == 180 def test_skips_special_entries(self): real = self._make_metrics([10, 20, 30], [15, 25, 35], [0.1]) special = self._make_metrics( [99, 99, 99], [99, 99, 99], [9.0], num_images=999 ) summary = aggregate_scene_metrics( [("scene1", real), ("__avg__", special), ("__all__", special)], error_thresholds=np.array([0.5, 1.0, 2.0]), error_type="relative_auc", ) np.testing.assert_array_equal(summary["__avg__"].aucs, real.aucs) np.testing.assert_array_equal(summary["__all__"].errors, [0.1]) def test_empty_input(self): assert ( aggregate_scene_metrics( [], error_thresholds=np.array([0.5, 1.0, 2.0]), error_type="relative_auc", ) == {} ) def test_no_errors_omits_all(self): # When no scene carries raw errors (e.g. metrics restored without # the errors field), __all__ cannot be reconstructed. scene_metrics = [ ("scene1", self._make_metrics([10, 20, 30], [15, 25, 35], [])), ("scene2", self._make_metrics([20, 30, 40], [25, 35, 45], [])), ] summary = aggregate_scene_metrics( scene_metrics, error_thresholds=np.array([0.5, 1.0, 2.0]), error_type="relative_auc", ) assert "__all__" not in summary assert "__avg__" in summary class TestGetScores: def test_get_auc_scores(self): metrics = Metrics( aucs=np.array([10.0, 20.0, 30.0]), recalls=np.array([15.0, 25.0, 35.0]), error_thresholds=np.array([0.5, 1.0, 2.0]), error_type="relative_auc", num_images=100, num_reg_images=90, num_components=1, largest_component=90, ) scores = get_scores("relative_auc", metrics) np.testing.assert_array_equal(scores, metrics.aucs) def test_get_recall_scores(self): metrics = Metrics( aucs=np.array([10.0, 20.0, 30.0]), recalls=np.array([15.0, 25.0, 35.0]), error_thresholds=np.array([0.5, 1.0, 2.0]), error_type="relative_recall", num_images=100, num_reg_images=90, num_components=1, largest_component=90, ) scores = get_scores("relative_recall", metrics) np.testing.assert_array_equal(scores, metrics.recalls) class TestDiffMetrics: def test_nominal(self): metrics_a = { "dataset1": { "category1": { "scene1": Metrics( aucs=np.array([20.0, 30.0, 40.0]), recalls=np.array([25.0, 35.0, 45.0]), error_thresholds=np.array([0.5, 1.0, 2.0]), error_type="relative_auc", num_images=100, num_reg_images=90, num_components=2, largest_component=80, ) } } } metrics_b = { "dataset1": { "category1": { "scene1": Metrics( aucs=np.array([10.0, 20.0, 30.0]), recalls=np.array([15.0, 25.0, 35.0]), error_thresholds=np.array([0.5, 1.0, 2.0]), error_type="relative_auc", num_images=100, num_reg_images=85, num_components=1, largest_component=85, ) } } } diff = diff_metrics(metrics_a, metrics_b) scene_diff = diff["dataset1"]["category1"]["scene1"] np.testing.assert_array_equal(scene_diff.aucs, [10.0, 10.0, 10.0]) np.testing.assert_array_equal(scene_diff.recalls, [10.0, 10.0, 10.0]) assert scene_diff.num_reg_images == 5 assert scene_diff.num_components == 1 def create_test_reconstruction(): pycolmap.set_random_seed(0) synthetic_dataset_options = pycolmap.SyntheticDatasetOptions() synthetic_dataset_options.num_cameras_per_rig = 1 synthetic_dataset_options.num_frames_per_rig = 5 synthetic_dataset_options.num_points3D = 0 return pycolmap.synthesize_dataset(synthetic_dataset_options) def extract_sub_reconstruction(reconstruction, keep_names): """Build a copy of reconstruction containing only the given image names. Mirrors what a real sub-model loaded from disk looks like: its images map holds exactly the registered subset. Rebuilding from a private copy keeps the source reconstruction untouched. """ keep_names = set(keep_names) source = pycolmap.Reconstruction(reconstruction) keep_frame_ids = { image.frame_id for image in source.images.values() if image.name in keep_names } sub = pycolmap.Reconstruction() for camera in source.cameras.values(): sub.add_camera(camera) for rig in source.rigs.values(): sub.add_rig(rig) for frame in source.frames.values(): if frame.frame_id in keep_frame_ids: frame.reset_rig_ptr() sub.add_frame(frame) for image in source.images.values(): if image.name in keep_names: image.reset_camera_ptr() image.reset_frame_ptr() sub.add_image(image) for frame_id in keep_frame_ids: sub.register_frame(frame_id) return sub class TestComputeAbsErrors: def test_identical_reconstruction(self): reconstruction = create_test_reconstruction() dts, dRs = compute_abs_errors( sparse_gt=reconstruction, sparse=reconstruction ) assert len(dts) == reconstruction.num_images() assert len(dRs) == reconstruction.num_images() np.testing.assert_allclose(dts, 0.0, atol=1e-10) np.testing.assert_allclose(dRs, 0.0, atol=1e-10) def test_transformed_reconstruction(self): gt_reconstruction = create_test_reconstruction() reconstruction = create_test_reconstruction() translation = np.array([1, 2, 3]) for frame in reconstruction.frames.values(): world_from_rig = frame.rig_from_world.inverse() world_from_rig.rotation = ( world_from_rig.rotation * pycolmap.Rotation3d([0, 1, 0, 0]) ) world_from_rig.translation += translation frame.rig_from_world = world_from_rig.inverse() dts, dRs = compute_abs_errors( sparse_gt=gt_reconstruction, sparse=reconstruction ) assert len(dts) == reconstruction.num_images() assert len(dRs) == reconstruction.num_images() np.testing.assert_allclose(dts, np.linalg.norm(translation), atol=1e-10) np.testing.assert_allclose(dRs, 180.0, atol=1e-10) def _single_gt_cluster(reconstruction) -> dict[str, int]: """Map every image of a reconstruction to a single GT cluster (id 0).""" return {image.name: 0 for image in reconstruction.images.values()} class TestComputeGroupedRelErrors: def test_identical_reconstruction(self): reconstruction = create_test_reconstruction() errors = compute_grouped_rel_errors( sparse_gt=reconstruction, sub_models=[reconstruction], image_name_to_component=_single_gt_cluster(reconstruction), min_proj_center_dist=0.01, ) num_images = reconstruction.num_images() # A n B covers every ordered pair; A - B and B - A are empty. assert len(errors) == num_images * (num_images - 1) np.testing.assert_allclose(errors, 0.0, atol=1e-5) def test_transformed_reconstruction(self): gt_reconstruction = create_test_reconstruction() reconstruction = create_test_reconstruction() # A global similarity transform leaves relative poses unchanged. reconstruction.transform( pycolmap.Sim3d( 1.0, pycolmap.Rotation3d(np.array([0, 1, 0, 0])), np.array([1, 2, 3]), ) ) errors = compute_grouped_rel_errors( sparse_gt=gt_reconstruction, sub_models=[reconstruction], image_name_to_component=_single_gt_cluster(gt_reconstruction), min_proj_center_dist=0.01, ) num_images = reconstruction.num_images() assert len(errors) == num_images * (num_images - 1) np.testing.assert_allclose(errors, 0.0, atol=1e-5) def test_different_reconstructions(self): gt_reconstruction = create_test_reconstruction() reconstruction = create_test_reconstruction() for image in reconstruction.images.values(): image.frame.rig_from_world.rotation = ( pycolmap.Rotation3d(np.array([0, 1, 0, 0])) * image.frame.rig_from_world.rotation ) image.frame.rig_from_world.translation += np.array([1, 2, 3]) errors = compute_grouped_rel_errors( sparse_gt=gt_reconstruction, sub_models=[reconstruction], image_name_to_component=_single_gt_cluster(gt_reconstruction), min_proj_center_dist=0.01, ) num_images = reconstruction.num_images() assert len(errors) == num_images * (num_images - 1) assert np.all(errors > 0.1) def test_nothing_registered_maxes_all_gt_edges(self): # No estimated sub-models: set A is empty, so every GT edge is in # B - A and must be scored as the maximum (180 degrees). reconstruction = create_test_reconstruction() errors = compute_grouped_rel_errors( sparse_gt=reconstruction, sub_models=[], image_name_to_component=_single_gt_cluster(reconstruction), min_proj_center_dist=0.01, ) num_images = reconstruction.num_images() assert len(errors) == num_images * (num_images - 1) np.testing.assert_allclose(errors, 180.0) def test_merged_estimate_of_separate_gt_clusters_maxes_cross_edges(self): # Two GT clusters, each perfectly reconstructed in its own sub-model. # There are no cross-cluster edges in B, and the within-cluster edges # are all in A n B with ~0 error. reconstruction = create_test_reconstruction() names = sorted(image.name for image in reconstruction.images.values()) half = len(names) // 2 image_name_to_component = { name: (0 if i < half else 1) for i, name in enumerate(names) } errors = compute_grouped_rel_errors( sparse_gt=reconstruction, sub_models=[reconstruction], image_name_to_component=image_name_to_component, min_proj_center_dist=0.01, ) # Edges within a cluster: A n B (scored). Cross-cluster edges are in # A - B (the single sub-model connects everything) and set to 180. n0 = half n1 = len(names) - half num_within = n0 * (n0 - 1) + n1 * (n1 - 1) num_cross = len(names) * (len(names) - 1) - num_within assert len(errors) == len(names) * (len(names) - 1) np.testing.assert_allclose(np.sort(errors)[:num_within], 0.0, atol=1e-5) assert int(np.sum(np.isclose(errors, 180.0))) == num_cross def test_fragmented_estimate_of_merged_gt_maxes_cross_edges(self): # One GT cluster (merged reconstruction) that the estimate splits into # two disjoint sub-models (fragmented estimation). Within-fragment edges # are in A n B (scored ~0); the GT edges bridging the two fragments are # in B - A and set to 180. reconstruction = create_test_reconstruction() names = sorted(image.name for image in reconstruction.images.values()) half = len(names) // 2 sub_model_0 = extract_sub_reconstruction(reconstruction, names[:half]) sub_model_1 = extract_sub_reconstruction(reconstruction, names[half:]) image_name_to_component = {name: 0 for name in names} errors = compute_grouped_rel_errors( sparse_gt=reconstruction, sub_models=[sub_model_0, sub_model_1], image_name_to_component=image_name_to_component, min_proj_center_dist=0.01, ) n0 = half n1 = len(names) - half num_within = n0 * (n0 - 1) + n1 * (n1 - 1) num_cross = len(names) * (len(names) - 1) - num_within assert len(errors) == len(names) * (len(names) - 1) np.testing.assert_allclose(np.sort(errors)[:num_within], 0.0, atol=1e-5) assert int(np.sum(np.isclose(errors, 180.0))) == num_cross def test_mismatched_gt_and_estimate_cluster_boundaries(self): # GT splits the images 1/3 vs 2/3, but the estimate splits them 2/3 vs # 1/3, so the two cluster boundaries disagree. Ordered pairs that share # both an estimated sub-model and a GT cluster are scored (~0 for a # perfect estimate); pairs grouped by only one of the two are maxed. reconstruction = create_test_reconstruction() names = sorted(image.name for image in reconstruction.images.values()) n = len(names) third = n // 3 two_thirds = 2 * n // 3 # GT: first third -> cluster 0, remaining two thirds -> cluster 1. image_name_to_component = { name: (0 if i < third else 1) for i, name in enumerate(names) } # Estimate: first two thirds and remaining third form two sub-models. sub_model_0 = extract_sub_reconstruction( reconstruction, names[:two_thirds] ) sub_model_1 = extract_sub_reconstruction( reconstruction, names[two_thirds:] ) errors = compute_grouped_rel_errors( sparse_gt=reconstruction, sub_models=[sub_model_0, sub_model_1], image_name_to_component=image_name_to_component, min_proj_center_dist=0.01, ) # Three groups by (sub-model, GT cluster): P = names[:third] (sub 0, # cluster 0), Q = names[third:two_thirds] (sub 0, cluster 1), R = # names[two_thirds:] (sub 1, cluster 1). Only intra-group edges share # both a sub-model and a cluster (A n B, ~0). P-Q share a sub-model but # not a cluster (A - B), and Q-R share a cluster but not a sub-model # (B - A); both are maxed to 180. n_p = third n_q = two_thirds - third n_r = n - two_thirds num_scored = n_p * (n_p - 1) + n_q * (n_q - 1) + n_r * (n_r - 1) num_maxed = 2 * (n_p * n_q) + 2 * (n_q * n_r) assert len(errors) == num_scored + num_maxed np.testing.assert_allclose(np.sort(errors)[:num_scored], 0.0, atol=1e-5) assert int(np.sum(np.isclose(errors, 180.0))) == num_maxed def test_registered_outlier_edges_are_maxed(self): # A single sub-model connects an outlier to every real image. The # outlier is never in a GT reconstruction, so all edges touching it are # in A - B and set to 180; the remaining real edges are A n B (~0). reconstruction = create_test_reconstruction() names = sorted(image.name for image in reconstruction.images.values()) outlier = names[0] image_name_to_component = { name: (OUTLIER_COMPONENT_ID if name == outlier else 0) for name in names } errors = compute_grouped_rel_errors( sparse_gt=reconstruction, sub_models=[reconstruction], image_name_to_component=image_name_to_component, min_proj_center_dist=0.01, ) num_images = reconstruction.num_images() assert len(errors) == num_images * (num_images - 1) # The 2 * (num_images - 1) ordered edges touching the outlier are maxed. num_maxed = int(np.sum(np.isclose(errors, 180.0))) assert num_maxed == 2 * (num_images - 1) def test_outlier_present_in_gt_maxes_its_edges(self): # An image that exists in the GT reconstruction and is perfectly # registered, but is flagged as an outlier, must still have large # edges: every edge touching it is in A - B and set to 180, while the # edges among the real images stay in A n B (~0). reconstruction = create_test_reconstruction() gt_names = [image.name for image in reconstruction.images.values()] outlier = sorted(gt_names)[0] assert outlier in gt_names # The outlier is present in the GT. image_name_to_component = { name: (OUTLIER_COMPONENT_ID if name == outlier else 0) for name in gt_names } errors = compute_grouped_rel_errors( sparse_gt=reconstruction, sub_models=[reconstruction], image_name_to_component=image_name_to_component, min_proj_center_dist=0.01, ) n = reconstruction.num_images() assert len(errors) == n * (n - 1) # All 2 * (n - 1) ordered edges touching the outlier are maxed; the # (n - 1) * (n - 2) edges among the real images are ~0. assert int(np.sum(np.isclose(errors, 180.0))) == 2 * (n - 1) np.testing.assert_allclose( np.sort(errors)[: (n - 1) * (n - 2)], 0.0, atol=1e-5 ) def test_outliers_excluded_from_gt_edges(self): # Nothing is registered, so every scored pair comes from B - A. The # outlier forms no GT edges, so it contributes none of them. reconstruction = create_test_reconstruction() names = sorted(image.name for image in reconstruction.images.values()) outlier = names[0] image_name_to_component = { name: (OUTLIER_COMPONENT_ID if name == outlier else 0) for name in names } errors = compute_grouped_rel_errors( sparse_gt=reconstruction, sub_models=[], image_name_to_component=image_name_to_component, min_proj_center_dist=0.01, ) num_real = len(names) - 1 assert len(errors) == num_real * (num_real - 1) np.testing.assert_allclose(errors, 180.0) def test_gt_names_absent_from_sparse_gt_form_no_edges(self): # A name in image_name_to_component that does not exist in sparse_gt # cannot form a measurable GT edge and must not add spurious B - A # edges. Nothing is registered, so every scored pair comes from B - A. reconstruction = create_test_reconstruction() names = sorted(image.name for image in reconstruction.images.values()) phantom = "phantom_not_in_sparse_gt" assert phantom not in names image_name_to_component = {name: 0 for name in names} image_name_to_component[phantom] = 0 errors = compute_grouped_rel_errors( sparse_gt=reconstruction, sub_models=[], image_name_to_component=image_name_to_component, min_proj_center_dist=0.01, ) # Only edges among the real images count; the phantom adds none. num_real = len(names) assert len(errors) == num_real * (num_real - 1) np.testing.assert_allclose(errors, 180.0) class TestComputeGroupedAbsErrors: def test_identical_single_cluster(self): reconstruction = create_test_reconstruction() errors = compute_grouped_abs_errors( sparse_gt=reconstruction, sub_models=[reconstruction], image_name_to_component=_single_gt_cluster(reconstruction), ) assert len(errors) == reconstruction.num_images() np.testing.assert_allclose(errors, 0.0, atol=1e-10) def test_keeps_one_error_per_reconstruction(self): # An image registered in n sub-models contributes n errors (not just # the smallest). Here every image appears in both sub-models. reconstruction = create_test_reconstruction() errors = compute_grouped_abs_errors( sparse_gt=reconstruction, sub_models=[reconstruction, reconstruction], image_name_to_component=_single_gt_cluster(reconstruction), ) assert len(errors) == 2 * reconstruction.num_images() np.testing.assert_allclose(errors, 0.0, atol=1e-10) def test_keeps_only_best_cluster(self): # Cluster 0 is reconstructed perfectly; cluster 1 is offset. The # best-mean cluster (0) is kept intact and cluster 1 is maxed out. gt_reconstruction = create_test_reconstruction() reconstruction = create_test_reconstruction() names = sorted( image.name for image in gt_reconstruction.images.values() ) half = len(names) // 2 image_name_to_component = { name: (0 if i < half else 1) for i, name in enumerate(names) } cluster1_names = {name for name in names[half:]} for image in reconstruction.images.values(): if image.name in cluster1_names: image.frame.rig_from_world.translation += np.array([5, 5, 5]) errors = compute_grouped_abs_errors( sparse_gt=gt_reconstruction, sub_models=[reconstruction], image_name_to_component=image_name_to_component, ) assert len(errors) == gt_reconstruction.num_images() finite = errors[np.isfinite(errors)] # Only the (perfect) best cluster remains finite. assert len(finite) == half np.testing.assert_allclose(finite, 0.0, atol=1e-10) assert int(np.sum(~np.isfinite(errors))) == len(names) - half def test_images_without_cluster_id_are_maxed(self): # GT images missing from the mapping (cluster id None) are always maxed # out, even when perfectly registered. reconstruction = create_test_reconstruction() names = sorted(image.name for image in reconstruction.images.values()) half = len(names) // 2 # Only the second half is mapped (all to cluster 1); the first half is # left unmapped (None). image_name_to_component = {name: 1 for name in names[half:]} errors = compute_grouped_abs_errors( sparse_gt=reconstruction, sub_models=[reconstruction], image_name_to_component=image_name_to_component, ) assert len(errors) == reconstruction.num_images() finite = errors[np.isfinite(errors)] # The mapped (best) cluster stays finite; unmapped images are maxed. assert len(finite) == len(names) - half np.testing.assert_allclose(finite, 0.0, atol=1e-10) def test_outlier_cluster_is_maxed(self): # An outlier is never a selectable cluster, so it is maxed out even # though it is perfectly aligned; the real cluster is kept intact. reconstruction = create_test_reconstruction() names = sorted(image.name for image in reconstruction.images.values()) outlier = names[0] image_name_to_component = { name: (OUTLIER_COMPONENT_ID if name == outlier else 0) for name in names } errors = compute_grouped_abs_errors( sparse_gt=reconstruction, sub_models=[reconstruction], image_name_to_component=image_name_to_component, ) assert len(errors) == reconstruction.num_images() finite = errors[np.isfinite(errors)] assert len(finite) == len(names) - 1 np.testing.assert_allclose(finite, 0.0, atol=1e-10) def test_outlier_present_in_gt_gets_large_error(self): # An image that exists in the GT reconstruction and is perfectly # registered, but is flagged as an outlier, must still receive a large # error: being present and well-aligned does not rescue an outlier. reconstruction = create_test_reconstruction() gt_names = [image.name for image in reconstruction.images.values()] outlier = sorted(gt_names)[0] assert outlier in gt_names # The outlier is present in the GT. image_name_to_component = { name: (OUTLIER_COMPONENT_ID if name == outlier else 0) for name in gt_names } errors = compute_grouped_abs_errors( sparse_gt=reconstruction, sub_models=[reconstruction], image_name_to_component=image_name_to_component, ) # Every GT image is registered exactly once, so errors map 1:1 to # gt_names in order. assert len(errors) == reconstruction.num_images() error_by_name = dict(zip(gt_names, errors, strict=True)) assert not np.isfinite(error_by_name[outlier]) others = [v for n, v in error_by_name.items() if n != outlier] np.testing.assert_allclose(others, 0.0, atol=1e-10) def test_credits_multiple_clusters_across_sub_models(self): # Two sub-models each perfectly reconstruct a different GT cluster and # offset the other. Per-sub-model selection credits both clusters, # which a single global choice could not do. gt_reconstruction = create_test_reconstruction() sub_model_0 = create_test_reconstruction() sub_model_1 = create_test_reconstruction() names = sorted( image.name for image in gt_reconstruction.images.values() ) half = len(names) // 2 image_name_to_component = { name: (0 if i < half else 1) for i, name in enumerate(names) } cluster0_names = set(names[:half]) cluster1_names = set(names[half:]) # sub_model_0 offsets cluster 1 -> its best cluster is 0. for image in sub_model_0.images.values(): if image.name in cluster1_names: image.frame.rig_from_world.translation += np.array([5, 5, 5]) # sub_model_1 offsets cluster 0 -> its best cluster is 1. for image in sub_model_1.images.values(): if image.name in cluster0_names: image.frame.rig_from_world.translation += np.array([5, 5, 5]) errors = compute_grouped_abs_errors( sparse_gt=gt_reconstruction, sub_models=[sub_model_0, sub_model_1], image_name_to_component=image_name_to_component, ) # Each GT image is registered in both sub-models -> two errors each. assert len(errors) == 2 * gt_reconstruction.num_images() finite = errors[np.isfinite(errors)] # Exactly one finite error per GT image: cluster 0 via sub_model_0 and # cluster 1 via sub_model_1, so both clusters are credited. assert len(finite) == gt_reconstruction.num_images() np.testing.assert_allclose(finite, 0.0, atol=1e-10) # The errors must map back to the right image and sub-model: they are # emitted in sparse_gt image order, each image contributing its two # errors in sub_model order [sub_model_0, sub_model_1]. A cluster-0 # image is credited only by sub_model_0 (finite, sub_model_1 maxed) and # a cluster-1 image only by sub_model_1 (sub_model_0 maxed, finite). gt_names = [image.name for image in gt_reconstruction.images.values()] for i, name in enumerate(gt_names): err_sub0, err_sub1 = errors[2 * i], errors[2 * i + 1] if name in cluster0_names: np.testing.assert_allclose(err_sub0, 0.0, atol=1e-10) assert not np.isfinite(err_sub1) else: assert not np.isfinite(err_sub0) np.testing.assert_allclose(err_sub1, 0.0, atol=1e-10) def test_mismatched_gt_and_estimate_cluster_boundaries(self): # GT splits the images 1/3 vs 2/3 while the estimate splits them 2/3 vs # 1/3. sub_model_0 spans all of GT cluster 0 plus the cluster-1 images # that leaked in; it can credit only one GT cluster, so the leaked # cluster-1 images are maxed. sub_model_1 holds the rest of cluster 1 # and credits them. reconstruction = create_test_reconstruction() names = sorted(image.name for image in reconstruction.images.values()) n = len(names) third = n // 3 two_thirds = 2 * n // 3 image_name_to_component = { name: (0 if i < third else 1) for i, name in enumerate(names) } sub_model_0 = extract_sub_reconstruction( reconstruction, names[:two_thirds] ) sub_model_1 = extract_sub_reconstruction( reconstruction, names[two_thirds:] ) # Offset the cluster-1 images that leaked into sub_model_0 so its best # cluster is unambiguously cluster 0. leaked_cluster1 = set(names[third:two_thirds]) for image in sub_model_0.images.values(): if image.name in leaked_cluster1: image.frame.rig_from_world.translation += np.array([5, 5, 5]) errors = compute_grouped_abs_errors( sparse_gt=reconstruction, sub_models=[sub_model_0, sub_model_1], image_name_to_component=image_name_to_component, ) # Sub-models are disjoint, so each GT image contributes exactly one # error, emitted in sparse_gt image order. assert len(errors) == reconstruction.num_images() gt_names = [image.name for image in reconstruction.images.values()] error_by_name = dict(zip(gt_names, errors, strict=True)) # Cluster 0 (first third) credited by sub_model_0. for name in names[:third]: np.testing.assert_allclose(error_by_name[name], 0.0, atol=1e-10) # Cluster-1 images that leaked into sub_model_0 are maxed. for name in names[third:two_thirds]: assert not np.isfinite(error_by_name[name]) # Remaining cluster 1 credited by sub_model_1. for name in names[two_thirds:]: np.testing.assert_allclose(error_by_name[name], 0.0, atol=1e-10) colmap-4.2.0/benchmark/reconstruction/requirements.txt000066400000000000000000000001141524536416500232760ustar00rootroot00000000000000pillow numpy opencv-python-headless requests pycolmap py7zr rich scipy tqdm colmap-4.2.0/benchmark/runtime/000077500000000000000000000000001524536416500164205ustar00rootroot00000000000000colmap-4.2.0/benchmark/runtime/CMakeLists.txt000066400000000000000000000021561524536416500211640ustar00rootroot00000000000000find_package(benchmark REQUIRED) add_executable(benchmark_cost_functions cost_functions.cc) target_link_libraries(benchmark_cost_functions PRIVATE colmap_estimators benchmark::benchmark) add_executable(benchmark_fundamental_matrix_degensac fundamental_matrix_degensac.cc) target_link_libraries(benchmark_fundamental_matrix_degensac PRIVATE colmap_estimators benchmark::benchmark) add_executable(benchmark_bundle_adjustment bundle_adjustment.cc) target_link_libraries(benchmark_bundle_adjustment PRIVATE colmap_estimators colmap_scene benchmark::benchmark) add_executable(benchmark_bundle_adjustment_convergence bundle_adjustment_convergence.cc) target_link_libraries(benchmark_bundle_adjustment_convergence PRIVATE colmap_estimators colmap_scene colmap_controllers) add_executable(benchmark_global_positioning global_positioning.cc) target_link_libraries(benchmark_global_positioning PRIVATE colmap_estimators colmap_scene benchmark::benchmark) add_executable(benchmark_incremental_mapping incremental_mapping.cc) target_link_libraries(benchmark_incremental_mapping PRIVATE colmap_controllers colmap_scene benchmark::benchmark) colmap-4.2.0/benchmark/runtime/README.md000066400000000000000000000012261524536416500177000ustar00rootroot00000000000000# Benchmarking ## Installation 1. Install [google/benchmark](https://github.com/google/benchmark). For example, using homebrew on a Mac: `brew install google-benchmark`. 2. Build and run the benchmarking executables: ```bash cmake .. -DBENCHMARK_ENABLED=ON ninja benchmark/runtime/benchmark_cost_functions ``` To reduce the variance, consider setting up your system appropriately [following these instructions](https://github.com/google/benchmark/blob/main/docs/reducing_variance.md). ## Running the benchmarks Cost functions: ```bash ./benchmark/runtime/benchmark_cost_functions --benchmark_display_aggregates_only=true --benchmark_repetitions=50 ``` colmap-4.2.0/benchmark/runtime/bundle_adjustment.cc000066400000000000000000000161311524536416500224400ustar00rootroot00000000000000// Copyright (c), ETH Zurich and UNC Chapel Hill. // All rights reserved. // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are met: // // * Redistributions of source code must retain the above copyright // notice, this list of conditions and the following disclaimer. // // * Redistributions in binary form must reproduce the above copyright // notice, this list of conditions and the following disclaimer in the // documentation and/or other materials provided with the distribution. // // * Neither the name of ETH Zurich and UNC Chapel Hill nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE // ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE // LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR // CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF // SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS // INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN // CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) // ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE // POSSIBILITY OF SUCH DAMAGE. #include "colmap/estimators/bundle_adjustment.h" #include "colmap/estimators/bundle_adjustment_ceres.h" #include "colmap/scene/reconstruction.h" #include "colmap/scene/synthetic.h" #include using namespace colmap; static void AddArguments(::benchmark::Benchmark* b) { for (const int track_length : {5, 20, 100}) { for (const int num_rigs : {1, 5}) { for (const int num_cameras_per_rig : {1, 3}) { for (const int num_frames_per_rig : {10, 50}) { const int num_images = num_rigs * num_cameras_per_rig * num_frames_per_rig; if (track_length > num_images) continue; for (const int num_points3D : {1000, 10000}) { b->Args({track_length, num_rigs, num_cameras_per_rig, num_frames_per_rig, num_points3D}); } } } } } } class BM_BundleAdjustment : public benchmark::Fixture { public: void SetUp(::benchmark::State& state) { SetPRNGSeed(42); SyntheticDatasetOptions dataset_options; dataset_options.track_length = state.range(0); dataset_options.num_rigs = state.range(1); dataset_options.num_cameras_per_rig = state.range(2); dataset_options.num_frames_per_rig = state.range(3); dataset_options.num_points3D = state.range(4); reconstruction_ = std::make_unique(); SynthesizeDataset(dataset_options, reconstruction_.get()); SyntheticNoiseOptions noise_options; noise_options.point2D_stddev = 1.0; noise_options.point3D_stddev = 0.05; noise_options.rig_from_world_translation_stddev = 0.01; noise_options.rig_from_world_rotation_stddev = 1.0; SynthesizeNoise(noise_options, reconstruction_.get()); for (const image_t image_id : reconstruction_->RegImageIds()) { config_.AddImage(image_id); } config_.FixGauge(BundleAdjustmentGauge::TWO_CAMS_FROM_WORLD); options_.print_summary = false; } void TearDown(::benchmark::State& /*state*/) { reconstruction_.reset(); options_ = BundleAdjustmentOptions(); config_ = BundleAdjustmentConfig(); } protected: void ReportSceneCounters(benchmark::State& state) const { state.counters["track_length"] = reconstruction_->ComputeMeanTrackLength(); state.counters["num_images"] = reconstruction_->NumRegImages(); state.counters["num_rigs"] = reconstruction_->NumRigs(); state.counters["num_cameras"] = reconstruction_->NumCameras(); state.counters["num_frames"] = reconstruction_->NumRegFrames(); state.counters["num_points3d"] = reconstruction_->NumPoints3D(); } std::unique_ptr reconstruction_; BundleAdjustmentConfig config_; BundleAdjustmentOptions options_; }; // Time column reports wall-clock time (ms) per full BA solve. BENCHMARK_DEFINE_F(BM_BundleAdjustment, Ceres)(benchmark::State& state) { BundleAdjustmentOptions opts = options_; opts.backend = BundleAdjustmentBackend::CERES; int total_lm_steps = 0; double total_ceres_time_s = 0.0; for (auto _ : state) { state.PauseTiming(); Reconstruction copy = *reconstruction_; state.ResumeTiming(); auto ba = CreateDefaultBundleAdjuster(opts, config_, copy); const auto summary = ba->Solve(); state.PauseTiming(); if (summary->termination_type == BundleAdjustmentTerminationType::NO_CONVERGENCE) { state.SkipWithError("Bundle adjustment did not converge"); break; } const auto* ceres_sum = dynamic_cast(summary.get()); if (ceres_sum == nullptr) { state.SkipWithError("Unexpected summary type for Ceres backend"); break; } total_lm_steps += ceres_sum->ceres_summary.num_successful_steps + ceres_sum->ceres_summary.num_unsuccessful_steps; total_ceres_time_s += ceres_sum->ceres_summary.total_time_in_seconds; state.ResumeTiming(); } state.PauseTiming(); ReportSceneCounters(state); const int64_t num_iters = state.iterations(); if (num_iters > 0) { state.counters["avg_lm_steps"] = static_cast(total_lm_steps) / num_iters; } if (total_lm_steps > 0) { state.counters["avg_ms_per_lm_step"] = total_ceres_time_s * 1000.0 / total_lm_steps; } state.ResumeTiming(); } #ifdef CASPAR_ENABLED // Time column reports wall-clock time (ms) per full BA solve. BENCHMARK_DEFINE_F(BM_BundleAdjustment, Caspar)(benchmark::State& state) { BundleAdjustmentOptions opts = options_; opts.backend = BundleAdjustmentBackend::CASPAR; for (auto _ : state) { state.PauseTiming(); Reconstruction copy = *reconstruction_; state.ResumeTiming(); auto ba = CreateDefaultBundleAdjuster(opts, config_, copy); const auto summary = ba->Solve(); state.PauseTiming(); if (summary->termination_type == BundleAdjustmentTerminationType::NO_CONVERGENCE) { state.SkipWithError("Bundle adjustment did not converge"); break; } state.ResumeTiming(); } state.PauseTiming(); ReportSceneCounters(state); state.ResumeTiming(); } #endif BENCHMARK_REGISTER_F(BM_BundleAdjustment, Ceres) ->Apply(AddArguments) ->Unit(benchmark::kMillisecond); #ifdef CASPAR_ENABLED BENCHMARK_REGISTER_F(BM_BundleAdjustment, Caspar) ->Apply(AddArguments) ->Unit(benchmark::kMillisecond); #endif int main(int argc, char** argv) { benchmark::Initialize(&argc, argv); if (benchmark::ReportUnrecognizedArguments(argc, argv)) return 1; benchmark::RunSpecifiedBenchmarks(); benchmark::Shutdown(); return 0; } colmap-4.2.0/benchmark/runtime/bundle_adjustment_convergence.cc000066400000000000000000000251021524536416500250140ustar00rootroot00000000000000// Copyright (c), ETH Zurich and UNC Chapel Hill. // All rights reserved. // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are met: // // * Redistributions of source code must retain the above copyright // notice, this list of conditions and the following disclaimer. // // * Redistributions in binary form must reproduce the above copyright // notice, this list of conditions and the following disclaimer in the // documentation and/or other materials provided with the distribution. // // * Neither the name of ETH Zurich and UNC Chapel Hill nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE // ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE // LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR // CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF // SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS // INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN // CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) // ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE // POSSIBILITY OF SUCH DAMAGE. // Outputs per-LM-iteration MSE convergence as CSV to stdout. // Columns: solver,iteration,time_ms,mse_px2 // // MSE convention matches the existing COLMAP reporting (sqrt(cost/n) = RMSE): // Ceres: mse = cost / num_residuals // Caspar: mse = score_best / num_residuals // Usage: // bundle_adjustment_convergence \ // [--track_length=N] [--num_frames=N] [--num_points3D=N] \ // [--max_ceres_iterations=N] [--max_caspar_iterations=N] #include "colmap/controllers/base_option_manager.h" #include "colmap/estimators/bundle_adjustment.h" #include "colmap/estimators/bundle_adjustment_caspar.h" #include "colmap/estimators/bundle_adjustment_ceres.h" #include "colmap/scene/reconstruction.h" #include "colmap/scene/synthetic.h" #include "colmap/sensor/models.h" #include #include #include using namespace colmap; int main(int argc, char** argv) { // -1 means "use solver default". Override only when flag is provided. int max_ceres_iterations = -1; int max_caspar_iterations = -1; int track_length = 20; int num_frames = 50; int num_points3D = 5000; int num_cameras_per_rig = 1; bool refine_focal_length = false; std::string label = "default"; BaseOptionManager args(/*add_project_options=*/false); args.AddDefaultOption("max_ceres_iterations", &max_ceres_iterations); args.AddDefaultOption("max_caspar_iterations", &max_caspar_iterations); args.AddDefaultOption("track_length", &track_length); args.AddDefaultOption("num_frames", &num_frames); args.AddDefaultOption("num_points3D", &num_points3D); args.AddDefaultOption("num_cameras_per_rig", &num_cameras_per_rig); args.AddDefaultOption("refine_focal_length", &refine_focal_length); args.AddDefaultOption("label", &label); if (!args.Parse(argc, argv)) { return EXIT_FAILURE; } SetPRNGSeed(42); SyntheticDatasetOptions dataset_options; dataset_options.track_length = track_length; dataset_options.num_cameras_per_rig = num_cameras_per_rig; dataset_options.num_points3D = num_points3D; // Use PINHOLE so fx≠fy refinement is testable without distortion coupling. dataset_options.camera_model_id = PinholeCameraModel::model_id; dataset_options.camera_params = {1280, 1280, 512, 384}; dataset_options.num_rigs = 1; dataset_options.num_frames_per_rig = num_frames; Reconstruction reconstruction; SynthesizeDataset(dataset_options, &reconstruction); SyntheticNoiseOptions noise_options; noise_options.point2D_stddev = 1.0; noise_options.point3D_stddev = 0.05; noise_options.rig_from_world_translation_stddev = 0.01; noise_options.rig_from_world_rotation_stddev = 1.0; SynthesizeNoise(noise_options, &reconstruction); BundleAdjustmentConfig config; for (const image_t image_id : reconstruction.RegImageIds()) { config.AddImage(image_id); } config.FixGauge(BundleAdjustmentGauge::TWO_CAMS_FROM_WORLD); BundleAdjustmentOptions options; options.print_summary = false; options.refine_focal_length = refine_focal_length; options.refine_sensor_from_rig = false; if (max_ceres_iterations >= 0) { options.ceres->solver_options.max_num_iterations = max_ceres_iterations; } if (max_caspar_iterations >= 0) { options.caspar->solver_iter_max = max_caspar_iterations; } options.caspar->collect_iteration_data = true; std::cerr << "Scene: track_length=" << track_length << " num_frames=" << num_frames << " num_points3D=" << num_points3D << " num_cameras_per_rig=" << num_cameras_per_rig << " refine_focal_length=" << refine_focal_length << " label=" << label << " max_ceres_iterations=" << options.ceres->solver_options.max_num_iterations << " max_caspar_iterations=" << options.caspar->solver_iter_max << "\n"; std::cout << "solver,scenario,iteration,time_ms,mse_px2\n"; // Ceres { Reconstruction copy = reconstruction; BundleAdjustmentOptions opts = options; opts.backend = BundleAdjustmentBackend::CERES; // Measure setup/build time. const auto t_setup_start = std::chrono::steady_clock::now(); auto ba = CreateDefaultBundleAdjuster(opts, config, copy); const auto t_setup_end = std::chrono::steady_clock::now(); const double setup_time_ms = std::chrono::duration(t_setup_end - t_setup_start) .count(); // Measure total Solve() wall time. const auto t_solve_start = std::chrono::steady_clock::now(); const auto summary = ba->Solve(); const auto t_solve_end = std::chrono::steady_clock::now(); const double solve_wall_ms = std::chrono::duration(t_solve_end - t_solve_start) .count(); const auto* s = dynamic_cast(summary.get()); if (!s) { std::cerr << "ERROR: unexpected Ceres summary type\n"; return 1; } const double n = static_cast(s->num_residuals); // Ceres cumulative iteration time reported internally. const double iter_total_ms = s->ceres_summary.iterations.empty() ? 0.0 : s->ceres_summary.iterations.back().cumulative_time_in_seconds * 1000.0; // Solve() wall time not accounted for by iteration timing. const double solve_overhead_ms = std::max(0.0, solve_wall_ms - iter_total_ms); // Emit t=0 anchor so the plot origin is consistent with Caspar. if (!s->ceres_summary.iterations.empty()) { std::cout << "ceres," << label << ",-1,0," << s->ceres_summary.iterations.front().cost / n << "\n"; } for (const auto& iter : s->ceres_summary.iterations) { const double mse = iter.cost / n; // Include: // setup time // + Solve() dispatch/overhead // + cumulative iteration time (includes initial eval — same convention // as Caspar's dt_tot which includes DoResJacFirst) const double time_ms = setup_time_ms + solve_overhead_ms + iter.cumulative_time_in_seconds * 1000.0; std::cout << "ceres," << label << "," << iter.iteration << "," << time_ms << "," << mse << "\n"; } std::cerr << "Ceres: " << s->ceres_summary.iterations.size() << " iterations, final mse=" << s->ceres_summary.final_cost / n << " px^2" << ", setup_ms=" << setup_time_ms << ", solve_overhead_ms=" << solve_overhead_ms << ", iter_ms=" << iter_total_ms << ", solve_wall_ms=" << solve_wall_ms << "\n"; } #ifdef CASPAR_ENABLED // Caspar { Reconstruction copy = reconstruction; BundleAdjustmentOptions opts = options; opts.backend = BundleAdjustmentBackend::CASPAR; // Measure setup/build time. const auto t_setup_start = std::chrono::steady_clock::now(); auto ba = CreateDefaultBundleAdjuster(opts, config, copy); const auto t_setup_end = std::chrono::steady_clock::now(); const double setup_time_ms = std::chrono::duration(t_setup_end - t_setup_start) .count(); // Measure total Solve() wall time. const auto t_solve_start = std::chrono::steady_clock::now(); const auto summary = ba->Solve(); const auto t_solve_end = std::chrono::steady_clock::now(); const double solve_wall_ms = std::chrono::duration(t_solve_end - t_solve_start) .count(); const auto* s = dynamic_cast(summary.get()); if (!s) { std::cerr << "ERROR: unexpected Caspar summary type\n"; return 1; } const double n = static_cast(s->num_residuals); // Caspar internal measured iteration time. const double iter_total_ms = s->iterations.empty() ? 0.0 : s->iterations.back().dt_tot * 1000.0; // Everything inside Solve() that is NOT accounted for by iteration timing. const double solve_overhead_ms = std::max(0.0, solve_wall_ms - iter_total_ms); // Emit initial state (iteration -1) at t=0 std::cout << "caspar," << label << ",-1,0," << (s->initial_score / n) << "\n"; for (const auto& iter : s->iterations) { const double mse = iter.score_best / n; // Include: // setup time // + Solve() dispatch/overhead // + cumulative iteration time const double time_ms = setup_time_ms + solve_overhead_ms + iter.dt_tot * 1000.0; std::cout << "caspar," << label << "," << iter.solver_iter << "," << time_ms << "," << mse << "\n"; } const double final_mse = s->iterations.empty() ? s->initial_score / n : s->iterations.back().score_best / n; std::cerr << "Caspar: " << s->iteration_count << " iterations, final mse=" << final_mse << " px^2" << ", setup_ms=" << setup_time_ms << ", solve_overhead_ms=" << solve_overhead_ms << ", iter_ms=" << iter_total_ms << ", solve_wall_ms=" << solve_wall_ms << "\n"; } #endif return 0; } colmap-4.2.0/benchmark/runtime/cost_functions.cc000066400000000000000000000161611524536416500217740ustar00rootroot00000000000000#include "colmap/estimators/cost_functions/reprojection_error.h" #include "colmap/geometry/rigid3.h" #include "colmap/sensor/models.h" #include "colmap/util/eigen_alignment.h" #include #include #include #include using namespace colmap; namespace { struct ReprojErrorData { Rigid3d cam_from_world; Eigen::Vector3d point3D; Eigen::Vector2d point2D; std::vector camera_params; }; // Nominal, in-frame camera parameters for each model. The 3D point is chosen so // that its normalized coordinates are small and valid for all models. template std::vector NominalCameraParams(); template <> std::vector NominalCameraParams() { return {1000, 320, 240}; } template <> std::vector NominalCameraParams() { return {1000, 1000, 320, 240}; } template <> std::vector NominalCameraParams() { return {1000, 320, 240, 0.01}; } template <> std::vector NominalCameraParams() { return {1000, 320, 240, 0.01, 0.001}; } template <> std::vector NominalCameraParams() { return {1000, 1000, 320, 240, 0.01, 0.001, 0.0001, 0.0001}; } template <> std::vector NominalCameraParams() { return {1000, 1000, 320, 240, 0.01, 0.001, 0.0001, 0.0001, 0.001, 0.0005, -0.0005, 0.0001}; } template <> std::vector NominalCameraParams() { return {1000, 1000, 320, 240, 0.5}; } template <> std::vector NominalCameraParams() { return {1000, 320, 240, 0.01}; } template <> std::vector NominalCameraParams() { return {1000, 320, 240, 0.01, 0.001}; } template <> std::vector NominalCameraParams() { return {1000, 1000, 320, 240, 0.01, 0.001, 0.0001, 0.0001}; } template <> std::vector NominalCameraParams() { return {1000, 1000, 320, 240, 0.01, 0.001, 0.0001, 0.0001, 0.001, 0.0005, 0.0001, 0.0001}; } template <> std::vector NominalCameraParams() { return {1000, 1000, 320, 240, 0.01, 0.001, 0.0001, 0.00001, 0.000001, 0.0000001, 0.0001, 0.0001, 0.0001, 0.00005, 0.0001, 0.00005}; } template <> std::vector NominalCameraParams() { return {1000, 320, 240, 0.01}; } template <> std::vector NominalCameraParams() { return {1000, 1000, 320, 240, 0.01}; } template <> std::vector NominalCameraParams() { return {1000, 320, 240}; } template <> std::vector NominalCameraParams() { return {1000, 1000, 320, 240}; } template <> std::vector NominalCameraParams() { return {1000, 1000, 320, 240, 0.5, 1.0}; } template <> std::vector NominalCameraParams() { return {640, 480}; } template ReprojErrorData CreateReprojErrorData() { ReprojErrorData data{ Rigid3d(Eigen::Quaterniond(0.9, 0.1, 0.1, 0.1).normalized(), Eigen::Vector3d(0.1, 0.2, 0.3)), Eigen::Vector3d(1, 2, 10), Eigen::Vector2d(320.1, 240.2), NominalCameraParams(), }; CHECK_EQ(data.camera_params.size(), CameraModel::num_params); return data; } // Fully-variable reprojection error (point, pose, calibration). // The autodiff variant is obtained directly from the functor; the analytic // variant is obtained through the production dispatch, which routes to the // hand-written Jacobian for models that implement ImgFromCamWithJac(). template void BM_ReprojError(benchmark::State& state, bool analytic) { ReprojErrorData data = CreateReprojErrorData(); std::unique_ptr cost_function( analytic ? CreateCameraCostFunction( CameraModel::model_id, data.point2D) : ReprojErrorCostFunctor::Create(data.point2D)); const double* parameters[3] = {data.point3D.data(), data.cam_from_world.params.data(), data.camera_params.data()}; double residuals[2]; double jacobian_point[2 * 3]; double jacobian_pose[2 * 7]; double jacobian_params[2 * CameraModel::num_params]; double* jacobians[3] = {jacobian_point, jacobian_pose, jacobian_params}; for (auto _ : state) { cost_function->Evaluate(parameters, residuals, jacobians); } } // Fixed-pose reprojection error (point, calibration), the local-BA hot path. template void BM_ReprojErrorConstantPose(benchmark::State& state, bool analytic) { ReprojErrorData data = CreateReprojErrorData(); std::unique_ptr cost_function( analytic ? CreateCameraCostFunction( CameraModel::model_id, data.point2D, data.cam_from_world) : ReprojErrorConstantPoseCostFunctor::Create( data.point2D, data.cam_from_world)); const double* parameters[2] = {data.point3D.data(), data.camera_params.data()}; double residuals[2]; double jacobian_point[2 * 3]; double jacobian_params[2 * CameraModel::num_params]; double* jacobians[2] = {jacobian_point, jacobian_params}; for (auto _ : state) { cost_function->Evaluate(parameters, residuals, jacobians); } } } // namespace #define REGISTER_MODEL(Model) \ BENCHMARK_CAPTURE(BM_ReprojError, Model##_AutoDiff, false); \ BENCHMARK_CAPTURE(BM_ReprojError, Model##_Analytic, true); \ BENCHMARK_CAPTURE( \ BM_ReprojErrorConstantPose, Model##_ConstPose_AutoDiff, false); \ BENCHMARK_CAPTURE( \ BM_ReprojErrorConstantPose, Model##_ConstPose_Analytic, true); REGISTER_MODEL(SimplePinholeCameraModel) REGISTER_MODEL(PinholeCameraModel) REGISTER_MODEL(SimpleRadialCameraModel) REGISTER_MODEL(RadialCameraModel) REGISTER_MODEL(OpenCVCameraModel) REGISTER_MODEL(FullOpenCVCameraModel) REGISTER_MODEL(FOVCameraModel) REGISTER_MODEL(SimpleRadialFisheyeCameraModel) REGISTER_MODEL(RadialFisheyeCameraModel) REGISTER_MODEL(OpenCVFisheyeCameraModel) REGISTER_MODEL(ThinPrismFisheyeCameraModel) REGISTER_MODEL(RadTanThinPrismFisheyeModel) REGISTER_MODEL(SimpleDivisionCameraModel) REGISTER_MODEL(DivisionCameraModel) REGISTER_MODEL(SimpleFisheyeCameraModel) REGISTER_MODEL(FisheyeCameraModel) REGISTER_MODEL(EUCMCameraModel) REGISTER_MODEL(EquirectangularCameraModel) BENCHMARK_MAIN(); colmap-4.2.0/benchmark/runtime/fundamental_matrix_degensac.cc000066400000000000000000000377071524536416500244600ustar00rootroot00000000000000// Copyright (c), ETH Zurich and UNC Chapel Hill. // All rights reserved. // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are met: // // * Redistributions of source code must retain the above copyright // notice, this list of conditions and the following disclaimer. // // * Redistributions in binary form must reproduce the above copyright // notice, this list of conditions and the following disclaimer in the // documentation and/or other materials provided with the distribution. // // * Neither the name of ETH Zurich and UNC Chapel Hill nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE // ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE // LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR // CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF // SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS // INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN // CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) // ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE // POSSIBILITY OF SUCH DAMAGE. // A/B comparison of plain LO-RANSAC vs DEGENSAC for fundamental matrix // estimation on synthetic two-view data with a dominant scene plane. Prints a // markdown table of accuracy, robustness, and runtime metrics across scene // configurations, in the style of the essential-matrix cheirality benchmark. // // For each configuration a number of independent problems are generated. Both // methods see the identical data and identical RANSAC randomness per problem, // so the comparison is apples-to-apples. Pose error is measured by decomposing // the estimated fundamental matrix into a relative pose and comparing it to // ground truth. #include "colmap/estimators/fundamental_matrix_degensac.h" #include "colmap/estimators/solvers/fundamental_matrix.h" #include "colmap/geometry/essential_matrix.h" #include "colmap/geometry/rigid3.h" #include "colmap/math/random.h" #include "colmap/optim/loransac.h" #include "colmap/optim/ransac.h" #include "colmap/optim/support_measurement.h" #include #include #include #include #include #include #include using namespace colmap; namespace { constexpr double kMaxError = 1.0; // Inlier pixel threshold. Eigen::Matrix3d RandomCalibrationMatrix() { return (Eigen::Matrix3d() << RandomUniformReal(800, 1200), 0, RandomUniformReal(400, 600), 0, RandomUniformReal(800, 1200), RandomUniformReal(400, 600), 0, 0, 1) .finished(); } struct Scene { Eigen::Matrix3d K; Rigid3d cam2_from_cam1; std::vector points1; std::vector points2; // True inliers are the non-outlier correspondences (on-plane or off-plane). std::vector true_inlier_mask; }; // Generates a scene where `plane_fraction` of the true-inlier correspondences // lie on a dominant plane and `outlier_fraction` of all correspondences are // gross mismatches. Scene GenerateScene(size_t num_points, double plane_fraction, double outlier_fraction, double noise) { Scene scene; scene.K = RandomCalibrationMatrix(); // A moderate forward-facing relative pose (limited rotation and a small // baseline relative to the scene depth) so that points stay in front of both // cameras and pose recovery is not affected by the twisted-pair ambiguity. const double angle = RandomUniformReal(5.0, 30.0) * M_PI / 180.0; const Eigen::Quaterniond rotation( Eigen::AngleAxisd(angle, Eigen::Vector3d::Random().normalized())); const Eigen::Vector3d translation = Eigen::Vector3d::Random().normalized() * RandomUniformReal(0.1, 0.4); scene.cam2_from_cam1 = Rigid3d(rotation, translation); const Eigen::Matrix3d K_inv = scene.K.inverse(); const Eigen::Vector3d normal = Eigen::Vector3d(0.2, -0.1, 1.0).normalized(); constexpr double kDistance = 2.0; const size_t num_outliers = static_cast(std::round(outlier_fraction * num_points)); const size_t num_inliers = num_points - num_outliers; const size_t num_on_plane = static_cast(std::round(plane_fraction * num_inliers)); for (size_t i = 0; i < num_points; ++i) { const Eigen::Vector2d point1 = scene.K.topRows<2>() * Eigen::Vector2d::Random().homogeneous(); const Eigen::Vector3d ray = K_inv * point1.homogeneous(); const bool is_outlier = i >= num_inliers; const bool on_plane = !is_outlier && i < num_on_plane; double depth; if (on_plane) { depth = kDistance / normal.dot(ray); } else { depth = RandomUniformReal(0.5, 3.0); } const Eigen::Vector3d point3D_in_cam1 = depth * ray; Eigen::Vector2d point2 = (scene.K * (scene.cam2_from_cam1 * point3D_in_cam1)).hnormalized(); if (is_outlier) { // Replace with a random mismatch somewhere in the image. point2 = scene.K.topRows<2>() * Eigen::Vector2d::Random().homogeneous(); } scene.points1.push_back(point1 + noise * Eigen::Vector2d::Random()); scene.points2.push_back(point2 + noise * Eigen::Vector2d::Random()); scene.true_inlier_mask.push_back(!is_outlier); } return scene; } double RotationErrorDeg(const Eigen::Matrix3d& R_gt, const Eigen::Matrix3d& R) { const Eigen::Quaterniond q_gt(R_gt); const Eigen::Quaterniond q(R); return q_gt.angularDistance(q) * 180.0 / M_PI; } double TranslationErrorDeg(const Eigen::Vector3d& t_gt, const Eigen::Vector3d& t) { const double cos_angle = std::clamp(std::abs(t_gt.normalized().dot(t.normalized())), 0.0, 1.0); return std::acos(cos_angle) * 180.0 / M_PI; } double Percentile(std::vector values, double percentile) { if (values.empty()) { return std::numeric_limits::quiet_NaN(); } std::sort(values.begin(), values.end()); const double rank = percentile / 100.0 * (values.size() - 1); const size_t lo = static_cast(std::floor(rank)); const size_t hi = static_cast(std::ceil(rank)); const double frac = rank - lo; return values[lo] * (1.0 - frac) + values[hi] * frac; } double Mean(const std::vector& values) { if (values.empty()) { return std::numeric_limits::quiet_NaN(); } double sum = 0; for (double v : values) { sum += v; } return sum / values.size(); } struct Stats { double success_rate = 0; // Pose within 5 deg of ground truth. double recall = 0; // % of true inliers recovered as inliers. double precision = 0; // % of reported inliers that are true inliers. double rot_med = 0; double rot_p90 = 0; double trans_med = 0; double trans_p90 = 0; double time_mean_ms = 0; double time_p90_ms = 0; double avg_trials = 0; }; // Recovers the relative pose from a fundamental matrix and returns rotation and // translation-direction error in degrees. void PoseErrors(const Scene& scene, const Eigen::Matrix3d& F, double* rot_err, double* trans_err) { const Eigen::Matrix3d E = EssentialFromFundamentalMatrix(scene.K, F, scene.K); std::vector rays1(scene.points1.size()); std::vector rays2(scene.points2.size()); const Eigen::Matrix3d K_inv = scene.K.inverse(); for (size_t i = 0; i < scene.points1.size(); ++i) { rays1[i] = (K_inv * scene.points1[i].homogeneous()).normalized(); rays2[i] = (K_inv * scene.points2[i].homogeneous()).normalized(); } Rigid3d cam2_from_cam1; std::vector valid; PoseFromEssentialMatrix(E, rays1, rays2, &cam2_from_cam1, &valid); *rot_err = RotationErrorDeg(scene.cam2_from_cam1.rotation().toRotationMatrix(), cam2_from_cam1.rotation().toRotationMatrix()); *trans_err = TranslationErrorDeg(scene.cam2_from_cam1.translation(), cam2_from_cam1.translation()); } // Inlier recall (fraction of true inliers reported) and precision (fraction of // reported inliers that are true inliers) for the estimated inlier mask. void RecallPrecision(const Scene& scene, const std::vector& inlier_mask, double* recall, double* precision) { size_t num_true = 0; size_t num_reported = 0; size_t num_true_reported = 0; for (size_t i = 0; i < scene.true_inlier_mask.size(); ++i) { const bool is_true = scene.true_inlier_mask[i]; const bool is_reported = i < inlier_mask.size() && inlier_mask[i]; num_true += is_true; num_reported += is_reported; num_true_reported += is_true && is_reported; } *recall = num_true == 0 ? 0.0 : 100.0 * num_true_reported / num_true; *precision = num_reported == 0 ? 0.0 : 100.0 * num_true_reported / num_reported; } enum class Method { kLoRansac, kDegensac }; Stats RunConfig(Method method, size_t num_points, double plane_fraction, double outlier_fraction, double noise, int num_problems) { int num_success = 0; std::vector rot_errs; std::vector trans_errs; std::vector times_ms; std::vector recalls; std::vector precisions; double sum_trials = 0; for (int p = 0; p < num_problems; ++p) { // Deterministic, distinct data per problem; identical for both methods. SetPRNGSeed(1000 + p); const Scene scene = GenerateScene(num_points, plane_fraction, outlier_fraction, noise); RANSACOptions ransac_options; ransac_options.max_error = kMaxError; ransac_options.confidence = 0.9999; ransac_options.min_inlier_ratio = 0.1; ransac_options.max_num_trials = 10000; ransac_options.random_seed = 5000 + p; LORANSAC::Report report; const auto start = std::chrono::high_resolution_clock::now(); if (method == Method::kDegensac) { FundamentalMatrixDegensacOptions options; options.ransac = ransac_options; const auto r = EstimateFundamentalMatrixDegensac( scene.points1, scene.points2, options); report.success = r.success; report.num_trials = r.num_trials; report.support = r.support; report.inlier_mask = r.inlier_mask; report.model = r.model; } else { LORANSAC loransac(ransac_options); report = loransac.Estimate(scene.points1, scene.points2); } const auto end = std::chrono::high_resolution_clock::now(); times_ms.push_back( std::chrono::duration(end - start).count()); sum_trials += report.num_trials; if (!report.success) { // A failed estimate counts as a full pose failure with worst-case error. rot_errs.push_back(180.0); trans_errs.push_back(90.0); recalls.push_back(0.0); precisions.push_back(0.0); continue; } double rot_err; double trans_err; PoseErrors(scene, report.model, &rot_err, &trans_err); if (rot_err < 5.0 && trans_err < 5.0) { ++num_success; } rot_errs.push_back(rot_err); trans_errs.push_back(trans_err); double recall; double precision; RecallPrecision(scene, report.inlier_mask, &recall, &precision); recalls.push_back(recall); precisions.push_back(precision); } Stats stats; stats.success_rate = 100.0 * num_success / num_problems; stats.recall = Mean(recalls); stats.precision = Mean(precisions); stats.rot_med = Percentile(rot_errs, 50); stats.rot_p90 = Percentile(rot_errs, 90); stats.trans_med = Percentile(trans_errs, 50); stats.trans_p90 = Percentile(trans_errs, 90); stats.time_mean_ms = Mean(times_ms); stats.time_p90_ms = Percentile(times_ms, 90); stats.avg_trials = sum_trials / num_problems; return stats; } void PrintRow(size_t num_points, double plane_fraction, double outlier_fraction, const char* method, const Stats& stats) { std::printf( "| %5zu | %5.0f%% | %4.0f%% | %-8s | %7.2f | %7.2f | %6.1f | %8.1f | " "%7.1f | %8.3f | %8.3f | %9.3f | %9.3f | %6.0f |\n", num_points, 100 * plane_fraction, 100 * outlier_fraction, method, stats.time_mean_ms, stats.time_p90_ms, stats.success_rate, stats.recall, stats.precision, stats.rot_med, stats.rot_p90, stats.trans_med, stats.trans_p90, stats.avg_trials); } } // namespace int main(int /*argc*/, char** /*argv*/) { constexpr int kNumProblems = 300; constexpr double kNoise = 0.5; // Pixel std-dev of observation noise. const std::vector point_counts = {200, 1000}; const std::vector plane_fractions = { 0.0, 0.3, 0.5, 0.8, 0.9, 0.95, 0.98}; const std::vector outlier_fractions = {0.0, 0.2, 0.4}; std::printf( "DEGENSAC vs plain LO-RANSAC for fundamental matrix estimation on " "synthetic\ntwo-view data with a dominant plane.\n\n"); std::printf( "- %d independent problems per configuration; both methods see identical " "data\n and identical RANSAC randomness per problem.\n" "- Observation noise: %.1f px std-dev; inlier threshold max_error=%.1f " "px.\n" "- `success` = %% of runs whose recovered pose is within 5 deg of ground " "truth.\n" "- `recall` = %% of true inliers recovered; `prec` = %% of reported " "inliers that\n are true inliers (both averaged over all problems, " "failures included).\n" "- `rot`/`trans` errors (deg) are over ALL returned models (a " "plane-corrupted\n model contributes its large error), so medians and " "p90 reflect degradation.\n\n", kNumProblems, kNoise, kMaxError); std::printf( "| Pts | Plane | Outl | Method | t mean | t p90 | Succ%% | " "Recall%% | Prec%% | Rot med | Rot p90 | Trans med | Trans p90 | " "Trials |\n"); std::printf( "|------:|-------:|-----:|:---------|--------:|--------:|-------:|" "--------:|-------:|---------:|---------:|----------:|----------:|" "-------:|\n"); for (size_t num_points : point_counts) { for (double plane_fraction : plane_fractions) { for (double outlier_fraction : outlier_fractions) { const Stats loransac_stats = RunConfig(Method::kLoRansac, num_points, plane_fraction, outlier_fraction, kNoise, kNumProblems); const Stats degensac_stats = RunConfig(Method::kDegensac, num_points, plane_fraction, outlier_fraction, kNoise, kNumProblems); PrintRow(num_points, plane_fraction, outlier_fraction, "loransac", loransac_stats); PrintRow(num_points, plane_fraction, outlier_fraction, "degensac", degensac_stats); } } } return 0; } colmap-4.2.0/benchmark/runtime/global_positioning.cc000066400000000000000000000150411524536416500226120ustar00rootroot00000000000000// Copyright (c), ETH Zurich and UNC Chapel Hill. // All rights reserved. // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are met: // // * Redistributions of source code must retain the above copyright // notice, this list of conditions and the following disclaimer. // // * Redistributions in binary form must reproduce the above copyright // notice, this list of conditions and the following disclaimer in the // documentation and/or other materials provided with the distribution. // // * Neither the name of ETH Zurich and UNC Chapel Hill nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE // ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE // LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR // CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF // SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS // INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN // CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) // ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE // POSSIBILITY OF SUCH DAMAGE. #include "colmap/estimators/global_positioning.h" #include "colmap/math/random.h" #include "colmap/scene/database_cache.h" #include "colmap/scene/database_sqlite.h" #include "colmap/scene/pose_graph.h" #include "colmap/scene/reconstruction.h" #include "colmap/scene/synthetic.h" #include #include #include #include using namespace colmap; struct CachedData { std::array dataset_args; Reconstruction reconstruction; colmap::PoseGraph pose_graph; }; static void BM_GlobalPositioning(benchmark::State& state) { FLAGS_minloglevel = 2; // Suppress INFO and WARNING logs. const std::array dataset_args = {state.range(0), state.range(1), state.range(2), state.range(3), state.range(4)}; const bool use_parameter_block_ordering = state.range(5); // Cache dataset to avoid resynthesizing for different ordering options. static std::unique_ptr cached; if (!cached || cached->dataset_args != dataset_args) { SetPRNGSeed(42); SyntheticDatasetOptions dataset_options; dataset_options.num_rigs = dataset_args[0]; dataset_options.num_cameras_per_rig = dataset_args[1]; dataset_options.num_frames_per_rig = dataset_args[2]; dataset_options.num_points3D = dataset_args[3]; // Compute sparsity from target number of neighbors per image. // sparsity ≈ 1 - num_neighbors / (num_images - 1) const int num_neighbors = dataset_args[4]; const int num_images = dataset_options.num_rigs * dataset_options.num_cameras_per_rig * dataset_options.num_frames_per_rig; dataset_options.match_sparsity = std::max( 0.0, 1.0 - static_cast(num_neighbors) / (num_images - 1)); dataset_options.match_config = SyntheticDatasetOptions::MatchConfig::SPARSE; dataset_options.two_view_geometry_has_relative_pose = true; cached = std::make_unique(); cached->dataset_args = dataset_args; auto database = Database::Open(kInMemorySqliteDatabasePath); Reconstruction gt_reconstruction; SynthesizeDataset(dataset_options, >_reconstruction, database.get()); DatabaseCache database_cache; DatabaseCache::Options cache_options; database_cache.Load(*database, cache_options); database.reset(); // Close database connection. cached->pose_graph.Load(*database_cache.CorrespondenceGraph()); cached->reconstruction = gt_reconstruction; for (const auto& [frame_id, _] : cached->reconstruction.Frames()) { Frame& frame = cached->reconstruction.Frame(frame_id); frame.SetRigFromWorld( Rigid3d(frame.RigFromWorld().rotation(), Eigen::Vector3d::Zero())); } } const Reconstruction& reconstruction = cached->reconstruction; const colmap::PoseGraph& pose_graph = cached->pose_graph; const int num_neighbors = dataset_args[4]; colmap::GlobalPositionerOptions base_options; base_options.use_gpu = false; base_options.random_seed = 42; base_options.solver_options.max_num_iterations = 50; base_options.solver_options.minimizer_progress_to_stdout = false; for (auto _ : state) { state.PauseTiming(); Reconstruction reconstruction_copy = reconstruction; colmap::GlobalPositionerOptions options = base_options; options.use_parameter_block_ordering = use_parameter_block_ordering; state.ResumeTiming(); colmap::GlobalPositioner positioner(options); positioner.Solve(pose_graph, reconstruction_copy); } state.counters["ord"] = use_parameter_block_ordering; state.counters["imgs"] = reconstruction.NumRegImages(); state.counters["rigs"] = reconstruction.NumRigs(); state.counters["cams"] = reconstruction.NumCameras(); state.counters["frms"] = reconstruction.NumRegFrames(); state.counters["pnts"] = reconstruction.NumPoints3D(); state.counters["nbrs"] = num_neighbors; } static void GenerateArguments(benchmark::Benchmark* b) { // Args: {num_rigs, num_cameras_per_rig, num_frames_per_rig, num_points3D, // num_neighbors, use_parameter_block_ordering} for (const int num_rigs : {1, 5}) { for (const int num_cameras_per_rig : {1, 3}) { for (const int num_frames_per_rig : {10, 50}) { for (const int num_points3D : {1000, 10000}) { for (const int num_neighbors : {10, 20}) { for (const bool use_parameter_block_ordering : {true, false}) { b->Args({num_rigs, num_cameras_per_rig, num_frames_per_rig, num_points3D, num_neighbors, use_parameter_block_ordering}); } } } } } } } BENCHMARK(BM_GlobalPositioning) ->Apply(GenerateArguments) ->Unit(benchmark::kMillisecond) ->UseRealTime(); BENCHMARK_MAIN(); colmap-4.2.0/benchmark/runtime/incremental_mapping.cc000066400000000000000000000146471524536416500227570ustar00rootroot00000000000000// Copyright (c), ETH Zurich and UNC Chapel Hill. // All rights reserved. // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are met: // // * Redistributions of source code must retain the above copyright // notice, this list of conditions and the following disclaimer. // // * Redistributions in binary form must reproduce the above copyright // notice, this list of conditions and the following disclaimer in the // documentation and/or other materials provided with the distribution. // // * Neither the name of ETH Zurich and UNC Chapel Hill nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE // ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE // LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR // CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF // SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS // INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN // CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) // ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE // POSSIBILITY OF SUCH DAMAGE. // End-to-end incremental mapping benchmark on synthetic data. Measures the // wall-clock runtime of a full reconstruction (registration, triangulation, // bundle adjustment) and is useful for evaluating the impact of changes to the // mapper or bundle adjustment. Parametrized over camera model and scene size; // the camera model determines which cost-function path bundle adjustment // exercises. All runs are deterministic (fixed seed, single thread) so the // produced reconstruction is stable and only the runtime varies across // configurations. #include "colmap/controllers/incremental_pipeline.h" #include "colmap/math/random.h" #include "colmap/scene/database.h" #include "colmap/scene/database_sqlite.h" #include "colmap/scene/reconstruction.h" #include "colmap/scene/reconstruction_manager.h" #include "colmap/scene/synthetic.h" #include "colmap/sensor/models.h" #include "colmap/util/logging.h" #include #include #include using namespace colmap; namespace { constexpr unsigned kSeed = 42; // Deterministic mapper options so all configurations perform identical work; // only the bundle-adjustment cost-function implementation varies between // builds. std::shared_ptr MakeOptions() { auto options = std::make_shared(); options->random_seed = kSeed; // Single-threaded to keep timings stable and isolate per-residual cost. options->num_threads = 1; options->extract_colors = false; return options; } void RunPipelineAndReport(benchmark::State& state, const std::shared_ptr& database) { std::shared_ptr largest; for (auto _ : state) { auto reconstruction_manager = std::make_shared(); IncrementalPipeline mapper(MakeOptions(), database, reconstruction_manager); mapper.Run(); state.PauseTiming(); largest = nullptr; for (size_t i = 0; i < reconstruction_manager->Size(); ++i) { const auto& reconstruction = reconstruction_manager->Get(i); if (largest == nullptr || reconstruction->NumRegImages() > largest->NumRegImages()) { largest = reconstruction; } } state.ResumeTiming(); } if (largest != nullptr) { state.counters["num_reg_images"] = largest->NumRegImages(); state.counters["num_points3d"] = largest->NumPoints3D(); state.counters["mean_track_length"] = largest->ComputeMeanTrackLength(); } else { state.SkipWithError("No reconstruction was produced"); } } std::vector DefaultCameraParams(const CameraModelId model_id) { // Nominal intrinsics for a 1024x768 image with moderate distortion. switch (model_id) { case PinholeCameraModel::model_id: return {1280, 1280, 512, 384}; case OpenCVCameraModel::model_id: return {1280, 1280, 512, 384, 0.05, 0.01, 0.001, 0.001}; default: LOG(FATAL) << "Unsupported camera model"; return {}; } } void BM_IncrementalMapping(benchmark::State& state, const CameraModelId camera_model_id) { SetPRNGSeed(kSeed); SyntheticDatasetOptions dataset_options; dataset_options.num_rigs = 1; dataset_options.num_cameras_per_rig = 1; dataset_options.num_frames_per_rig = static_cast(state.range(0)); dataset_options.num_points3D = static_cast(state.range(1)); dataset_options.camera_model_id = camera_model_id; dataset_options.camera_params = DefaultCameraParams(camera_model_id); // Sparse but connected view graph so per-image work stays bounded as the // image count grows. dataset_options.match_config = SyntheticDatasetOptions::MatchConfig::SPARSE; dataset_options.match_sparsity = 0.9; auto database = Database::Open(kInMemorySqliteDatabasePath); Reconstruction gt_reconstruction; SynthesizeDataset(dataset_options, >_reconstruction, database.get()); // Inject realistic observation noise so bundle adjustment performs // representative work. SyntheticNoiseOptions noise_options; noise_options.point2D_stddev = 1.0; noise_options.point3D_stddev = 0.05; noise_options.rig_from_world_translation_stddev = 0.01; noise_options.rig_from_world_rotation_stddev = 1.0; SynthesizeNoise(noise_options, >_reconstruction, database.get()); RunPipelineAndReport(state, database); } void AddArguments(::benchmark::Benchmark* b) { b->ArgNames({"num_frames", "num_points3D"}); for (const int num_frames : {25, 50, 100}) { b->Args({num_frames, /*num_points3D=*/1000}); } b->Unit(benchmark::kMillisecond) ->Iterations(1) ->Repetitions(5) ->ReportAggregatesOnly(true) ->UseRealTime(); } } // namespace BENCHMARK_CAPTURE(BM_IncrementalMapping, PINHOLE, PinholeCameraModel::model_id) ->Apply(AddArguments); BENCHMARK_CAPTURE(BM_IncrementalMapping, OPENCV, OpenCVCameraModel::model_id) ->Apply(AddArguments); BENCHMARK_MAIN(); colmap-4.2.0/cmake/000077500000000000000000000000001524536416500140635ustar00rootroot00000000000000colmap-4.2.0/cmake/CMakeHelper.cmake000066400000000000000000000160601524536416500172100ustar00rootroot00000000000000if(POLICY CMP0043) cmake_policy(SET CMP0043 NEW) endif() if(POLICY CMP0054) cmake_policy(SET CMP0054 NEW) endif() # Avoid warning about DOWNLOAD_EXTRACT_TIMESTAMP in CMake 3.24: if(CMAKE_VERSION VERSION_GREATER_EQUAL "3.24") cmake_policy(SET CMP0135 NEW) endif() if(CMAKE_VERSION VERSION_GREATER_EQUAL "3.30") cmake_policy(SET CMP0167 NEW) endif() # Determine project compiler. if(CMAKE_CXX_COMPILER_ID STREQUAL "MSVC") set(IS_MSVC TRUE) endif() if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU") set(IS_GNU TRUE) endif() if(CMAKE_CXX_COMPILER_ID MATCHES ".*Clang") set(IS_CLANG TRUE) endif() # Determine project architecture. if(CMAKE_SYSTEM_PROCESSOR MATCHES "[ix].?86|amd64|AMD64") set(IS_X86 TRUE) elseif(CMAKE_SYSTEM_PROCESSOR MATCHES "aarch64|arm64|ARM64") set(IS_ARM64 TRUE) endif() # Determine project operating system. string(REGEX MATCH "Linux" IS_LINUX ${CMAKE_SYSTEM_NAME}) string(REGEX MATCH "DragonFly|BSD" IS_BSD ${CMAKE_SYSTEM_NAME}) string(REGEX MATCH "SunOS" IS_SOLARIS ${CMAKE_SYSTEM_NAME}) if(WIN32) set(IS_WINDOWS TRUE BOOL INTERNAL) endif() if(APPLE) set(IS_MACOS TRUE BOOL INTERNAL) endif() string(TOLOWER "${CMAKE_BUILD_TYPE}" CMAKE_BUILD_TYPE_LOWER) if(CMAKE_BUILD_TYPE_LOWER STREQUAL "debug" OR CMAKE_BUILD_TYPE_LOWER STREQUAL "relwithdebinfo") set(IS_DEBUG TRUE) endif() # Enable solution folders. set_property(GLOBAL PROPERTY USE_FOLDERS ON) set(CMAKE_TARGETS_ROOT_FOLDER "cmake") set_property(GLOBAL PROPERTY PREDEFINED_TARGETS_FOLDER ${CMAKE_TARGETS_ROOT_FOLDER}) set(COLMAP_TARGETS_ROOT_FOLDER "colmap_targets") set(COLMAP_SRC_ROOT_FOLDER "colmap_sources") # This macro will search for source files in a given directory, will add them # to a source group (folder within a project), and will then return paths to # each of the found files. The usage of the macro is as follows: # COLMAP_ADD_SOURCE_DIR( # # # ) macro(COLMAP_ADD_SOURCE_DIR SRC_DIR SRC_VAR) # Create the list of expressions to be used in the search. set(GLOB_EXPRESSIONS "") foreach(ARG ${ARGN}) list(APPEND GLOB_EXPRESSIONS ${SRC_DIR}/${ARG}) endforeach() # Perform the search for the source files. file(GLOB ${SRC_VAR} RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} ${GLOB_EXPRESSIONS}) # Create the source group. string(REPLACE "/" "\\" GROUP_NAME ${SRC_DIR}) source_group(${GROUP_NAME} FILES ${${SRC_VAR}}) # Clean-up. unset(GLOB_EXPRESSIONS) unset(ARG) unset(GROUP_NAME) endmacro(COLMAP_ADD_SOURCE_DIR) # Replacement for the normal add_library() command. The syntax remains the same # in that the first argument is the target name, and the following arguments # are the source files to use when building the target. # Supports TYPE argument: STATIC (default) or INTERFACE (header-only libraries). # For INTERFACE libraries, use INTERFACE_LINK_LIBS instead of PRIVATE/PUBLIC_LINK_LIBS. macro(COLMAP_ADD_LIBRARY) set(options) set(oneValueArgs TYPE) set(multiValueArgs NAME SRCS PRIVATE_LINK_LIBS PUBLIC_LINK_LIBS INTERFACE_LINK_LIBS) cmake_parse_arguments(COLMAP_ADD_LIBRARY "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN}) if(COLMAP_ADD_LIBRARY_TYPE STREQUAL "INTERFACE") # Header-only library add_library(${COLMAP_ADD_LIBRARY_NAME} INTERFACE) set_target_properties(${COLMAP_ADD_LIBRARY_NAME} PROPERTIES FOLDER ${COLMAP_TARGETS_ROOT_FOLDER}/${FOLDER_NAME}) target_link_libraries(${COLMAP_ADD_LIBRARY_NAME} INTERFACE ${COLMAP_ADD_LIBRARY_INTERFACE_LINK_LIBS}) target_compile_definitions(${COLMAP_ADD_LIBRARY_NAME} INTERFACE ${COLMAP_COMPILE_DEFINITIONS}) else() # Regular library (TYPE can be STATIC to override BUILD_SHARED_LIBS). add_library(${COLMAP_ADD_LIBRARY_NAME} ${COLMAP_ADD_LIBRARY_TYPE} ${COLMAP_ADD_LIBRARY_SRCS}) set_target_properties(${COLMAP_ADD_LIBRARY_NAME} PROPERTIES FOLDER ${COLMAP_TARGETS_ROOT_FOLDER}/${FOLDER_NAME}) if(CLANG_TIDY_EXE) set_target_properties(${COLMAP_ADD_LIBRARY_NAME} PROPERTIES CXX_CLANG_TIDY "${CLANG_TIDY_EXE};-header-filter=.*") endif() target_link_libraries(${COLMAP_ADD_LIBRARY_NAME} PRIVATE ${COLMAP_ADD_LIBRARY_PRIVATE_LINK_LIBS} PUBLIC ${COLMAP_ADD_LIBRARY_PUBLIC_LINK_LIBS}) target_compile_definitions(${COLMAP_ADD_LIBRARY_NAME} PUBLIC ${COLMAP_COMPILE_DEFINITIONS}) endif() endmacro(COLMAP_ADD_LIBRARY) # Replacement for the normal add_executable() command. The syntax remains the # same in that the first argument is the target name, and the following # arguments are the source files to use when building the target. macro(COLMAP_ADD_EXECUTABLE) set(options) set(oneValueArgs) set(multiValueArgs NAME SRCS LINK_LIBS) cmake_parse_arguments(COLMAP_ADD_EXECUTABLE "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN}) add_executable(${COLMAP_ADD_EXECUTABLE_NAME} ${COLMAP_ADD_EXECUTABLE_SRCS}) set_target_properties(${COLMAP_ADD_EXECUTABLE_NAME} PROPERTIES FOLDER ${COLMAP_TARGETS_ROOT_FOLDER}/${FOLDER_NAME}) target_link_libraries(${COLMAP_ADD_EXECUTABLE_NAME} ${COLMAP_ADD_EXECUTABLE_LINK_LIBS}) if(VCPKG_BUILD) install(TARGETS ${COLMAP_ADD_EXECUTABLE_NAME} DESTINATION tools/) else() install(TARGETS ${COLMAP_ADD_EXECUTABLE_NAME} DESTINATION ${CMAKE_INSTALL_BINDIR}) endif() if(CLANG_TIDY_EXE) set_target_properties(${COLMAP_ADD_EXECUTABLE_NAME} PROPERTIES CXX_CLANG_TIDY "${CLANG_TIDY_EXE};-header-filter=.*") endif() target_compile_definitions(${COLMAP_ADD_EXECUTABLE_NAME} PRIVATE ${COLMAP_COMPILE_DEFINITIONS}) endmacro(COLMAP_ADD_EXECUTABLE) # Wrapper for test executables. macro(COLMAP_ADD_TEST) set(options) set(oneValueArgs) set(multiValueArgs NAME SRCS LINK_LIBS) cmake_parse_arguments(COLMAP_ADD_TEST "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN}) if(TESTS_ENABLED) # ${ARGN} will store the list of link libraries. set(COLMAP_ADD_TEST_TARGET "colmap_${FOLDER_NAME}_${COLMAP_ADD_TEST_NAME}") add_executable(${COLMAP_ADD_TEST_TARGET} ${COLMAP_ADD_TEST_SRCS}) set_target_properties(${COLMAP_ADD_TEST_TARGET} PROPERTIES FOLDER ${COLMAP_TARGETS_ROOT_FOLDER}/${FOLDER_NAME} OUTPUT_NAME "${COLMAP_ADD_TEST_NAME}") if(CLANG_TIDY_EXE) set_target_properties(${COLMAP_ADD_TEST_TARGET} PROPERTIES CXX_CLANG_TIDY "${CLANG_TIDY_EXE};-header-filter=.*") endif() target_link_libraries(${COLMAP_ADD_TEST_TARGET} ${COLMAP_ADD_TEST_LINK_LIBS} colmap_gtest_main) add_test(NAME "${FOLDER_NAME}/${COLMAP_ADD_TEST_NAME}" COMMAND $) if(IS_MSVC) install(TARGETS ${COLMAP_ADD_TEST_TARGET} DESTINATION ${CMAKE_INSTALL_BINDIR}) endif() target_compile_definitions(${COLMAP_ADD_TEST_TARGET} PRIVATE ${COLMAP_COMPILE_DEFINITIONS}) endif() endmacro(COLMAP_ADD_TEST) colmap-4.2.0/cmake/CMakeUninstall.cmake.in000066400000000000000000000015331524536416500203460ustar00rootroot00000000000000if(NOT EXISTS "@CMAKE_CURRENT_BINARY_DIR@/install_manifest.txt") message(FATAL_ERROR "Cannot find install manifest: @CMAKE_CURRENT_BINARY_DIR@/install_manifest.txt") endif() file(READ "@CMAKE_CURRENT_BINARY_DIR@/install_manifest.txt" files) string(REGEX REPLACE "\n" ";" files "${files}") foreach(file ${files}) message(STATUS "Uninstalling $ENV{DESTDIR}${file}") if(IS_SYMLINK "$ENV{DESTDIR}${file}" OR EXISTS "$ENV{DESTDIR}${file}") exec_program("@CMAKE_COMMAND@" ARGS "-E remove \"$ENV{DESTDIR}${file}\"" OUTPUT_VARIABLE rm_out RETURN_VALUE rm_retval) if(NOT "${rm_retval}" STREQUAL 0) message(FATAL_ERROR "Problem when removing $ENV{DESTDIR}${file}") endif() else() message(STATUS "File $ENV{DESTDIR}${file} does not exist.") endif() endforeach() colmap-4.2.0/cmake/FindCHOLMOD.cmake000066400000000000000000000110521524536416500167520ustar00rootroot00000000000000# Copyright (c), ETH Zurich and UNC Chapel Hill. # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are met: # # * Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer. # # * Redistributions in binary form must reproduce the above copyright # notice, this list of conditions and the following disclaimer in the # documentation and/or other materials provided with the distribution. # # * Neither the name of ETH Zurich and UNC Chapel Hill nor the names of # its contributors may be used to endorse or promote products derived # from this software without specific prior written permission. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. # Find package module for CHOLMOD library. # # The following variables are set by this module: # # CHOLMOD_FOUND: TRUE if CHOLMOD is found. # CHOLMOD::CHOLMOD: Imported target to link against. # # The following variables control the behavior of this module: # # CHOLMOD_INCLUDE_DIR_HINTS: List of additional directories in which to # search for CHOLMOD includes. # CHOLMOD_LIBRARY_DIR_HINTS: List of additional directories in which to # search for CHOLMOD libraries. set(CHOLMOD_INCLUDE_DIR_HINTS "" CACHE PATH "CHOLMOD include directory") set(CHOLMOD_LIBRARY_DIR_HINTS "" CACHE PATH "CHOLMOD library directory") unset(CHOLMOD_FOUND) unset(CHOLMOD_INCLUDE_DIRS) unset(CHOLMOD_LIBRARIES) find_package(CHOLMOD CONFIG QUIET) if(TARGET CHOLMOD::CHOLMOD) set(CHOLMOD_FOUND TRUE) message(STATUS "Found CHOLMOD") message(STATUS " Target : CHOLMOD::CHOLMOD") else() list(APPEND CHOLMOD_INCLUDE_SEARCH_PATHS ${CHOLMOD_INCLUDE_DIR_HINTS} /usr/include /usr/local/include /sw/include /opt/include /opt/local/include) # Some distros don't package suitesparse under a /suitesparse subdirectory (e.g. NixOS). # Search for both layouts separately so that the suitesparse/ subdirectory # layout is reliably preferred across all search paths. A single find_path # call with both names would iterate search paths first and names second, # which could pick up a bare cholmod.h from an earlier path over # suitesparse/cholmod.h from a later one. find_path(CHOLMOD_INCLUDE_DIRS NAMES suitesparse/cholmod.h PATHS ${CHOLMOD_INCLUDE_SEARCH_PATHS}) if(NOT CHOLMOD_INCLUDE_DIRS) unset(CHOLMOD_INCLUDE_DIRS CACHE) find_path(CHOLMOD_INCLUDE_DIRS NAMES cholmod.h PATHS ${CHOLMOD_INCLUDE_SEARCH_PATHS}) endif() find_library(CHOLMOD_LIBRARIES NAMES cholmod PATHS ${CHOLMOD_LIBRARY_DIR_HINTS} /usr/lib64 /usr/lib /usr/local/lib64 /usr/local/lib /sw/lib /opt/lib /opt/local/lib) if(CHOLMOD_INCLUDE_DIRS AND CHOLMOD_LIBRARIES) set(CHOLMOD_FOUND TRUE) message(STATUS "Found CHOLMOD") message(STATUS " Includes : ${CHOLMOD_INCLUDE_DIRS}") message(STATUS " Libraries : ${CHOLMOD_LIBRARIES}") else() set(CHOLMOD_FOUND FALSE) endif() if(EXISTS "${CHOLMOD_INCLUDE_DIRS}/suitesparse/cholmod.h") set(CHOLMOD_INTERFACE_INCLUDE_DIRS "${CHOLMOD_INCLUDE_DIRS}/suitesparse") else() set(CHOLMOD_INTERFACE_INCLUDE_DIRS "${CHOLMOD_INCLUDE_DIRS}") endif() add_library(CHOLMOD::CHOLMOD INTERFACE IMPORTED) target_include_directories( CHOLMOD::CHOLMOD INTERFACE ${CHOLMOD_INTERFACE_INCLUDE_DIRS}) target_link_libraries( CHOLMOD::CHOLMOD INTERFACE ${CHOLMOD_LIBRARIES}) endif() if(NOT CHOLMOD_FOUND AND CHOLMOD_FIND_REQUIRED) message(FATAL_ERROR "Could not find CHOLMOD") endif() colmap-4.2.0/cmake/FindCryptoPP.cmake000066400000000000000000000065501524536416500174140ustar00rootroot00000000000000# Copyright (c), ETH Zurich and UNC Chapel Hill. # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are met: # # * Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer. # # * Redistributions in binary form must reproduce the above copyright # notice, this list of conditions and the following disclaimer in the # documentation and/or other materials provided with the distribution. # # * Neither the name of ETH Zurich and UNC Chapel Hill nor the names of # its contributors may be used to endorse or promote products derived # from this software without specific prior written permission. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. # Find package module for CryptoPP library. # # The following variables are set by this module: # # CryptoPP_FOUND: TRUE if CryptoPP is found. # cryptopp: Imported target to link against. # # The following variables control the behavior of this module: # # CryptoPP_INCLUDE_DIR_HINTS: List of additional directories in which to # search for CryptoPP includes. # CryptoPP_LIBRARY_DIR_HINTS: List of additional directories in which to # search for CryptoPP libraries. set(CryptoPP_INCLUDE_DIR_HINTS "" CACHE PATH "CryptoPP include directory") set(CryptoPP_LIBRARY_DIR_HINTS "" CACHE PATH "CryptoPP library directory") unset(CryptoPP_FOUND) unset(CryptoPP_INCLUDE_DIRS) unset(CryptoPP_LIBRARIES) list(APPEND CryptoPP_CHECK_INCLUDE_DIRS ${CryptoPP_INCLUDE_DIR_HINTS} /usr/include /usr/local/include /opt/include /opt/local/include ) list(APPEND CryptoPP_CHECK_LIBRARY_DIRS ${CryptoPP_LIBRARY_DIR_HINTS} /usr/lib /usr/local/lib /opt/lib /opt/local/lib ) find_path(CryptoPP_INCLUDE_DIRS NAMES cryptopp/cryptlib.h PATHS ${CryptoPP_CHECK_INCLUDE_DIRS}) find_library(CryptoPP_LIBRARIES NAMES cryptopp PATHS ${CryptoPP_CHECK_LIBRARY_DIRS}) if(CryptoPP_INCLUDE_DIRS AND CryptoPP_LIBRARIES) set(CryptoPP_FOUND TRUE) endif() if(CryptoPP_FOUND) message(STATUS "Found CryptoPP") message(STATUS " Includes : ${CryptoPP_INCLUDE_DIRS}") message(STATUS " Libraries : ${CryptoPP_LIBRARIES}") else() if(CryptoPP_FIND_REQUIRED) message(FATAL_ERROR "Could not find CryptoPP") endif() endif() add_library(cryptopp INTERFACE IMPORTED) target_include_directories( cryptopp INTERFACE ${CryptoPP_INCLUDE_DIRS}) target_link_libraries( cryptopp INTERFACE ${CryptoPP_LIBRARIES}) colmap-4.2.0/cmake/FindDependencies.cmake000066400000000000000000000703501524536416500202610ustar00rootroot00000000000000if(COLMAP_FIND_QUIETLY) set(COLMAP_FIND_TYPE QUIET) else() set(COLMAP_FIND_TYPE REQUIRED) endif() # Track all the compile definitions set(COLMAP_COMPILE_DEFINITIONS) if(LSD_ENABLED) list(APPEND COLMAP_COMPILE_DEFINITIONS COLMAP_LSD_ENABLED) message(STATUS "Enabling LSD support") else() message(STATUS "Disabling LSD support") endif() find_package(OpenMP REQUIRED COMPONENTS C CXX) find_package(Boost ${COLMAP_FIND_TYPE} COMPONENTS graph program_options OPTIONAL_COMPONENTS system) # Hash map backend selection for the scene/SfM containers. Adds the compile # definition consumed by src/colmap/util/hash_containers.h. Both backends are # header-only (boost-unordered is provided by the Boost::boost target), so no # extra linking is required. # # BOOST (boost::unordered_flat/node maps) is preferred, but its node maps # (boost::unordered_node_map) require Boost >= 1.84. When COLMAP_HASH_MAP_BACKEND # is empty we auto-select BOOST if the found Boost is new enough, else STD (so # e.g. builds against the system Boost on older distributions keep working). # # Note: downstream consumers re-run this file via find_package(colmap) with # COLMAP_HASH_MAP_BACKEND unset; they get the actual COLMAP_HASH_* macro from the # exported colmap targets, so the value re-derived here is only used to keep the # selection message and any local sources consistent. set(COLMAP_HASH_MAP_BACKEND_MIN_BOOST_VERSION "1.84.0") if(DEFINED Boost_VERSION_STRING AND Boost_VERSION_STRING) set(_colmap_boost_version "${Boost_VERSION_STRING}") else() set(_colmap_boost_version "${Boost_VERSION}") endif() string(TOUPPER "${COLMAP_HASH_MAP_BACKEND}" COLMAP_HASH_MAP_BACKEND) if(NOT COLMAP_HASH_MAP_BACKEND) if(_colmap_boost_version VERSION_GREATER_EQUAL "${COLMAP_HASH_MAP_BACKEND_MIN_BOOST_VERSION}") set(COLMAP_HASH_MAP_BACKEND "BOOST") else() set(COLMAP_HASH_MAP_BACKEND "STD") endif() endif() if(COLMAP_HASH_MAP_BACKEND STREQUAL "STD") list(APPEND COLMAP_COMPILE_DEFINITIONS COLMAP_HASH_STD) elseif(COLMAP_HASH_MAP_BACKEND STREQUAL "BOOST") if(_colmap_boost_version VERSION_LESS "${COLMAP_HASH_MAP_BACKEND_MIN_BOOST_VERSION}") message(FATAL_ERROR "COLMAP_HASH_MAP_BACKEND=BOOST requires Boost >= " "${COLMAP_HASH_MAP_BACKEND_MIN_BOOST_VERSION} " "(boost::unordered_node_map), but found Boost " "${_colmap_boost_version}. Upgrade Boost or set " "-DCOLMAP_HASH_MAP_BACKEND=STD.") endif() list(APPEND COLMAP_COMPILE_DEFINITIONS COLMAP_HASH_BOOST) else() message(FATAL_ERROR "Unknown COLMAP_HASH_MAP_BACKEND " "'${COLMAP_HASH_MAP_BACKEND}' (expected STD, BOOST or empty)") endif() message(STATUS "Using ${COLMAP_HASH_MAP_BACKEND} hash map backend " "(Boost ${_colmap_boost_version})") find_package(Eigen3 ${COLMAP_FIND_TYPE}) find_package(OpenImageIO ${COLMAP_FIND_TYPE}) find_package(Metis ${COLMAP_FIND_TYPE}) find_package(SQLite3 ${COLMAP_FIND_TYPE}) # Older CMake versions define SQLite::SQLite3 instead of SQLite3::SQLite3. if(NOT TARGET SQLite3::SQLite3 AND TARGET SQLite::SQLite3) add_library(SQLite3::SQLite3 ALIAS SQLite::SQLite3) endif() set(OpenGL_GL_PREFERENCE GLVND) find_package(OpenGL ${COLMAP_FIND_TYPE}) find_package(Glew ${COLMAP_FIND_TYPE}) find_package(Git) find_package(CHOLMOD REQUIRED) # Ceres and glog expose gflags::gflags in their interface dependencies, but gflags # only defines the namespaced target when GFLAGS_USE_TARGET_NAMESPACE is ON. Bridge # the gap so consumers can resolve the expected target when only the plain target exists. find_package(gflags CONFIG QUIET) if(NOT TARGET gflags::gflags AND TARGET gflags) add_library(gflags::gflags ALIAS gflags) endif() # Ceres is found before Glog on purpose. Some distributions (e.g. Fedora) ship a # Ceres whose bundled FindGlog.cmake unconditionally calls add_library(glog::glog) # in module mode. If we created the glog::glog target first, that call collides # with a "target already exists" error (see issue #3347). By finding Ceres first, # Ceres creates glog::glog itself, and our subsequent find_package(Glog) reuses # the existing target instead. find_package(Ceres ${COLMAP_FIND_TYPE}) if(NOT TARGET Ceres::ceres) # Older Ceres versions don't come with an imported interface target. add_library(Ceres::ceres INTERFACE IMPORTED) target_include_directories( Ceres::ceres INTERFACE ${CERES_INCLUDE_DIRS}) target_link_libraries( Ceres::ceres INTERFACE ${CERES_LIBRARIES}) endif() find_package(Glog ${COLMAP_FIND_TYPE}) if(DEFINED glog_VERSION_MAJOR) # Older versions of glog don't export version variables. list(APPEND COLMAP_COMPILE_DEFINITIONS GLOG_VERSION_MAJOR=${glog_VERSION_MAJOR}) list(APPEND COLMAP_COMPILE_DEFINITIONS GLOG_VERSION_MINOR=${glog_VERSION_MINOR}) endif() if(TESTS_ENABLED) find_package(GTest ${COLMAP_FIND_TYPE}) endif() if(HIP_ENABLED) # Locate the ROCm installation. Precedence: an explicit -DROCM_PATH, then the # ROCM_PATH environment variable, then a pip/venv ROCm install (AMD's TheRock # packaging exposes a "rocm-sdk" helper that reports its own root), then the # system default /opt/rocm. This lets a non-default install (e.g. a Python # virtualenv) be picked up without hand-setting paths. find_program(ROCM_SDK_EXECUTABLE rocm-sdk) set(_rocm_path_default "/opt/rocm") if(DEFINED ENV{ROCM_PATH}) set(_rocm_path_default "$ENV{ROCM_PATH}") elseif(ROCM_SDK_EXECUTABLE) execute_process( COMMAND "${ROCM_SDK_EXECUTABLE}" path --root OUTPUT_VARIABLE _rocm_sdk_root OUTPUT_STRIP_TRAILING_WHITESPACE ERROR_QUIET RESULT_VARIABLE _rocm_sdk_root_result) if(_rocm_sdk_root_result EQUAL 0 AND IS_DIRECTORY "${_rocm_sdk_root}") set(_rocm_path_default "${_rocm_sdk_root}") endif() endif() set(ROCM_PATH "${_rocm_path_default}" CACHE PATH "Path to ROCm installation") list(APPEND CMAKE_PREFIX_PATH "${ROCM_PATH}") find_package(hip REQUIRED) find_package(hiprand REQUIRED) find_package(rocrand REQUIRED) # enable_language(HIP) introduces a separate CMake HIP language with its # own flag namespace (CMAKE_HIP_FLAGS / CMAKE_HIP_ARCHITECTURES). Only # files marked with set_source_files_properties(... LANGUAGE HIP) are # compiled by the HIP toolchain; ordinary C++ files keep using the host # compiler. This is the same pattern PyTorch uses to compile a small # number of HIP translation units inside an otherwise plain C++ build. enable_language(HIP) if(NOT DEFINED CMAKE_HIP_ARCHITECTURES OR CMAKE_HIP_ARCHITECTURES STREQUAL "") # When the user does not pin the target architectures, try to discover # them from the local ROCm install via "rocm-sdk targets" (TheRock); it # prints the gfx IDs the SDK was built for as a Python-style list, e.g. # ['gfx1100', 'gfx1101']. Extract the gfx tokens regardless of quoting or # separators, and fall back to a portable default set of validated parts. set(_hip_archs "") if(ROCM_SDK_EXECUTABLE) execute_process( COMMAND "${ROCM_SDK_EXECUTABLE}" targets OUTPUT_VARIABLE _rocm_sdk_targets OUTPUT_STRIP_TRAILING_WHITESPACE ERROR_QUIET RESULT_VARIABLE _rocm_sdk_targets_result) if(_rocm_sdk_targets_result EQUAL 0) string(REGEX MATCHALL "gfx[0-9a-fA-F]+" _hip_archs "${_rocm_sdk_targets}") endif() endif() if(NOT _hip_archs) set(_hip_archs "gfx90a;gfx942;gfx1100") endif() set(CMAKE_HIP_ARCHITECTURES "${_hip_archs}" CACHE STRING "AMD GPU architectures to compile HIP code for" FORCE) endif() list(APPEND COLMAP_COMPILE_DEFINITIONS COLMAP_HIP_ENABLED) endif() if(CGAL_ENABLED) set(CGAL_DO_NOT_WARN_ABOUT_CMAKE_BUILD_TYPE TRUE) # We do not use CGAL data. This prevents an unnecessary warning by CMake. set(CGAL_DATA_DIR "unused") find_package(CGAL ${COLMAP_FIND_TYPE}) endif() if(CGAL_FOUND) list(APPEND COLMAP_COMPILE_DEFINITIONS COLMAP_CGAL_ENABLED) list(APPEND CGAL_LIBRARY ${CGAL_LIBRARIES}) message(STATUS "Found CGAL") message(STATUS " Includes : ${CGAL_INCLUDE_DIRS}") message(STATUS " Libraries : ${CGAL_LIBRARY}") if(NOT TARGET CGAL) # Older CGAL versions don't come with an imported interface target. add_library(CGAL INTERFACE IMPORTED) target_include_directories( CGAL INTERFACE ${CGAL_INCLUDE_DIRS} ${GMP_INCLUDE_DIR}) target_link_libraries( CGAL INTERFACE ${CGAL_LIBRARY} ${GMP_LIBRARIES}) endif() list(APPEND COLMAP_LINK_DIRS ${CGAL_LIBRARIES_DIR}) else() if(CGAL_ENABLED) set(CGAL_ENABLED OFF) message(STATUS "Disabling CGAL support (not found)") else() message(STATUS "Disabling CGAL support") endif() endif() if(DOWNLOAD_ENABLED) # The OpenSSL package in vcpkg seems broken under Windows and leads to # missing certificate verification when connecting to SSL servers. We # therefore use curl[sspi] (i.e., native Windows SSL/TLS) under Windows # and curl[openssl] otherwise. find_package(CURL QUIET) set(CRYPTO_FOUND FALSE) if(IS_MSVC AND IS_ARM64) # OpenSSL crashes for ARM64 under Windows. We therefore fall back to # CryptoPP as an alternative to OpenSSL for SHA256 computation. find_package(CryptoPP QUIET) if(CryptoPP_FOUND) set(CRYPTO_FOUND TRUE) else() message(STATUS "CryptoPP not found") endif() else() find_package(OpenSSL QUIET COMPONENTS Crypto) if(OpenSSL_FOUND) set(CRYPTO_FOUND TRUE) else() message(STATUS "OpenSSL::Crypto not found") endif() endif() if(CURL_FOUND AND CRYPTO_FOUND) message(STATUS "Enabling download support") list(APPEND COLMAP_COMPILE_DEFINITIONS COLMAP_DOWNLOAD_ENABLED) else() set(DOWNLOAD_ENABLED OFF) message(STATUS "Disabling download support (Curl/Crypto not found)") endif() else() message(STATUS "Disabling download support") endif() if(NOT FETCH_POSELIB) find_package(PoseLib ${COLMAP_FIND_TYPE}) endif() if(NOT FETCH_FAISS) find_package(faiss ${COLMAP_FIND_TYPE}) endif() set(COLMAP_LINK_DIRS ${Boost_LIBRARY_DIRS}) set(CUDA_MIN_VERSION "7.0") if(CUDA_ENABLED) if(CMAKE_VERSION VERSION_LESS 3.17) find_package(CUDA QUIET) if(CUDA_FOUND) message(STATUS "Found CUDA version ${CUDA_VERSION} installed in " "${CUDA_TOOLKIT_ROOT_DIR} via legacy CMake (<3.17) module. " "Using the legacy CMake module means that any installation of " "COLMAP will require that the CUDA libraries are " "available under LD_LIBRARY_PATH.") message(STATUS "Found CUDA ") message(STATUS " Includes : ${CUDA_INCLUDE_DIRS}") message(STATUS " Libraries : ${CUDA_LIBRARIES}") enable_language(CUDA) macro(declare_imported_cuda_target module) add_library(CUDA::${module} INTERFACE IMPORTED) target_include_directories( CUDA::${module} INTERFACE ${CUDA_INCLUDE_DIRS}) target_link_libraries( CUDA::${module} INTERFACE ${CUDA_${module}_LIBRARY} ${ARGN}) endmacro() declare_imported_cuda_target(cudart ${CUDA_LIBRARIES}) declare_imported_cuda_target(curand ${CUDA_LIBRARIES}) set(CUDAToolkit_VERSION "${CUDA_VERSION_STRING}") set(CUDAToolkit_BIN_DIR "${CUDA_TOOLKIT_ROOT_DIR}/bin") else() message(STATUS "Disabling CUDA support (not found)") endif() else() find_package(CUDAToolkit QUIET) if(CUDAToolkit_FOUND) set(CUDA_FOUND ON) enable_language(CUDA) else() message(STATUS "Disabling CUDA support (not found)") endif() endif() else() message(STATUS "Disabling CUDA support") endif() if(CUDA_ENABLED AND CUDA_FOUND) if(NOT DEFINED CMAKE_CUDA_ARCHITECTURES) set(CMAKE_CUDA_ARCHITECTURES "native") endif() # Caspar's Symforce-generated kernels use cooperative_groups::labeled_partition # and atomicAdd_block, which require compute capability >= 7.0. Fail early with # a clear message instead of a cryptic nvcc error deep in the kernel build. The # numeric check handles list entries and -real/-virtual suffixes; the special # values native/all/all-major cannot be resolved statically here, so they only # get a warning (nvcc may fall back to an older default arch in build # environments without a visible GPU >= 7.0, e.g. containerized builds). if(CASPAR_ENABLED) foreach(_caspar_arch IN LISTS CMAKE_CUDA_ARCHITECTURES) string(REGEX MATCH "^([0-9]+)" _caspar_arch_num "${_caspar_arch}") if(_caspar_arch_num AND _caspar_arch_num LESS 70) message(FATAL_ERROR "CASPAR_ENABLED requires CUDA architecture >= 70 (compute " "capability 7.0), but CMAKE_CUDA_ARCHITECTURES contains " "'${_caspar_arch}'. Set -DCMAKE_CUDA_ARCHITECTURES to 70+.") endif() endforeach() if(CMAKE_CUDA_ARCHITECTURES MATCHES "native|all|all-major") message(WARNING "CASPAR_ENABLED with CMAKE_CUDA_ARCHITECTURES='${CMAKE_CUDA_ARCHITECTURES}': " "Caspar requires compute capability >= 7.0, which cannot be " "verified statically for this value. In an environment without a " "visible GPU >= 7.0 (e.g. containerized builds) nvcc may fall back " "to an older default arch and fail with cryptic kernel errors. " "Set -DCMAKE_CUDA_ARCHITECTURES explicitly (e.g. 75, 86).") endif() endif() list(APPEND COLMAP_COMPILE_DEFINITIONS COLMAP_CUDA_ENABLED) # Do not show warnings if the architectures are deprecated. set(CMAKE_CUDA_FLAGS "${CMAKE_CUDA_FLAGS} -Wno-deprecated-gpu-targets") # Suppress warnings related to Eigen: # Calling a constexpr __host__ function from a __host__ __device__ function is not allowed. set(CMAKE_CUDA_FLAGS "${CMAKE_CUDA_FLAGS} --expt-relaxed-constexpr") # Explicitly set PIC flags for CUDA targets. if(NOT IS_MSVC) set(CMAKE_CUDA_FLAGS "${CMAKE_CUDA_FLAGS} --compiler-options -fPIC") endif() # Handle MSVC runtime library for CUDA to support static CRT linking. # CMake's default CUDA flags use /MD (dynamic), but if the user is building # with static CRT (/MT), we need to override the CUDA flags to match. if(IS_MSVC) # Detect the runtime library from CMAKE_MSVC_RUNTIME_LIBRARY or CXX flags set(_COLMAP_USE_STATIC_RUNTIME OFF) if(DEFINED CMAKE_MSVC_RUNTIME_LIBRARY) if(CMAKE_MSVC_RUNTIME_LIBRARY MATCHES "MultiThreaded" AND NOT CMAKE_MSVC_RUNTIME_LIBRARY MATCHES "DLL") set(_COLMAP_USE_STATIC_RUNTIME ON) endif() elseif(CMAKE_CXX_FLAGS_DEBUG MATCHES "/MTd" OR CMAKE_CXX_FLAGS_RELEASE MATCHES "/MT[^d]" OR CMAKE_CXX_FLAGS MATCHES "/MT") set(_COLMAP_USE_STATIC_RUNTIME ON) endif() if(_COLMAP_USE_STATIC_RUNTIME) message(STATUS "CUDA: Using static MSVC runtime library (/MT)") # Replace /MD with /MT in CUDA flags for each build type foreach(_BUILD_TYPE DEBUG RELEASE RELWITHDEBINFO MINSIZEREL) if(DEFINED CMAKE_CUDA_FLAGS_${_BUILD_TYPE}) string(REPLACE "-MDd" "-MTd" CMAKE_CUDA_FLAGS_${_BUILD_TYPE} "${CMAKE_CUDA_FLAGS_${_BUILD_TYPE}}") string(REPLACE "-MD" "-MT" CMAKE_CUDA_FLAGS_${_BUILD_TYPE} "${CMAKE_CUDA_FLAGS_${_BUILD_TYPE}}") string(REPLACE "/MDd" "/MTd" CMAKE_CUDA_FLAGS_${_BUILD_TYPE} "${CMAKE_CUDA_FLAGS_${_BUILD_TYPE}}") string(REPLACE "/MD" "/MT" CMAKE_CUDA_FLAGS_${_BUILD_TYPE} "${CMAKE_CUDA_FLAGS_${_BUILD_TYPE}}") endif() endforeach() endif() unset(_COLMAP_USE_STATIC_RUNTIME) endif() message(STATUS "Enabling CUDA support (version: ${CUDAToolkit_VERSION}, " "archs: ${CMAKE_CUDA_ARCHITECTURES})") else() set(CUDA_ENABLED OFF) endif() if(ONNX_ENABLED) if(FETCH_ONNX) include(FetchContent) message(STATUS "Configuring onnxruntime...") set(ONNX_VERSION "1.27.1") # ONNX Runtime now ships separate GPU binaries per CUDA major version # (gpu_cuda12 / gpu_cuda13). We consume the CUDA 12 build below, so CUDA # >= 12 is required for the GPU execution provider. if(ONNX_VERSION VERSION_GREATER_EQUAL "1.22" AND CUDA_ENABLED AND CUDA_FOUND AND CUDAToolkit_VERSION VERSION_LESS "12.0") message(WARNING "ONNX Runtime ${ONNX_VERSION} GPU binary is built with CUDA >= 12, " "but CUDA ${CUDAToolkit_VERSION} was detected. The ONNX Runtime CUDA " "execution provider may fail at runtime, CPU execution will continue to work. " "Consider upgrading CUDA to >= 12 or using a source-built onnxruntime.") endif() if(IS_MACOS) if(CMAKE_OSX_ARCHITECTURES) set(_COLMAP_MACOS_ARCH ${CMAKE_OSX_ARCHITECTURES}) else() set(_COLMAP_MACOS_ARCH ${CMAKE_SYSTEM_PROCESSOR}) endif() if(_COLMAP_MACOS_ARCH STREQUAL "x86_64") message(FATAL_ERROR "x86_64 is not supported for onnxruntime") else() FetchContent_Declare(onnxruntime URL https://github.com/microsoft/onnxruntime/releases/download/v${ONNX_VERSION}/onnxruntime-osx-arm64-${ONNX_VERSION}.tgz URL_HASH SHA256=e42b77a7281cc6e55141bf44fcfbac2c782b823a491bbb6ac33c781dd991f8a6 ${_fetch_content_declare_args} ) endif() elseif(IS_LINUX) if(IS_ARM64) FetchContent_Declare(onnxruntime URL https://github.com/microsoft/onnxruntime/releases/download/v${ONNX_VERSION}/onnxruntime-linux-aarch64-${ONNX_VERSION}.tgz URL_HASH SHA256=33c67e33d1e25b816878366ea276589a024f71f000e7ff955c4b33224d639edd ${_fetch_content_declare_args} ) else() if(CUDA_ENABLED) FetchContent_Declare(onnxruntime URL https://github.com/microsoft/onnxruntime/releases/download/v${ONNX_VERSION}/onnxruntime-linux-x64-gpu_cuda12-${ONNX_VERSION}.tgz URL_HASH SHA256=08b568bd69500c36606aff7c3896ee4fa7d3531719f6b00f43e6a34db41dc4bf ${_fetch_content_declare_args} ) else() FetchContent_Declare(onnxruntime URL https://github.com/microsoft/onnxruntime/releases/download/v${ONNX_VERSION}/onnxruntime-linux-x64-${ONNX_VERSION}.tgz URL_HASH SHA256=25b1ef1fea1acd210d63f8f24dc870ad6e077795ce1f54876252c6d3803c15af ${_fetch_content_declare_args} ) endif() endif() elseif(IS_WINDOWS) FetchContent_Declare(onnxruntime URL https://github.com/microsoft/onnxruntime/releases/download/v${ONNX_VERSION}/onnxruntime-win-x64-gpu_cuda12-${ONNX_VERSION}.zip URL_HASH SHA256=78d4de5ab262f79ac5dd59f08ff0d049b1cea605497f375f8df5ba1a52f26111 ${_fetch_content_declare_args} ) endif() FetchContent_MakeAvailable(onnxruntime) set(ONNX_INCLUDE_DIR ${onnxruntime_BINARY_DIR}/include/onnxruntime) if(NOT EXISTS ${ONNX_INCLUDE_DIR}) file(MAKE_DIRECTORY ${ONNX_INCLUDE_DIR}) file(COPY ${onnxruntime_SOURCE_DIR}/include/ DESTINATION ${ONNX_INCLUDE_DIR}/) endif() set(onnxruntime_LIB_DIR ${onnxruntime_BINARY_DIR}/${CMAKE_INSTALL_LIBDIR}) if(NOT EXISTS ${onnxruntime_LIB_DIR}) file(MAKE_DIRECTORY ${onnxruntime_LIB_DIR}) file(COPY ${onnxruntime_SOURCE_DIR}/lib/ DESTINATION ${onnxruntime_LIB_DIR}) file(REMOVE_RECURSE ${onnxruntime_LIB_DIR}/cmake) file(REMOVE_RECURSE ${onnxruntime_LIB_DIR}/pkgconfig) endif() if(NOT IS_WINDOWS) set(ONNX_DATA_DIR ${onnxruntime_BINARY_DIR}/share/onnxruntime) if(NOT EXISTS ${ONNX_DATA_DIR}) file(MAKE_DIRECTORY ${ONNX_DATA_DIR}) file(COPY ${onnxruntime_SOURCE_DIR}/lib/cmake/onnxruntime/ DESTINATION ${ONNX_DATA_DIR}/cmake/) file(REMOVE_RECURSE ${onnxruntime_SOURCE_DIR}/lib/cmake) # The downloaded cmake configs may reference lib64/ (e.g. on Linux x64), # but the actual install directory depends on CMAKE_INSTALL_LIBDIR # (lib/ or lib64/ depending on the distro). Patch the configs to match. if(IS_LINUX AND NOT IS_ARM64) file(GLOB _onnx_cmake_configs "${ONNX_DATA_DIR}/cmake/*.cmake") foreach(_config_file ${_onnx_cmake_configs}) file(READ "${_config_file}" _config_content) string(REPLACE "/lib64/" "/${CMAKE_INSTALL_LIBDIR}/" _config_content "${_config_content}") file(WRITE "${_config_file}" "${_config_content}") endforeach() endif() endif() set(onnxruntime_CONFIG_DIR_HINTS ${ONNX_DATA_DIR}/cmake CACHE PATH "ONNX Runtime config directory hints") endif() set(onnxruntime_INCLUDE_DIR_HINTS ${onnxruntime_BINARY_DIR}/include CACHE PATH "ONNX Runtime include directory hints") set(onnxruntime_LIBRARY_DIR_HINTS ${onnxruntime_BINARY_DIR}/lib CACHE PATH "ONNX Runtime library directory hints") find_package(onnxruntime ${COLMAP_FIND_TYPE}) install(DIRECTORY "${onnxruntime_BINARY_DIR}/include/" TYPE INCLUDE) if(IS_WINDOWS) # On Windows, selectively install Libs to lib/. Always install core Libs. # For not supporting TensorRT/ROCM/etc. as a runtime, so not installing it intentionally. install(FILES "${onnxruntime_LIB_DIR}/onnxruntime.lib" "${onnxruntime_LIB_DIR}/onnxruntime_providers_shared.lib" TYPE LIB) # Only install CUDA provider Lib if CUDA is enabled. if(CUDA_ENABLED) install(FILES "${onnxruntime_LIB_DIR}/onnxruntime_providers_cuda.lib" TYPE LIB) endif() # On Windows, selectively install DLLs to bin/. Always install core DLLs. # For not supporting TensorRT/ROCM/etc. as a runtime, so not installing it intentionally. install(FILES "${onnxruntime_LIB_DIR}/onnxruntime.dll" "${onnxruntime_LIB_DIR}/onnxruntime_providers_shared.dll" TYPE BIN) # Only install CUDA provider DLL if CUDA is enabled. if(CUDA_ENABLED) install(FILES "${onnxruntime_LIB_DIR}/onnxruntime_providers_cuda.dll" TYPE BIN) endif() else() # On Linux/macOS, selectively install library files. Always install core libraries. # Not supporting TensorRT/ROCM/etc. as a runtime, so not installing them. if(IS_MACOS) file(GLOB onnxruntime_CORE_LIBS "${onnxruntime_LIB_DIR}/libonnxruntime.dylib" "${onnxruntime_LIB_DIR}/libonnxruntime.*.dylib" "${onnxruntime_LIB_DIR}/libonnxruntime_providers_shared.dylib") install(FILES ${onnxruntime_CORE_LIBS} TYPE LIB) else() file(GLOB onnxruntime_CORE_LIBS "${onnxruntime_LIB_DIR}/libonnxruntime.so*" "${onnxruntime_LIB_DIR}/libonnxruntime_providers_shared.so*") install(FILES ${onnxruntime_CORE_LIBS} TYPE LIB) # Only install CUDA provider if CUDA is enabled. if(CUDA_ENABLED) file(GLOB onnxruntime_CUDA_LIBS "${onnxruntime_LIB_DIR}/libonnxruntime_providers_cuda.so*") install(FILES ${onnxruntime_CUDA_LIBS} TYPE LIB) endif() endif() endif() if(EXISTS "${onnxruntime_BINARY_DIR}/share") install(DIRECTORY "${onnxruntime_BINARY_DIR}/share/" TYPE DATA) endif() message(STATUS "Configuring onnxruntime... done") else() find_package(onnxruntime ${COLMAP_FIND_TYPE}) if(NOT onnxruntime_FOUND) message(STATUS "Disabling ONNX support (not found)") set(ONNX_ENABLED OFF) endif() endif() else() message(STATUS "Disabling ONNX support") endif() if(TARGET onnxruntime::onnxruntime) list(APPEND COLMAP_COMPILE_DEFINITIONS COLMAP_ONNX_ENABLED) message(STATUS "Enabling ONNX support") # The prebuilt macOS onnxruntime binaries ship with the CoreML execution # provider, which accelerates ONNX inference on the GPU / Apple Neural # Engine. Enable it as the GPU backend on Apple platforms (CUDA is # unavailable there). if(IS_MACOS) list(APPEND COLMAP_COMPILE_DEFINITIONS COLMAP_COREML_ENABLED) message(STATUS "Enabling ONNX CoreML execution provider") endif() endif() if(GUI_ENABLED) find_package(QT NAMES Qt5 Qt6 REQUIRED) set(COLMAP_QT_COMPONENTS Core OpenGL Svg Widgets) if(${QT_VERSION_MAJOR} GREATER_EQUAL 6) list(APPEND COLMAP_QT_COMPONENTS OpenGLWidgets) endif() find_package(Qt${QT_VERSION_MAJOR} ${COLMAP_FIND_TYPE} COMPONENTS ${COLMAP_QT_COMPONENTS}) message(STATUS "Found Qt") message(STATUS " Module : ${Qt${QT_VERSION_MAJOR}Core_DIR}") message(STATUS " Module : ${Qt${QT_VERSION_MAJOR}OpenGL_DIR}") message(STATUS " Module : ${Qt${QT_VERSION_MAJOR}Svg_DIR}") message(STATUS " Module : ${Qt${QT_VERSION_MAJOR}Widgets_DIR}") if(${QT_VERSION_MAJOR} GREATER_EQUAL 6) message(STATUS " Module : ${Qt${QT_VERSION_MAJOR}OpenGLWidgets_DIR}") endif() if(Qt5_FOUND) # Qt5 was built with -reduce-relocations. if(Qt5_POSITION_INDEPENDENT_CODE) set(CMAKE_POSITION_INDEPENDENT_CODE ON) # Workaround for Qt5 CMake config bug under Ubuntu 20.04: https://gitlab.kitware.com/cmake/cmake/-/issues/16915 if(TARGET Qt5::Core) get_property(core_options TARGET Qt5::Core PROPERTY INTERFACE_COMPILE_OPTIONS) string(REPLACE "-fPIC" "" new_qt5_core_options "${core_options}") set_property(TARGET Qt5::Core PROPERTY INTERFACE_COMPILE_OPTIONS ${new_qt5_core_options}) set_property(TARGET Qt5::Core PROPERTY INTERFACE_POSITION_INDEPENDENT_CODE "ON") if(NOT IS_MSVC) set(CMAKE_CXX_COMPILE_OPTIONS_PIE "-fPIC") endif() endif() endif() endif() if(QT_FOUND) # Enable automatic compilation of Qt resource files. set(CMAKE_AUTORCC ON) endif() endif() if(GUI_ENABLED AND Qt${QT_VERSION_MAJOR}_FOUND) list(APPEND COLMAP_COMPILE_DEFINITIONS COLMAP_GUI_ENABLED) message(STATUS "Enabling GUI support") else() set(GUI_ENABLED OFF) message(STATUS "Disabling GUI support") endif() if(MVS_ENABLED) list(APPEND COLMAP_COMPILE_DEFINITIONS COLMAP_MVS_ENABLED) message(STATUS "Enabling MVS support") else() message(STATUS "Disabling MVS support") endif() if(OPENGL_ENABLED) if(NOT GUI_ENABLED) message(STATUS "Disabling GUI also disables OpenGL") set(OPENGL_ENABLED OFF) else() message(STATUS "Enabling OpenGL support") endif() else() message(STATUS "Disabling OpenGL support") endif() set(GPU_ENABLED OFF) if(OPENGL_ENABLED OR CUDA_ENABLED) list(APPEND COLMAP_COMPILE_DEFINITIONS COLMAP_GPU_ENABLED) message(STATUS "Enabling GPU support (OpenGL: ${OPENGL_ENABLED}, CUDA: ${CUDA_ENABLED})") set(GPU_ENABLED ON) endif() colmap-4.2.0/cmake/FindGlew.cmake000066400000000000000000000067621524536416500165770ustar00rootroot00000000000000# Copyright (c), ETH Zurich and UNC Chapel Hill. # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are met: # # * Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer. # # * Redistributions in binary form must reproduce the above copyright # notice, this list of conditions and the following disclaimer in the # documentation and/or other materials provided with the distribution. # # * Neither the name of ETH Zurich and UNC Chapel Hill nor the names of # its contributors may be used to endorse or promote products derived # from this software without specific prior written permission. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. # Find package module for Glew library. # # The following variables are set by this module: # # GLEW_FOUND: TRUE if Glew is found. # GLEW::GLEW: Imported target to link against. # # The following variables control the behavior of this module: # # GLEW_INCLUDE_DIR_HINTS: List of additional directories in which to # search for Glew includes. # GLEW_LIBRARY_DIR_HINTS: List of additional directories in which to # search for Glew libraries. set(GLEW_INCLUDE_DIR_HINTS "" CACHE PATH "Glew include directory") set(GLEW_LIBRARY_DIR_HINTS "" CACHE PATH "Glew library directory") unset(GLEW_FOUND) unset(GLEW_INCLUDE_DIRS) unset(GLEW_LIBRARIES) find_package(Glew CONFIG QUIET) if(TARGET GLEW::GLEW) set(GLEW_FOUND TRUE) message(STATUS "Found Glew") message(STATUS " Target : GLEW::GLEW") else() find_path(GLEW_INCLUDE_DIRS NAMES GL/glew.h PATHS ${GLEW_INCLUDE_DIR_HINTS} /usr/include /usr/local/include /sw/include /opt/include /opt/local/include) find_library(GLEW_LIBRARIES NAMES GLEW Glew glew glew32 PATHS ${GLEW_LIBRARY_DIR_HINTS} /usr/lib64 /usr/lib /usr/local/lib64 /usr/local/lib /sw/lib /opt/lib /opt/local/lib) if(GLEW_INCLUDE_DIRS AND GLEW_LIBRARIES) set(GLEW_FOUND TRUE) message(STATUS "Found Glew") message(STATUS " Includes : ${GLEW_INCLUDE_DIRS}") message(STATUS " Libraries : ${GLEW_LIBRARIES}") else() set(GLEW_FOUND FALSE) endif() add_library(GLEW::GLEW INTERFACE IMPORTED) target_include_directories( GLEW::GLEW INTERFACE ${GLEW_INCLUDE_DIRS}) target_link_libraries( GLEW::GLEW INTERFACE ${GLEW_LIBRARIES}) endif() if(NOT GLEW_FOUND AND GLEW_FIND_REQUIRED) message(FATAL_ERROR "Could not find Glew") endif() colmap-4.2.0/cmake/FindGlog.cmake000066400000000000000000000101261524536416500165560ustar00rootroot00000000000000# Copyright (c), ETH Zurich and UNC Chapel Hill. # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are met: # # * Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer. # # * Redistributions in binary form must reproduce the above copyright # notice, this list of conditions and the following disclaimer in the # documentation and/or other materials provided with the distribution. # # * Neither the name of ETH Zurich and UNC Chapel Hill nor the names of # its contributors may be used to endorse or promote products derived # from this software without specific prior written permission. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. # Find package module for Glog library. # # The following variables are set by this module: # # GLOG_FOUND: TRUE if Glog is found. # glog::glog: Imported target to link against. # # The following variables control the behavior of this module: # # GLOG_INCLUDE_DIR_HINTS: List of additional directories in which to # search for Glog includes. # GLOG_LIBRARY_DIR_HINTS: List of additional directories in which to # search for Glog libraries. set(GLOG_INCLUDE_DIR_HINTS "" CACHE PATH "Glog include directory") set(GLOG_LIBRARY_DIR_HINTS "" CACHE PATH "Glog library directory") unset(GLOG_FOUND) find_package(glog CONFIG QUIET) if(TARGET glog::glog) set(GLOG_FOUND TRUE) message(STATUS "Found Glog") message(STATUS " Target : glog::glog") else() # Older versions of glog don't come with a find_package config. # Fall back to custom logic to find the library and remap to imported target. include(FindPackageHandleStandardArgs) list(APPEND GLOG_CHECK_INCLUDE_DIRS /usr/local/include /usr/local/homebrew/include /opt/local/var/macports/software /opt/local/include /usr/include) list(APPEND GLOG_CHECK_PATH_SUFFIXES glog/include glog/Include Glog/include Glog/Include src/windows) list(APPEND GLOG_CHECK_LIBRARY_DIRS /usr/local/lib /usr/local/homebrew/lib /opt/local/lib /usr/lib) list(APPEND GLOG_CHECK_LIBRARY_SUFFIXES glog/lib glog/Lib Glog/lib Glog/Lib x64/Release) find_path(GLOG_INCLUDE_DIRS NAMES glog/logging.h PATHS ${GLOG_INCLUDE_DIR_HINTS} ${GLOG_CHECK_INCLUDE_DIRS} PATH_SUFFIXES ${GLOG_CHECK_PATH_SUFFIXES}) find_library(GLOG_LIBRARIES NAMES glog libglog PATHS ${GLOG_LIBRARY_DIR_HINTS} ${GLOG_CHECK_LIBRARY_DIRS} PATH_SUFFIXES ${GLOG_CHECK_LIBRARY_SUFFIXES}) if(GLOG_INCLUDE_DIRS AND GLOG_LIBRARIES) set(GLOG_FOUND TRUE) message(STATUS "Found Glog") message(STATUS " Includes : ${GLOG_INCLUDE_DIRS}") message(STATUS " Libraries : ${GLOG_LIBRARIES}") endif() add_library(glog::glog INTERFACE IMPORTED) target_include_directories(glog::glog INTERFACE ${GLOG_INCLUDE_DIRS}) target_link_libraries(glog::glog INTERFACE ${GLOG_LIBRARIES}) endif() if(NOT GLOG_FOUND AND GLOG_FIND_REQUIRED) message(FATAL_ERROR "Could not find Glog") endif() colmap-4.2.0/cmake/FindMetis.cmake000066400000000000000000000073071524536416500167560ustar00rootroot00000000000000# Copyright (c), ETH Zurich and UNC Chapel Hill. # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are met: # # * Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer. # # * Redistributions in binary form must reproduce the above copyright # notice, this list of conditions and the following disclaimer in the # documentation and/or other materials provided with the distribution. # # * Neither the name of ETH Zurich and UNC Chapel Hill nor the names of # its contributors may be used to endorse or promote products derived # from this software without specific prior written permission. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. # Find package module for Metis library. # # The following variables are set by this module: # # METIS_FOUND: TRUE if Metis is found. # metis: Imported target to link against. # # The following variables control the behavior of this module: # # METIS_INCLUDE_DIR_HINTS: List of additional directories in which to # search for Metis includes. # METIS_LIBRARY_DIR_HINTS: List of additional directories in which to # search for Metis libraries. set(METIS_INCLUDE_DIR_HINTS "" CACHE PATH "Metis include directory") set(METIS_LIBRARY_DIR_HINTS "" CACHE PATH "Metis library directory") unset(METIS_FOUND) find_package(metis CONFIG QUIET) if(TARGET metis) set(METIS_FOUND TRUE) message(STATUS "Found Metis") message(STATUS " Target : metis") else() list(APPEND METIS_CHECK_INCLUDE_DIRS ${METIS_INCLUDE_DIR_HINTS} /usr/include /usr/local/include /opt/include /opt/local/include ) list(APPEND METIS_CHECK_LIBRARY_DIRS ${METIS_LIBRARY_DIR_HINTS} /usr/lib /usr/local/lib /opt/lib /opt/local/lib ) find_path(METIS_INCLUDE_DIRS NAMES metis.h PATHS ${METIS_CHECK_INCLUDE_DIRS}) find_library(METIS_LIBRARIES NAMES metis PATHS ${METIS_CHECK_LIBRARY_DIRS}) find_library(GK_LIBRARIES NAMES GKlib PATHS ${METIS_CHECK_LIBRARY_DIRS}) if(GK_LIBRARIES) set(METIS_LIBRARIES ${METIS_LIBRARIES} ${GK_LIBRARIES}) message(STATUS "Found GKlib") endif() if(METIS_INCLUDE_DIRS AND METIS_LIBRARIES) set(METIS_FOUND TRUE) message(STATUS "Found Metis") message(STATUS " Includes : ${METIS_INCLUDE_DIRS}") message(STATUS " Libraries : ${METIS_LIBRARIES}") endif() add_library(metis INTERFACE IMPORTED) target_include_directories( metis INTERFACE ${METIS_INCLUDE_DIRS}) target_link_libraries( metis INTERFACE ${METIS_LIBRARIES}) endif() if(NOT METIS_FOUND AND METIS_FIND_REQUIRED) message(FATAL_ERROR "Could not find Metis") endif() colmap-4.2.0/cmake/Findonnxruntime.cmake000066400000000000000000000133421524536416500202570ustar00rootroot00000000000000# Copyright (c), ETH Zurich and UNC Chapel Hill. # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are met: # # * Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer. # # * Redistributions in binary form must reproduce the above copyright # notice, this list of conditions and the following disclaimer in the # documentation and/or other materials provided with the distribution. # # * Neither the name of ETH Zurich and UNC Chapel Hill nor the names of # its contributors may be used to endorse or promote products derived # from this software without specific prior written permission. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. # Find package module for onnxruntime library. # # The following variables are set by this module: # # onnxruntime_FOUND: TRUE if onnxruntime is found. # onnxruntime::onnxruntime: Imported target to link against. # # The following variables control the behavior of this module: # # onnxruntime_CONFIG_DIR_HINTS: List of additional directories in which to # search for onnxruntime CMake configs. # onnxruntime_INCLUDE_DIR_HINTS: List of additional directories in which to # search for onnxruntime includes. # onnxruntime_LIBRARY_DIR_HINTS: List of additional directories in which to # search for onnxruntime libraries. set(onnxruntime_CONFIG_DIR_HINTS "" CACHE PATH "onnxruntime config directory") set(onnxruntime_INCLUDE_DIR_HINTS "" CACHE PATH "onnxruntime include directory") set(onnxruntime_LIBRARY_DIR_HINTS "" CACHE PATH "onnxruntime library directory") unset(onnxruntime_FOUND) unset(onnxruntime_INCLUDE_DIRS) unset(onnxruntime_LIBRARIES) find_package(onnxruntime CONFIG QUIET PATHS ${onnxruntime_CONFIG_DIR_HINTS}) if(TARGET onnxruntime::onnxruntime) set(onnxruntime_FOUND TRUE) message(STATUS "Found onnxruntime") message(STATUS " Target : onnxruntime::onnxruntime") else() find_path(onnxruntime_INCLUDE_DIRS NAMES onnxruntime/onnxruntime_cxx_api.h PATHS ${onnxruntime_INCLUDE_DIR_HINTS} /usr/include /usr/local/include /sw/include /opt/include /opt/local/include) list(APPEND onnxruntime_LIBRARY_DIR_HINTS /usr/lib64 /usr/lib /usr/local/lib64 /usr/local/lib /sw/lib /opt/lib /opt/local/lib) find_library(onnxruntime_LIBRARIES NAMES onnxruntime libonnxruntime PATHS ${onnxruntime_LIBRARY_DIR_HINTS}) find_library(onnxruntime_PROVIDERS_SHARED_LIBRARY NAMES onnxruntime_providers_shared libonnxruntime_providers_shared PATHS ${onnxruntime_LIBRARY_DIR_HINTS}) if(CUDA_ENABLED) find_library(onnxruntime_PROVIDERS_CUDA_LIBRARY NAMES onnxruntime_providers_cuda libonnxruntime_providers_cuda PATHS ${onnxruntime_LIBRARY_DIR_HINTS}) endif() if(onnxruntime_INCLUDE_DIRS AND onnxruntime_LIBRARIES) if(onnxruntime_PROVIDERS_SHARED_LIBRARY) list(APPEND onnxruntime_LIBRARIES ${onnxruntime_PROVIDERS_SHARED_LIBRARY}) endif() if(onnxruntime_PROVIDERS_CUDA_LIBRARY) list(APPEND onnxruntime_LIBRARIES ${onnxruntime_PROVIDERS_CUDA_LIBRARY}) endif() set(onnxruntime_FOUND TRUE) message(STATUS "Found onnxruntime") message(STATUS " Includes : ${onnxruntime_INCLUDE_DIRS}") message(STATUS " Libraries : ${onnxruntime_LIBRARIES}") else() set(onnxruntime_FOUND FALSE) endif() add_library(onnxruntime::onnxruntime INTERFACE IMPORTED) target_include_directories( onnxruntime::onnxruntime INTERFACE ${onnxruntime_INCLUDE_DIRS}/onnxruntime) target_link_libraries( onnxruntime::onnxruntime INTERFACE ${onnxruntime_LIBRARIES}) # This is a hack to make sure that the onnxruntime dll is copied to the output directory, # since vcpkg's custom add_library/add_executable macros copy any dependencies from vcpkg's # installed directory to the output directory. # See: https://github.com/microsoft/vcpkg/blob/fb7ba3b89b0d8e3e56b0508a144fe85015edfab6/scripts/buildsystems/vcpkg.cmake#L607 if(IS_WINDOWS AND VCPKG_INSTALLED_DIR) foreach(_lib ${onnxruntime_LIBRARIES}) get_filename_component(_lib_dir "${_lib}" DIRECTORY) get_filename_component(_lib_name "${_lib}" NAME_WE) set(_dlls "${_lib_dir}/${_lib_name}.dll" "${_lib_dir}/../bin/${_lib_name}.dll") foreach(_dll ${_dlls}) if( EXISTS "${_dll}" ) file(COPY "${_dll}" DESTINATION "${VCPKG_INSTALLED_DIR}/${VCPKG_TARGET_TRIPLET}/bin") endif() endforeach() endforeach() endif() endif() if(NOT onnxruntime_FOUND AND onnxruntime_FIND_REQUIRED) message(FATAL_ERROR "Could not find onnxruntime") endif() colmap-4.2.0/cmake/GenerateVersionDefinitions.cmake000066400000000000000000000057341524536416500223720ustar00rootroot00000000000000# Copyright (c), ETH Zurich and UNC Chapel Hill. # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are met: # # * Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer. # # * Redistributions in binary form must reproduce the above copyright # notice, this list of conditions and the following disclaimer in the # documentation and/or other materials provided with the distribution. # # * Neither the name of ETH Zurich and UNC Chapel Hill nor the names of # its contributors may be used to endorse or promote products derived # from this software without specific prior written permission. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. if (DEFINED GIT_COMMIT_ID OR DEFINED GIT_COMMIT_DATE) message(STATUS "Using custom-defined GIT_COMMIT_ID (${GIT_COMMIT_ID}) " "and GIT_COMMIT_DATE (${GIT_COMMIT_DATE})") elseif(Git_FOUND AND EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/.git") execute_process(COMMAND "${GIT_EXECUTABLE}" rev-parse --short HEAD WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}" OUTPUT_VARIABLE GIT_COMMIT_ID ERROR_QUIET OUTPUT_STRIP_TRAILING_WHITESPACE) execute_process(COMMAND "${GIT_EXECUTABLE}" log -1 --format=%ad --date=short WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}" OUTPUT_VARIABLE GIT_COMMIT_DATE ERROR_QUIET OUTPUT_STRIP_TRAILING_WHITESPACE) # Re-generate version.cc if the git index changes. set_property( DIRECTORY APPEND PROPERTY CMAKE_CONFIGURE_DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/.git/index" ) else() set(GIT_COMMIT_ID "Unknown") set(GIT_COMMIT_DATE "Unknown") endif() # Parse COLMAP_VERSION to extract MAJOR, MINOR, PATCH components. string(REGEX MATCH "^([0-9]+)\\.([0-9]+)\\.([0-9]+)" _version_match "${COLMAP_VERSION}") set(COLMAP_VERSION_MAJOR "${CMAKE_MATCH_1}") set(COLMAP_VERSION_MINOR "${CMAKE_MATCH_2}") set(COLMAP_VERSION_PATCH "${CMAKE_MATCH_3}") configure_file("${CMAKE_CURRENT_SOURCE_DIR}/src/colmap/util/version.cc.in" "${CMAKE_CURRENT_SOURCE_DIR}/src/colmap/util/version.cc") colmap-4.2.0/cmake/colmap-config-version.cmake.in000066400000000000000000000034141524536416500216750ustar00rootroot00000000000000# Copyright (c), ETH Zurich and UNC Chapel Hill. # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are met: # # * Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer. # # * Redistributions in binary form must reproduce the above copyright # notice, this list of conditions and the following disclaimer in the # documentation and/or other materials provided with the distribution. # # * Neither the name of ETH Zurich and UNC Chapel Hill nor the names of # its contributors may be used to endorse or promote products derived # from this software without specific prior written permission. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. set(PACKAGE_VERSION "@COLMAP_VERSION@") if("${PACKAGE_VERSION}" VERSION_EQUAL "${PACKAGE_FIND_VERSION}") set(PACKAGE_VERSION_COMPATIBLE TRUE) else() set(PACKAGE_VERSION_COMPATIBLE FALSE) endif() colmap-4.2.0/cmake/colmap-config.cmake.in000066400000000000000000000071751524536416500202220ustar00rootroot00000000000000# Copyright (c), ETH Zurich and UNC Chapel Hill. # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are met: # # * Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer. # # * Redistributions in binary form must reproduce the above copyright # notice, this list of conditions and the following disclaimer in the # documentation and/or other materials provided with the distribution. # # * Neither the name of ETH Zurich and UNC Chapel Hill nor the names of # its contributors may be used to endorse or promote products derived # from this software without specific prior written permission. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. # Find package config for COLMAP library. # # The following variables are set by this config: # # COLMAP_FOUND: TRUE if COLMAP is found. # COLMAP_VERSION: COLMAP version. # # The colmap::colmap imported interface target is defined. @PACKAGE_INIT@ set(COLMAP_FOUND FALSE) # Set hints for finding dependency packages. set(METIS_INCLUDE_DIR_HINTS @METIS_INCLUDE_DIR_HINTS@) set(METIS_LIBRARY_DIR_HINTS @METIS_LIBRARY_DIR_HINTS@) set(GLEW_INCLUDE_DIR_HINTS @GLEW_INCLUDE_DIR_HINTS@) set(GLEW_LIBRARY_DIR_HINTS @GLEW_LIBRARY_DIR_HINTS@) set(GLOG_INCLUDE_DIR_HINTS @GLOG_INCLUDE_DIR_HINTS@) set(GLOG_LIBRARY_DIR_HINTS @GLOG_LIBRARY_DIR_HINTS@) set(CryptoPP_INCLUDE_DIR_HINTS @CryptoPP_INCLUDE_DIR_HINTS@) set(CryptoPP_LIBRARY_DIR_HINTS @CryptoPP_LIBRARY_DIR_HINTS@) # Find dependency packages. set(TEMP_CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH}) set(CMAKE_MODULE_PATH ${PACKAGE_PREFIX_DIR}/share/colmap/cmake) # Set the exported variables. set(COLMAP_FOUND TRUE) set(COLMAP_VERSION @COLMAP_VERSION@) set(OPENMP_ENABLED @OPENMP_ENABLED@) set(CUDA_ENABLED @CUDA_ENABLED@) set(CUDA_MIN_VERSION @CUDA_MIN_VERSION@) set(ONNX_ENABLED @ONNX_ENABLED@) set(DOWNLOAD_ENABLED @DOWNLOAD_ENABLED@) set(GUI_ENABLED @GUI_ENABLED@) set(CGAL_ENABLED @CGAL_ENABLED@) set(MVS_ENABLED @MVS_ENABLED@) set(LSD_ENABLED @LSD_ENABLED@) set(FETCH_POSELIB @FETCH_POSELIB@) set(FETCH_FAISS @FETCH_FAISS@) set(FETCH_ONNX FALSE) if(@FETCH_ONNX@) if(ONNX_ENABLED AND EXISTS ${PACKAGE_PREFIX_DIR}/share/onnxruntime/cmake) set(onnxruntime_DIR ${PACKAGE_PREFIX_DIR}/share/onnxruntime/cmake) endif() set(onnxruntime_INCLUDE_DIR_HINTS ${PACKAGE_PREFIX_DIR}/include CACHE PATH "ONNX Runtime include directory hints") set(onnxruntime_LIBRARY_DIR_HINTS ${PACKAGE_PREFIX_DIR}/@CMAKE_INSTALL_LIBDIR@ CACHE PATH "ONNX Runtime library directory hints") endif() include(${PACKAGE_PREFIX_DIR}/share/colmap/colmap-targets.cmake) include(${PACKAGE_PREFIX_DIR}/share/colmap/cmake/FindDependencies.cmake) check_required_components(colmap) # Reset to previous value set(CMAKE_MODULE_PATH ${TEMP_CMAKE_MODULE_PATH}) colmap-4.2.0/cmake/vcpkg/000077500000000000000000000000001524536416500151755ustar00rootroot00000000000000colmap-4.2.0/cmake/vcpkg/ports/000077500000000000000000000000001524536416500163445ustar00rootroot00000000000000colmap-4.2.0/cmake/vcpkg/ports/README.md000066400000000000000000000025321524536416500176250ustar00rootroot00000000000000# vcpkg overlay ports These ports override the versions from the registry baseline for vcpkg manifest builds. They are registered in the repository's `vcpkg-configuration.json`, so they apply to both CI and local builds. ## METIS and GKlib The newer METIS/GKlib pair in the pinned vcpkg baseline crashes in `METIS_PartGraphKway` on Windows. The older versions used here are known to work on Windows, but selecting them with `overrides` in `vcpkg.json` is not sufficient: the old METIS port enables `-march=native`, which can produce AVX-512 instructions when a package is built on one machine and then cause an illegal-instruction failure when the binary cache restores it on another. The overlay keeps the known-good METIS/GKlib source revisions and patches their build scripts to use the compiler flags supplied by the vcpkg triplet. This makes the packages portable while preserving the Windows behavior. vcpkg overlay ports replace a complete registry port; they cannot add a patch to an existing port. Consequently, the port manifests, portfiles, and upstream vcpkg patches are copied here as a unit. The `overrides` entries would be redundant because overlay ports take precedence during version resolution. Remove these overlays after a newer METIS/GKlib pair passes the Windows graph cut and scene clustering tests and produces portable binaries on Linux. colmap-4.2.0/cmake/vcpkg/ports/gklib/000077500000000000000000000000001524536416500174345ustar00rootroot00000000000000colmap-4.2.0/cmake/vcpkg/ports/gklib/android.patch000066400000000000000000000006311524536416500220750ustar00rootroot00000000000000diff --git a/GKlibSystem.cmake b/GKlibSystem.cmake index 31a1cf1..848fd05 100644 --- a/GKlibSystem.cmake +++ b/GKlibSystem.cmake @@ -113,7 +113,9 @@ endif(GKRAND) # Check for features. +if(NOT ANDROID OR ANDROID_NATIVE_API_LEVEL GREATER 32) check_include_file(execinfo.h HAVE_EXECINFO_H) +endif() if(HAVE_EXECINFO_H) set(GKlib_COPTIONS "${GKlib_COPTIONS} -DHAVE_EXECINFO_H") endif(HAVE_EXECINFO_H) colmap-4.2.0/cmake/vcpkg/ports/gklib/build-fixes.patch000066400000000000000000000055101524536416500226710ustar00rootroot00000000000000diff --git a/CMakeLists.txt b/CMakeLists.txt index 9cd1b4b..3912b26 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,4 +1,4 @@ -cmake_minimum_required(VERSION 2.8) +cmake_minimum_required(VERSION 3.22) project(GKlib C) option(BUILD_SHARED_LIBS "Build shared libraries (.dll/.so) instead of static ones (.lib/.a)" OFF) @@ -22,10 +22,11 @@ if(UNIX) target_link_libraries(GKlib m) endif(UNIX) -include_directories("test") -add_subdirectory("test") install(TARGETS GKlib - ARCHIVE DESTINATION lib/${LINSTALL_PATH} - LIBRARY DESTINATION lib/${LINSTALL_PATH}) + EXPORT GKlibTargets + INCLUDES DESTINATION "include/GKlib" +) +install(EXPORT GKlibTargets FILE "GKlibConfig.cmake" DESTINATION "share/gklib") +install(FILES "win32/adapt.h" DESTINATION "include/${HINSTALL_PATH}/win32") install(FILES ${GKlib_includes} DESTINATION include/${HINSTALL_PATH}) diff --git a/GKlibSystem.cmake b/GKlibSystem.cmake index 31a1cf1..172a386 100644 --- a/GKlibSystem.cmake +++ b/GKlibSystem.cmake @@ -18,7 +18,6 @@ option(NO_X86 "enable NO_X86 support" OFF) # Add compiler flags. if(MSVC) - set(GKlib_COPTS "/Ox") set(GKlib_COPTIONS "-DWIN32 -DMSC -D_CRT_SECURE_NO_DEPRECATE -DUSE_GKREGEX") elseif(MINGW) set(GKlib_COPTS "-DUSE_GKREGEX") @@ -33,6 +32,8 @@ if(CMAKE_COMPILER_IS_GNUCC) set(GKlib_COPTIONS "${GKlib_COPTIONS} -std=c99 -fno-strict-aliasing") if(VALGRIND) set(GKlib_COPTIONS "${GK_COPTIONS} -march=x86-64 -mtune=generic") +elseif(1) + # Use flags from toolchain and triplet else() # -march=native is not a valid flag on PPC: if(CMAKE_SYSTEM_PROCESSOR MATCHES "power|ppc|powerpc|ppc64|powerpc64" OR (APPLE AND CMAKE_OSX_ARCHITECTURES MATCHES "ppc|ppc64")) @@ -46,6 +47,7 @@ endif(VALGRIND) endif(NOT MINGW) # GCC warnings. set(GKlib_COPTIONS "${GKlib_COPTIONS} -Werror -Wall -pedantic -Wno-unused-function -Wno-unused-but-set-variable -Wno-unused-variable -Wno-unknown-pragmas -Wno-unused-label") + string(REPLACE " -Werror " " " GKlib_COPTIONS "${GKlib_COPTIONS}") elseif(${CMAKE_C_COMPILER_ID} MATCHES "Sun") # Sun insists on -xc99. set(GKlib_COPTIONS "${GKlib_COPTIONS} -xc99") @@ -75,6 +77,8 @@ endif(NO_X86) if(GDB) set(GKlib_COPTS "${GKlib_COPTS} -g") set(GKlib_COPTIONS "${GKlib_COPTIONS} -Werror") +elseif(1) + # Use flags from toolchain and triplet else() set(GKlib_COPTS "-O3") endif(GDB) diff --git a/gk_ms_inttypes.h b/gk_ms_inttypes.h index b89fc10..7247c38 100644 --- a/gk_ms_inttypes.h +++ b/gk_ms_inttypes.h @@ -35,6 +35,8 @@ #ifndef _MSC_INTTYPES_H_ // [ #define _MSC_INTTYPES_H_ +#include +#elif 0 #if _MSC_VER > 1000 #pragma once diff --git a/gk_ms_stdint.h b/gk_ms_stdint.h index 7e200dc..1c51958 100644 --- a/gk_ms_stdint.h +++ b/gk_ms_stdint.h @@ -35,6 +35,8 @@ #ifndef _MSC_STDINT_H_ // [ #define _MSC_STDINT_H_ +#include +#elif 0 #if _MSC_VER > 1000 #pragma once colmap-4.2.0/cmake/vcpkg/ports/gklib/portfile.cmake000066400000000000000000000014441524536416500222650ustar00rootroot00000000000000# Keep GKlib paired with the known-good METIS revision and make its compiler # flags portable in build-fixes.patch. if(VCPKG_TARGET_IS_WINDOWS) vcpkg_check_linkage(ONLY_STATIC_LIBRARY) endif() vcpkg_from_github( OUT_SOURCE_PATH SOURCE_PATH REPO KarypisLab/GKlib REF 8bd6bad750b2b0d90800c632cf18e8ee93ad72d7 SHA512 128cd9a48047b18b8013288162556f0b0f1d81845f5445f7cc62590ab28c06ee0a6c602cc999ce268ab27237eca3e8295df6432d377e45071946b98558872997 PATCHES android.patch build-fixes.patch ) vcpkg_cmake_configure( SOURCE_PATH "${SOURCE_PATH}" OPTIONS -DHINSTALL_PATH=GKlib ) vcpkg_cmake_install() vcpkg_cmake_config_fixup() file(REMOVE_RECURSE "${CURRENT_PACKAGES_DIR}/debug/include") vcpkg_install_copyright(FILE_LIST "${SOURCE_PATH}/LICENSE.txt") colmap-4.2.0/cmake/vcpkg/ports/gklib/vcpkg.json000066400000000000000000000005351524536416500214440ustar00rootroot00000000000000{ "name": "gklib", "version-date": "2023-03-27", "description": "General helper libraries for KarypisLab.", "homepage": "https://github.com/KarypisLab/GKlib/", "license": "Apache-2.0", "dependencies": [ { "name": "vcpkg-cmake", "host": true }, { "name": "vcpkg-cmake-config", "host": true } ] } colmap-4.2.0/cmake/vcpkg/ports/metis/000077500000000000000000000000001524536416500174655ustar00rootroot00000000000000colmap-4.2.0/cmake/vcpkg/ports/metis/build-fixes.patch000066400000000000000000000106501524536416500227230ustar00rootroot00000000000000diff --git a/CMakeLists.txt b/CMakeLists.txt index a15d19a..7210a61 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,9 +1,9 @@ -cmake_minimum_required(VERSION 2.8) +cmake_minimum_required(VERSION 3.22) project(METIS C) set(SHARED FALSE CACHE BOOL "build a shared library") -if(MSVC) +if(0) set(METIS_INSTALL FALSE) else() set(METIS_INSTALL TRUE) @@ -34,19 +34,8 @@ include(./conf/gkbuild.cmake) # #set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${METIS_COPTIONS}") - -# Add include directories. -# i.e., the -I equivalent -include_directories(build/xinclude) -include_directories(${GKLIB_PATH}/include) -include_directories(${CMAKE_INSTALL_PREFIX}/include) - -# List of paths that the compiler will search for library files. -# i.e., the -L equivalent -link_directories(${GKLIB_PATH}/lib) -link_directories(${CMAKE_INSTALL_PREFIX}/lib) - # Recursively look for CMakeLists.txt in subdirs. -add_subdirectory("build/xinclude") +add_subdirectory("include") add_subdirectory("libmetis") -add_subdirectory("programs") + +include(install_config.cmake) diff --git a/conf/gkbuild.cmake b/conf/gkbuild.cmake index 96435e5..772cd12 100644 --- a/conf/gkbuild.cmake +++ b/conf/gkbuild.cmake @@ -16,7 +16,6 @@ option(GKRAND "enable GKRAND support" OFF) # Add compiler flags. if(MSVC) - set(GK_COPTS "/Ox") set(GK_COPTIONS "-DWIN32 -DMSC -D_CRT_SECURE_NO_DEPRECATE -DUSE_GKREGEX") elseif(MINGW) set(GK_COPTS "-DUSE_GKREGEX") @@ -31,14 +30,16 @@ if(CMAKE_COMPILER_IS_GNUCC) set(GK_COPTIONS "${GK_COPTIONS} -std=c99 -fno-strict-aliasing") if(VALGRIND) set(GK_COPTIONS "${GK_COPTIONS} -march=x86-64 -mtune=generic") +elseif(1) + # Use flags from toolchain and triplet else() set(GK_COPTIONS "${GK_COPTIONS} -march=native") endif(VALGRIND) if(NOT MINGW) set(GK_COPTIONS "${GK_COPTIONS} -fPIC") endif(NOT MINGW) # GCC warnings. - set(GK_COPTIONS "${GK_COPTIONS} -Werror -Wall -pedantic -Wno-unused-function -Wno-unused-but-set-variable -Wno-unused-variable -Wno-unknown-pragmas -Wno-unused-label") + set(GK_COPTIONS "${GK_COPTIONS} -Wall -pedantic -Wno-unused-function -Wno-unused-but-set-variable -Wno-unused-variable -Wno-unknown-pragmas -Wno-unused-label") elseif(${CMAKE_C_COMPILER_ID} MATCHES "Sun") # Sun insists on -xc99. set(GK_COPTIONS "${GK_COPTIONS} -xc99") @@ -69,7 +70,7 @@ endif(OPENMP) if(GDB) set(GK_COPTS "${GK_COPTS} -g") set(GK_COPTIONS "${GK_COPTIONS} -Werror") -else() +elseif(0) set(GK_COPTS "-O3") endif(GDB) diff --git a/include/metis.h b/include/metis.h index 7fef0e7..f8e5dcf 100644 --- a/include/metis.h +++ b/include/metis.h @@ -30,7 +30,7 @@ GCC does provides these definitions in stdint.h, but it may require some modifications on other architectures. --------------------------------------------------------------------------*/ -//#define IDXTYPEWIDTH 32 +#define IDXTYPEWIDTH 32 /*-------------------------------------------------------------------------- @@ -40,7 +40,7 @@ 32 : single precission floating point (float) 64 : double precission floating point (double) --------------------------------------------------------------------------*/ -//#define REALTYPEWIDTH 32 +#define REALTYPEWIDTH 32 @@ -72,10 +72,14 @@ typedef __int64 int64_t; #define PRId64 "I64d" #define SCNd32 "ld" #define SCNd64 "I64d" +#ifdef _WIN32 +#include +#else #define INT32_MIN ((int32_t)_I32_MIN) #define INT32_MAX _I32_MAX #define INT64_MIN ((int64_t)_I64_MIN) #define INT64_MAX _I64_MAX +#endif // ^^^ !_WIN32 #else #include #endif diff --git a/libmetis/CMakeLists.txt b/libmetis/CMakeLists.txt index fc6cec6..8aeb89a 100644 --- a/libmetis/CMakeLists.txt +++ b/libmetis/CMakeLists.txt @@ -6,10 +6,9 @@ file(GLOB metis_sources *.c) # Build libmetis. add_library(metis ${METIS_LIBRARY_TYPE} ${metis_sources}) +find_package(GKlib CONFIG REQUIRED) +target_link_libraries(metis PUBLIC GKlib) +target_include_directories(metis PRIVATE "../include") -if(METIS_INSTALL) - install(TARGETS metis - LIBRARY DESTINATION lib - RUNTIME DESTINATION lib - ARCHIVE DESTINATION lib) -endif() +install(TARGETS metis EXPORT metisTargets + INCLUDES DESTINATION include) diff --git a/libmetis/metislib.h b/libmetis/metislib.h index dc224f4..1efccda 100644 --- a/libmetis/metislib.h +++ b/libmetis/metislib.h @@ -31,7 +31,7 @@ #include "proto.h" -#if defined(COMPILER_MSC) +#if defined(COMPILER_MSC) && (_MSC_VER < 1900) #if defined(rint) #undef rint #endif colmap-4.2.0/cmake/vcpkg/ports/metis/install_config.cmake000066400000000000000000000024521524536416500234650ustar00rootroot00000000000000install(EXPORT metisTargets FILE metisTargets.cmake DESTINATION share/metis ) file(WRITE "${CMAKE_CURRENT_BINARY_DIR}/metisConfig.cmake" "include(CMakeFindDependencyMacro) find_dependency(GKlib CONFIG) include(\"\${CMAKE_CURRENT_LIST_DIR}/metisTargets.cmake\") ") # Copied from https://github.com/ceres-solver/ceres-solver/blob/2.2.0/cmake/FindMETIS.cmake#L69-L77 file(READ "${PROJECT_SOURCE_DIR}/include/metis.h" _METIS_VERSION_CONTENTS) string(REGEX REPLACE ".*#define METIS_VER_MAJOR[ \t]+([0-9]+).*" "\\1" METIS_VERSION_MAJOR "${_METIS_VERSION_CONTENTS}") string(REGEX REPLACE ".*#define METIS_VER_MINOR[ \t]+([0-9]+).*" "\\1" METIS_VERSION_MINOR "${_METIS_VERSION_CONTENTS}") string(REGEX REPLACE ".*#define METIS_VER_SUBMINOR[ \t]+([0-9]+).*" "\\1" METIS_VERSION_PATCH "${_METIS_VERSION_CONTENTS}") set(METIS_VERSION "${METIS_VERSION_MAJOR}.${METIS_VERSION_MINOR}.${METIS_VERSION_PATCH}") include(CMakePackageConfigHelpers) write_basic_package_version_file( "${CMAKE_CURRENT_BINARY_DIR}/metisConfigVersion.cmake" VERSION ${METIS_VERSION} COMPATIBILITY SameMajorVersion ) install(FILES "${CMAKE_CURRENT_BINARY_DIR}/metisConfig.cmake" "${CMAKE_CURRENT_BINARY_DIR}/metisConfigVersion.cmake" DESTINATION "share/metis" ) colmap-4.2.0/cmake/vcpkg/ports/metis/portfile.cmake000066400000000000000000000020521524536416500223120ustar00rootroot00000000000000# Newer METIS revisions crash in METIS_PartGraphKway on Windows. Keep this # known-good revision and make its compiler flags portable in build-fixes.patch. vcpkg_check_linkage(ONLY_STATIC_LIBRARY) vcpkg_from_github( OUT_SOURCE_PATH SOURCE_PATH REPO KarypisLab/METIS REF 94c03a6e2d1860128c2d0675cbbb86ad4f261256 SHA512 9f24329fa0f0856d0b5d10a489574d857bc4538d9639055fc895363cf70aa37342eaf7bc08819500ff6d5b98a4aa99f4241880622b540d4c484ca19e693d3480 PATCHES build-fixes.patch ) file(COPY "${CMAKE_CURRENT_LIST_DIR}/install_config.cmake" DESTINATION "${SOURCE_PATH}") vcpkg_cmake_configure(SOURCE_PATH "${SOURCE_PATH}") vcpkg_cmake_install() vcpkg_copy_pdbs() vcpkg_cmake_config_fixup() file(REMOVE_RECURSE "${CURRENT_PACKAGES_DIR}/debug/include") file(INSTALL "${SOURCE_PATH}/LICENSE" DESTINATION "${CURRENT_PACKAGES_DIR}/share/${PORT}" RENAME copyright) file(WRITE "${CURRENT_PACKAGES_DIR}/share/${PORT}/usage" [=[ metis provides CMake targets: find_package(metis CONFIG REQUIRED) target_link_libraries(main PRIVATE metis) ]=]) colmap-4.2.0/cmake/vcpkg/ports/metis/vcpkg.json000066400000000000000000000006211524536416500214710ustar00rootroot00000000000000{ "name": "metis", "version-date": "2022-07-27", "port-version": 1, "description": "Serial Graph Partitioning and Fill-reducing Matrix Ordering", "homepage": "https://github.com/KarypisLab/METIS", "license": "Apache-2.0", "dependencies": [ "gklib", { "name": "vcpkg-cmake", "host": true }, { "name": "vcpkg-cmake-config", "host": true } ] } colmap-4.2.0/doc/000077500000000000000000000000001524536416500135505ustar00rootroot00000000000000colmap-4.2.0/doc/COLMAP.desktop000066400000000000000000000003601524536416500161150ustar00rootroot00000000000000[Desktop Entry] Name=COLMAP Comment=Structure-from-Motion and Multi-View Stereo Exec=colmap gui Icon=colmap Terminal=false Categories=Graphics;3DGraphics; Keywords=3d;reconstruction;structure-from-motion;multi-view-stereo; Type=Application colmap-4.2.0/doc/Makefile000077500000000000000000000163011524536416500152140ustar00rootroot00000000000000# Makefile for Sphinx documentation # # You can set these variables from the command line. SPHINXOPTS = SPHINXBUILD = sphinx-build PYTHON = python PAPER = BUILDDIR = _build PORT = 8000 # User-friendly check for sphinx-build ifeq ($(shell which $(SPHINXBUILD) >/dev/null 2>&1; echo $$?), 1) $(error The '$(SPHINXBUILD)' command was not found. Make sure you have Sphinx installed, then set the SPHINXBUILD environment variable to point to the full path of the '$(SPHINXBUILD)' executable. Alternatively you can add the directory with the executable to your PATH. If you don't have Sphinx installed, grab it from http://sphinx-doc.org/) endif # Internal variables. PAPEROPT_a4 = -D latex_paper_size=a4 PAPEROPT_letter = -D latex_paper_size=letter ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . # the i18n builder cannot share the environment and doctrees with the others I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . .PHONY: help clean viewer-assets html serve dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest gettext help: @echo "Please use \`make ' where is one of" @echo " html to make standalone HTML files" @echo " serve to build and serve HTML at http://localhost:$(PORT)" @echo " dirhtml to make HTML files named index.html in directories" @echo " singlehtml to make a single large HTML file" @echo " pickle to make pickle files" @echo " json to make JSON files" @echo " htmlhelp to make HTML files and a HTML help project" @echo " qthelp to make HTML files and a qthelp project" @echo " devhelp to make HTML files and a Devhelp project" @echo " epub to make an epub" @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" @echo " latexpdf to make LaTeX files and run them through pdflatex" @echo " latexpdfja to make LaTeX files and run them through platex/dvipdfmx" @echo " text to make text files" @echo " man to make manual pages" @echo " texinfo to make Texinfo files" @echo " info to make Texinfo files and run them through makeinfo" @echo " gettext to make PO message catalogs" @echo " changes to make an overview of all changed/added/deprecated items" @echo " xml to make Docutils-native XML files" @echo " pseudoxml to make pseudoxml-XML files for display purposes" @echo " linkcheck to check all external links for integrity" @echo " doctest to run all doctests embedded in the documentation (if enabled)" clean: rm -rf $(BUILDDIR)/* rm -rf _static/viewer viewer-assets: @if command -v npm >/dev/null 2>&1 && test -d node_modules; then \ npm run build; \ elif test "$(STRICT_VIEWER)" = "1"; then \ echo "npm and doc/node_modules are required when STRICT_VIEWER=1"; exit 1; \ else \ echo "WARNING: skipping 3D viewer build (npm or doc/node_modules not found)"; \ fi html: viewer-assets $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html @echo @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." serve: html $(PYTHON) -m http.server $(PORT) --directory $(BUILDDIR)/html dirhtml: viewer-assets $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml @echo @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." singlehtml: viewer-assets $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml @echo @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." pickle: $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle @echo @echo "Build finished; now you can process the pickle files." json: $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json @echo @echo "Build finished; now you can process the JSON files." htmlhelp: $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp @echo @echo "Build finished; now you can run HTML Help Workshop with the" \ ".hhp project file in $(BUILDDIR)/htmlhelp." qthelp: $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp @echo @echo "Build finished; now you can run "qcollectiongenerator" with the" \ ".qhcp project file in $(BUILDDIR)/qthelp, like this:" @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/COLMAP.qhcp" @echo "To view the help file:" @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/COLMAP.qhc" devhelp: $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp @echo @echo "Build finished." @echo "To view the help file:" @echo "# mkdir -p $$HOME/.local/share/devhelp/COLMAP" @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/COLMAP" @echo "# devhelp" epub: $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub @echo @echo "Build finished. The epub file is in $(BUILDDIR)/epub." latex: $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex @echo @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." @echo "Run \`make' in that directory to run these through (pdf)latex" \ "(use \`make latexpdf' here to do that automatically)." latexpdf: $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex @echo "Running LaTeX files through pdflatex..." $(MAKE) -C $(BUILDDIR)/latex all-pdf @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." latexpdfja: $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex @echo "Running LaTeX files through platex and dvipdfmx..." $(MAKE) -C $(BUILDDIR)/latex all-pdf-ja @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." text: $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text @echo @echo "Build finished. The text files are in $(BUILDDIR)/text." man: $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man @echo @echo "Build finished. The manual pages are in $(BUILDDIR)/man." texinfo: $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo @echo @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo." @echo "Run \`make' in that directory to run these through makeinfo" \ "(use \`make info' here to do that automatically)." info: $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo @echo "Running Texinfo files through makeinfo..." make -C $(BUILDDIR)/texinfo info @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo." gettext: $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale @echo @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale." changes: $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes @echo @echo "The overview file is in $(BUILDDIR)/changes." linkcheck: $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck @echo @echo "Link check complete; look for any errors in the above output " \ "or in $(BUILDDIR)/linkcheck/output.txt." doctest: $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest @echo "Testing of doctests in the sources finished, look at the " \ "results in $(BUILDDIR)/doctest/output.txt." xml: $(SPHINXBUILD) -b xml $(ALLSPHINXOPTS) $(BUILDDIR)/xml @echo @echo "Build finished. The XML files are in $(BUILDDIR)/xml." pseudoxml: $(SPHINXBUILD) -b pseudoxml $(ALLSPHINXOPTS) $(BUILDDIR)/pseudoxml @echo @echo "Build finished. The pseudo-XML files are in $(BUILDDIR)/pseudoxml." colmap-4.2.0/doc/_static/000077500000000000000000000000001524536416500151765ustar00rootroot00000000000000colmap-4.2.0/doc/_static/colmap-logo-dark.svg000066400000000000000000000021531524536416500210500ustar00rootroot00000000000000 COLMAP colmap-4.2.0/doc/_static/colmap-logo.svg000066400000000000000000000021531524536416500201310ustar00rootroot00000000000000 COLMAP colmap-4.2.0/doc/_static/custom.css000066400000000000000000000026061524536416500172260ustar00rootroot00000000000000/* Brand palette: charcoal + dark red, matching the COLMAP logo. Overrides the pydata-sphinx-theme accent colors for both light and dark mode. */ html[data-theme="light"] { --pst-color-primary: #b91c1c; --pst-color-primary-highlight: #7f1d1d; --pst-color-secondary: #374151; --pst-color-secondary-highlight: #1f2937; --pst-color-link: #b91c1c; --pst-color-link-hover: #7f1d1d; --pst-color-inline-code: #b91c1c; } html[data-theme="dark"] { --pst-color-primary: #f87171; --pst-color-primary-highlight: #ef4444; --pst-color-secondary: #9ca3af; --pst-color-secondary-highlight: #d1d5db; --pst-color-link: #f87171; --pst-color-link-hover: #fca5a5; } /* Fixes https://github.com/readthedocs/sphinx_rtd_theme/issues/1301#issuecomment-1876120817 */ .py.property { display: block !important; } /* Better display of multi-line signatures on the autodoc API pages. */ dt.sig > dl > dd { margin-bottom: 0px; } dt.sig > dl { margin-bottom: 0px; } /* Interactive sparse reconstruction viewer. */ body:has(#colmap-viewer-root) .bd-main .bd-content, body:has(#colmap-viewer-root) .bd-main .bd-content .bd-article-container { max-width: none; width: 100%; } body:has(#colmap-viewer-root) .bd-article { padding-left: clamp(0.75rem, 2vw, 2rem); padding-right: clamp(0.75rem, 2vw, 2rem); } #colmap-viewer-root { margin: 0.75rem 0 2rem; } colmap-4.2.0/doc/_static/external_links.js000066400000000000000000000031611524536416500205570ustar00rootroot00000000000000// Open external links in a new browser tab. // // Any anchor pointing to a different host than the current site (e.g. the // GitHub repository, contributor profiles, or release downloads) gets // target="_blank" plus rel="noopener noreferrer" so it opens in a new tab // without exposing the opener. Same-site links (including absolute // https://colmap.github.io/ URLs) are left untouched. Covers both // reStructuredText links and raw-HTML anchors (e.g. the landing page hero // buttons), as well as anchors injected after load (e.g. the install // selector's download links), which a MutationObserver picks up. (function () { "use strict"; function markExternal(anchor) { if (anchor.hostname && anchor.hostname !== window.location.hostname) { anchor.target = "_blank"; anchor.rel = "noopener noreferrer"; } } function scan(root) { var anchors = root.querySelectorAll( 'a[href^="http://"], a[href^="https://"]' ); anchors.forEach(markExternal); } document.addEventListener("DOMContentLoaded", function () { scan(document); // Re-scan anchors added after the initial load (e.g. the install // selector rebuilds its output on every click). var observer = new MutationObserver(function (mutations) { mutations.forEach(function (mutation) { mutation.addedNodes.forEach(function (node) { if (node.nodeType !== Node.ELEMENT_NODE) return; if (node.tagName === "A") markExternal(node); if (node.querySelectorAll) scan(node); }); }); }); observer.observe(document.body, { childList: true, subtree: true }); }); })(); colmap-4.2.0/doc/_static/favicon.svg000066400000000000000000000012171524536416500173450ustar00rootroot00000000000000 COLMAP colmap-4.2.0/doc/_static/install_selector.js000066400000000000000000000224431524536416500211070ustar00rootroot00000000000000// Interactive install selector for the COLMAP landing page. // // Renders a pytorch.org-style grid: the user picks an operating system, an // install method, and a compute backend (CUDA vs CPU), and the widget shows the // exact command or download link. The script is loaded on every page but only // activates when the landing page's mount point (#colmap-install-selector) is // present. (function () { "use strict"; // Helpers to build the per-cell result shown in the output box. function cmd(text, note) { return { kind: "command", text: text, note: note || null }; } function link(text, url, note) { return { kind: "link", text: text, url: url, note: note || null }; } // Install matrix: MATRIX[os].methods keeps the display order; MATRIX[os].cells // maps method -> { cuda, cpu }, where each entry is a result object or null // (unavailable / disabled). var MATRIX = { linux: { label: "Linux", methods: ["pip", "conda", "docker", "binary", "source"], cells: { pip: { cuda: cmd("pip install pycolmap-cuda12"), cpu: cmd("pip install pycolmap"), }, conda: { cuda: null, cpu: cmd("conda install -c conda-forge colmap"), }, docker: { cuda: cmd( "docker pull colmap/colmap:latest", "Run with GPU flags (NVIDIA Container Toolkit) to enable CUDA." ), cpu: cmd( "docker pull colmap/colmap:latest", "The image is CUDA-based but also runs CPU-only." ), }, binary: { cuda: null, cpu: link( "Distribution packages (Repology)", "https://repology.org/metapackage/colmap/versions", "Distro packages ship without CUDA. For GPU support, build from source." ), }, source: { cuda: link( "Build from source (with CUDA)", "https://colmap.github.io/install.html#debian-ubuntu", "Install the CUDA toolkit, then configure COLMAP with CUDA enabled." ), cpu: link( "Build from source", "https://colmap.github.io/install.html#debian-ubuntu" ), }, }, }, macos: { label: "macOS", methods: ["binary", "pip", "conda", "brew", "docker", "source"], cells: { binary: { cuda: null, cpu: link( "Download from GitHub Releases", "https://github.com/colmap/colmap/releases", "Use the colmap-arm64-macos package." ), }, pip: { cuda: null, cpu: cmd("pip install pycolmap") }, conda: { cuda: null, cpu: cmd("conda install -c conda-forge colmap") }, brew: { cuda: null, cpu: cmd("brew install colmap") }, docker: { cuda: null, cpu: cmd( "docker pull colmap/colmap:latest", "GPU acceleration is not available on macOS." ), }, source: { cuda: null, cpu: link( "Build from source", "https://colmap.github.io/install.html#mac" ), }, }, }, windows: { label: "Windows", methods: ["binary", "pip", "conda", "vcpkg", "docker"], cells: { binary: { cuda: link( "Download from GitHub Releases", "https://github.com/colmap/colmap/releases", "Use the colmap-x64-windows-cuda package." ), cpu: link( "Download from GitHub Releases", "https://github.com/colmap/colmap/releases", "Use the colmap-x64-windows-nocuda package." ), }, pip: { cuda: null, cpu: cmd( "pip install pycolmap", "Windows wheels are CPU-only. For GPU, use the binary package or vcpkg." ), }, conda: { cuda: null, cpu: cmd("conda install -c conda-forge colmap") }, vcpkg: { cuda: cmd("vcpkg install colmap[cuda,tests]:x64-windows"), cpu: cmd("vcpkg install colmap:x64-windows"), }, docker: { cuda: cmd( "docker pull colmap/colmap:latest", "Requires WSL2 with the NVIDIA Container Toolkit for GPU support." ), cpu: cmd("docker pull colmap/colmap:latest"), }, }, }, }; var OS_ORDER = ["linux", "macos", "windows"]; var METHOD_LABELS = { pip: "pip", conda: "Conda", docker: "Docker", binary: "Binary", brew: "Homebrew", vcpkg: "vcpkg", source: "Source", }; var COMPUTE_ORDER = ["cuda", "cpu"]; var COMPUTE_LABELS = { cuda: "CUDA (GPU)", cpu: "CPU" }; function escapeHtml(s) { return String(s) .replace(/&/g, "&") .replace(//g, ">") .replace(/"/g, """); } // Current selection. var state = { os: "linux", method: "pip", compute: "cuda" }; function computesFor(os, method) { var cell = MATRIX[os].cells[method]; return COMPUTE_ORDER.filter(function (c) { return cell && cell[c]; }); } // Coerce the selection into a valid (os, method, compute) triple. function normalize() { if (!MATRIX[state.os]) state.os = "linux"; var methods = MATRIX[state.os].methods; if (methods.indexOf(state.method) === -1) state.method = methods[0]; var avail = computesFor(state.os, state.method); if (avail.indexOf(state.compute) === -1) state.compute = avail[0]; } function button(label, active, disabled, onClick) { var btn = document.createElement("button"); btn.type = "button"; btn.className = "cis__btn" + (active ? " cis__btn--active" : "") + (disabled ? " cis__btn--disabled" : ""); btn.textContent = label; if (disabled) { btn.setAttribute("aria-disabled", "true"); btn.disabled = true; } else { btn.addEventListener("click", onClick); } btn.setAttribute("aria-pressed", active ? "true" : "false"); return btn; } function row(root, labelText, options) { var r = document.createElement("div"); r.className = "cis__row"; var lbl = document.createElement("div"); lbl.className = "cis__label"; lbl.textContent = labelText; var opts = document.createElement("div"); opts.className = "cis__options"; options.forEach(function (o) { opts.appendChild(o); }); r.appendChild(lbl); r.appendChild(opts); root.appendChild(r); } function render(root) { normalize(); root.innerHTML = ""; // OS row. row( root, "OS", OS_ORDER.map(function (os) { return button(MATRIX[os].label, state.os === os, false, function () { state.os = os; render(root); }); }) ); // Method row. row( root, "Package", MATRIX[state.os].methods.map(function (m) { return button(METHOD_LABELS[m], state.method === m, false, function () { state.method = m; render(root); }); }) ); // Compute row (disable unavailable backends for the current os+method). var avail = computesFor(state.os, state.method); row( root, "Compute", COMPUTE_ORDER.map(function (c) { var disabled = avail.indexOf(c) === -1; return button( COMPUTE_LABELS[c], state.compute === c, disabled, function () { state.compute = c; render(root); } ); }) ); // Output box. var result = MATRIX[state.os].cells[state.method][state.compute]; var out = document.createElement("div"); out.className = "cis__output"; if (result && result.kind === "command") { var box = document.createElement("div"); box.className = "cis__cmd"; var code = document.createElement("code"); code.innerHTML = '$ ' + escapeHtml(result.text); var copy = document.createElement("button"); copy.type = "button"; copy.className = "cis__copy"; copy.textContent = "Copy"; copy.addEventListener("click", function () { var done = function () { copy.textContent = "Copied!"; setTimeout(function () { copy.textContent = "Copy"; }, 1500); }; if (navigator.clipboard && navigator.clipboard.writeText) { navigator.clipboard.writeText(result.text).then(done, function () {}); } else { done(); } }); box.appendChild(code); box.appendChild(copy); out.appendChild(box); } else if (result && result.kind === "link") { var a = document.createElement("a"); a.className = "cis__link"; a.href = result.url; a.textContent = result.text; a.rel = "noopener"; out.appendChild(a); } if (result && result.note) { var note = document.createElement("p"); note.className = "cis__note"; note.textContent = result.note; out.appendChild(note); } root.appendChild(out); } function init() { var root = document.getElementById("colmap-install-selector"); if (!root) return; root.classList.add("cis"); render(root); } if (document.readyState === "loading") { document.addEventListener("DOMContentLoaded", init); } else { init(); } })(); colmap-4.2.0/doc/_static/landing.css000066400000000000000000000136761524536416500173410ustar00rootroot00000000000000/* Styles for the COLMAP landing page: hero, feature cards, and the interactive install selector. Colors are derived from pydata-sphinx-theme CSS variables so everything adapts automatically to light and dark mode. */ /* ------------------------------------------------------------------ Hero --- */ /* Center and enlarge the page title so it reads as a hero wordmark. The `.. rst-class:: hero__title` directive normalizes to the class `hero-title` and applies it to the wrapping
, so we scope to its direct-child

only (never the whole section, which contains the rest of the page). */ section.hero-title > h1 { text-align: center; font-size: 3rem; font-weight: 800; letter-spacing: 0.06em; margin: 2rem 0 0.25rem; } /* Hide the anchor "#" link on the hero title. */ section.hero-title > h1 > a.headerlink { display: none; } .hero { text-align: center; padding: 0.5rem 1rem 1rem; max-width: 900px; margin: 0 auto; } /* Hero figure (reuses the existing sparse-reconstruction screenshot). */ .hero-figure { max-width: 900px; margin: 0.5rem auto 1rem; text-align: center; } .hero-figure img.hero__image { width: 100%; border-radius: 0.75rem; border: 1px solid var(--pst-color-border); } .hero-figure figcaption, .hero-figure .caption { font-size: 0.85rem; color: var(--pst-color-text-muted); margin-top: 0.5rem; } .hero__logo { width: 84px; height: 84px; margin-bottom: 1rem; } .hero__title { font-size: 3rem; font-weight: 800; letter-spacing: 0.06em; margin: 0 0 0.25rem; line-height: 1.1; } .hero__tagline { font-size: 1.35rem; font-weight: 600; color: var(--pst-color-primary); margin: 0 0 1rem; } .hero__desc { font-size: 1.05rem; color: var(--pst-color-text-muted); max-width: 680px; margin: 0 auto 1.75rem; } .hero__cta { display: flex; flex-wrap: wrap; gap: 0.75rem; justify-content: center; margin-bottom: 2rem; } .hero__cta a { display: inline-block; padding: 0.6rem 1.4rem; border-radius: 0.5rem; font-weight: 600; text-decoration: none; border: 1px solid var(--pst-color-primary); transition: transform 0.06s ease, background 0.15s ease, color 0.15s ease; } .hero__cta a:hover { transform: translateY(-1px); text-decoration: none; } .hero__cta a.hero__cta--primary { background: var(--pst-color-primary); color: #fff; } .hero__cta a.hero__cta--primary:hover { background: var(--pst-color-primary-highlight, var(--pst-color-primary)); } .hero__cta a.hero__cta--secondary { background: transparent; color: var(--pst-color-primary); } .hero__cta a.hero__cta--secondary:hover { background: var(--pst-color-surface); } .hero__image { width: 100%; border-radius: 0.75rem; border: 1px solid var(--pst-color-border); margin-top: 0.5rem; } .hero__image-caption { font-size: 0.85rem; color: var(--pst-color-text-muted); margin-top: 0.5rem; } /* Section headings on the landing page. */ .landing-section-title { text-align: center; font-size: 1.9rem; font-weight: 700; margin: 3rem 0 0.5rem; } .landing-section-subtitle { text-align: center; color: var(--pst-color-text-muted); margin: 0 auto 1.5rem; max-width: 640px; } /* ------------------------------------------------------ Install selector --- */ .cis { max-width: 780px; margin: 0 auto 1rem; border: 1px solid var(--pst-color-border); border-radius: 0.75rem; padding: 1.25rem 1.5rem 1.5rem; background: var(--pst-color-surface); } .cis__row { display: flex; align-items: flex-start; gap: 1rem; padding: 0.5rem 0; } .cis__row + .cis__row { border-top: 1px solid var(--pst-color-border); } .cis__label { flex: 0 0 92px; padding-top: 0.4rem; font-size: 0.8rem; font-weight: 700; text-transform: uppercase; letter-spacing: 0.04em; color: var(--pst-color-text-muted); } .cis__options { display: flex; flex-wrap: wrap; gap: 0.5rem; } .cis__btn { cursor: pointer; padding: 0.4rem 0.95rem; border-radius: 2rem; border: 1px solid var(--pst-color-border); background: var(--pst-color-background); color: var(--pst-color-text-base); font-size: 0.92rem; font-weight: 500; transition: background 0.15s ease, color 0.15s ease, border-color 0.15s ease; } .cis__btn:hover:not(.cis__btn--disabled):not(.cis__btn--active) { border-color: var(--pst-color-primary); color: var(--pst-color-primary); } .cis__btn--active { background: var(--pst-color-primary); border-color: var(--pst-color-primary); color: #fff; } .cis__btn--disabled { opacity: 0.4; cursor: not-allowed; } .cis__output { margin-top: 1rem; } .cis__cmd { display: flex; align-items: center; justify-content: space-between; gap: 1rem; background: var(--pst-color-on-background, var(--pst-color-background)); border: 1px solid var(--pst-color-border); border-radius: 0.5rem; padding: 0.75rem 1rem; } .cis__cmd code { background: transparent; border: none; padding: 0; font-size: 0.95rem; color: var(--pst-color-text-base); overflow-x: auto; white-space: nowrap; } .cis__prompt { color: var(--pst-color-text-muted); user-select: none; margin-right: 0.4rem; } .cis__copy { flex: 0 0 auto; cursor: pointer; padding: 0.35rem 0.85rem; border-radius: 0.4rem; border: 1px solid var(--pst-color-primary); background: transparent; color: var(--pst-color-primary); font-size: 0.85rem; font-weight: 600; } .cis__copy:hover { background: var(--pst-color-primary); color: #fff; } .cis__link { display: inline-block; padding: 0.55rem 1.2rem; border-radius: 0.5rem; background: var(--pst-color-primary); color: #fff !important; font-weight: 600; text-decoration: none; } .cis__link:hover { text-decoration: none; filter: brightness(1.08); } .cis__note { margin: 0.75rem 0 0; font-size: 0.88rem; color: var(--pst-color-text-muted); } @media (max-width: 500px) { .cis__row { flex-direction: column; gap: 0.4rem; } .cis__label { flex: none; padding-top: 0; } .hero__title { font-size: 2.25rem; } } colmap-4.2.0/doc/_static/og-image.png000066400000000000000000001451361524536416500174030ustar00rootroot00000000000000‰PNG  IHDR°vÀ"ì Ê%IDATxÚìÝu”Wð{g&¾îÎâîJiq—–Bqi ´Ôõ«{©P¡ÔŠ”â-îîîÎúfw³‘‘ûý1e›"K’ìf—çwzzXØ$“™É$Oî½ïK«4"pëcØ · »üH` ;€`„ ÜAß ‚B@ B@ B@ B@ B@ B@ B@ B@ B@ B@ B@ BB@ B@ B@ B@ B@ B@ B@ B@ B@ B@ B@ BB@ B@ B@ B@ B\cØ #„€@„€@„€@„€@„€@„€@„PBöÁ! ! ! ! ! ! ! ! ! ! ! !! ! ! ! ! ! ! ! ! ! !\MÀ.€[¥Tý?¥ÿþ¨bŒ1Vüm¯þ}BcE`ؽ€@(ÙÏ=ø©yOQ?…7ŽãÔÄè‘ Ô ¡”£”( SåÚzté²K–®8uú V!”û(X4;´M«æ½zv½«Óíáv»Ýf+¤”Tøñ@âŘ!±XÌ&“);;篫ÿ\¸tãæmäÊôž êÚívŽãP0†hWxFQ“Éd6›÷î=ðýO¿ý¹p©$ËEû»¡T /I2!¤M«æ“&ŽmÓª¥Ý^XPP€(è×Xd2™7nÞ2ýÓ™j%Ò¢€@¤ºÌ«mÓ+¥$=8q\Ÿ^ÝdYÎÏÏGùPRZÅHƒƒƒyžÿãÏ%Ÿ|ú噳çÝ !ø·¥„ cF5rHtTtVV¥X+Xú±EFFfdf|óíÏ_}û“$IhM*>(4{4/£æÖ­›øÞú÷R%??_ŽC3 Rº«7)Çq6[Ñh¼£cûöíÚœ=þìÙ êaB& !Ä^ )¥Š¢DEE<2éþþýzrŸŸŸ‡å‚$0‡(ŠýÄA'äY­‚ÀcŽ( ¸¤´   $$¤oï‘aÛ·ïr:]‚À+ 2!À­c°€hP?FnݲùG¼Ù©c‡ìì,Y’AÀΠض¢(Úíö–-šßÞ¡í©SgΞ;eN/b!!€—ÓD?êõWžµZsAÀŠÁÀÏ„j±™ØØ˜>½ºËмmû.L@  ^MŠŒxÿWG ’——çr‰‚ÀcÏ”£Xètº$IºëÎ;j׬¾yËv›­ÓGÈM+”ȲҺeóϦ¿ß¤Qƒ¬¬Lž/—+EaŒ©ÿWÿ "„\ù»«ÂdYVÍí/ c¬Ü•Ïá8J)-(ȯ[·ö]wÝqìè ·é£PñÛNÔÃ^o#„:‚ôÀøQOº_QäÂÂÂò²bPMzj„£”Bõz¥œÑh „ð<Ïqê's¹\ŒÝ(½^O%„(Š,Ë2!Äáp2¦¸\¢z÷ju®\ìI’Ìf3ÇñMÿâó߸h@  ê3YV‚ƒƒ^zîÉýûdff2Æxž ü¨lêõz½^¯Óé8Ž%Q’¤¬ÌL—(8yšã¸óçÏ_¼”Æqœ$JûR$™¦¿+!”øúuj :AQ”„øØ¤¤$EQªVIÕë„Ȩ(At‚NQdQ].—Ëå*zôχ²¬PJ£¢¢æÌýã¥WßÊÏ/P7N{B"¼$ÉÉI‰o¿ù¿6­Z^¾|I§Óàv2Æ…©3u:ÑhT·³ À–kͽx1íÈ‘£iYÇŽÏÎÍ9uú¬è l¶>hÅ¢Óë*§¦D„…W¯Q-6:²fÍ ±a¡aAABˆ(ЇCEõ¥§ÎÕ À½'Šb\\üÆÍ[žxêÅsç/¨'?! ÊuêÔšù金Qá¹¹¹–‹Vô ‚`4 ƒ¢(Y™™'OŸ;qêôλž8yþü«5\¯¼Š:´(¤_lÓý×Ô_T‡"¯’””X£j•&MU­œZ%592*Jmïp8$IRg–Ú°¡(ŠaaaY™9c'>rðàadBB¸µÓ ÏK²Ü»g·ž}ÂhÔÔ¢Aµ !L¯7˜L&AÐåææœ=waï¾k×m:pðð¥Ë—É5_)¥j’S—É•¼×•¡Tý3cìÚÉ–ñqquëÔjß®uƒúuS’ÃÂÂ%I´Ûí.—“Êq\àŒªK ×˯½½`áõÀ n;óJÿ¾=Þzý………N§+@zKȲ˜"BPPÏ —.]:tèȆÍÛÖ­ßtúÌ÷<¦®rTGóJ³ÏžZYFyWmOj¥JínkݶUóÚµkÆÇÇ˲TPP I¥\€¬É”$Ù`Ð›Íæ'ŸyqîüEE='ñ¢@ „[% RJE™úèƒcG /,´É²RæqEQÆB¨Åd4ò¬y[·ïZ¼ô¯M[¶¦¥e\*èéúªpݺe‹î]ïjѬqHhˆÃá´Ù a”re>•T=Üf³eæ7ß¿ûþ'ÇõØB¸%ÆŸvê¸Ñ#ÒÓÓIYÉ”e…0E§×»\®/\²|ÝúM'Nž"W–ªÍ<·\Y:H…->¬Z¥r»ÛZ÷ìÖ¹nÝZz½>??_t¹HYª›óå×ß½òÚ»'@ „[( ¾ðìÔÑ#‡§¥]¡ W¸É²B3[,Fƒ1-=}ÍÚ .Zºeëvu¨­(\•Ç rÕÆó<ײE³^=ºvhß66&ÆátÚl„Ð2Œ…Œ1I’bcã¾þöû—‘ Ë­è¨È† êVJIJINLINJJвXÌf“Éd2›LŒ)…v‡Ýn/,´§¥eœ;áÜ…K§OŸÝ¹{ßÉSg*Ìá ­Y£j¥”ä”äÄJ)I)I‰!!Áf³Éd2šÍ&“Ñ$Ë’Ýî(´; íç/^:wîâ¹óΞ»°ÿÀ¡K—Óq"!Üri0==­ KȨ ƒ‚‚ôzýáÃG-ý{îü…iiÿ|,ãž¹ ²•kÇQŽÊWêyÆÆÆôïÛ³G×;kÕªér¹ Êvy¡$I11±È„å.v¹«c‹æ›7mT95Å·;±ZóvîÞ·j͆ÅKWœ¿p±Ü턤ĄÖ-›¶hÞ¤eóÆÕ«U)É[—.¥íصgûÎ=+Wo8zìDY=£å kP¿ŽW7ùìËïþ÷ê»~¤¦N™8uò­îíÛï}êù×ü´©_ϘֽK'­îíµ·¦}üéWeµÛçüúUÛÖ-¼½UÍÖ°ù6[až0íok5ë§/‰:W1ÆSDQ’dYE§Kt:‡³°°°ÀVX`³åääæäXÓ32/]N;{KÞ¿  ¬Ø°i°(  †½{|÷㯋–,Wûø©¹H–™\Ú!(ŠBBåyJIKKÿ싯g~ýCnGÜ7¸AƒºN§³ c¡ ééi£G'„\É„™00…‡‡õìvgŸ^]Û´j^òiÞ¡¡!;´íØ¡íË/<±oÿ¡Ùsÿüå·yù¾b¢£úôêÚ¿O÷ÆêkuŸññ±=ã;÷ìÞù¥ç?~âÔÂÅ-\ü×þƒ‡qÊ öíZ¿}y×¶U󊱗*¥$µñé¹Y,ýzwÿñ—ß+ä§ õ]˜Þ«öZ'OÙ¸yÛÒå«Ö¬Ý(J^ƒ€@Hƒ%FŠòoüþÇ_ÿ\¼L’$5 *Êu::T L–YѱEqþ‚E /ëÕ½Ëp·XÈqePr™0ð¥VJž0~äàA} ƒ?î¿~½ÚõëÕ~â±I¿Îšÿù—ßæ€aÇmï;¼]ÛV~ýê¤ZÕÊ“?ù¡ñ»vïûìËï-ù«B_—ÊŸ*•+%&Ä_¸xɯ‚ÐЊ±—†ÜÓßç1óaCVÈ@X’S®JåJ÷Ý;03+ûç_çÎøê‡Ì¬lì(8츪ÒIÙ¦AEQ$I2 ‘‘‘GŽŸúÄ󃆌š·`‘$Ijó@YVn‘¢v2¤”ò<'IÒ¼‹ 5õ‰ç9i0$I*ý¹²E™ð…g§ª›8½oqU«Tš1ýÝ «ޏïn?¥Aâ680vÔЫÿ|öÉÉAKàì„î]:ýµhÖ/ß~{û6¥6Þ¸QýÓßݲnɘ‘Ct¾f íokå»m×¶eÅØ?<ÏÝ3¨Ï7oØ nýºµqš]+*2âáIc·®_úÄcúûj €@šÏ‘àEyþé©£G•M”e™ã¸èè˜ôŒÌ[9 z Ó32££c8Ž“K½_ü?™pÔð矞ª(Šztð *C&“ñéÇ^½|^ïž]Js:±^¯hâ˜MkõëÓ=òðìŸg~=cZýzµËj¥âkÿ{zÕò¹;´Å9Y±aûÛZWŒýsÇííâbcJrÆÂiv#f³éчï_ó×¼¦`o!”<ÇI’üØä‰ãÇH»|¹”Ó ,Ë’$‡†…BÞŸöÉÀ{F"  Þ3òýiŸBBÃÂ$I.åX(BÚåËãÇxlòDI’yWR†Ó#×­øã‘ÇyµÖ…hZ·æ³ÞúdÚÁAAeUiÊC÷¯Z67ÆmªU­üË÷ŸÿÕÇñq189Ë\»ÛZiþu•^¯oѬqÅØ?C(á=ôïÛÝb1ãL#ÅNãŸÿûwcFÁ®Bt‚ÀK²Ü¯O÷ñcG¥¥¥ó›7md4V„I€1ÑQwÞÑž”xöx¿ÞÝq¦O'¯ýïé—ž{»!°Ó $÷èÞùí7^±Ù J³û¼,˲,GFFefe?<婉M=qâtѨ A¼A.,-<å©Ì¬ìÈÈ(u–Ú° !Äf+xûWzvë,Ir¶Ä¸5?éþöãŒ)Ý8óuS’Ìù¡M)V_lܨþß‹÷Ó´ÀŠˆÿñ›é/>7« IÅš5ZaÞ=°·&_ä 2§™'7ü•ŸÄ~B 9È IrÚ5_~á)›­@–•RKƒWÃçÎ[0èÞ‘K—ÿ­VÎĨ ç£…ê[ºüïA÷Žœ;oAXXxiªËf+øß‹OÕ­S«4Ož[\rRÂÂy?ÞÖ&à>˜†ýöã½{v)ús~ù*gfRJ'Œñýן˜Í&œ´e[—‹u‰¥ïÞ»ûkr?(-ã¹q£ï{äÁqØ€@× ]–•„ø¸™Ÿ¤×\.±tyÔR¢‘‘‘™YÙO~òñ§_ÌÌÌVKÚTŒó¤tûsð<—™™ýøÓ/><ùÉ̬ìÈÈÈR+@ÊóœË%¦é¾l±0ÆP`†ø•Ú‚ß¿O­”˜›§Óé>ýð­®;?Wýñ›éå"huìÐvöÏ3ÃÂBqê–‰V-šj8HÔ°A½Š°[Z6­Z¥’V÷†Ò2ž{jêC]îêˆý„@çÛkÆXppÐ{ï¼n·–Î20IRK‰FÏÿç Á#—,ÿûß9¢@|›yûÏ Ò%Ëÿ4xäÜùFGGs'IréL9¶Ù ’’’<1Æq„~T«fµ?fK{úŒéï6ñ[a½»:u˜ñé{µh°xM7øcö·1ÑQ8IYTzlÚ´¡V÷Ö¦uóŠ17¾äådJËøúÑëÃw_)aqWB Ží0Æ^xöñ6­Zåææ–NYQIƒ‚,}vÄè ?ü<ëÊÀ ŒÃA´+Ò£þðó¬£'œ>}6**ZÅRI¤Ôét>6e"ÏsŠ‚x¯=£ÑðýW'&Ä—— Ž‹ùà—‰ÖÍüfºÉd,G°Aý:3¦¿‹¯KÊu]™Š±€°ßšJEiâå4‡ÛÛ·Á~B e^H¦eó&Sž˜‘‘^ iP–eYVbbãæÍ_8xØØÝ{÷ ý8T(üî½û;oþ˜Ø8YVü¼yž+,,¬S«vÓÆ cøÔ«¹O>x½QÃr¶xéÎ;Ú¾»¯†‘ø«Ï§…‡‡•߃xçí_ûß38™KY£õ4i’U£zU‚ù¢¥e4ðìS“± p ö­XHFQXdDø›¯¿(Ib)$2I’FƒN§ó>ŸñMQ"űðë>çy.//ÿ±'Ÿ?vâä#> Š.‡Ãé׉vŒ)F£±G.[·ïÂ!ÐÖ{ú÷ÔhŠ×¥Ëé›·lߺ}÷ÉS§OŸ9gÍË·Øx  ‹iÖ´Q‹fš4j ÉüÆž~tÉÒÖ¼ü’ßÕ›¯<§aŸCQ÷ì;¸eëŽÃGŽŸ>sîüÅK…¶ÂÂB;¡Ôl6Y,ÉÉ •R’êÖ®ÙªEÓºujiõÇÈa÷¬ß¸uáâå8«KsFL›ÖÍ—ýµŠ`¾(!õêÔª_Ï/ÉmØÐAO<ó2Î7Õ¯[»më6mÅ®B e± ™( {õåç’“’³³³ü½tP’$“Éìtºžéõ?þ\¢Î'DýRZ•f8Ž~>ã›K—.?ÿÌ&“Én/ôß§”s8ìõëÖ35”÷¿JÚÑØårÍžûç¬ßlݾëÚ‘yQ’.]J»t)íà¡£+W¯'„˜Í¦Aý{~o 3XDDøc“'¼ðòÛ%Üþ}{h2ØÈ[½vãÜù‹/[a³^÷w¬VÑjÍ»pñÒæ-;È•5?½ºß5°_¯–-š”|Þyýù­Ûv¦gdâÜ&¥8kT‹@X拽·?ñ×LÔî/½úNa¡=À÷@Æí³³sÈ ¾1§”pÏóœ^§t:“Ñh2CB‚ÃÃBcc¢““7¬ß¢YcM¾/5üB@ „²\:صË]éi¥ò²rÆ>ððƒ‡u‚ –V‹<¸2}”éá?—?qjæçEF†û¯€¥Äét&&ÄGEF^NKã8MD4ñÁÛ/—d›¢(ßÿ8kÚ'_^NK÷üV……öï~œõݳºwéôú+Ï”¤&Þˆûîžþù7ié>ßCxxXÉ:3Æþøsé´Of>rÜÛÛææZøù÷~þ½iã>ò@§ŽíJ²%ááaÓÞ}eȈ 8·Ë×2Âv·•ûBƒÁпO?ݹZZæ§_çòÜɉ"ËŠ(‡Ãy£_Ó B×.wŒ3¬yÓF¤DsÈ;Y,6^¡@°†Hé.lѬñ”‡'fgeú; Š¢±ïÀ¡c&8xXx¤Á2!J’ ð1f¾‡ÂÃ#Dÿ”™Q]„††UJITó!”\¯;´ó½Fâ±ã'{ööÔó¯y•Ý-^¶¢}§>¿ÌšW’Ï “U’ðâ3ED”¨¶ê±ã'ûÞ=ò‡žð! ºÛ±kïБ‡yèâÅË%¹Ÿ;n¿­oïn8½KMõjUâãJTè¿JåJ娤Óôìvghhˆÿîø­ÑP”¤?-ïÕؤGžÊÉÉ%%XÝñö¶xy!Òí:HL&ãóÏ=Á˜âïI›¢(FEEmß±kÄè G(ï‹9Ž+×íõÔ%…G1zÂö»¢¢¢D¿•U«ÚàG4úç©©ù|ó%ËVvë=d箽%ÜŒ¼ü‚)¿ðÊø|C÷²X|»mó¦îÔ§$ÛÿÓ¯s:u´eëN­ŽËò¿WßÞ¹ß_+Ö’õ§Ö ˜¤SÒ3“x1¾×ºÔj²h–øe¾è¿Þÿ­VZfÎüE]z >}æœÏ÷жu ¼J3ÒPEQôá:µjøµ¸ˆš·nÛ9~âä‚Ïså7!¨]EQÆq”–Ûa/µ=`AmüÄÉ[·íô_&ä8®Üõˆ XwèSµJªo·ýñ—ßGß?YÃÉHÓ?ÿúÑ'^ô­2pÅâs¨{ú‰‡}~Ý)ŠòÔó¯=öäK.—Kó(2|ÌCŸ|öµÏ÷Z)yØ­1œâ?[¶î ¥5kÔó@¨(ʶ€,¬•Z)¹uËfþ~”[í¬>{îBïÃ/]Jóíæ­Z4Å ”Ú8ƒ,+wvê0rø’qOƒùùWŽÓ ÏsêxWåÔ”úõë* cŒq!´4Ó`9-+Âqœe-óÈC?ýäý7^û߀¢¢¢YaŒ•Ó¤ŠâÇL¨(Š^¯OO¿|áâ%õã2^}%Ñ宎¾­YÚ±kïãϼì§ýÿöûŸlڼ݇Ö­]Ó‡‚¥O™èó¦>û⛳~ÿÃ߇é÷§ýÝ/¾Ý622|@ß8ÕKbýFO‹4ÆÆD׬ácÉܺujzÞsæm$ ¿ß¼{`oâýw@ħÒ2·Úy¸uû®9óŸJýU­R /d@ R*}&”'Ž«T©’Ãa÷_ÇðŠ‘Ýæˆ*]»ÜùÙô&M/‚^Ç÷èÑuâ„:w¾Ëh4( ã8ŽR™¸ ˜¦CGO8.up¯>Rê«}ò Æ>0ÅkDeYypÊ3¾Ý¿·ëԮѺ•3Ü~™5ï›ï)#õÂËo5¨ðÖˆa÷àT/‰›·•¬Q¯Nf#NÛ{[+øø‰SϽôf~AAi|ô©³'*@±"@ R.&‹¶lÞd`ÿ>YY™þ›™ IRDDdyOƒEsDkתñÁ{¯¿ôÂ3)ÉIé²$ñ<dÒë úž={>ôÐC 5T…1¥<.,tÏ„[¶íŒˆˆEI«L¸âïÕxÝ•\Rb‚oÅEß›öÙ¥Ëé~ݶ /ýôË߯<½üXy·o[xòÔ™'Ÿ}•”bѦ ?q£®†äf]›6n€Þ÷@¸©taKÏ¿1ñje#)Åù¢ÞÞäç_çÚçÎ_LPZÆ'Nž>vü¤7Œ‰Æ ø¹²(ÓétO=1…çyEaþKƒ¡¡aûöí/¿iP8•e%22≩“?šövËͳ³³‡ Ï˲dÎÊÊŒ‰‰vß}ãÆINNþga!W^3áý'ïÝ·?8(¨„e`… †³çέ\³ŽRê¿“í1øî¾çõõùÔé³3¿ù©6ïÃé_úP¦¥VÍj)ɉÄã%”ýúø8ñìÉg_ѼŠLñ.]Në½Oˆ•úïÆ ï³Ì¬lÏ?…·nÕ̇’W:ÎóÊûòªöiéˆ‰ŽºóŽöÄËÎ ³æ, „üøËï><â}CÞ‚gã†[}kLŠ2 ‚¿+‹²aCînØ ~~~žŸj?J’l2™²³sŸxæ¥üüu¾ey\.ÈóÂ}CÏüâãþý{3ƬV« EÊc”’¤Ä8Nçt:íöÂÚµëLœø@¯Þ½‚,5ÿ”¯X¨( ÏóùùO?÷²¤(Ï—äÀ)Š:cæwv»ƒã(æ‹–P×Î}¸Õ_ýP:ý].]Nÿ{Õ:O.ÇŽŸ\°pÙÛï}2jü#­Úw?wþ¢‡Ѿ]ëÐ`¶mÑÒ¿×mØRú‡ì«oò­ú|—»n÷ßL~| 'ÿ]ÞÖ¤‘×ã±Íš64™ŒžnÌæ@\@xÏ >Þ~økŚ̬lBȾý‡öí?äí#è×ã,˜tÖãë›;£AW1 ‚sŽ¢°¤ÄøqãFZ­VFˆGÓcd½^ïr‰cîðø‰Så«Ã„ûrÁ;:¶ÿêËœ8>44$;+‹1ÆóüU¿,IrxXHtT¸,+<Ïä+ŠrGÇŽS›Ò¦MŽç¯,,¤å§î¨,üÑc'¾ýîLjÈHY–}" ‹X·nýïsÿPO<¼K"6&ºníšÞÞÊš—ÿÛìù¥¶‘K–®¸ö//^¼¼rõúéŸýàä§;uX¹VóvúŒŸ4õý¾X²låé3ç<ÿ¦ {×N¾mØ´g”UßéŸûÒ…",,´YÓF8í}„~^Fèݸ€ðÞ»ûïç‹·6¥e<•ííôzB({èŠK*t-öÐÄñ1QQYY™~j5A)5SŸxîà¡#‚À—£îójv•e–ššrÿ¸ÑíÛµ•e);;‹ã¸bö•¢°øØè̬\512Æòòò,fó A[¶l¹xñ¢#GŽ^©ìÊÊÅ(™ÚŸðÃO¾hØ ^»ÛÚ¦§§yûæ$I’ÅbÉÌÌzúùW…©•ñ,‰;n¿Í‡¯–,]a·;Jm#—¯X“™•}âÄéC‡:rìð‘ã‡Õp²œoK(׬ÛäÃh†V~û}Á“SŠŠŒðö†;uزu'Î|âÿº2ínkõî´Ïü¹€0àŽcëVͪTö®Žåå´ôUkÖý8wþâŸêíˆßð¡ƒ~úuÎ-u*r>Õ™+åùí!$·Z-™¦öë×ËEQŒŽŽyÿÃé —,× ByIƒê`©,+aa¡=8~柴o×&??¯°°Ð}ŽèuÓ¯,ËAsTd˜$É”RJ© ð¢(æççÅ'Ä?~Ƚ÷ÆÆÆ(ŠÚ𢼾cŠÂ…=4å©Í[·ÆÅÅ‹¢èùÜQQt…††æåŒ}àá /QJËi£‘@ „>ÜêÏÅËKs#­Ö¼zM:ô4â©ç_ûîÇY[¶íÔ0 VNMIJLðᆿ–âéu?ØýñçRnèíú.p—••säèq¹Iã‹™x3ÒÕ¸a}yï¾6)ÿåd~›ý‡ûdŸü‚‚ —”–¹™ð°Pne+,īʜ€¯òIE¬%£(DtNž$‰’ŸJ’3köÜ/¾üNày—Fe*K' ò×§OáÃî‹ÍÏÏËËË»j‚h1öï ¡ûݺFH³ÍêÖ«»fÍÚ5«×8œNJ©:Tà™ã8«5ü„Gß}óå.]:YssN'Ïó7Ê´ê<[J¹ØØ¸C‡>úøs)_†™çšu›+Ìõ¼E³Æ>ܪ°Ð¾dÙÊ²Ý sæ/3rˆ··ªY£ZHp°çÝÌ}yŠŒ”ƒÓƒùø¤6lÚæaA ´nÑ쯕k=|„V-½¨C³~Ó¶v2 ”£Ô«{goß~þmÞUÛóÃ/¿¾»/ñ¾´ÌϼâÏ7/oå§½]«VunUPPˆâ€BðS-¥kçŽmZ·*(È÷G¹I’-– ÃG޾üÚ»”R9à…Ü— 6kÚø“Þyê‰)!ÁÁ¹¹Ù×.,þ~dY úwðßâ8Žã òó a=zöxèá›5oªŽ¿þ‹ŽNzôõ7ß³;±±qƒA–I’d7ê‚ DFF™Í¦~šu÷ÑHƒŠˆ‹ñöV»öì÷_ïÁÒרa=nµbõºÒœ4{];vîMÏÈôá†õëÕÁÉO|o>±Ýó_nß®•7k[ú©FéЯ§Ñhðê&›6o¿¶<Òö{<†ý÷ÑûÞZ¥e<¯Fë.Ïão‚Á«VD§Ó9¬°°ÀÓE^R”ÇŸz!/??ðKJuLMMyþ¹'>šöV½zu³³³EÑÅó‚QMQ”„¸½^¸vz¤º°Ð𛣶¦?~lÕªUËÅ REQ(¥Ç͘ùýà{GOÿlÆ¥Ki!!ÁÑÑÑááá¡¡¡¡¡aaaaÑÑÑaaa…vÇìßç÷Ðs/¾–——¯–iÅ«OõëÖòáV[·U¨h ëû’ŽÖoˆz›6o÷å)7@ $%©+ãùÛPûÛZû£¢Œ$É[ïe8d°×óEúmîuÿþ‡Ÿ½^dñ¹yL¹ÓºeSÏÛêÿ¶ ÂKŠÊ€æÃƒ²¬ôìvgÃõüTKFQ”ÈȨ§Ÿ{yÿÃ^H†ã(cD–•°Ðý{<0(((ÏjU“›Ï‘[–‹Å}æÜ%N¸êƒ¥”çy—Ëe·;jÕ®U½zµ­[·/[¶LýPíBuî(cŒç¹ÓgÏ¿ýÞ'Ó?ÿº^ÝÚõëÕ©S«zHHˆ¢(¢,:täð‘cûöLKËPö¢0¬Ô2ÖóeÕÍÞ²«¤âÕ«W)µ&`š[¿qkŸ^]K'ƒ*;;çÈѵjVó°fLt”'¹Q‘Þ'!dÏÞ6[`-«W·V/¯'yù ÿuÝš=÷Ï矞l0x7Þ8|ÈÀŸ~¹%JË<­>‚¿Ø´Íó×N»ÛZΙ·È“’¤žO!Y¿)àN õ~xpîüE‡óºÿ”›k]´dEÿ¾Ý½þ]¿ní}UìÓ¯W÷ξ»t9=оG‚)£P†cƒïîW­J‡Ã¡yŸtYVŒFã¹sç_ó}5|’À#ªÈ²Ò¤qƒ÷ß}í/>‘•™éÕrAr³aR£A)ËJ1Ÿxžã8./Ïj±˜{÷î=eÊà 5Tâ8ŽìÂBEQÔ’<Ï ‚Z\FEÕÕHŒAHLˆ÷áx:}®ÂìÊ©É>ÜêèѲýGŸôáëŽ;_›Ox8k´}Ûr¼€Ð`0 èד” ýàµ~ð¾!!!dØÐûÜ«Y£Ú´w_öí¶;wíÅ‹h>>6ªøA¢ÍãyÞápÚlõê×›<ù‘Þ}zY,5Lj>–ýy©@‰@ G G)G)O‰@ O Åkõ&Ðë8”™™}‹ïBȵuËйsJ- ¹²ŒððO¿Hˆ­^µrñ¿S)%)9ÉÓf˜»÷ì/,´Ô¹oð¢éð ê'~‡Ü¥edÒØ?ç|îó¬B B ZêtºÑ£îEQóÞƒW&‹ž{ûÝ9Ž ¨"÷–wtlÿÕ—?8i¼ÑhÈÊÊTc˜ÿW’äø¸è  ³,Ëž ?ª©ÕV`cLétÇ<òH³æÍ8Ž+­)<¤F>…1‰‰……1…1™‰™F o(<<ÌÛñ„̬ c¢£|¸•oÍüÄ·ILˆÃK€”l!Ñ®ùDûÛZ•»‚FER+%·nÕÌ«›¸\®ÙóÞô×~5_”¼n;<|H…š5yÿØá[×-yæÉGôz½Ï÷³{Ïþ ÞÏ&@Q¸IqÑî]:ù­¸(3™Ì¯¼ñ\vNn@uœS7F–YíÚ5ÆÑ¢e3¦(ÙÙYÇù£Â굓*u:!>6úØÉ3¼—m0¬VkhhÐða÷µlÙbù²¿Ž;VT´³œNÈäa„ÈŒBªé…Æf}ŠŽ¯cJJωÒ—¼ÇîÚcmŒ©ÑQÆÌÓÿŠŒóáV99¹i'„ø4s2=#«¼ˆðp¼HÉ–Ž5Äóe„_}û Ñb¾¨·Y””J· o¿a\²len®•xðõÓÒå+½mv_ŽJËPJ9Ž ‚Π×F‹Å•˜W­JåúõjU¯VE“/pg|ý#^¶€@Ú&B8 ·(º4h’e9,,lÙ_+þ^±&pÒ Ú¿A–•¨ÈˆÑ£†w¾ëv‹%ÈjÍ¥”–Bt$ŒŠ »”–QXhW›zxCAàEQt:s+§¦N˜pÿöí;–/_ž™™à­)ŠTÓ];‹a|„¥•Ù,ð”RùÊá¡”ŠŠrÎ%þ–kÿ!Ç–%+¥ ëÝFnåpº*T òáVêk'@dçX}:ú¼J¾ŒÐÃwÀ6­›óvF)½­u WÅm;vÔZú{öööVžÏýñç9ÞBBȰ¡Ÿxæ•@Ø?‡v¯ „ÍHKÏXðç2¼l`Ê(hxõW¥YÓFM›4ÊÏ/Ðv’¤¢(ǼóîÇj×ûY.¨(L„þ}{}5ó“ý{3ƬÖ\µ f)W^>>6Zñ~×p'¼Ãa/,´µhÙ⡇&u¾ë.£Ñpe)W¾Ò`%=ÿmrä/•¢î6)„e‰b¦KÌÿù/[”2]bž$Å üÓqaË«Ä °¨Õip"ÿôeö‘(Ši'øVžÁî ²:.—«Ô¾â6T~èð1Ï¿whÔ°ÞþµNíž/ Û¹{ŸÝîœýЩcû¸XïjS¿pq݆-þòšu›Î¿èíVõïÛ½—–ñÁ‡Ÿ|éÃä[B¸‰~}{&ÆÍK§DDDÎøêûÓgÏ•yeQ÷å‚-[4ùù‡O<þHHppvv–†-%|$ ²xº’ðºù6ÏjÕëõ={÷|èá‡6j¨( cJ ·¦¸* v 6þQ)ªkˆÉ*I¹’Äþ©+CÕÿ®•¡NÆ2]bOßIˆø$1<˜£ ®D%‹Ί5B¨×éJ-ƒù‰oE_M!,± 5Ÿðra`ͽï^¯Ûþ2k¾çoîŒ1JËUÈÒ2¾9pðÈ·?ü†ý„ åp™,+I‰ ];wR‡È4MƒÌh4ž8uê§_~Wåʺ» “e%55å­×_zïÝ׫T«’››«yK âý !Ïs)Iñ%i7/¼,ËÖÜœ˜èèáÃî7nLrJò•Ö4ÀÓà Pó×)QÁ<—)Šjü£Å^tJ\Œe¸ÄAÁß§D†ò«uJÌ·HPÁv/ðå=û¶1&ÆOJÊ«f€Å¤¾vmËë˜è¨NÛ{ûµï/³æ{u“_gÍóañH+-S’ïÙŸ~þõ@+Õ„PîËÉBzvï!I’¶ E¶X‚>øðS«5RRV«½Ôç(ËJXhȘQþüâ£víÛÚ ì……¥?GôF= #ÂBÂBƒ=iAQ|k —Ëa·Ö©Sgâ„û ¬æðŒ…jlmÖ˜‘+INEÑyüÜ9Bt”¤9m‚Í&„1Æ8$BB8Η,¤×ë*ÒNà}Ú Š}[ÆŒ/EJ¹aó¦ M¦ëŒÉë¡U‹&Äã¡éí;háàA}/¿RY»~³·µ./]N_±j­-ûêÕ­…³ôƒflÙ¶ûAã(b0è{të\XX¨íª3YV,– ={÷/[¾ª¬†)ýg¹ ÏqݺÝ5sÆÇ÷ß?š0–gµª3-IÀôp'”ÄÇÅ”<6«O¹  €1vÛmm§L™Ü¶mŽç…Q@3H9B¡Q<÷AB¸]–c¼÷Ûfภ‡³[¨eRdÌË ömª¡NW¡¡o“? †šoéÛZЀZYNåæZ:êù §uËëôfhÒ¸ÅböðNvìÚç¤wï=ýˆÚ^뇟çøp«áCÝâ§è†[ßö^ª€@Ú7£oÙ¼iíÚ5 yžÓ4ä(F£ñëoE±ô‡)¥¼À3¦(ŠÒ¬iãO>zç/>‘•™YVËošÌÃBƒK8HHþ;?6/Ïj6› 8ùá‡kÕªÁ˜:ƒ4 :RJÆ‹ ªlÔÛ_Òà•á š+Š“£ƒ+ëy…îÖ%ñí“¥¾bBßrQIºi}Ú!Ѧù)Ù¬ÑòÛ°M«fU*W"^VâY²|•µbÕÚK—Ó JËï–޾JÙÖb@ $µÛÄ€~=5ŸnT4<¸´,†ÿY.(ÉÉI /<÷äGÓÞªW¯nVff™/,~RC4 Ï”RžçEQÌÏÏKHŒ7nÜèÑ#cccEQcaÙ^8dF*ëøþaæQh‰îJb$Xî b„PrK7¡°Ûí¤´÷p*ö¥`c@}Êô||©äO®²Þ›„vÝì×î¶–~*cãoCðö&¿Ï[èÛ˜¼,+¿ÎšGPZÆcG4t\®5/R@ ¢í¨”¢(‘áÍš5-,´i“ÊjxP*ËŠÅb5bègÓ?èѽK^^^a¡M„À™#ZÌ aDXˆ&ƒ„î ‡Ý^X¿~½Gy¸s—»,‹¢(jÿܲ$„Œ ÓédFh‰×"ZE±oˆ)UÏË·ö ¡oƒc¾µ³¯`äÑQ‘ó¢££0BXV6oÙáù̵í%ÌfS“Æ ˆÇs¼wìÜK¦gÏîwy{«Ÿ›çó#þôë\»nÍÒ2kÖmê5`DVV^¡€@Dóa4BH‡vmâãb].—†a©L†Ý[JtírççŸ~0iâx“ɘ““•c<]IHHJR¼:©yN.(°Âzôè1ù‘‡›5oʻұ°T%DfÄÄÑC¡,—<“RB$FÂuºŽ!„»… ­>}y^‘v‚­°Ð‡[ÅÆF“@ªôèËÑÏÃÐr­yž/#¤”^UP´u˦:kíØ¹×·u¿þ0 _O£—eŠwïÙïù¾ºÖ¹ó×®ßLPZ†Ü¤¦èÇŸ}}ïðrs­xy!øã*Ã(¥}ûô´ïmÊ Ciµ”¨]»Æ´÷_ÿߋϤ$'ed¤K’Äó)O5~ä ‹9*2L’dÍsÚ? ­¹!¡!Ãî»oüø±ÕªV-ý¤êóª¢ããu¼C–5z`&3ÖĬ'ä–ž3š‘™åC—yßâGÀºt9͇[Å{Ù‰Û¯bc}9"/^Æû)õe„þ;kÔ«†ë7С޷üɧr2î~Di™b:|¬G¿a¯¾ñšL!økj¥¢()ɉõêÖ¶Ùò5,²"ËŠÅb9xððÒå«(¥þTÇ9eY‰ŠŒxbê䧽ݲEóìì,‡ÃàsDoÔ‹ÞAúÊÂB—Õj­U»ÖýŒ»ûîQÑ‘ê¼Ò™Aª.óKÔñ&žW´ud‰/P¢z ¯ f>Ôi0™Œ±1Ñf'ø–‹ªW«Læ‹¡ª•S}¸á…K„D£e„ÞÔ•i×ÊçŠ2¦a½ºµê×­M¼œ¢<Á’>îÒå+3³² JË\{»”öÄ3¯ÜÙ}ÐÎ]{ñ’„À'`”ßú¢ŠB]xxxff†om¯n¸zÐ`úsÑRQyžóß×ZjØ“e…ç…!ƒèß+>!>ÏjµZ­>2$¼œ–¥Ó þˆ…jÇ [ãhÛÛn«W¯îš5kW­^«Èÿ KúuPWkµ:žR¢Qx£”º%EÏ›)ÍS½…Ç /\¼œ’œèí­*§&§¥gT˜=àíjÕ¬ ÛŸ’œèÇ]—Ë…õED³e„ÛEñðûĤĄʩ)§NŸ%„DD„ש]Ãó@µc×¾yÊ÷Ýëu9£ÑpìÀ¦2ÙÚà  ¾½»ý\âñÉÀ´{Ïþï~š=gÞ¢À™N €@H*ð|QBH«V͵mF¯(ŠN§»”ž6gÞŸEâÔÄqTšwtl?bø½µjÖ,,´egeñ<h-%|$ÌÌÊõk0SWó¬¹z½¾wïÞ7úûï•{vï¹ÒÄ¿±PfþØoxeo;D«ªV­¼yëΊòͺ/°j•Êz½Þ·z‰ÚªU³ºoã 8ùµbÍË?pèˆç#fíok¥ÂvmZxþ~º}çî@8ß!ƒ¡ßåë:¨"œœÜ=û®Y·é¯k?‰× )µú¢Q‘uk×´Û µ „áá‹—ÎÉÊÎñÓð :ÙU–YjjÊãG·o×F–åìì,ŽãÊïÀàu ƒÌ qÑgÏ_öÓ ¡[,ä%IÊ˳ÆÇÅ1l“Æ‹-NKK¿2Œì¯‡öGyŽâÅMö¥ÆC“†õ~úeN)oê¸QC{öèü׊5Ëÿ^sôØ ­îö칋¢$é¼¼߬Iƒ›·—ùlÙ¼±·:rô8N~¢eûïmžÂíZ÷ã,BH;oæ‹z51Õ¯zõ¸+´¼õžQKËì?p˜p1YQ$Qr‰¢Ëå²Û6[a~AA^^ANnnVVNFfÖÅK—ÏŸ¿tòÔæÍ ‚CC’$·iÓ"!!!;;KÃ!5J9—Ë5oþ"¿^dÃÆ ܧww³É”ŸŸG¡Eÿ÷8D`[BµE\lTZF¶¶C¸ÅL¾U›˜Õ«_¯zõê›6o^ù÷ʛͧ˜CQfEMƒµszž?kw2F)-µ6'hÏÞƒ>ܪi“†¥¿©:´iÕ¢I«MžzÊ™³ç—¯Xó×ßk6nÞîC]w¢(>rÌÛQj9@„mÛ´ðå¸ï;H@Ã@¸iÛã†{~ÈÔo*Ëé¡÷ô/ÇhøÐAO<óJé?níFí³³1=EeHE¨:uJ›6­u‚ ù£«}/ˆ²]³£„ÂSzA’%vK· „ìÛЇ“°fªa¡!¤tg©µiÙ¬èÇJ)IãF õÓŒÃ{Ö}ýÅ÷ÞÝ·$÷ú”Š;vhSæ‡/,,Ô‡(ëóSRì2BOZhHƒúu’*¥$yx»Ý±sw@, ¬œšÒºU³òxŒn…Ò2#„@ü9úd1ׯSÛn·køqŸ1Å`0.\´\’dAà%IÖ|š«ÁlêоÃÙ éœP» 2Æxž7 n Ž2Æ ½Á gŒQ·Ì`6¯Tï¤EÒMF£û>PÿM§Ó]ÛKýÚ}ÅØuÚ\5ÍRýTÁ˜Bõ¼ê©z˜bb"ÓÒ³Dÿ’ÿ¶¦ÈÏÏ zàþ1°Zó´sSïê¤(_åD½àP-b!å)ÝYè"„Ð[~ùÓ™³çS+%{ûu@§;ÚÍ™·¨Ô¶óööm,óµdéÑíÎÝîdŒíÞ³ùŠ5ý½vßC^Ýùz_$£q£ú©•’OŸ9W†‡¯W÷»ÔŽ#„e*/¿`ÿÁ# êÕö¼ùD†7³þ¶nßU‘p­ Ü?§É[¾´ @¹ „ {”·ú¢²ÌêÔ®™šZ)//Ï·7ZfµZ×®[OSÅç#L±Zó,²¬(ŒE„”$ÙnwÞ w\²®}ãÓétW¥¼&%RƘ ðf“ñª(Šn1„ žça”YQxž7MzÎér:ÏÁ+Šb4ècc"Ïœ»äï•„WeI’‚̦ £,IEC¤DËÑöà¥Ëé¯\³üï5ë6l¶Û7½ó]¾Ž½ ìßóÝ>-Ãc7°OâKƒï éþ.ËÊÃËŠix“ ·xÛ·k•‘‘å͔ԭÅn-+•çNxž»g`ïò{­1tÐÏ¿Î)‹Ó }0BHÊý!!¤Iã:Ž1E«y¿²,‡††®\µö̹ êj ÿÅžçå?)ó|tª(.¨$éÚßs:ó=K˜W2Bt‚ÀqTÝf³Åår­Zµš§l̘±11ùùy²,{²nóÊJÂèÌì\»Ý©ŽÝ‘R©@«Ó UR“N»ß&-BÈ̬‚¡fž–t¡ÌH¤^÷mVÞi—ÄS"³[ý­zùŠ5>ŒÝuG‡Ð`k^~)lahHpÏîwyþûñq1Æ 6dФÉOý>÷æ©uÿÁÃié>4WzOÿ?ž!^ïšP ªW­Ü²ynø÷ÊuxkóÇ2 ãGzøË-š5ÎÏ·yËÞwt(×=H¿´ ÁB Üp¢qãÚV+aŒ ‚ná¢eŒ1®Ô«=2æÅú¾«^w áµK o´(±hÕ¢JàyI’ìv'ÏëA¿k×î·ß~wѢŠ.9êþŸ~ùçùÐÐPY–=ÉÌŠ¢èõBb\Œÿöukœ&ÄÅXÌ&EVü4“H!„§ä”(Ï͵…ët+Ñ] ”äKÒYùôÊÅ[Üšu}h`e2ï-­Úƒïîg4|X¥ìaòaŒý½b­–w÷ >euà~pœo×ä¿V¬ÁiO´_F¸Ãó•ðz½>22ÜÃ_.,´ïÙs žãÐÁýËûa6dÎUBðe%^HHpÍÕÍ*Šb0.\¸°iË6õs)ÿuw<˜ÌíV’$étº°°°K—.Íœ9ó›o¾MOOç8Êó\N®õãO¾˜ôðÔM›¶›L&I’ŠOz”RI’##‚Ìò•®ñ~==$I ¶$ÄG‹’DüùpŒŽÒ÷2 N9\&Žú<¬'+,L§›–‘wÊ%s”( 3yˆÝîX¿Ñ—ñ‡±£†\»zVs:Îów[¶íÌ͵zøËËþ^íÛæ=2iœ^¯/ý£–Z)¹Ÿ¥y¸¡xùûúeèi˶e5 í.6&ºSÇöåý0 è×¥eÁë„„šÕ«ÅÆÄ:.ÏkœÜ4>™Læ‡gfe«…^nÁ}«– µÙ ÿX°`Ú‡>|DoT¦þ+Ïs‡yôñg^xñÕsçÎGEG ‚ ËÒ͆^ùøØèRØ«êènåJIZÅìQÂ2eeÊÅlÏSêK&t*J´Ñ°ÄZ0=«€§~iv_NÍñ©õKrRâØQCý½m#‡Ýãà /ùËó_^»~“'« ¯U)%iòƒãJÿ½ñʳ‚àK Uk6ø0 ijå¸þ¸Ûi81xP_ßN9x¥ep® ñva³¦ŒF#cЦóE…Í›·eNr‹µñe9((ˆRº~ýúiÓ¦­\±R‘eŽ£î(c²¬¨3KW®Z;þG>ýt†Ýî,f©:jf1›ü:H¨>PlLdHˆE’J£×…ÌOɦB×#ç³ÂÁÀq¢Ç™P!Dd$Öhܘ_øÈÅ\J©‚4èæÏEË}ë”õè#ÄÅÆøoÃ"#Ãt’7%iî‚ÅÄ›q³y,öm#ž4¶VÍê¥y¼úöîvÇí·ùvÛ~žÞO6úg¥ßúÀX@xï=ý*Æa1ônœ«„@¼]@X¯^m£… ð¹¹9«V¯»¶éBEߟŠ,Ëz½>8$ôСß~öùìÙ¿çåå«#l×ÝŠ¢(ŠÂ󜭰ð›ï~š0iò¢ÅKCBBÌfËb¡ÚZ#1!öº].4lFb1S’âeY.µ"äj&œm-}6#_V¢t:‰1©ØÁP…‰=¥ÑzÝìì¼ág3­²rK·¢¿—Ëõˬù>Ü0$8è‹éïøoÜàÝ7^ öá†+V®ÍÊò.â~ûïÄ×I­ß̘æÛvú ZÕÔwß|Ñ·Ûž9{~Õš 8áýdóÖš¯€°Ù ÷î+û„mZ7¯œšR1“ZZ§+!x±€Ðd2VNMq:Z5l“eÅh4:söüÅËêCÜ"ûS’džçÃÂÂÓÓ3~üñ‡3fœ;{Ní*qÓðÏ R?wþâ˯¾õðä'öï?¡Óéeùê@¤z‰Ž  öߨcJrbœN'”r¤W3á’|GŸ3™Kó C!L(!cSÿÏ$ÆdF$FdÆ ”Féu¹2{üböƒròÆ¢àå}ïšåÛ‹±U‹¦¯¾ô´?6iܨ¡Ý»vòí¶?ÿ6xßšÏçþU*Wúbú»:A(…‚«ßÎü88(È磌/Cˆ?—îÛPë¹CÛ&½¾zÏ€Št¤PZ¼›/ššœœœ”äp8´ê@¨ö£ß»÷€$I·È|Qu”/$4Ôårý¹àÏ?þxû¶”RJ9EQ<ü|Æ“%™RŽã¸í;v=øðÔ7ß~?/??,,BM€×¶¼ˆ‹¡”hþùR*IRtTDTT¸(J¥ß¤XÍ„g\ÒÈsY÷žÉX™oçÔé¢ôºpÝ?ÿEè„(½.DÒ$ùË9O¦}Ÿ] ¶tD¼®ÓgÎù¶’2jøàŸªíötí|Çÿ^xÒ·Ûž=-– «5W]pX4¥3,48,48'7_x c¡¢(z®Rr‚¢°ÒOƒE™#„²Îæ\gsVÓ Í†_ÇÀ ”(='Jg\Ò»¸Çî²1FAËÁ›zóûôìâ[Í̉÷ŒŒ ŸúÔÿ\.WÉ·¤O¯®Ó?|Óç/¡>›ño£óþXüЄ1µkù¸ °[—NßÌüè/(°i~tâbc~øæ“õêø|ŸÍøÖ·•¢àU~›ôÀ( ï0JÂöïÛǾ/ÃÇ<´ì¯UþÞ6“ɸÇš  ñ¾´ÌÏ¿ÎÅ €BðHjJ Ïk'EÑét™™©Ø c’$«-%N>ýÙgŸÿüóÏ™™Y<¯Î-î‰Ó›MÏU?ìò<—{¥5ÅÖmÛ"""F£$ý3ƒ”1F©öƒ„jžOJŒ3õe;ÝW!„ÂSB 9î’fçÚÞËÈs>gŬ¡g3Ÿº”ûYVÁÆB§1õwPSô¦Î_¸øÍ÷¿ú|ó{öYðûw>_‘+ ŒŸyâ‘/>yÇç¡¶ôŒÌß~ÿÃç Ô«o~P’í¿ëŽöKüRµJª¶‡¦YÓ†ËýV’4˜••óéŒoq’¿/#Ü¡á2BÌAõÁÐ{½ž/š›k]¹z}étÍY°h¹7ŽY£„àyE™êÕ«j8-1b4Ož:‘™Ei…-í¡.ù µZó¿ÿá‡Ï>ýìØ±cêrAY¾ÉQžæun¾°ç:2ùѧ_üßkgÏŠŽVg ††«¿©UeѰÐà¸Ø(I’Ëjxðª¡BFG©@‰@ G G)G)O‰@IQDôÐû}‘‘™åóÍ7ª¿rÙœ'ìË"·&,YðË#¾ö[W½óþ§%i«ð÷ʵ%¬ò_½Z•K~Ÿ0~¤&Óì ÃsOOùcö÷±1Ñ%;²ŸÛl…8Ãý­ À¦a T©ñVýºµë×­íí­þ\¼\ÅÒÙÂßfÏ÷íb…Ò2„àÑ@Ñh¨R¹’Ëå$D«@¨ètú'O) « Õå‚– ¥ÜŠ•+?üðÃíÛ¶+ŠÂqô¦Ë9J9BdF ”„óT1<½i‹ZS,]ö÷&ñÅLBihX˜ZÑ”P’’ÇóÚ4{dŒñ—’_´L1Pvû?eˆÂˆÂ˜òoQDAâí÷ú=ùRIîA¯×?6yÂöM=ÿô£Õ«Uñðjs[›–ßõñ’?~.É !äè±?ýú{ wÂÓ/¼^‰¯&“ñ¥ç¦®X2§g÷»|·‚À¹§ßú• š0¦„u\wïÙïs U e·Œpc4œðax2gÞ"RŠ£²§NŸ%(-€@~ª(!Š.­†‚ԻݶmWÑŸ+R”$Ùh4šLæýûöO›6mÁ l6[1-%Üçˆò”(Œ)„Ün1,ª³¤rìÀ03¯.–£”+v_¹·¦˜ùõãîxñ’åÁÁÁf³Ååt™£"ÃK> §&ÄG‡„ùµÃ!”­e­úeÖ¼’W¡xpÂèõ+¬_¹à­×ž¿{@ïfMÆÆD[,fŽã CTdD£†õêûÞ›/íÞºbί_u¹«cÉ7þ¹—Þ,ùˆÊá#Ç^û£’oLíZÕ¿úüƒu+<8at|œÝ+§¦<ñè¤-ë–|ðÎ+)ɉ%Ü ‡Ã9iòSP©’Ü2˼ӽçŒFCÿ¾=¼½ÕÅ‹—7oÝQšÛ9k΂Ò2Ee@û@H!‰ q¡¡aùùùZ•å8®  ÿرi!cLQ½^o4šÎœ>³xÉâǨ•c#Å/´£„p”ȌȌT7OÇ„v6ÉŒIŒMOŠf?#o½ÍIþIŒÅw©óB9Žž>}æ•WßZþ׊ûÇ®]«¦Ë刎 ÏÈÌ.ɘž: Õb6ÆÇÅ VxϽôf«M5i;V½Z•êÕªŒvO)löO¿ÌY³n“&wõù—ßÝuGû¶mZh°ªV~þéGŸ{jÊ‘£Ç7nÞ¾oÿÁ“§Îž=ÁV`³RÊY̦àààJ)I•SS7¬×ºU³*•+i¸[^yãýã'N—ò)4aüÈ ãG–á9¼pñ_c˜BÊh¡$É%oΙ—_°ïÀá²½ôì~— 6çþ±¸”§üöûO<:ÉÛ7&”–@ $$Æó<¯Uss55eçä\¼t)Ðæ–äIqš••µlù_«W¯Q®ä¥›&^þJŒä¹£‚ï ·˜yÞ*ŠjËp¹š› ¿¥Æüžkû$3ï˜S*ºI±3H™:&¹eËö;v÷êÙmÄð{S’“22³/^Ê0ô¾ívÆ!4µR’NÇÈêA þ\5täÄÅó -/Û|ñâå_yGÃoy|ô™å ‹ŽŠ$MލU³z­šÕKy·,]¾ò«oÆ)]šÔVòM7(i°Ü²½Ìûô<Ї[Í™¿°”·óÂ…K6m½­MKâ}iB‚)£PüÜΤ„N§UrcŒèõúK—Ò í£¢Œq)¥ëׯŸ6mÚÊ+Yæ8Êóp¹ @Èðˆ åUb'D…0ÆrE‘§”§”#DGiž$YEñî0ËÂÔèÇ¢C"xNf„RZüÂBu)ÇQI’æÍÿs̸gÿ>/!>6(8H’$Ÿ'‹ÆD‡G„‡ Þ"Nœ<=rÜ#¥V¢„\.ט æh›0ï9Ñnw”߃¸{ïzèKß-fjÞÒÐ[•SSZ·lêí­Ž=~ðÐÑÒßÚ_gùXZ¦nš8c¡¸£Uª¤Š¢¨]‰QE§Ó{öïyyù¾,¬óV|xO3EQ&Døï®(å)ÍEBȱa +G 51ÆÔÎìœG繬¬ì7ßþà‰§ž·ò‚àÃWΊ¢úäÄx­ª•–Þ÷„p„ðôŸÿ8­Š#Ý6mÙþà”gʼȡ'ž|ö•»öú#PŸ4µ\ìk;¡¼Úòk£Y®ÌÜ߇ ~i–“q·pÉ_¾õÿ>ônœ±„Pœ`m¿]æ8.--½bT”aŒ™ÍfŽÈçΞãy^­ËzÓ(È‘idÒÿù}¥èÚ]–(ºÓQz£—‡ŽR…L—'ðÓ“£J‰jc6ÈŒ(„žµ¦áàÁÃ'Ž0™LÞOµ}Erb|™7ô*ª ¢æåª{Œ]ù'$COÌ_°äþIS|œðÃé_þüÛ¼¸rìÈÈFHö•å‚^½~t”æˆb¡,ˆ ™_9frTp0GÕÖ7]XèCW¦Óñ©•’)k>)ù§ ½@HÿPógIË+Ç,Kž='5fvjô²Ôèå•c>KŠèj‘Ù?7â­\½¾ÿàÑ/¥Ú†}ôéÌ^~»è…Ëî5©°ÐøëÀÁ#½ú;{îÎ[Rž—–ùÂû|l?¸° ·Ù熄ÃÑnTQ&&&*(Èâ[’ëN—••}æÜ¹ Ðs‚1%%9N§НÀJÉ?Ë{†˜W‰y^].èÙ5Ë=§.,Ìp¹Ì”>1?5z`¨ImÅNµÏ½rB\ŒÅl üV%ŒFÈÀPÓ¢Ê1Ÿ%Gõ µ$ëÏåIR¾$åI’‰ç’õBßPËgÉQ‹*Ç 5©7á ofÇÎ=wv¨US‡’%iòÔç_{sZ©=âêµ{ vâäé@>L,\ÖgàÌ ˆ@X²€Ê´%}lLô··óöV’$û6i“”uCB”–@ „6!Œ‹ •eE­•¢ÉBQE—Xþ'‹JÑQQ‘áb±i™Âi`Òÿ”ùyRd-£>Ó%º´XÀ¦£Tb,ÃéªjЩ [™ L»ñ.5 †„ÅÇGþdQµIc4Ï}“9=9º–Q—áre‹¢CQ$ÆÔ‘XžR‰1‡¢d‹b†ËU˨›žýMrd4Ï) ÓGo.++gð°ûß|÷c—ËEÊzJdŸ#~™5¯”wÿÃwõ¸{öÜ?ðè8Î'Ÿ}eüÄÇ´-µ >Ûº}—èëש¹¹Ö2)ÔYdð ¾>ôQ\µvCvvNÙîöß~ÿ÷É,(-€@ä•ÜEY–‰vsP É“§m…vŽãÊïBEQô:]¥äEQŠIvjÀjZP9öö`“U’ $I Z¾8Bt”Êr†ËÕ!È8¿Jì€P“VKãÔ”’ÏüÁR{3Ö5êW‰ébÎt‰6YÖQ*PÊý÷¢Ãý³“ê(µÉr¦Kìb^\%¦®Q'#zvòðÑ·wî¿vý¦²Ú€ï~œuG×;vî!e´6ìÁÉOOšüTVVNà—Ý{öwí}ï·?ü†S4pÚ÷ì9àÛm7nÞ^†W]Jé½÷ô#åm¾(qkHHPZ´š2Z«fu½^¯UWú+w\¾Ûª¥D“ãŒÆ›txW‡+éÏeŠ¢àårAoâÕQš-Š„J:h1H¨Ö’‰‹ ðáAŽR™‘á—”¨XÏ%Ov5O©@I–(Å ü/)Q5 ‚º/âA‹ÂACÆŸøØñ§Kóq7nÞÞ¥çà'žy¹Ì×òý>wa«öݾ˜ù½¨ÑŒzŸedfMyüù®½ï-Û% ×_F¸Õ×®e9_´u«f•SS|ÀËþZ»Ý·†„(-€@תíPÇqÎ_,š’ZN+‹†…ÇÅFyXRÜÅcL œÿGÉ8õá´*™c1S’â% RB(aJ?HøsîÙßhܲÓëoˆƒ¤Â-#ÌÎÎ9|äxYmshHpnwùpòj?H´kHˆÒ2¥FÀ.(G$Yû¯½õ:=)·“EEQª”bE‰Vй…êÓŒ‰ŽˆŠ ð§ÉQ*3VYψÎq¹„$9£9.׈ˆàsm§\2G©‚!â]ó±m;v?÷Ò·µiÙ±Cۖ͛ԯ_G'ørÁ—$yï¾ë6nY½vãæ-;ÊEGø‹/¿üú{ï|0½s§=ºw¾³c;‹Å¬ù£¤gd.^ºâÏEË7–u‡:ð<™ìÞ³¿yÓF¤ü, Я§Ñhð!Ä®^@_Ùü6{þŸ–Az÷“Ͼ‚S¹2ü•œ˜¨ùŒA—(–ÛÆƒrÅ”S^ú³ß»ƒI‰qå¡ë #„<$ð™.YW‚ƒB  ÓñD?y)—¤A_Ȳ²fÝ&µ5…Ñhhܰ~õjU*¥$¥VJNNN 6›Mf“Éd21ÆœN§ÓéÌ/°¥¥g\NK¿xñò‘£Ç9~øÈ1»ÝQN?ýÿ±pÙ — †íZ·nÙ¬i“ êÕ1™Œ¤óBwíÞ·cçžµë7oÛ±9”ÃY£Þ²ì@8dpâÓò2™8MŠmHèÃJÈýzüïµwËEÇQ€rÆ$×Å^|Ç)ŠòÕŒÚßÖÖjÍåyžhña188xä˜ ›¶lçyN–G8ƘÉdœúØcAÁA¢(–°y†¢°Ú5+‡‡…BY–CBB7lØøøS/¨{ì¿Ý‰ÄÈC‘AÏÅGdºDÁÏRb$J¯{õRöÇYêCÿgopS”1cF×­W×V`ãy®˜ŽUR“⣽TE¯×çäæŽy~­J©5{¢yne•GåÏGWá q*쎓é²B B!h@øÚ5kT¯^%9)!9)!9)16&Úb1›LF“Ñh4 !v‡ÝnwØí›­ðÒå´sç/ž;áìÙ ‡Ž;~âÖF¡ÌH¢ävÒåte\ldDxhÅž,ê^2'ðÛÐËŒ41é#õºQJ¼µ!"c‘z]“~YC½€’^E%yßCû®@Q™òüq—´<΢4õɉñ²¬TàÉ¢Œ1žãR’â=/™Sæçf ƒÀS ‹éPžÒ‚Ê2„ÿô`HNŒ7õx:<˜$Ëå`‘¤Xk…1ífw2…±š`¾(!€“"ÂCb¢Ã+p-µdŽÅlŒ‹)iÂ%„„ <Ó:g† |Ñý![¸ÍÓéøÔJI„Ð \Ú1FM­”¤ÓñŠ‚±1ðõÒÊIB þ;Rê—™PÂGQKn&'&XÌÆbjɰ<£þ¾Ô'Rªzõ¿ñ®p+™Rò’9EûÁÿÇ—1F¬¢Lµ^šheÆÊÅ:J‚Bâ¿Y”!!AññѾñ`y,™£nèa—ÄiZT†£ô°KBQBðË OyvQ·3%)žç¸ hf1S’⥢7ä¹J) å±d#„§$CV–ä;Bt:¹ÄÛ/3¢Ó-ÉwdÈ O1B€@xk¯ #„œ;Óz¤N§+I)91N§*|ãÁÄø˜ ‹©¼´š¸nç‰Ï²òó%Y ”•,^ ”æKògYùè9€@„ ¼æ÷)Š®I¢(EG…GE…—¼äfÀ—̱$&Ä”ß9± c<%§\òwÙùáz½T‚Yž’ÂÂõúï²óO¹džFçxÍCHrR àÒ2Š¢ètBRbœV[¨aÁ ëJÉœ®œ—ÌQá)y?#{Aa¸N}z*"#á:a{Aáûù<ÅêAB „’›kÕv¢(‰I ¥Ò¤ÎÇȪ(JJR¼ÅlÔªƒRJ©Äü>õTfŠúpž,­0%s!ŒP›Â&_ÈÉ’äžó6ŠŒ„ð\–$O¾cS#yÜò '!äð‘c.—KÓ–lŒ1˜ D]S¥ILb„PBθ$»¬Détc²r°Ì˜ÈX„NG9ãºIËJ¨¢0£QŸ”W1Jæ¨G:¥Ág2Ó$9R'HŒx²«eÆ$F"uBš$>“yÔ)a²(!üK¯×iXe”Rêt:«VIµ˜MEc<Ç¥$Åk5£Uf„ò»ÕÞëdÚê|{¨  ‚Ĉ†c… !"#fžÖë×8úœLûÝj§WúF‰PQX¥ä½NWaJæÈŒð”pˆÝN¤/Í+ŒÒë,ÍÊ?å’Õ;¿éª9ŽãdYNNŠ7kW2'kÌíê¹Ö¹ÖB#G#x.U/ÈŒñ”žvIٲ⸒ÿ0M€` !\7#QJm¶Âóç/êõ­Z'¨°jÕÔgÀÔ’‰‰ñ- º\¢¢(¥ÅßVf„ÂS’&)O\Êt&ëï¼Â Aâyу;…‰¥Ñzýª|û½g2¿”{Ê% ”ÜdÑ !”RãE1 Z•Ì!=}TÝÕ<%…]å6ç–B×F›ó¢(«óuyö Þê¬ùùÚ†EQâbc§¡¢(F£>91Þ‡A3J©ÃaO­”’R)E’eÆXñC…Ì-«ì¶»î;›5ìLÆA§©Óé)-&ŠŒq„Déu—%yÒÙŒ!g27Úœ<%!»IXçyŽ1&JRíÚ5o»­¥Ëéd·@!MuW«{›»Õ=F¯ü Á”Q¸ñ Cª(ää‰Ó:N«A)'Š®:µk 4"葨àD½.O’Äÿ.,T— Fêty’ôVZî7ÙÙ’B)ån¶\°h3dY‰ŠŒ3zxç»:êt:»Ý®aåØrÒ¨ ü ‚/ue.\¼(Š¢Vƒ„”—Ëk6›òò (¥e8Z¥¦Áˆð˜èpŸgQò<ïpØãc#o¿½CýõÖ¯Û°jõåJešbžûÂÂï² æ>|_„%L¬¢øO©ÆB£tV®í㌼cN©h¹ |³¤ª~ ‚ЧW·#†ÄÅÆååYoµ4„P".\’e™ªÑ¨#'ŠbxxXB||^þ±² „ŠÂt:>µR!”1ß‹¬0FÌf£AG CŸ¾}5l¸hñâǨctŒ ÿ™ÓHI–¤¼”fý)×ötlh—`“̘ÄXN·¡Àþ^zÞz›S‚ »ùrAŽ£²¬BZ¶löÀøÑuj×´ÛíÙÙY<Ï# !xÑŠðÂ¥ËVk®^¯—eY«V„AAÁ5ªW=|ô˜:+•”Ù𠔜˜`1µhD©ÄAµñ ”çí œW{LøøØhåzOxY–sssbc£ï6ìþûÇ'§$« oºþY.(ÉÉI />ÿäǾ]¿^ÝììlQtñ¼PúK1Ë< v 6.Hêb¶JR®$1BJªþŸ ”ò””ð”:Ët‰a<}'1â“Äð`Ž*¸ ‚çueöï?è>ÞUr’$‡……w¼½]ÑCø?RY–S’â-f£Z÷Åk¢"Â,æë.¼ä8Žçy—Ë•Ÿg­]»Ö¤‰Ü}÷À`µQáuwÇqÇ©-%Fúù§Óztïš——WXhãy¾”ât€¥Á»ÃÌßTŠæ¹LQTã-ö¢#Pâb,Ã%ÞòCJT(ϱ[*C )Á¬Ñ;v;J9mû·nÕœ\Y©èï<(ÉrHˆ%6&RÃÊ¢7$¼ñó¢”ò<_PPÀ»í¶Û¦L™Ü©ÓÏ+ S«Ñ¸ý§(Š¢(:¶ÿò‹'Mo2sr²nÁ(X”[›õ&EæJ’SQtJŽ%iG›óG aŒ1‰€` !›×•9|ôxZzZdD„ËåÒ$‡PJíöºµkEEFdfeSJý\éƒñW¹RÇQIRüÕA˜èð̬œ\k¾ ÜpdUš—gµXÌ}úônÔ°á¢Å‹>¢ªõod™Õ®]sܘá-[4W93#ƒã8žnÍ/“¡Q<–a—eÆïýq4p\†ÃÙ-,h’Ý5=³uG0B7_F˜——äèq£Ñ¤Ulã8Îét&&&¶nÙ\FþLhD’䄸è`‹¿‡‹v¥41>†RRüS‡ EQ´Z­ ‰ñãÇ=zTllŒ¢0YVÂ#ÂyøÏ>y¯M›V6[ÃáR޹µ¦`Œ0Ï”÷S‘R¢065:¨²QoW|IƒÿäpŽæŠâ”èÊz^a„ÃÔQ‚B(v¡¢»öv¾³“†¹‚R*IbÏž].Yî¿Y£”PYQÌSB|Œ†Ý=inœ“[Ü ¡Û*Aâp8!õÔ«U«æŠ¿WPÊî?*&&¦ ?¯°°ãyʈNÇ_“®}RŒ]§—áuw²šé•„IopÛ=ÍÒü&If¤ŠŽïfÉE–è®$F"uÂý‘ÁO]Êå†ÜdáÎ]{EQÔp!Ïóùùù­Z4«”’xúÌyµÓº?¶_‘•”Ä8Nð_-™ ’ø¸˜œÜ|S´:×V`¾k·®:îôÙK‡ŽœäyŽ£#„Rb2ÝžU×+šMÆ«Ú`Ð z·¬GaFƒçyF˜{üÓétš$Luүߪ·J adlTP˜N—é*Q T×"ZE±_¨éó¬‚Ó.‰£TAoBB(fáÁƒ‡OŸ>“ït:´*g"ËrxxDûvmOŸùM‡Ôœ$Ë©©Éq±ÑVk©öm§”J’ž‘™]Ô§Þ“žŠ"Ûl65RªõEå+móœÎüö›„:FˆN8Žºÿ²V “)LQdN¯Ó Ì/üDfÄÄÑc¡,—¼ %Db$J¯ëh1|ã’8ÂЖÈ»xž+°î;p¨jÕ*‡]»ÔÄ9Ž^=ºüüëﲬøcõ£$I .ŒŸŸ/Ër)6pgŒ‘Ä„Øì«WSm)ån´‚Ày1Dé6¨G)QKÔ\õk%O˜’$ o2Yrs/]ºÌnwh^"H½Ã*:!^Ç;dY£ULf¬©YÿMŽ ƒƒ¥†·„Dc/”¿ÃÆsŠ¢†n]î²ÛíZru:ÉÉÉ›7o½pñÏóÚN:T‹©9z|ÉÒ¿xޝ]«fPÅétªE_ˆÿ E1 §3/ßVŠAô†³.é5xž»Ï_ýÇq”RÎý†'˲$É&³YtIkÖ¬ýþûïOœ8ɘB´^•ÇQÂkbÖ ¶+Š&¢ã¸''çï«NŸ:R)%%9YQ—ËåÞÞOƒ„&c  V~êtº  à /Ì›;oÉ’¥¶›z>ø©¹G‰BHs³¡C°I«@Èá)-TØ÷96'#„ká¦Í'6mÞ¦mó ŽãDQŒ‰п÷Œ™ßq'˲?2¡,ËjhY±jíšµï½wÐÀþ½ãâó¬V¿.,¤”ÊŠ’œ—“›/I-Ï]ïdYá8fÍÍùkù_+W¯Q®Œû©B¬;}QýqE„fÍ›·hÙ¢f­š¡¡¡!!!¢(Úl¶sçÎ9|dÕŠ•çÎ+þL&Ó¨1£gÏš‘ž^¾ž{ùÝòâEÇÄôë߯iÓ¦‘Q‘z½¾   ==}ïž½_ÏüªŸÉO=ótûí¯úË%‹—|üáG×þr\\Ü×ß}síßß?vüMOxMαG§>vç]wªÎÊÌ6ô>RnúÁÒ6mÚÜÞ©cêÕCÃÂ!¹¹¹‡^¶tÙ®;+Æáóäèh{d¿˜9#99¹èÇ×^~uÆ Åü~½úõÞ~÷¢O?ñФËõy…·$B å«ÖèÊUëžt¿ ðÚ¾Á8œö^=º~óÝÏjdòÓ@“Zxž—dé‡Y²dù˜ÑÃ;ßÕ144477W]>ç§Ç5 q1‘gÎ]Òé„òØ#^QEaAÁA²$mX·~Ū•™™jž/…(È%„vŠ2cWúehðžçψ®BÆüwʸÆOxpbRRÒUïÇ&“)**ªqãÆƒï¼vÍÚO§Oϳæ]ÿš4ydÊ#111sfÿ^Ξ{¹ÝòâÕ¬Yóõ·Þ0™LEf³ÙnÁ3¼QãF×ýû†7ø{œcųX,Ͻð|ÃF Ýÿ2&&&&&¦}‡ö«V®úà½÷%Iª‡¯4ìšU«ï>¬èÇÛÚ·+>¶mÛÖýÇÕ«Vá-©b_Ø!°"ÇqgÏßà`«–-òò¬Zªñú¸¼¾Ò?²«ÿ[´l¡×ë].×~¿MÛ6îïÑkV¯Á[Rž°# ´Y£’¤ÌûcÑmn#­8Î1£†-]¾R­XãïµI=Çq‡yäѧºu½kÈ»ëÔªeµæŠ¢‹çí úØò6H(Ë Ïs!¡aé—ÓfÏžµmÛBÇñŒ•RtïƒqR”.‰R¢^çP-b!å)ÝQè$„Ð[¯Äh\\ÜSÏ<åáwÉ)ÉLyäå—^vÿËNwÞYNßzËï–{òÝSí:µ‹~<~üøk/¿š••¦á¸ )oƒ„Ë—-¿:Q4jX¶çØûï¾÷þ»ï‘ò6zÓ¸I“¢-\ôËO?;]Î:L|p’z1éÖ½Ûü¹óΟ?_®_éÙ‹/=z´FäʤÇfÍ›mܰñº¿\½Fõ蘘¢÷íÛ—™™Y~Ï+¼%!)KÈ!kÖn¸t9-8ȬV•$š 4lP¿t ¯]X¸dé_k×m¼{P¿ýûDGGçäd«qQÛA¸ØèÌì\»ÝÉó\€gB5ïYìvÇ¢… ×oØ`+°©x¥´‡ÓÔÑ<»ÂÖØœcMƒוa„”äˆâ*›“r ¶èÛ¿Ÿ^¯/úñò¥Ë¿þòËþý2ÒÓAHMMíÛ¿o»öíÝ¿»­Z­ê‰ã'p$=£Ïýªµ~íº´´4BHÑçEBBHJJJxx8ÎoÕkP¿èϹ99Ÿ~2]}[¼hqõ5ºtí¢¾Ó5iÚį°¢¾Õ+WBBÈmíÚÝ(^=_tå*¼%Ay„"¤\w¨ç8.+;gûöf³Eóæã‡c̨a:®túºwPP§­~óíL|dÑâ¥!!!f³E’$ ÇÁEÑë…ĸ˜Ò[óm;eY6&“yïÞýÓ¦}¸lÙrµŽ¨:}´¬Î=BÈÌÌü\QäK<¢'3ªÓͳžvI<¥Ê­·€°IÓ¿éÏÊÊzèÁ—/[~ñÂQívû¡C‡Þ|ýÍ«±´jÝ×À§ÓéÜ´Û·æ~ÈÉù·ÍO£F®_ú÷o²³³qÚ¿·-ú³Áhtÿøî>y/((‡ÏkV¯q{mÙª¥ûv׿¶¡$Ië×­Ç[Œ)õ)I„9óôèÞUÛOÑe2Hè¾°RÊñܹóÿ÷Ê›‹/3zX³fMl‡ƒçù’TJ©$É‘‘aAi……vžçmðÊrA½Á`<{æì¢Å‹>R&sD¯S á)=)Êssm££Ã2\¢Žú~W%ù’ôEVÁ-Û>,,¬èϰخ=æÎžãþUtÑBÿ¡ÃîzßЫ~ÿ›ï¿Uÿðèä)‡&„|6ãóJ•*©ùÁ{ïïÛ·oì¸qõê×3 —.^Z·víüyóívûëo¾QT:âø±c?øðU÷üÂK/½ñŸ?~ü˜q×>ààà®Ýº6mÞ,)))$$D’¤œœœãÇŽ¯_·~ýºuE/4·¼$›äá³.úýÚuêtîÒ¹AÑ‘²"çdgïß`ͪջvíòê€>2e²:Dãn¤ &M „œ33cûöóçÍ»|é²·{õô©SÄ­ã}Ç}õåLBHtt´û¹wñâ¥^49|š\Xütdoƒ÷îÙ[´Í&“©i³¦›6nºê×*Uªä^seû¶mÄ›§^s^~õ•fÍ›©Þ¸aã«/¿âþ¯½ûô~`â„¢'=0ñ”ÛéÁqÜo¿Ï²X,ê?ýøÓO?üXæoI¾]uKç’îËH™QJ7oÝqðÐáªU*Ûí…6l($,µ•„WÏ •dJ9JÉö;wíÚÝ¥ËcF K©”bÍÍÕ¤5cL§âc£<ÃærÁÐììì?þøsÓæÍn-%ä@ ¦ôÝŒüŽÁæhs* ïSJ—aÐÿïRö)—ÄS*ß’õEóòòŠ¾È¯R¥ª ×®1;tèP÷.Ý4y¸·Þ~«hÝKjåÔ„Ä„,ÐäÎïêÜyüã‹>|¨UéâãããããÛµowòÄ=¯¾òŠýý¬u:ÝÓétg'÷›˜;wé¼cûö·ß|;??¿¬6R¹rå)S­V­ùoO‹è˜˜V­[ ¹oè'~¼uëÖââ·Þt_ñÞ¢E‹-Z,]²ôÓO¦O|pRW·e?‘‘‘‘‘‘Íš7[¼hÑô§—äú¿oßÞÛ;v,ŠE‰‚RZßmêã¾½{‹„%ß ¾ÑäqïètÇÄ'™Í梿IHLì˜Ø­{·÷Þywíšµ^mÒæM›m6KÐ?/´;zôâ…‹Ï¿ø|hhhÑ…eËæÍ%úZ¾òeõªUîq·]ûv×B÷áABȪ•«‰73<¿ælÞ´¹(6lÔðªŠâõ6p¿“ú 껚µj¹_·xðýNé¼%•ðªë§KzY]d¦ŒB ?”ó<çtº-Yn6›µ ljQÓ²x‚Šúв¢,^²|ÌøIŸñ¡4$4´äE5ÕA¨Ȱ ‹Y¾·à˜*Š¢QJׯ_ÿþûlذA‘euŽhà c*„PB2eeò…,ÏSŸ²œSQ¢†%¹Ó3óoÙ4H9°ÿ@ÑŸ“ŸyîÙ¸ø8ÿ=Üà{ïu¯‚@Ù²y‹& 0å±)î>®R¥j•·Þ~ËýëçRS̳æyþåW_¹ê£ƒ»¦Íš½ûþ{ÅŸá>ZR¶‡¯ÜÙ°~ƒ(ŠäßY£­®5ê>Df·Û·lÙìùç+¯®9[·l!n‹“kÖ¬éþI¦~½úÿ=cëÿ÷®šý933óøñãð–Tò«®?.éeu‘A Í.\´,;;[4.˜ÉqœÍV0eò¤ÐÐÒ\IxƒŽ…\nnÞW_?vüCëÖn°™ÌfY–K ÕD’_´.®Ì— êõF“É|ðàÁéŸ~>kÖì¼¼<5ŠàZG™1žÒM…®GÎg† ‚ãD÷¡BˆÈX¬Ñ¸1ÏöðÅzK.,òç‚?ÝO¿V­[}õÍ×o¼õf¯Þ½“‹¿íO?üؽK·Ï¦æþ—£†ìÞ¥[÷.Ý®šœóÏ牠«ßW®XQògQ§nQcF·îÌO<öxßÞ}FµðÏ…Äí«Ö±ãÇú¶å%Q̳:ì¾¢¤$Ëò7_}=dð~½û¾úò+EË¢’S’ïŸð€‡õáÓºwé6tð÷¿ülúgêSSç‹z¾yáááÏ¿øBQû ‡Ã1ãó/†Üso¿Þ}_|þ… ç/}.œ0i¢ûâŸk‹Ü¸\®Ï¦6øî{î0hÞÜyîÿKY·vÝø1ãúöîóîÛïº ¾w0)ÑÓ>â6|Q4¿£‘[@uO×òm'”üÓjçÇÇÇSJwlß1åáÉýz÷ôÀÄC‡¹gÂîÞÄücþû÷í/úQ¯×%I’>xïM^ך>Í•ÂÕÃf³mÛº¸ÍËuOV„¸¸¸*U«ý¸qÃF—ÓÓÉ„Þ^s233ÝK¶¸Ÿi•R+…„†ü'ÕÔ«ûŸðÓ´©{d ·¤’_u5¿¤kõbG „2K<ÏŸ¿pqéò¿CCô]éÇqœÃá¨Z¹ò}÷*«A«ZSð<úô™'ž~áÑÇž>yüdXX˜^¯—$É·8§– V↓ž$É<χ†…§g¤ÿý÷3f|yîìYŽã(¥\öFÍ„³r GÉÈ—•(Nb¬øƒ¡"1¢§4Z¯Ÿ••7ìl¦UVnÑVôW?vìן¹êÌlبá„I¾üjæw?|÷ȔɭZ·¾ªHII¸œ®7_c@ßþcGõëo;¶ï(ù}1¼è‘•™õÔOîß¿ßåt]¾|ùÓO¦»¯C»½cǨ¨¨ÒßÏ×}Ö!!!ýû÷/úøqö¬Ù¹99N§sㆯ¼ôrѹÙéÎN~¹½ÑA1jdpppÑwXÿ{ñ¥ùóæçææ:Îm[·=öè£jñRõŠýÐ#  ƒL{ÚŸ äYó ¾úræUU@víÚõæëoœ?Þåt­\±bíš5ýøå¹ ç/=–Ùl®^£:¹¦cÁ¾½ûй w‚W4|Ü]»v½ô‹GŽq:§Nz寸/Fª]»¶·Ûf0ÜW9sæÌ£“ýkùr­ÎÌ’>RngºÿØ®};÷ÛÞæc?zß®9[Ü Ý;Ž4hÐàªû /ZÑ\£f ·™Æ›á-I««®¶—ô²ºÈ ‚ÆæÍ_èpØ54Çegg;"µRJ™g¢ÖÇmÙº}Ìý½ýδ¼üüˆˆHJ©oa˜1F(‰‹¡´l Õ¹¯!¡¡.—ëÏ~ôáÇ»wïá8ŽRNQ”ÀJj&\’oï}:ci^a¨ „ %DbLbêÿ™Ä˜ÌˆÄˆÌ˜Ò(½.Wf_ÈzðBv¾Â8B”[þõûÃ÷?üøý× ÿÑ11]ºvyá¥~þí—û¸ÿª/ƒ}óóO?­]³Ön·_¼páÛo¾-y[¼¸¸¸ný©üñÇUsPÕ"uÿõ÷'}üÐij²²J'_÷Ywìt‡ÞðϸŠËåš7g®ûMŽ9²ûJùJi§;ï,åÍ ½£Óä߯ÀWîÙ½ÇýVyÖ¼¯g~MÜúÚw¸þ:®ÌÌÌ5«W»_yNüOœø}ÖïîœcÇþ3µ,"<¢$ÏÎ},«qãÆ„AêÖ«ç¾íF·Õp'xEÛÇýåÇŸÝߤrsrŽ=ZôcT´w_‘4nÒä‹™3zöîuí?íÛ³÷ø±cÚžœ%9|å×Ö-[ É fº/ ÌÍÍݵÓÓÒS¾]sÜ—ƒÖ¬U³húñU É•©äŸ5Ÿ‹>Úíö«Nà²zKÒꪫá%½¬.2„ qMNŽã¶íؽcçîàà`Í Õ%mO}¨ g^› 8Ž“$iμ£ÇLš3÷J©:@êíxš:HZêƒ„Š¢H’l4šÌfËÖ-[>úø“åË—;uŸ3VnR’š ϸ¤g3ŸÎX™oçÔé¢ôºpÝ?ÿEè„(½.DÒDùõË9w¼ü]vG)Eü÷½íç ÷?°rÅŠÍ;²X,}úõýâËM›5+ác­Ðh.ÙµŸ?T{¯ùt¸yÓ¦±£Ç¾ÿî{‹->uêT™|ÓqÝgí¾å—/_¾¶‚ÜéS§I †qJ¸yÍ[4wÿz劕×þΦÝ—ŠµlÕâº÷âø‰«v»{EDBÈÑ#GþûÈêþcÑg,Râuh 7R?ÔFõo222._¾a©! w‚W4|\ÆØáÃWÏcÌÎÊvîó|úvëúÊk¯Üh˜½gï^­Û´.šw£~ ¥vøÊ/—ËåÞ~Ðd25¹2ý2""¢V­ZEÿ´vÍZÏ?{øvÍ9~ìxÑ8-Ïóêpî ÝÛ-#tŸæºcû¯¾ûóß[’VW] /éeu‘!¨2 Äý'fÿþGÛ6­5ÿ¤Åó|nnn׻Óí­X]Ê-(n¶°ÏÌÊzë,\ÿÌÔ»vçææ]Uò¸lGG)¥Ç:täÑ©OwêØ~øˆ¡µkÖ,(Èw¹\ÎðV§›†„EE†]NËÒé¿ÆBµ¥DhhhZZúìÙsvìØ¡xª)±Ÿ„ê×”*ŒwIÇ]7ü&’§Daiܸ˜ÁêU«V¯ZE)­\¥r³æÍÛµkWµZUâ6V3þþñMyÌ·û/Iÿ襂àà}Òõ7ƒÊžuÑ¢OÕTLHH˜ùÍW×ÿè¼wß“?¡ÕA)j! ~"¼Qv«5÷º7qç¼æ›r÷ ݵ‡ìF—Aßžû¹³çrsrÂÂà !:®nÝzî=Í‹_¦áN ^NÕêq¯ûŠøÏW«ÔÓ³}ÒCºG‘ysçÍœñ¥N¯‹ÿàƒ¢'AAAO<õäk¯¾Ö°QCAjÕ®U«v­3§ÏúKrø4|kÅósx÷®ÝEOœÒªu+N'ŠbÛÛn+úË—.{UÆÆ·k!dËæÍÿÂ&ÉîݳW„¸®q„蘘˜˜£ÑX4Œ¬(Ê6_›%hþ–äóðß%½¬.2SFhÞŽã\¢øÕ×?jXyÂ}Ðáp$''?9õ5|Ôs/ZX¸bÕÚ1c'}<ý ‡Ã¥Öíô÷̳îÝkש“œ’ìÛÝÚ =û¢„»î¥ÀÃ+ñËTß7éFÏÚ«Âz½ÞOŸ\o´yž~õævMfÊõ_^¬Ø»bþÿŽoŸÛ:´–­ZÖª]ËÃ•îæ¡hò¸×}'òášß¨q#uèÊ~Û3sÆ—Œ1—Óõê˯¸Ï®[¯î»ï½S4_”1¶­dÍÓ|>|¾ŠIY|Û¾ví:òßZ£ÁÁÁîóWy3½‹~¼­]»àà÷g±ÚË@èó5ÇåtíÚµ«e«–äÊtТ„gÏžÍÍÍuOæõë×‹ŠŽ.úqóæÍó–¤ÉUWÛKzEºÒŒ rœÝî˜=g~HH¨Ÿ2Fa¡íùg¦†‡‡–yÅQrãoayž;þ›o0ùѧ6oÙéak …1 ʲ¬ÓéBBB/]¾üÝ·ß}ýõ×iiißRBmò!<ÏóÜê5D\Þ¹¯U0›ÍmÚ¶½ÑoŠÿòç>1Æ«1×bf&»Ÿ£¡ø·CwçÏŸwÿ1ñJÑó›_¸n¶å>o’‡Ïú‚Û–ÇÅÅ–a[ëþýéÓgܬz½ŽÉ‚ TªT‰¸•Ñ Ð@è6±Ð½DÍM'–p'ø<#!w¾ñ¿µgΞ;ëþãŽíÛüþ‡ëÞpîœyeuø4|{{õÐÄáC‡Ýo«Ö­nïØÁ½V“·_Ì•äšã^k´O¿¾E Õ(˜••UÔ.¯aãFõÝ:Ôoñxa)¼%irÕÕö’^‘®´#„ þúÛÜ»õ‹‰ŠÔ|ðÊÄѤgžšúø“Ïó<˜qF–J9Ž£;wíÙ¹kO·®w3¼RJŠ57W–åMMQW™â¢Ïž¿¬É ¡,+”’Ь¬¬åË–¯\½F‘e5Mrä8Žã¨$É×~/X´Õ<Ï3FEÆë®„6mÚt÷à{Š~wÿ¸Ã‡edd\ûéªóééÿnQþûÖX|bWn\ÀÖnÿwεÅL&S|B<¹YUzBHãÆvíÜéþ7‘Q‘Ó>úððáÇ:tèÐñcÇDQôdË}Þ$ŸõýŠJä%$&&$$\¼xñ¦÷vñâÅî]ºi:¨~ýÍÛµs§{yçŽwt¼jÇBZ·iSTïñªŒþàós?{æLž5ïÚ"õ7-IRÂàÕ«#ÀwþUýZb¢c._úOyÏ_ùµzê­Z·&ÿ-µ¿cûö>´Ï‡OÃWñÕ¯_¬·çðšÕkß;¸(#¹·ôvxÐçk¹Ò £èœt¯žRtöìÙ­öŽwŸZ|îì9Ï¢Þ’J²ütIÀ;F”p0×j6mºÅäÈ!BVVÖÝú¹g€$É‚Àê®PÔ……”Ò%Kÿ;nÒç_Ì$”††…3ES8e0èK¸÷c’$[‚,ƒaýúõ|0íï+YV‹ÇòrAžçÕ~‹¹e‹fãFÿàW¿úâÃ/?ûàÓß}hâ¸;;Ý-˲¢È-,¹#‡œ8~¢èǨ¨¨éŸ:tØ}UªT±X,‚ ÄÅÅuëÞí£O>Љ‰)úµ¼¼¼ƒ’KDþ§d…NïãBâËnÕAM&“û2!BÈ=ƒï¹ÑòŒË—/»/_éÖ½{DÄÚÖuïÞ=22²mÛ¶cÇ}ïƒ÷Šf=ÝtË}Þ$­Z¹Êýõ8j̨«>ª~ýí7¯½ñÚ}Ç5kÞìFå ü'33sËæRwº³“{¿GBHHhÈè±£ÿýäwî܆õöjßþëŒ&í½ÙS w‚ϯŽÜùÛ·ow?]ûôë{Õ/ètºÂBûUY³fÍ‘£F–ð bŸŸÿ^ÅZ]÷<¸J¬¼Ñ>Y³zMi^s²³³;^ÌøížÝ{¯›ñê-ɯW]ßî¼"]iáŸABŽã–,_¹gï>‹Åâe~Çåäd?ýôcõêÖ–$9`ׂ­Üãy.77﫯;þ¡Å‹—›Í–u,TÅ`ÐÇÅDúÜ“P-r£×ëCCC:üÙg3fÍš——çÝÂåÿ·wŸñQ•iÇïS¦eJzBGº®‚Š]\‘fw-ëº*Š»«î³®e­koØëbEÙµ¬‚l`/Te•®Ô@€ôL&Óç”çÅ!Ä$Ê„~ß/ É™3g&óŸë¾¯Kì5¢²,ëºÞ£{ñ ×]óþ;ÿþ÷äÝzóu§}üqÇœ0üøSNþíu×^=é™Gߟúúƒ÷Ý1ìðCu]·>†à©·;ž}æÙÔ_`çÂß_øôsϼ=í>œþò«¯\ý×kòS~õ !Þ|ý «ÂÖt ™bøðáv‡½_¿~Éæx¢µ]7*ô]ã ƒlwØ;wîü§¿ü)õcã¦^{õµäåíö¸˜øà!C†Øöœœœ³Ï=çw Ÿ¯[Mù’ Ðwxä»sH­Q^^þÉÇŸ$ÿzô1ÇÜð:wéb³Ùz÷é}çÝwu*:dÈ .¼à®{î>uÔ©my¼ôâ‹ÉN’$Ýq×§qzff¦Ãá8ô°C~ô‘ÂÂÂdWô§²=¿È,iª««7mܘ֓°;ÏŽövòË6—¥NH;ò¨#oºåæÞ}z[O´“GŒxîù¥Î×N:÷¼ß=ôðÄÎÇôÙצgñžzÝ­h²ºfÍšf÷UVUUµñkNÓÂTIIIr?ÛâE‹š~â}è»e¨ÔðN~ Í;ÚÛɇÃ×_{Ý×_}½ý_+óçÍ›0þŠW'¿‹Å’¯·ßzÛ.§ÁÝyøÒô,ÞS¯{­ñõ—_msÂ5MûîÛïÚþ5gõªÕÛ”%5ÞÆùsãÜnm;lo¿’Òúª»Ëß|Ÿy¥¥BˆFEÂGúÍ×_I×¥£Ú*++~wÎY+W­™ôÂd›ª&4­ýGe«^§úÔi|þå×øýù§ãv»­_rÖÒG+"äç–WÖ„ÃEÙAÓQ«°æt¹YY0oÁgŸž:R¢ýO´ ¼>Ÿ÷ùç;êˆ#ÊËËEÙþ4K¹aŒGyyY¯^Ý_~á™K/ÿËÒå¿Xw™çà®ùé?¿ä²C;lØÃúöí›—Ÿçv»eYŽ„#UU•k×®ûù矿ùêë–âÖ”W_Û¼iӨѣ»÷è.Ër} ¾¤¤d}Éú=Œ 6\}ÕUcÆŽ=ö¸c;uê$„¨®®^ôó¢YŸÎüå—_NqòöÿùÌOgÎ;÷ÔQ£†:´¸¸ØëõjšV[S»fÍšysç~ýÕ×ñ&ãÑwxä»yH­î¹ëîÁ>~øðÁÎÉÉq»ÝÑh´¬¬lù²å_|þÅòeËöîåñ믿^ý竆 zÜñÇ8° ¿@’%¿ß¿zÕê¹sæ|ñùZ»¶~=-]²$µåÉN°Ûå“°›ÏŽövò#‘ȃ÷?ðß7ß:áÄßxà……¯×Ð`(¸±tãŠ+¾øìó’’!Ä[o¼ùÙÌYãN?íˆ#ޏïž{­//MÏâ=õº·C•••K—,M?øÃ‚v']ïÎkÎüyóF½uogã:í¢ŸN“1wÎÜöù+)­¯º»üÍ÷WÚ]y+˜_<÷aû$k ßÄûï<û¬Ó«««v³ïÂv²Çãýûõ·LÿèSUU4MïXçGÑ£G÷+'\vü±Gëº ­¨cš¦Í¦–•W¯\SbS[l7j»Ãá\_²þÃ>\±â+/µóÎ1ÛìB¼òÂÓÇ{tEEyrq+išæv»ëêêÏúÝ6m.kç³4Ðè]q†7Ÿ³°’„Ë–¯3z¤Ãn7 =Ý ­ ØðãùöÛ9e啊¢Fǘ˜ñÎ;S+**:Çtˆ0¸%kš~í_ÿtÞ¹gïB´Òo,ËÉÉéß·Ï{Ó?²î¸Éüy€Ž}B±¯.U¥®.`Æ)'Ÿ …ÒÑ R–åDBs:£Fžòå×ßUW×ìpe;\^+IÒš5ë¦Oÿ$‰ôîÕ£ ° ‹išf·Û$!U×øEÞf¦…išÇ0ô¯¿ùö?ÿþÏêÕ«­oÕ±ŠcV™´_ß>ÜwG(”$i×>5e9 8hó¦Í‹—,ëX×û,Y–—,YvüqGuéÜ9¦)Æãñì쬃üÑ'³¢Ñ˜U^ë@™Ð Ϻ®ÿ¼hɬϾr9}z÷ôù|Á`Èãɇ#ápÔÊ„Ö C‡Ãe·Û—/_þúëoΟ7?Ot 5¢Û4’ñy=Ï<9±0?/‹í^WI×µú½õö»‰„ÆpB!ÚÅ›~MÓÖ¬-9m쩺ž®·é²,G"á=º:ôOg~u°LhÅB!„¢È¡Pø»ïç.øaaQQÁ}PdY×õªêZk@Ÿªª^_ææÍ›¦N›öáŒêêꬌÝá bÉ4øÂ¤'‡ò›ºº:›MÝͽˆ‰D¢°°ð—+]¹š"!íeáè†Ò…yGyD}} M³dY…‚Ð{èƒ;h&LÙS¥¢¢ò“O?+-ÝTܵˀýª«k‚ÁPVvv(ülæço½õviéFY–…LÓèˆXiðÅIO;lHuuõö{жþì¹\ =ñɧŸwÄG€@ˆ}µ1lÑâ¥#GüÖëõjZBNÏØ@EQ‚ÁŸ S7®\¹zæ§_D#‘ÁƒúGcÚ÷³ç¼õæ[‹/Ö4­a» Ù¡Óàᇠ©ªªÚ#i0ÉnSßžú>«F:FXÈëÂØ‰ýeÅÉ' ñ_OVWW¥u€x"‘ÈËË›¿`áø ×êƒz0]r4E÷îÝÜîŒeËV!dE6 ³ƒ–¿Ò…¦)uîE«×–0“€ !DûY8ºjõÚ¬Lß‘G …‚i*6['ì¸ÛÉVʵµþÊʪ-Û ³ãæÛt§A]×½^ïÌY_”nÜĪQ€öOæì' ÃeùÁ‡ŸX¶b…ÇãIëy›ÍVUUuøaC^œô¤×ãÖu=­5ÉtgBkÞ Uïê¸ Çªvz=îô¥Á䕦iÏ8!Ú]±+‰Þy׃’$o3X/M™ðС‡¼6yR¿¾}t]WUEtä8Ý¡W?ªê–yƒ¯MžtèÐCÒ—…VWžq‚%£ho™PU• ¥›‰Äˆ'ëëÓ·pÔ*I…áîݺŽqÒœ¹óËÊ+mªÊ¦²¶gSUMÓìÿêKÏöìÑÕï¯M_mPUÕp8ôÜó“ƒÁ$I,TÑ~hš®(Ê¿^˜üñ'3srrÓ½´OUU¿ßïñ¸§Lžtú¸Q MS…æ“¢ Ì*Š’Ð´ÓÇš2y’ÇãöûýªªŠ´}âàp8J7m®ªª–$‰ð ¨¢†!Äì9óO=åÄÌL_"‘HkP–åx<îpØGŽ8I×õù ZÅCjGmÓBÆ4Í?O¸ô¶[®—$3¦/ ! Ãðù2g}þåg_|ÃC @ „h·GCá𲿞}æi‰D"Ý%;Y–‰D<?eÄI]‹;Ï™· ªªb‘ÖMƒ>Ÿ÷þ{nŸpù%~mlã´æ7>8ñ‰›6Ób€@ˆö½™pÃÆD"qêÈ@]º»€X]:ëC‡òÛãY²tù¦Íå´i[&ªëúÁ¿üü³wìQ•ªª¦µ,„Ðu=#ý|ÅŠÇŸš$„`½(¢·Í4UU™ÿÃÿºt):bØám …²¢ƒõ§ †~úy1ËGÓ´LôþîÉÍÍöûkÕ´5ÝæsÇ{í·­_¿‘Ç€@Ñîë„B’¤9srð={öˆD"é." !dY‰Fc’$FQ\ÜyáÂE¡PˆR¡Øs…Á¼ÜÜ»ï¼ùê¿\‡£ÑXZ7 Š­ÍŠ´ììÜצ¼>å?o[‡Á#Ð1ÞFæuÈYØoYÃÖ;w*z÷í)^¯'‰´Í´@k¬_nnîúõ¥<ôøGŸ~&„PY×Yg(v©0¸åÔ:å¤ÜðݺWWW[ËtÛ¦u­Ûí®¬¬5ö¼`8d­IæA BÑ!ÌõsæÌ?}Ü©6›ªiZ¤I’dY…B™™¾qcGuëÚeáO‹B¡°,Ë ¯ÛÙHo xÈË˽ûŸ7ýýoWÙí¶@  ªjÛŒ÷ÐuÝn·%ñË®¸¦tÓ&zÉÑñÌ”WT–nÚ|ÚØQ‰DÂ0Œ¶ÉV÷Ñh4:tÈ!#Gü¶¬¼båªÕVFåqiåQkÇà©§œôôãyÄáµµ5š¦µÍ2Q!„aŠ¢x<Þnºã»Ùó¬ãá¡`É(D‡›R iú™§~èþ»êëVZk³Ÿ®išËår8œ³>ÿòáGŸ^µz­°–A¦ ÜÔ\TdÉZ#Ú§wÏ뮽êäOˆÅ¢‘H¤Í¢`²¨×ë»á¦Û§½÷¡u ñà´s3{ô´o¹H.^_=;ãœìþùù¼wA·†õû’êyátUE+úwNþõ¨•e•šÁõØM2§Ö60UQ¦½÷á¤^.,,h㦠ªªF£Q¿ßòI'L}kò5W]‘¥ë†0MEQÚ¦\Ùª‚Â4uÝÈÎκæª+¦¾5ùä“Nðûýé:/š[,ZXX0é…—§½÷¡ª±ïËQäë |f»9!öAºa¨ª2ñÑg&=?¹°¨HÓ4ѶóTU©óû…×ýíêio¿væéclªªëºI,lˆ‚¦iêºnSÕ3O3íí×®ûÛÕBˆ:¿_U•6^g«iZaQѤç'O|ôUUtVŠbŸV *·f~Õ§pB®Ç#ó`Ÿ¢r 6êº!Ëò÷N’éÅåm\t²6¡UVVæç=:ñž?^tþä)oLŸñIBÓR‡ìíoQP–e]×­(8vÌÈ?^tþA ‡Ã••²,·ý–KMÓ _|éÕ;ï(˲®4’Á¾aÄêŠf¿~s¡o´ÏµÿÜ_ûo&´r×÷LbïdBkXB,‹D"ýúõÙŸcaKQ0‹YS%Úø¡i”_~õÎ{&@ˆ}9Z­#÷b&LÆÂP( “±ð•)oÌøðÓD"aÅB!„®Bì{iDRÙÚ¤§ëºÍf3ú”KR¢ $I{% ’„ ж\A*„Ø ºûòK~ÿádz¦¾;½¬|ËJ'EULÃÜ7¦Ȳ,É’®éVSŸ¢Â‚³Î;úÔ“ èÇ÷n$ ¶Å/ÄØL×)^×.[¶"›BTkÆ/±ÄÌúèu‘x g»C=+3ãH·£“MñÈRf,Ž&fÕG?„õÖ=>½ìê§½ ¬?Pùû¦Ú#2dgšaÏRäÝø>{®*¸.® !rUy|Žç$¯³“M èæ’h|rM¨Ùî”I:++ãD³¯CÍVeU’¢†Y®é?EâoÖ†ÿ‰osû¥ý;Ù%I±1¡_U>Øi»(Ç=,ѯÊ!Ã\I¼á}^;¹÷ïò\ÏñGg›6Ì¥Ñĵ¡™õÑñ¹ž |Ömn/«{£6$ZèºùBße9žÔïy]ﺟⲠÕß›¹ã6Iº0;c¬ÏÕËa“…X×> D^« E?‚ ûyš9[[¹<šHý¿s(ÊS·üßsÖUýÔätYòUyB®÷·^g¡* sQ$þ¶?<³Õgi‡]Fwÿê±dÂòò²6›uÞR, …B½z÷¼þï×üá¢ó¾úú»÷g|aÓX…ÂÁ`†Ëù»sÎ8ã´ÑK—.ŸþÑÌo¾™½jÍZ]7Å–µ¦[’a{N,’$¥æ@ëàûôêyÜqG5bРv»½¾¾¾¦ºJX£&ö«úÚ©S§ç_œLL“BUy«{^®Úâ“«·CÜ-wôšŠ°a&GÒMî–{X†½¥ÒÓ®þ·GÞEë«Evâ]ûHŸ«ÙƒÈ¥gŠsܲähò©$ľïB±U1-Ù½úéâœmÒà6ÿä/yÞÃño›+-æ«ò­E™ÍÆXŸ+f˜7möïðŽä²=Wœ­6÷Öé™®ZÝ‘¦‡òžN™¶æ~h‡zS¡ï†Mþ=ø³î’ÝìKïóÎYÛX»;Óqu„è™P–å;F/¿ôâp8¤ëú^Ì'ÖÖ4­¦¦ZiàÀC†¨ Ì[°ðÃOfÍ™3¯¬¼2¹zÔºq»J/VŸ!„5NÃ:Ô¢Âü#6zäÉÃâËôE£±`0(„)I²¢îåg¨õpgd¸Ÿxú_=ü”,ˤÁ4¥ˆdÜ”Ðï(«›йdiŒÏus¡ÏJ5Å6åÒÏÓU[ªjÍó%߯ë¦x²ªþm¸^7†fØo-ÌìãP­÷\qÎÈÕ•õ­^S- a ñ\Uý¿kCÃüsž÷ò\Or Ÿbq4qGYÝêXâxóÁÎYNIB¨’4Öçz¬¡âw‚×y Ófý9b˜7möŒê¦ì²=Ú9»“mË ÈI^g³ÐZ8ús$~wy`y4QlSn*ô ÷8­ÿ{vVÆûu‘¹;þ~oQV2 ÖéÆeuŸ£nY:?Ë}u¾7[ií[”(<Þ%;ÙeôáŠÀ¤ê`K··IÒáøƒÑDg›òϢ̣Ü[Âç(ŸëöÍuÑ=÷ôQ„˜ŠÝ_X×zÙÕ› }ÉŸ5ÆçzÇþ~WͧéêÑûŽ>ôðS«W¯øÀ]áp8‹«ª²w÷ÚY#4C¡`0X¯ªê Ã9éÄá›6o^¾ü—ïçÌÿæÛÙk×­·v⥄CÓ4EW¥-„’­£R¥gnÇ{ÔÑG>`@¿Î:éº «ªª¤½][7 ê‡=##ãúÜ>õÝÔÓ¤CM¦]ˆñªWÆ4!DT7§Ô† lÊÑnÇüpl~(þCÃF²LE¾,wëZ¾ÛËüÿõ‡­?ŠWR5½g¾•» Te|®û±–g65¥&”¼ý#•s³2’kDºqÉúê:ÝB|ˆívœÛ°¸´gJ=ð»`ìë«v;Žv;¦""Ö×Ç_¨Þ^”iýµØÖâ/  ý¢õÕÃB¬‰kW–Öü·{ÞA®-å9îí¡.{ÿ†D*„øsiÍüpÜJ§OUÕ !®É÷¦éÑÜœÐ/Y_m¥¾5qíÚMµ³(’6Uöt¨ÛìÜ+¢‰ñj¦)„XKŒßPónϼ~Ž-wü‚l÷®´^]!:è|B]U”©ïÎÐ4ãŽÛnÌÈp…ÃaUÝûœLÓ¬««Âôy=Ã?æ¤Oðûk×o(ýyÑÒ¯¿™½tÙŠM›ËRáõ%IXÑÆZ¹û9ÇŠ}VÐúæVLýÎ; ØÿøãŽúÍAƒºu-ÎÊÊÖ´D$©©©BÚ[“$Zj!“‘‘ÆÿvÝ­ïOÿHU­ñ9Äž’LƒBˆÃñ•±F©<Ò䟜–é²7”¿ÖĵäûuÑP{¶:xwCî:-3c§Þ²¿S·õ»é¦Ø˜Ð“ðãúh]Ê.¸5)‡êI©¹ÅLóûP¬Ù4R’ÐR—¡¶t “k‚ÃL=ŒjBOuÙpÛe!¶S–:½uEèÏ‘¸•“^ª ^žëq¥g¾ü{u‘Ô`µfT$ô¢†¢hë+“­ñrM(‘ò³¦ùjMè¾NYÖ_oyÁçö¥õêÑQiº®ªÊûÓ?ZµzÍË“žÊÍËöûý6›M´u˜V2L$ñxÜ4MUUûÐçÀÁƒ.8ïìꪪ5k7¬Z»öÇúuåêÒÒþº€Þ$Û4¹­}s¶ŸSofE>+Xnó³2}ÅÅ]úÐ{èЃûôìÙ«g×ܼ+Á”hߣ}¹š¶MýJ–"g+ò.´±IëÕ ¢#gBMWUeé²gŸÿÇGºû¨#‡••mn'™05ÔYå>«+©Âér rаaC/<ïì`0ä÷û7m._¾â×ŠŠª_~]Y[ë_³®$ׂ¡ànþtÛc³«½ztÏÎÎê×÷€‚‚¼ýûvîT˜••åñ¸­”Fý~òhÛO=p›4XTÔiöœy¿á¶ ¥Iƒé–Z5ªkÝÛ÷Î6%ubÓ”5~È T¹•oÙuS¤–æDãBÜ6‡gl÷S“.6e\¦ë·ó@§­ÙrÜvîjÓSÝøe*òvaVÊ)­nîê­ÒÒµí-ÐäÈS{¢J;ÓÖe‡šž¥mîWæ.Âô]]!ö…L¨(ʆÒã'\sÇíÿ8ç¬Ó«ª*MÓl'{ÞšÖ ­¥Á`Ð*âI’”UXXpøaCdYIh MÓª«ªâ mõ𵲬lØPºiS™,Ë M[¼d™ÑÂ"IYQ<Цª†atî\Ôµk±aè½{õ´ÛÔܼ3þí¾ü)÷7¯¹` lJ;Øž-ZZJ* ‘ÑŠ×"U©lüÈæ4Þ£X·KcÛùÕ bï3 ÃJ;ÏNzù§ŸßwïíÝ»×ÔÔ´ÛL¸|(„H$BˆX,ºMé@–å–Ê’$ Ö“›Ûpûi0''§dCéÍ·Ü5{îë¾t“oËc‰qbËHƒ­Zƒ½<šhØÃ%Žq73Rïè”­z%q­²m˼§L3½6tgY]òJ:ÑëL]ž*Z¹nŸÝ¸'Ío\¶Ô¥’åÛ½G¿Æ´‘ >¸É^A, tîÜZ÷tÔÀRÇ{¯ídkÕëÈ0·cqãM§©wvcBîRñ®_]€=ÿö˜S±«# UU™=wÁÙç^<ë³/óó Ãèˆ%Y–eYV¨ R¿¸ kûŸ%õ‹¥(—| ÃÈÏ/˜õÙ—gŸ{ñì¹ T•ñmê«`4ùçNÛ!ÌXŸkfï‚»‹2Çø\ U²ˆž2³þ¬†Ù¢açØŸ† !¦ÕEÚøõHAñi}4õs…#3­ÙPwA¶;?¥ % qqÎÖ¹Ãi ߤÜà —m›~›Îó:w²¹‹fno9뮩M ăŽðü”D½—ä¸S7 *ÒÎ¥–´ó« @ „ho[ «ªk®øóµ<ôXF†ÛívkšÆ™é@…A·Û‘á~à¡Ç®øóµUÕ5ll{«bZj&|¶8g¤Ïå’%¯,ò¹þY”ÙÓ®ž—í~¬Kvr¢@•f¼“2 àÞ¢¬?åyòUÙ!IG¹¯wÏK®ôÛ”Ð'×Ûø¥62¹±À7ÈisHÒõŽ¢ÌÔÄâlyðC–"ÿ»{Þ‘n‡]’ºÚ”G»d‘’$_« %ÿ<³wÁÊ­ÿ’cÙŽÄ¥4}¦8çTŸË)I…ªrcïò”<ÓJÁ”jù0·=S‘{;Ô‚ÝǺ4ºµ#è„\Ï)^§K–:Ù” |Zw„ª2¥{îav‡$õ´«Ïç j¨|šBL©ÝÁYjI;¿º‚%£í¯ Irùèm·\? ¿ššjÑaOî?œ"77oùŠ_î¾wbr™(›÷Š[6×}ÐÓž«ÊBˆÐi{¯g~³7Ëk9PE ³—]}­[nÓÿ5¥6ô¿&ãšúg™ÿÍyI²âå“OiÄ0wjá/)+3ÈpüзHqã&ÿ´ºð.Ÿ¥ê"gdf$G2>]œ#ÏÆÈØîÆLÓ!Iý¶×»ç5ý¿/UW´n„I³ÚóÕT!ÚñòÑsλdÊÞÌÉÉu8” E;. :œÎœœÜ)ÿyóœó.a™è^W¡éç•T­Šµø”©Ñ+6Ô¬H™q3Í JªÞô·HVÆ´³×U.ßT°Ë^¨.Ç›iñ`E`iÃ!Û”|µùßAÝX»¡¹IïøÃ÷–Õµæ–DÛXiWL!ž®ªŸ޵rx†åãúhu“Ö)ÅöÝúÌë»PìåÊkoùÃv°seL›XhöÐ_¯ MLùì@ìÒþÆv{uB´çå£ÁPè¶;îŸ=gÁM7ü_ݪªª¥BÑî ƒùùùëÖ­¿î¡Û?þô3ëb™è^·.®Y[qzfÆH¯sÓž­Hº5š±2¦}ŠNó‡›6 æm›ý¯ÕOÏÌ8ÊíèbS<²T£‹#‰Oê#3ê"{ëA™æÖW_˜1Îç:ÀaS$Q–пÅ^© ­kÙŠl•ž$!NËÌx±º™PTׯ¬©˜ë9ÕçêbS†ùS$þïÚÐ×ÁØ7«>:vmåø÷±g¾*tsa$þJMð‡pü…®[ËqÑVÂ:Ý8·¤êoùÞ#3™ŠT§›kâÚŠÝŽC÷—„ãf»9mnYªÒŒ…‘øÛþðìP,¹ÐA®ˆ}«7¬a^¯Ï0ô©Ó>xì©UUU[ËD) ¢=XÚ¿srúÂ)«Ë×ÄÓ¸ÄàÕnyÉ­t—®¯þ6åüö[J†/Ÿ³±GW*Š …¾øêÛ…?-îßï€^={hš–H$È„bï­µÛíÙÙÙK—-¿þ¦;^òF8Q…Áh?þ’çUá¿kCµú®_œ¹ì¯uÏ;Èe/P!¤½Ñ…®HâÚŸ§áåèÑÊ@=á‚%£€ØÃfdYž=gÞYçþá²K/ºô’ßäåWWWI’Ä Ò6~,LÓÌÍÍ«¨ª|ú¹_zyJBÓ¬(Hÿì«6&´vµ—]µº¶üŽß¼¹¶4¡Û$ÑÓ®ŽÏõ6ô³YMlJðD–Œi!+Š¡ëBˆîÝ»^ó— §¥ëz}}=±°Í¢ ×ëUå½>zò™I%%R@ì»KFï’3ÚçÚáÍ&l¨þ"ÈzQ€`É( Ò´|T¡ªJm­æ¬/~øñݺ÷ï×O’¤h4*„öЈgˆÆÛu]÷x<™™Yóøñ·ÜùÒ+Sêꪪ;!öù%£BˆïBÑ߸ì]íj˳æÍ{+ïíÆÜB •¬Ýƒ†a¨Š2n쩼èüƒ‡#‘ýföxç—Ë•‘‘±hÑ’ÉSÞø`úÇš®'Ï?§bj*ó[óŸk ÓÖYU2dÉ¢Þ0KâÚüpìÍÚP)‹E ¢-)Šbí[³©êØ1#¯¾jBï^=ƒõõVµE¤b·çI8N×»zÍÚ§žž4}Æ' MK=í{y0…"ËV>ÉÎκð¼³Ï:s\÷n] -¬—$¡(4:ÚÙ(¨™¦ðx¼6›Z²~ÃÔiüçÍwjký[¢ aÖˆ€@ˆö Ýî#†_rÑ48¡iõ€5¸‚í…bGû3­n®^ŸÏ¦ª‹-yeÊë3g~ ‡ˆ‚ ¢L±—b¡µˆôì³NrðoœNg P—H$hF*Znj³Ù|¾Ìh4ºð§Ÿß™ú~êQÃ0è!:X,Buäá§5âärr²#‘H(’$ɺØïƘ¦iš¦Ûív¹\55µ3g}ùþŒfÏ™/¶h@ DG…É<Ó«gQ#O9òäýúJ’…Éx\j ö³¥¡›ÝîÎÈ0M±ü—_?ùdÖGŸ|¶fíº¦§ BtÜYöRCÏL›Ívì1GœyÚ˜C‡),*Ð5- iš¶?Ô “õ@UUÝn·¢ªåe?ü¸pÚû3¾ýnn"‘B(Šb Á”y±¯Í-”$)¹Ž´¨0ÿØcŽ>cÜèAƒúeeekZ"‰Äã1!öµdØPè3ív‡ËåRU›ß_»té/ï~ðá·ß}_V^)V‡š¦É\A±¯#MM>½{÷8þØ£>òðúwîÔÉ4H$FMÓì¸eÃd1P’$§Óér¹$IÞ´yóòå+¾Ÿ3ÿëo¿_½z]jNfu(„Øw ! ò>jØðãŽ8 _Ýl6»¦%¢Ñh,ëá05:§Ó©ª¶D"¾nÝúeËùê›ï¾Ÿ=¯¼¢²¥»!öÃ¥¤²,ÆÖš¡Ûqà ‡|à!÷ï×»¨°Èáphš‹Å¬Êa2Zÿ|oÅ?ÑÐÆ:§Óép8TUÅbeåe+~Y½páOÿûiñâ¥ËB¡pKw VÑLbë>C!Df¦¯ß>C‡ü›ƒ÷ìÙ­kq±Ëå’e9¡iñXLÓ4«‹•ʬo’Œ^{0øY?"õÙl6UUí‡MU ÈD"JK×®]ÿó¢%?.üiů«êêÉo¢(ЦUAäØÁjRIÛTÒœ.ç½zöéÓ«[·®ýûöéÝ»GNvvVV–¢(ªª&4Í4Œx<®ë†aè‰DB’¤†'R³¢Øî@ˆ† !$kX¼,+Š"ÛívI–mªªiš®ë~¿¿¦¶võêu+~]µ~ý†U«Ö¬\³6‰ŠÆ•OÓ¬ Ø“áPáp:ss²ºw)**èV\Ü£G÷ì¬Ì.‹<ËåÌÌÌ2 Ýf·+²lÅ1ÃÐu]’$š¦3IÓTE–+ ꆑˆÇeY©«óG"Ñ`0¸qSY­¿nݺ’õ¥¥eeJ7V×øcѨh²ü•!°çÃarŽ}KÛðœN‡ÃîÈÍÍ*,,Ô‰¾}ÈÊÌ4 ]Ñ©Sa׮ʼnx¢i©Ð4M›Ý¶aCéæÍåBYVüuu¿þºRµÙÊËË««ý±x,‰6@šæÖYó for SEO, without changing the visible hero heading. Other pages keep the theme's default title. #} {%- block htmltitle -%} {%- if pagename == "index" -%} Structure-from-Motion and Multi-View Stereo{{ titlesuffix }} {%- else -%} {{ super() }} {%- endif -%} {%- endblock -%} colmap-4.2.0/doc/_templates/version.html000066400000000000000000000004331524536416500202600ustar00rootroot00000000000000{# Footer stamp recording the COLMAP release plus the git commit hash and date the docs were built from (see get_git_revision() in conf.py). Keeps the build provenance visible now that the theme sidebar no longer shows it. #} colmap-4.2.0/doc/bibliography.rst000077500000000000000000000050161524536416500167620ustar00rootroot00000000000000Bibliography ============ .. [schoenberger_thesis] Johannes L. Schönberger. "Robust Methods for Accurate and Efficient 3D Modeling from Unstructured Imagery." ETH Zürich, 2018. .. [furukawa10] Furukawa, Yasutaka, and Jean Ponce. "Accurate, dense, and robust multiview stereopsis." Transactions on Pattern Analysis and Machine Intelligence, 2010. .. [caspar] Martens, Emil and Miller, Aaron and Varnum, Matias and Stahl, Annette. "Caspar: CUDA Accelerator for Symbolic Programming with Adaptive Reordering." International Conference on Robotics and Automation (ICRA), 2026. .. [cohen-steiner2004] Cohen-Steiner, David, and Da, Franck. "A greedy Delaunay-based surface reconstruction algorithm." The Visual Computer, 2004. .. [garland1997] Garland, Michael, and Heckbert, Paul S. "Surface simplification using quadric error metrics." Proceedings of SIGGRAPH, 1997. .. [hofer16] Hofer, M., Maurer, M., and Bischof, H. Efficient 3D Scene Abstraction Using Line Segments, Computer Vision and Image Understanding, 2016. .. [jancosek11] Jancosek, Michal, and Tomás Pajdla. "Multi-view reconstruction preserving weakly-supported surfaces." Conference on Computer Vision and Pattern Recognition, 2011. .. [kazhdan2013] Kazhdan, Michael and Hoppe, Hugues "Screened poisson surface reconstruction." ACM Transactions on Graphics (TOG), 2013. .. [schoenberger16sfm] Schönberger, Johannes Lutz and Frahm, Jan-Michael. "Structure-from-Motion Revisited." Conference on Computer Vision and Pattern Recognition, 2016. .. [schoenberger16mvs] Schönberger, Johannes Lutz and Zheng, Enliang and Pollefeys, Marc and Frahm, Jan-Michael. "Pixelwise View Selection for Unstructured Multi-View Stereo." European Conference on Computer Vision, 2016. .. [schoenberger16vote] Schönberger, Johannes Lutz and Price, True and Sattler, Torsten and Frahm, Jan-Michael and Pollefeys, Marc "A Vote­-and­-Verify Strategy for Fast Spatial Verification in Image Retrieval." Asian Conference on Computer Vision, 2016. .. [lowe04] Lowe, David G. "Distinctive image features from scale-invariant keypoints". International journal of computer vision 60.2 (2004): 91-110. .. [waechter2014] Waechter, Michael and Moehrle, Nils and Goesele, Michael. "Let there be color! Large-scale texturing of 3D reconstructions." European Conference on Computer Vision, 2014. .. [wu13] Wu, Changchang. "Towards linear-time incremental structure from motion." International Conference 3D Vision, 2013. colmap-4.2.0/doc/cameras.rst000066400000000000000000000117731524536416500157260ustar00rootroot00000000000000Camera Models ============= COLMAP implements different camera models of varying complexity. If no intrinsic parameters are known a priori, it is generally best to use the simplest camera model that is complex enough to model the distortion effects: - ``SIMPLE_PINHOLE``, ``PINHOLE``: Use these camera models, if your images are undistorted a priori. These use one and two focal length parameters, respectively. Note that even in the case of undistorted images, COLMAP could try to improve the intrinsics with a more complex camera model. - ``SIMPLE_RADIAL``, ``RADIAL``: This should be the camera model of choice, if the intrinsics are unknown and every image has a different camera calibration, e.g., in the case of Internet photos. Both models are simplified versions of the ``OPENCV`` model only modeling radial distortion effects with one and two parameters, respectively. - ``OPENCV``, ``FULL_OPENCV``: Use these camera models, if you know the calibration parameters a priori. You can also try to let COLMAP estimate the parameters, if you share the intrinsics for multiple images. Note that the automatic estimation of parameters will most likely fail, if every image has a separate set of intrinsic parameters. - ``SIMPLE_RADIAL_FISHEYE``, ``RADIAL_FISHEYE``, ``OPENCV_FISHEYE``, ``FOV``, ``THIN_PRISM_FISHEYE``, ``RAD_TAN_THIN_PRISM_FISHEYE``: Use these camera models for fisheye lenses and note that all other models are not really capable of modeling the distortion effects of fisheye lenses. The ``FOV`` model is used by Google Project Tango (make sure to not initialize ``omega`` to zero). - ``SIMPLE_FISHEYE``, ``FISHEYE``: Use these camera models for fisheye lenses with equidistant projection where distortion can be ignored or has been pre-corrected. These models use the equidistant projection (theta = atan(r)) without any distortion parameters. ``SIMPLE_FISHEYE`` has a single focal length (f), while ``FISHEYE`` has two (fx, fy). - ``SIMPLE_DIVISION``, ``DIVISION``: Use these camera models, if you know the calibration parameters a priori. Similar to ``SIMPLE_RADIAL`` and ``RADIAL`` models, they can model simple radial distortion effects. The two models have first-order local equivalence for small distortions. - ``EUCM``: Use this camera model for wide-angle fisheye cameras and catadioptric systems. It represents radial distortion using two parameters in addition to the standard pinhole parameters. You can inspect the estimated intrinsic parameters by double-clicking specific images in the model viewer or by exporting the model and opening the ``cameras.txt`` file. Projection ---------- All perspective camera models map a 3D point in the camera coordinate system to a 2D pixel coordinate in three steps: perspective division, distortion, and the intrinsic transform (focal length and principal point). COLMAP uses a corner-based pixel convention, in which the center of the top-left pixel is at ``(0.5, 0.5)`` (see :doc:`database`). Taking ``SIMPLE_RADIAL`` (parameter list ``f, cx, cy, k``) as a worked example, a point :math:`(X, Y, Z)` in the camera frame, which looks down the positive :math:`Z` axis, is projected as follows: 1. Perspective division onto the normalized image plane: .. math:: u = X / Z, \qquad v = Y / Z 2. Radial distortion with :math:`r^2 = u^2 + v^2`: .. math:: u' = u \, (1 + k \, r^2), \qquad v' = v \, (1 + k \, r^2) 3. Focal length and principal point, giving the pixel coordinate: .. math:: x = f \, u' + c_x, \qquad y = f \, v' + c_y The inverse mapping (pixel to normalized camera ray) subtracts the principal point, divides by the focal length, and then removes the distortion iteratively. All other perspective models share this three-step structure and differ only in the number of focal length parameters (a single shared ``f`` or separate ``fx``, ``fy``) and in the distortion function, e.g. ``RADIAL`` adds a second radial term ``k2`` and ``OPENCV`` adds tangential terms ``p1, p2``. The fisheye models instead replace the perspective division with an equidistant projection. The exact parameter list of every model is given by its ``params_info`` string and defined in the camera models header: https://github.com/colmap/colmap/blob/main/src/colmap/sensor/models.h Configuration ------------- To achieve optimal reconstruction results, you might have to try different camera models for your problem. Generally, when the reconstruction fails and the estimated focal length values / distortion coefficients are grossly wrong, it is a sign of using a too complex camera model. Contrary, if COLMAP uses many iterative local and global bundle adjustments, it is a sign of using a too simple camera model that is not able to fully model the distortion effects. You can also share intrinsics between multiple images to obtain more reliable results (see :ref:`Share intrinsic camera parameters `) or you can fix the intrinsic parameters during the reconstruction (see :ref:`Fix intrinsic camera parameters `). colmap-4.2.0/doc/changelog.rst000066400000000000000000000000561524536416500162320ustar00rootroot00000000000000.. _changelog: .. include:: ../CHANGELOG.rst colmap-4.2.0/doc/cli.rst000066400000000000000000000530011524536416500150500ustar00rootroot00000000000000.. _cli: Command-line Interface ====================== The command-line interface provides access to all of COLMAP's functionality for automated scripting. Each core functionality is implemented as a command to the ``colmap`` executable. Run ``colmap -h`` to list the available commands (or ``COLMAP.bat -h`` under Windows). Note that if you run COLMAP from the CMake build folder, the executable is located at ``./src/colmap/exe/colmap``. To start the graphical user interface, run ``colmap gui``. Example ------- Assuming you stored the images of your project in the following structure:: /path/to/project/... +── images │   +── image1.jpg │   +── image2.jpg │   +── ... │   +── imageN.jpg The command for the automatic reconstruction tool would be:: # The project folder must contain a folder "images" with all the images. $ DATASET_PATH=/path/to/project $ colmap automatic_reconstructor \ --workspace_path $DATASET_PATH \ --image_path $DATASET_PATH/images Note that any command lists all available options using the ``-h,--help`` command-line argument. In case you need more control over the individual parameters of the reconstruction process, you can execute the following sequence of commands as an alternative to the automatic reconstruction command:: # The project folder must contain a folder "images" with all the images. $ DATASET_PATH=/path/to/dataset $ colmap feature_extractor \ --database_path $DATASET_PATH/database.db \ --image_path $DATASET_PATH/images $ colmap exhaustive_matcher \ --database_path $DATASET_PATH/database.db $ mkdir -p $DATASET_PATH/sparse $ colmap mapper \ --database_path $DATASET_PATH/database.db \ --image_path $DATASET_PATH/images \ --output_path $DATASET_PATH/sparse $ mkdir -p $DATASET_PATH/dense $ colmap image_undistorter \ --image_path $DATASET_PATH/images \ --input_path $DATASET_PATH/sparse/0 \ --output_path $DATASET_PATH/dense \ --output_type COLMAP \ --max_image_size 2000 $ colmap patch_match_stereo \ --workspace_path $DATASET_PATH/dense \ --workspace_format COLMAP \ --PatchMatchStereo.geom_consistency true $ colmap stereo_fusion \ --workspace_path $DATASET_PATH/dense \ --workspace_format COLMAP \ --input_type geometric \ --output_path $DATASET_PATH/dense/fused.ply $ colmap poisson_mesher \ --input_path $DATASET_PATH/dense/fused.ply \ --output_path $DATASET_PATH/dense/meshed-poisson.ply $ colmap delaunay_mesher \ --input_path $DATASET_PATH/dense \ --output_path $DATASET_PATH/dense/meshed-delaunay.ply $ colmap advancing_front_mesher \ --input_path $DATASET_PATH/dense \ --output_path $DATASET_PATH/dense/meshed-advancing-front.ply # Optionally simplify a dense mesh to reduce its size. $ colmap mesh_simplifier \ --input_path $DATASET_PATH/dense/meshed-poisson.ply \ --output_path $DATASET_PATH/dense/meshed-poisson-simplified.ply \ --MeshSimplification.target_face_ratio 0.25 # Optionally texture a mesh using the undistorted images. $ colmap mesh_texturer \ --workspace_path $DATASET_PATH/dense \ --input_path $DATASET_PATH/dense/meshed-poisson.ply \ --output_path $DATASET_PATH/dense/textured Graceful shutdown and resuming ------------------------------ The feature extraction and matching commands, ``mapper``, ``pose_prior_mapper``, ``bundle_adjuster``, ``point_triangulator``, ``image_registrator``, the image undistortion commands, ``patch_match_stereo``, and ``stereo_fusion`` handle ``SIGINT`` and ``SIGTERM`` cooperatively. ``automatic_reconstructor`` supports graceful shutdown when using the incremental mapper. The first signal stops work at a safe point and writes any usable in-progress results. The process then exits with status 130 for ``SIGINT`` or 143 for ``SIGTERM``. A second signal terminates immediately and may interrupt an output write. Feature extraction and matching can be resumed by rerunning the same command against the same database. PatchMatch can likewise be rerun against the same workspace; complete depth and normal maps are skipped. An interrupted incremental reconstruction is written to its normal numbered model directory and can be continued with ``--input_path``:: $ colmap mapper \ --database_path $DATASET_PATH/database.db \ --image_path $DATASET_PATH/images \ --input_path $DATASET_PATH/sparse/0 \ --output_path $DATASET_PATH/sparse/0 Point triangulation, image registration, and bundle adjustment write their usable partial results before exiting. Stereo fusion writes non-empty partial results to a sibling path containing ``.partial`` so that an existing completed result is not overwritten. Interrupted image undistortion can be restarted by rerunning the same command. Ceres optimizations stop between iterations. The Caspar bundle-adjustment backend can only stop after its current solver invocation. The global mapper does not support graceful shutdown because its intermediate reconstructions cannot currently be resumed. The equivalent pycolmap functions automatically handle ``SIGINT`` (Ctrl-C) by performing a graceful shutdown and raising ``KeyboardInterrupt``. For other termination events, they accept an optional ``cancellation_token``. Explicit cancellation finishes cleanup and then raises ``InterruptedError``. This allows applications to connect cloud-preemption signals (like ``SIGTERM``) to the token without pycolmap replacing the host application's signal handlers. Automatic ``SIGINT`` handling requires calling the pycolmap function from Python's main thread; use a cancellation token when calling it from another thread:: import signal import pycolmap token = pycolmap.CancellationToken() signal.signal(signal.SIGTERM, lambda *_: token.cancel()) pycolmap.incremental_mapping( database_path, image_path, output_path, cancellation_token=token, ) To use the global SfM pipeline instead of the incremental mapper, replace the ``mapper`` step with ``global_mapper``. The global mapper depends on good focal length priors, so if reliable intrinsics are not available (e.g., from EXIF or lab calibration), you should run ``view_graph_calibrator`` first. This step is optional but recommended to improve the quality of global SfM, as was always the default in `GLOMAP `_. Note that ``view_graph_calibrator`` modifies camera intrinsics and two-view geometries in the database in-place, so it is recommended to work on a copy of the database:: $ colmap feature_extractor \ --database_path $DATASET_PATH/database.db \ --image_path $DATASET_PATH/images $ colmap exhaustive_matcher \ --database_path $DATASET_PATH/database.db # Optional but often needed: calibrate intrinsics from the view graph. # This modifies the database in-place, so work on a copy. $ cp $DATASET_PATH/database.db $DATASET_PATH/database_global.db $ colmap view_graph_calibrator \ --database_path $DATASET_PATH/database_global.db $ mkdir -p $DATASET_PATH/sparse $ colmap global_mapper \ --database_path $DATASET_PATH/database_global.db \ --image_path $DATASET_PATH/images \ --output_path $DATASET_PATH/sparse If you want to run COLMAP on a computer without an attached display (e.g., cluster or cloud service), COLMAP automatically switches to use CUDA if supported by your system. If no CUDA enabled device is available, you can manually select to use CPU-based feature extraction and matching by setting the ``--FeatureExtraction.use_gpu 0`` and ``--FeatureMatching.use_gpu 0`` options. Help ---- The available commands can be listed using the command:: $ colmap help Usage: colmap [command] [options] Documentation: https://colmap.github.io/ Example usage: colmap help [ -h, --help ] colmap gui colmap gui -h [ --help ] colmap automatic_reconstructor -h [ --help ] colmap automatic_reconstructor --image_path IMAGES --workspace_path WORKSPACE colmap feature_extractor --image_path IMAGES --database_path DATABASE colmap exhaustive_matcher --database_path DATABASE colmap mapper --image_path IMAGES --database_path DATABASE --output_path MODEL ... Available commands: help gui automatic_reconstructor bundle_adjuster color_extractor database_cleaner database_creator database_merger delaunay_mesher exhaustive_matcher feature_extractor feature_importer geometric_verifier global_mapper guided_geometric_verifier hierarchical_mapper image_deleter image_filterer image_rectifier image_registrator image_undistorter image_undistorter_standalone mapper matches_importer mesh_simplifier mesh_texturer model_aligner model_analyzer model_clusterer model_comparer model_converter model_cropper model_merger model_orientation_aligner model_splitter model_transformer patch_match_stereo point_filtering point_triangulator pose_prior_mapper poisson_mesher project_generator rig_configurator rotation_averager sequential_matcher spatial_matcher stereo_fusion transitive_matcher view_graph_calibrator vocab_tree_builder vocab_tree_matcher vocab_tree_retriever And each command has a ``-h,--help`` command-line argument to show the usage and the available options, e.g.:: $ colmap feature_extractor -h Options can either be specified via command-line or by defining them in a .ini project file passed to ``--project_path``. -h [ --help ] --default_random_seed arg (=0) --log_target arg (=stderr_and_file) {stderr, stdout, file, stderr_and_file} --log_path arg --log_level arg (=0) --log_severity arg (=0) 0:INFO, 1:WARNING, 2:ERROR, 3:FATAL --log_color arg (=1) --project_path arg --database_path arg --image_path arg --camera_mode arg (=-1) --image_list_path arg --descriptor_normalization arg (=l1_root) {'l1_root', 'l2'} --ImageReader.mask_path arg --ImageReader.camera_model arg (=SIMPLE_RADIAL) --ImageReader.single_camera arg (=0) --ImageReader.single_camera_per_folder arg (=0) --ImageReader.single_camera_per_image arg (=0) --ImageReader.existing_camera_id arg (=-1) --ImageReader.camera_params arg --ImageReader.default_focal_length_factor arg (=1.2) --ImageReader.camera_mask_path arg --FeatureExtraction.type arg (=SIFT) --FeatureExtraction.max_image_size arg (=3200) --FeatureExtraction.num_threads arg (=-1) --FeatureExtraction.use_gpu arg (=1) --FeatureExtraction.gpu_index arg (=-1) --SiftExtraction.max_num_features arg (=8192) --SiftExtraction.first_octave arg (=-1) --SiftExtraction.num_octaves arg (=4) --SiftExtraction.octave_resolution arg (=3) --SiftExtraction.peak_threshold arg (=0.0066666666666666671) --SiftExtraction.edge_threshold arg (=10) --SiftExtraction.estimate_affine_shape arg (=0) --SiftExtraction.max_num_orientations arg (=2) --SiftExtraction.upright arg (=0) --SiftExtraction.domain_size_pooling arg (=0) --SiftExtraction.dsp_min_scale arg (=0.16666666666666666) --SiftExtraction.dsp_max_scale arg (=3) --SiftExtraction.dsp_num_scales arg (=10) The available options can either be provided directly from the command-line or through a ``.ini`` file provided to ``--project_path``. Commands -------- The following list briefly documents the functionality of each command, that is available as ``colmap [command]``: - ``gui``: The graphical user interface, see :ref:`Graphical User Interface ` for more information. - ``automatic_reconstructor``: Automatically reconstruct sparse and dense model for a set of input images. Key options include ``--quality`` (LOW, MEDIUM, HIGH, EXTREME), ``--data_type`` (INDIVIDUAL, VIDEO, INTERNET) to tune settings for different capture scenarios, ``--feature`` (SIFT, ALIKED, LOMA, LOMA128) to select the feature extraction algorithm, ``--mapper`` (INCREMENTAL, HIERARCHICAL, GLOBAL) to choose the SfM pipeline, and ``--mesher`` (POISSON, DELAUNAY, ADVANCING_FRONT) to select the surface reconstruction method. - ``project_generator``: Generate project files at different quality settings. - ``feature_extractor``, ``feature_importer``: Perform feature extraction or import features for a set of images. - ``exhaustive_matcher``, ``vocab_tree_matcher``, ``sequential_matcher``, ``spatial_matcher``, ``transitive_matcher``, ``matches_importer``: Perform feature matching after performing feature extraction. - ``geometric_verifier``: Run standalone geometric verification on existing feature matches in the database. This estimates two-view geometries (fundamental/essential matrices, homographies) for matched image pairs. - ``guided_geometric_verifier``: Run geometric verification guided by an existing sparse reconstruction. Uses the known relative camera poses to improve match verification results. - ``mapper``: Sparse 3D reconstruction / mapping of the dataset using SfM after performing feature extraction and matching. - ``global_mapper``: Sparse 3D reconstruction using the global SfM pipeline. Unlike the incremental ``mapper``, the global approach solves for all camera poses simultaneously using rotation averaging and global positioning. This can be faster for large datasets but may be less robust to outliers. The global mapper depends on reasonably good focal length priors to perform well. Run ``view_graph_calibrator`` before ``global_mapper`` to calibrate camera intrinsics and estimate relative poses from the view graph, or provide camera calibrations manually. - ``pose_prior_mapper``: Sparse 3D reconstruction / mapping using pose priors. - ``hierarchical_mapper``: Sparse 3D reconstruction / mapping of the dataset using hierarchical SfM after performing feature extraction and matching. This parallelizes the reconstruction process by partitioning the scene into overlapping submodels and then reconstructing each submodel independently. Finally, the overlapping submodels are merged into a single reconstruction. It is recommended to run a few rounds of point triangulation and bundle adjustment after this step. - ``image_undistorter``: Undistort images and/or export them for MVS or to external dense reconstruction software, such as CMVS/PMVS. - ``image_rectifier``: Stereo rectify cameras and undistort images for stereo disparity estimation. - ``image_filterer``: Filter images from a sparse reconstruction. - ``image_deleter``: Delete specific images from a sparse reconstruction. - ``patch_match_stereo``: Dense 3D reconstruction / mapping using MVS after running the ``image_undistorter`` to initialize the workspace. - ``stereo_fusion``: Fusion of ``patch_match_stereo`` results into to a colored point cloud. - ``poisson_mesher``: Meshing of the fused point cloud using Poisson surface reconstruction. - ``delaunay_mesher``: Meshing of the reconstructed sparse or dense point cloud using a graph cut on the Delaunay triangulation and visibility voting. - ``advancing_front_mesher``: Meshing of the fused point cloud using advancing front surface reconstruction. Supports visibility-based filtering and block-wise parallel processing for large-scale scenes. Requires CGAL. - ``mesh_simplifier``: Simplify a triangle mesh (PLY format) using Quadric Error Metric (QEM) decimation. This reduces the number of faces in a mesh while preserving its overall shape and appearance. Key options include ``--MeshSimplification.target_face_ratio`` to control the fraction of faces to retain (default 0.1), ``--MeshSimplification.max_error`` to set a maximum quadric error threshold (0 = disabled), and ``--MeshSimplification.boundary_weight`` to control boundary edge preservation (default 1000). Supports multi-threaded initialization via ``--MeshSimplification.num_threads``. - ``mesh_texturer``: Produce a texture atlas and UV coordinates for a triangle mesh using calibrated multi-view images. - ``image_registrator``: Register new images in the database against an existing model, e.g., when extracting features and matching newly added images in a database after running ``mapper``. Note that no bundle adjustment or triangulation is performed. - ``point_triangulator``: Triangulate all observations of registered images in an existing model using the feature matches in a database. - ``point_filtering``: Filter sparse points in model by enforcing criteria, such as minimum track length, maximum reprojection error, etc. - ``bundle_adjuster``: Run global bundle adjustment on a reconstructed scene, e.g., when a refinement of the intrinsics is needed or after running the ``image_registrator``. The solver backend is selected via ``--BundleAdjustment.backend`` (``CERES`` or ``CASPAR``). Caspar [caspar]_ is an experimental GPU-accelerated backend; see :doc:`faq` for details and limitations. - ``database_cleaner``: Clean specific or all database tables. - ``database_creator``: Create an empty COLMAP SQLite database with the necessary database schema information. - ``database_merger``: Merge two databases into a new database. Note that the cameras will not be merged and that the unique camera and image identifiers might change during the merging process. - ``model_analyzer``: Print statistics about reconstructions. - ``model_clusterer``: Split a reconstruction into smaller sub-model clusters. Useful for managing and processing large-scale reconstructions. - ``model_aligner``: Align/geo-register model to coordinate system of given camera centers. - ``model_orientation_aligner``: Align the coordinate axis of a model using a Manhattan world assumption. - ``model_comparer``: Compare statistics of two reconstructions. - ``model_converter``: Convert the COLMAP export format to another format, such as PLY or NVM. - ``model_cropper``: Crop model to specific bounding box described in GPS or model coordinate system. - ``model_merger``: Attempt to merge two disconnected reconstructions, if they have common registered images. - ``model_splitter``: Divide model in rectangular sub-models specified from file containing bounding box coordinates, or max extent of sub-model, or number of subdivisions in each dimension. - ``model_transformer``: Transform coordinate frame of a model. - ``color_extractor``: Extract mean colors for all 3D points of a model. - ``rig_configurator``: Configure rigs and frames after feature extraction. - ``vocab_tree_builder``: Create a vocabulary tree from a database with extracted images. This is an offline procedure and can be run once, while the same vocabulary tree can be reused for other datasets. Note that, as a rule of thumb, you should use at least 10-100 times more features than visual words. Pre-trained trees can be downloaded from https://demuc.de/colmap/. This is useful if you want to build a custom tree with a different trade-off in terms of precision/recall vs. speed. - ``vocab_tree_retriever``: Perform vocabulary tree based image retrieval. - ``rotation_averager``: Run standalone rotation averaging on the view graph. Estimates global camera rotations from pairwise relative rotations. - ``view_graph_calibrator``: Calibrate camera intrinsics using the view graph. Estimates focal lengths and other intrinsic parameters from pairwise geometric relations. Should be run before ``global_mapper``, if no good prior camera intrinsics are known, since the global mapper depends on reasonably good focal length priors to perform well. Visualization ------------- If you want to quickly visualize the outputs of the sparse or dense reconstruction pipelines, COLMAP offers you the following possibilities: - The sparse point cloud obtained with the ``mapper`` can be visualized via the COLMAP GUI by importing the model files: choose ``File > Import Model`` and select the folder containing the sparse model files (``cameras.txt``, ``images.txt``, ``points3D.txt``, etc.). - The dense point cloud obtained with the ``stereo_fusion`` can be visualized via the COLMAP GUI by importing ``fused.ply``: choose ``File > Import Model from...`` and then select the file ``fused.ply``. - The dense mesh model ``meshed-*.ply`` obtained with the ``poisson_mesher`` or the ``delaunay_mesher`` can currently not be visualized with COLMAP, instead you can use an external viewer, such as Meshlab. Use the ``mesh_simplifier`` command to reduce the mesh size for faster visualization or downstream processing. Use the ``mesh_texturer`` command to produce a textured mesh with a texture atlas that can be visualized in Meshlab or other 3D viewers. colmap-4.2.0/doc/colmap.1000066400000000000000000000021021524536416500151000ustar00rootroot00000000000000.TH colmap 1 "January 4 2018" .SH NAME colmap \- Structure-from-Motion and Multi-View Stereo .SH SYNOPSIS .B colmap .RI "[command] [options]" .SH DESCRIPTION This manual page documents briefly the .B colmap command. .PP COLMAP is a general-purpose Structure-from-Motion (SfM) and Multi-View Stereo (MVS) pipeline with a graphical and command-line interface. It offers a wide range of features for reconstruction of ordered and unordered image collections. .SH OPTIONS This program offers a graphical and command-line interface. Each command offers summary of all available options. .TP .B help [ \-h, \-\-help ] Show summary of all options. .TP .B gui Start graphical interface. .TP .B gui \-h [ \-\-help ] Show summary of graphical interface options. .TP .B feature_extractor \-h [ \-\-help ] Show summary of feature extractor options. .TP .B feature_extractor \-\-image_path IMAGES \-\-database_path DATABASE Extract features for images in the given folder and store them in the database. .br .TP .B ... .br .SH ONLINE DOCUMENTATION The program is documented at https://colmap.github.io/ colmap-4.2.0/doc/concepts.rst000077500000000000000000000050421524536416500161240ustar00rootroot00000000000000.. _concepts: Key Concepts ============ Starting from COLMAP 3.12, the concepts of rigs and frames have been introduced to enable a principled modeling of multi-sensor platforms as well as 360° panorama images. These concepts provide a structured framework to organize sensors and their measurements, enabling more flexible calibration and fusion of diverse data types (e.g., see :ref:`rig-support`). These additions are backward-compatible and do not affect the traditional, default usage of COLMAP for single-camera capture setups. .. _sensors: Sensors and Measurements ------------------------ A **sensor** is a device that captures data about the environment, producing measurements at specific timestamps. The most common sensor type is the camera, which captures images as its measurements. Other examples include IMUs (Inertial Measurement Units), which record acceleration and angular velocity, and GNSS receivers, which provide absolute position data. Currently, COLMAP supports only cameras and their image measurements, though the sensor concept is designed to extend to other types such as IMUs and GNSS for future support of multi-modal data fusion. .. _rigs: Rigs ---- A **rig** models a platform composed of multiple sensors with fixed relative poses, enabling synchronized and consistent multi-sensor data collection. Examples include stereo camera setups, headworn AR/VR devices, and autonomous driving sensor suites. It can also be virtual — for example, a rig modeling multiple virtual cameras arranged to capture overlapping views used to create seamless 360° panoramic images. In COLMAP, each sensor must be uniquely associated with exactly one rig. Each rig has a single reference sensor that defines its origin. For example, in a stereo camera rig, one camera is designated as the reference sensor with an identity ``sensor_from_rig`` pose, while the second camera’s pose is defined relative to this reference. In a single-camera setup, the camera itself serves as the sole reference sensor for its rig. .. _frames: Frames ------ A **frame** represents a rig captured at a single timestamp, containing measurements from one or more sensors within that rig. For example, if a rig consists of three sensors, a frame may include measurements from all three sensors, or only a subset, depending on availability. This concept allows association of multi-sensor data at specific points in time. For instance, in a stereo camera rig recording video, each frame corresponds to a set of two images—one from each camera—captured at the same moment. colmap-4.2.0/doc/conf.py000077500000000000000000000322641524536416500150610ustar00rootroot00000000000000# COLMAP documentation build configuration file, created by # sphinx-quickstart on Wed Jan 28 09:31:25 2015. # # This file is execfile()d with the current directory set to its # containing dir. # # Note that not all possible configuration values are present in this # autogenerated file. # # All configuration values have a default; values that are commented out # serve to show the default. import re import subprocess from sphinx.ext import autodoc def get_git_revision(): try: commit_id = ( subprocess.check_output(["git", "rev-parse", "--short", "HEAD"]) .decode() .strip() ) commit_date = ( subprocess.check_output( ["git", "log", "-1", "--format=%cd", "--date=short"] ) .decode() .strip() ) return f"{commit_id} ({commit_date})" except Exception: return "Unknown" # If extensions (or modules to document with autodoc) are in another directory, # add these directories to sys.path here. If the directory is relative to the # documentation root, use os.path.abspath to make it absolute, like shown here. # sys.path.insert(0, os.path.abspath('.')) # -- General configuration ------------------------------------------------ # If your documentation needs a minimal Sphinx version, state it here. # needs_sphinx = '1.0' # Add any Sphinx extension module names here, as strings. They can be # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom # ones. extensions = [ "sphinx.ext.mathjax", "sphinx.ext.autodoc", "sphinx_design", "sphinx_sitemap", "sphinxext.opengraph", ] # Add any paths that contain templates here, relative to this directory. templates_path = ["_templates"] # The suffix of source filenames. source_suffix = ".rst" # The encoding of source files. # source_encoding = 'utf-8-sig' # The master toctree document. master_doc = "index" # General information about the project. project = "COLMAP" copyright = "2026, COLMAP Team" # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the # built documents. # # The short MAJOR.MINOR.PATCH version. version = "4.2.0" + " | " + get_git_revision() # The full version, including alpha/beta/rc tags. release = version # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. # language = None # There are two options for replacing |today|: either, you set today to some # non-false value, then it is used: # today = '' # Else, today_fmt is used as the format for a strftime call. # today_fmt = '%B %d, %Y' # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. exclude_patterns = ["_build"] # The reST default role (used for this markup: `text`) to use for all # documents. # default_role = None # If true, '()' will be appended to :func: etc. cross-reference text. # add_function_parentheses = True # If true, the current module name will be prepended to all description # unit titles (such as .. function::). # add_module_names = True # If true, sectionauthor and moduleauthor directives will be shown in the # output. They are ignored by default. # show_authors = False # The name of the Pygments (syntax highlighting) style to use. pygments_style = "sphinx" # A list of ignored prefixes for module index sorting. # modindex_common_prefix = [] # If true, keep warnings as "system message" paragraphs in the built documents. # keep_warnings = False # -- Options for HTML output ---------------------------------------------- # The theme to use for HTML and HTML Help pages. See the documentation for # a list of builtin themes. html_theme = "pydata_sphinx_theme" # Theme options are theme-specific and customize the look and feel of a theme # further. For a list of options available for each theme, see the # documentation. html_theme_options = { "logo": { "text": "COLMAP", "image_light": "_static/colmap-logo.svg", "image_dark": "_static/colmap-logo-dark.svg", }, "icon_links": [ { "name": "GitHub", "url": "https://github.com/colmap/colmap", "icon": "fa-brands fa-github", }, { "name": "PyPI", "url": "https://pypi.org/project/pycolmap/", "icon": "fa-brands fa-python", }, { "name": "Docker Hub", "url": "https://hub.docker.com/r/colmap/colmap", "icon": "fa-brands fa-docker", }, ], "navbar_align": "left", # Keep the primary guides and 3D Viewer visible. Collapse the rest into # More. "header_links_before_dropdown": 4, "navigation_with_keys": True, "show_prev_next": True, "pygments_light_style": "default", "pygments_dark_style": "monokai", "footer_start": ["copyright"], "footer_center": ["version"], "footer_end": ["theme-version"], } # Follow the visitor's OS/browser preference for light/dark mode by default. html_context = {"default_mode": "auto"} # -- SEO ----------------------------------------------------------------- # Canonical site URL. Base URL for the sitemap and Open Graph tags, and makes # every page emit a self-referential . (This alone does # not dedupe the hosted legacy// copies; those would each need their # own HTML updated to point here.) html_baseurl = "https://colmap.github.io/" # sphinx-sitemap: the docs are not multi-version/multi-language, so emit plain # page URLs (no {version}/{lang} path segments). sitemap_url_scheme = "{link}" # sphinxext-opengraph: Open Graph / Twitter card metadata + meta description. ogp_site_url = html_baseurl ogp_site_name = "COLMAP" ogp_description_length = 200 ogp_enable_meta_description = True ogp_image = "https://colmap.github.io/_static/og-image.png" ogp_image_alt = "COLMAP — Structure-from-Motion & Multi-View Stereo" ogp_use_first_image = False ogp_custom_meta_tags = [ '', ] # Copy robots.txt (which points crawlers to the sitemap) to the site root. html_extra_path = ["robots.txt"] # The name for this set of Sphinx documents. If None, it defaults to # " v documentation". html_title = "COLMAP" # A shorter title for the navigation bar. Default is the same as html_title. # html_short_title = None # The name of an image file (relative to this directory) to place at the top # of the sidebar. html_logo = "_static/colmap-logo.svg" # The name of an image file (within the static path) to use as favicon of the # docs. html_favicon = "_static/favicon.svg" # Give the landing page a full-width layout by dropping the left sidebar. html_sidebars = {"index": [], "viewer": []} # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, # so a file named "default.css" will overwrite the builtin "default.css". html_static_path = ["_static"] html_css_files = ["custom.css", "landing.css"] html_js_files = ["install_selector.js", "external_links.js"] # Add any extra paths that contain custom files (such as robots.txt or # .htaccess) here, relative to this directory. These files are copied # directly to the root of the documentation. # html_extra_path = [] # If not '', a 'Last updated on:' timestamp is inserted at every page bottom, # using the given strftime format. # html_last_updated_fmt = '%b %d, %Y' # If true, SmartyPants will be used to convert quotes and dashes to # typographically correct entities. # html_use_smartypants = True # Custom sidebar templates, maps document names to template names. # html_sidebars = {} # Additional templates that should be rendered to pages, maps page names to # template names. # html_additional_pages = {} # If false, no module index is generated. # html_domain_indices = True # If false, no index is generated. # html_use_index = True # If true, the index is split into individual pages for each letter. # html_split_index = False # If true, links to the reST sources are added to the pages. # html_show_sourcelink = True # If true, "Created using Sphinx" is shown in the HTML footer. Default is True. # html_show_sphinx = True # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. # html_show_copyright = True # If true, an OpenSearch description file will be output, and all pages will # contain a tag referring to it. The value of this option must be the # base URL from which the finished HTML is served. # html_use_opensearch = '' # This is the file name suffix for HTML files (e.g. ".xhtml"). # html_file_suffix = None # Output file base name for HTML help builder. htmlhelp_basename = "COLMAPdoc" # -- Options for LaTeX output --------------------------------------------- latex_elements = { # The paper size ('letterpaper' or 'a4paper'). # 'papersize': 'a4paper', # The font size ('10pt', '11pt' or '12pt'). # 'pointsize': '10pt', # Additional stuff for the LaTeX preamble. # 'preamble': '', } # Grouping the document tree into LaTeX files. List of tuples # (source start file, target name, title, # author, documentclass [howto, manual, or own class]). latex_documents = [ ( "index", "COLMAP.tex", "COLMAP Documentation", "Johannes L. Schoenberger", "manual", ), ] # The name of an image file (relative to this directory) to place at the top of # the title page. # latex_logo = None # For "manual" documents, if this is true, then toplevel headings are parts, # not chapters. # latex_use_parts = False # If true, show page references after internal links. latex_show_pagerefs = True # If true, show URL addresses after external links. latex_show_urls = "footnote" # Documents to append as an appendix to all manuals. # latex_appendices = [] # If false, no module index is generated. latex_domain_indices = False # -- Options for manual page output --------------------------------------- # One entry per manual page. List of tuples # (source start file, name, description, authors, manual section). man_pages = [ ( "index", "colmap", "COLMAP Documentation", ["Johannes L. Schoenberger"], 1, ) ] # If true, show URL addresses after external links. # man_show_urls = False # -- Options for Texinfo output ------------------------------------------- # Grouping the document tree into Texinfo files. List of tuples # (source start file, target name, title, author, # dir menu entry, description, category) texinfo_documents = [ ( "index", "COLMAP", "COLMAP Documentation", "Johannes L. Schoenberger", "COLMAP", "Structure-from-Motion and Multi-View Stereo.", "Miscellaneous", ), ] # Documents to append as an appendix to all manuals. # texinfo_appendices = [] # If false, no module index is generated. # texinfo_domain_indices = True # How to display URL addresses: 'footnote', 'no', or 'inline'. # texinfo_show_urls = 'footnote' # If true, do not generate a @detailmenu in the "Top" node's menu. # texinfo_no_detailmenu = False # Configure how Python API docs are displayed. autoclass_content = "both" autodoc_member_order = "bysource" autodoc_typehints = "both" python_maximum_signature_line_length = 120 autodoc_use_legacy_class_based = True def sort_members( self, documenters: list[tuple[autodoc.Documenter, bool]], order: str ) -> list[tuple[autodoc.Documenter, bool]]: """Order the members by their definition order.""" class_names = list(self.object.__dict__) def keyfunc(entry: tuple[autodoc.Documenter, bool]) -> int: name = entry[0].name.split("::")[1].split(".")[1] if name in class_names: return class_names.index(name) else: return len(class_names) documenters.sort(key=keyfunc) return documenters # autodoc_member_order=bysource does not work for C++-defined classes since they # cannot be introspected and do not have an __all__ list. Instead, # we extract the definition order from object.__dict__. autodoc.ClassDocumenter.sort_members = sort_members def process_doc(app, what, name, obj, options, lines): if not lines: return has_overload = lines[0] == "Overloaded function." for i in range(len(lines)): lines[i] = lines[i].replace("pycolmap._core", "pycolmap") if has_overload and re.search(r"^\d+\. ", lines[i]): index, signature = lines[i].split(". ", 1) signature = "``" + signature.replace("->", "→") + "``" lines[i] = ". ".join([index, signature]) def process_sig(app, what, name, obj, options, signature, return_annotation): if signature is None: return None, return_annotation signature = signature.replace("pycolmap._core", "pycolmap") if isinstance(return_annotation, str): return_annotation = return_annotation.replace( "pycolmap._core", "pycolmap" ) return signature, return_annotation def setup(app): # Remap types from the C++ module pycolmap._core to the Python namespace. app.connect("autodoc-process-docstring", process_doc) app.connect("autodoc-process-signature", process_sig) colmap-4.2.0/doc/contribution.rst000066400000000000000000000016731524536416500170300ustar00rootroot00000000000000Contribution ============ Contributions (bug reports, bug fixes, improvements, etc.) are very welcome and should be submitted in the form of new issues and/or pull requests on GitHub. Please, adhere to the Google coding style guide:: https://google.github.io/styleguide/cppguide.html by using the provided ".clang-format" file. Document functions, methods, classes, etc. with inline documentation strings describing the API, using the following format:: // Short description. // // Longer description with a few sentences and multiple lines. // // @param parameter1 Description for parameter 1. // @param parameter2 Description for parameter 2. // // @return Description of optional return value. Add unit tests for all newly added code and make sure that algorithmic "improvements" generalize and actually improve the results of the pipeline on a variety of datasets. colmap-4.2.0/doc/database.rst000077500000000000000000000133171524536416500160560ustar00rootroot00000000000000.. _database-format: Database Format =============== COLMAP stores all extracted information in a single SQLite database file. The database can be accessed with the database management toolkit in the COLMAP GUI, the provided C++ database API (see ``src/colmap/scene/database.h``), or using Python with pycolmap. The database contains the following tables: - rigs - cameras - frames - images - keypoints - descriptors - matches - two_view_geometries To initialize an empty SQLite database file with the required schema, you can either create a new project in the GUI or run the ``colmap database_creator`` command. Rigs and Sensors ---------------- The relation between rigs and sensors (cameras, etc.) is 1-to-N with one sensor being chosen as the reference sensor to define the origin of the rig. Each sensor must only be part of one rig. Rigs and Frames --------------- The relation between rigs and frames is 1-to-N, where a frame defines a specific instance of the rig with all or a subset of sensors exposed at the same time. Cameras and Images ------------------ The relation between cameras and images is 1-to-N. This has important implications for Structure-from-Motion, since one camera shares the same intrinsic parameters (focal length, principal point, distortion, etc.), while every image has separate extrinsic parameters (orientation and location). The intrinsic parameters of cameras are stored as contiguous binary blobs in ``float64``, ordered as specified in ``src/colmap/sensor/models.h``. COLMAP only uses cameras that are referenced by images, all other cameras are ignored. The ``name`` column in the images table is the unique relative path in the image folder. As such, the database file and image folder can be moved to different locations, as long as the relative folder structure is preserved. When manually inserting images and cameras into the database, make sure that all identifiers are positive and non-zero, i.e. ``image_id > 0`` and ``camera_id > 0``. Keypoints and Descriptors ------------------------- The detected keypoints are stored as row-major ``float32`` binary blobs, where the first two columns are the X and Y locations in the image, respectively. COLMAP uses the convention that the upper left image corner has coordinate ``(0, 0)`` and the center of the upper left most pixel has coordinate ``(0.5, 0.5)``. If the keypoints have 4 columns, then the feature geometry is a similarity and the third column is the scale and the fourth column the orientation of the feature (according to SIFT conventions). If the keypoints have 6 columns, then the feature geometry is an affinity and the last 4 columns encode its affine shape (see ``src/colmap/feature/types.h`` for details). The extracted descriptors are stored as row-major binary blobs, where each row describes the feature appearance of the corresponding entry in the keypoints table. The data type and dimensionality depend on the feature extractor: - **SIFT**: ``uint8`` descriptors with 128 dimensions (128 bytes per feature). - **ALIKED**: ``float32`` descriptors with 128 dimensions (512 bytes per feature). - **LOMA_B**: ``float32`` descriptors with 256 dimensions (1024 bytes per feature). - **LOMA_B128**: ``float32`` descriptors with 128 dimensions (512 bytes per feature). The ``cols`` column in the descriptors table specifies the number of bytes per descriptor row. For ``uint8`` descriptors, this equals the descriptor dimension. For ``float32`` descriptors, this equals ``4 * dimension``. In both tables, the ``rows`` table specifies the number of detected features per image, while ``rows=0`` means that an image has no features. For feature matching and geometric verification, every image must have a corresponding keypoints and descriptors entry. Note that only vocabulary tree matching with fast spatial verification requires meaningful values for the local feature geometry, i.e., only X and Y must be provided and the other keypoint columns can be set to zero. The rest of the reconstruction pipeline only uses the keypoint locations. Matches and two-view geometries ------------------------------- Feature matching stores its output in the ``matches`` table and geometric verification in the ``two_view_geometries`` table. COLMAP only uses the data in ``two_view_geometries`` for reconstruction. Every entry in the two tables stores the feature matches between two unique images, where the ``pair_id`` is the row-major, linear index in the upper-triangular match matrix, generated as follows:: def image_ids_to_pair_id(image_id1, image_id2): if image_id1 > image_id2: return 2147483647 * image_id2 + image_id1 else: return 2147483647 * image_id1 + image_id2 and image identifiers can be uniquely determined from the ``pair_id`` as:: def pair_id_to_image_ids(pair_id): image_id2 = pair_id % 2147483647 image_id1 = (pair_id - image_id2) / 2147483647 return image_id1, image_id2 The ``pair_id`` enables efficient database queries, as the matches tables may contain several hundred millions of entries. This scheme limits the maximum number of images in a database to 2147483647 (maximum value of signed 32-bit integers), i.e. ``image_id`` must be smaller than 2147483647. The binary blobs in the matches tables are row-major ``uint32`` matrices, where the left column are zero-based indices into the features of ``image_id1`` and the second column into the features of ``image_id2``. The column ``cols`` must be 2 and the ``rows`` column specifies the number of feature matches. The F, E, H blobs in the ``two_view_geometries`` table are stored as 3x3 matrices in row-major ``float64`` format. The meaning of the ``config`` values are documented in the ``src/colmap/estimators/two_view_geometry.h`` source file. colmap-4.2.0/doc/datasets.rst000066400000000000000000000021461524536416500161150ustar00rootroot00000000000000.. _datasets: Datasets ======== A number of different datasets are available for download at: https://demuc.de/colmap/datasets/ - **Gerrard Hall**: 100 high-resolution images of the "Gerrard" hall at UNC Chapel Hill, which is the building right next to the "South" building. The images are taken with the same camera but different focus using a wide-angle lens. - **Graham Hall**: 1273 high-resolution images of the interior and exterior of "Graham" memorial hall at UNC Chapel Hill. The images are taken with the same camera but different focus using a wide-angle lens. - **Person Hall**: 330 high-resolution images of the "Person" hall at UNC Chapel Hill. The images are taken with the same camera using a wide-angle lens. - **South Building**: 128 images of the "South" building at UNC Chapel Hill. The images are taken with the same camera, kindly provided by Christopher Zach. A number of sample reconstructions produced by COLMAP can be viewed here: **Sparse reconstructions**: - https://youtu.be/PmXqdfBQxfQ - https://youtu.be/DIv1aGKqSIk **Dense reconstructions**: - https://youtu.be/11awtGWSqQU colmap-4.2.0/doc/faq.rst000066400000000000000000001224271524536416500150610ustar00rootroot00000000000000Frequently Asked Questions ========================== Adjusting the options for different reconstruction scenarios and output quality ------------------------------------------------------------------------------- COLMAP provides many options that can be tuned for different reconstruction scenarios and to trade off accuracy and completeness versus efficiency. The default options are set for medium to high quality reconstruction of unstructured input data. There are several presets for different scenarios and quality levels, which can be set in the GUI as ``Extras > Set options for ...``. To use these presets from the command-line, you can save the current set of options as ``File > Save project`` after choosing the presets. The resulting project file can be opened with a text editor to view the different options. Alternatively, you can generate the project file also from the command-line by running ``colmap project_generator``. Extending COLMAP ---------------- If you need to simply analyze the produced sparse or dense reconstructions from COLMAP, you can load the sparse models using pycolmap in Python or the scripts in ``scripts/matlab`` for Matlab. If you want to write a C/C++ executable that builds on top of COLMAP, there are two possible approaches. First, the COLMAP headers and library are installed to the ``CMAKE_INSTALL_PREFIX`` by default. Compiling against COLMAP as a library is described :ref:`here `. Alternatively, you can start from the ``src/colmap/tools/example.cc`` code template and implement the desired functionality directly as a new binary within COLMAP. Choosing between SIFT, ALIKED, and LoMa features ------------------------------------------------ COLMAP supports three feature extraction algorithms: SIFT (default), ALIKED, and LoMa (the latter two require ONNX support). Here are some guidelines for choosing between them: - **SIFT** is the most widely tested and robust choice. It works well for scenarios with moderate to high view overlap, sufficient scene texture, and captured under similar illumination conditions. It supports both GPU and CPU extraction. - **ALIKED** is a learned feature extractor that can produce more repeatable features in some cases, particularly for scenes with limited view overlap, little scene texture, and drastic illumination changes. It requires ONNX Runtime at build time (``-DONNX_ENABLED=ON``). - **LoMa** is a learned feature extractor targeting the same difficult scenarios as ALIKED, paired with dedicated matchers that trade inference cost for matching quality. It also requires ONNX Runtime at build time. SIFT and ALIKED support brute-force matching as well as LightGlue neural network-based matching. LightGlue typically produces higher inlier ratios, especially for image pairs with large viewpoint or illumination changes, but requires ONNX support. LoMa descriptors are matched either brute-force or with one of the dedicated LoMa matchers. See :ref:`Feature Extraction and Matching ` for details on available options. Do not mix different feature types (e.g., SIFT and ALIKED) in the same database, as the descriptors are incompatible. .. _faq-choosing-camera-model: Choosing the right camera model ------------------------------- COLMAP supports many camera models with varying numbers of parameters (see :doc:`cameras` for the full list). Choosing the right model depends on your lens type and reconstruction requirements: - **SIMPLE_RADIAL** (default): A good starting point for most standard cameras. Models a single focal length, principal point, and one radial distortion parameter. - **PINHOLE**: Use if your images have negligible lens distortion (e.g., already undistorted images or high-quality industrial lenses). - **OPENCV**: A good choice for wider-angle lenses with moderate distortion. Models 2 focal lengths, principal point, and 4 distortion parameters (2 radial + 2 tangential). - **SIMPLE_RADIAL_FISHEYE** or **OPENCV_FISHEYE**: Use for fisheye lenses with a field of view significantly larger than 120 degrees. - **FULL_OPENCV**: Use only when you have many images sharing intrinsics and need to model complex distortion patterns. With 12 parameters, this model requires a large number of observations to converge reliably. As a rule of thumb, use the simplest model that adequately describes your lens. Overly complex models with many parameters can lead to degenerate or overfitted calibration, especially when few images share intrinsics. If in doubt, start with ``SIMPLE_RADIAL`` and inspect the reprojection errors in the model statistics. Using calibration from OpenCV, Kalibr, or other tools ----------------------------------------------------- If you already calibrated your camera with an external tool such as OpenCV or Kalibr, you can reuse those intrinsics in COLMAP (see :ref:`Fix intrinsics ` to keep them constant during reconstruction). Two conventions have to be matched first. **Pixel coordinate convention.** COLMAP places the origin at the top-left *corner* of the image, so the center of the top-left pixel is at ``(0.5, 0.5)`` and a centered principal point is ``(width / 2, height / 2)``. OpenCV and Kalibr place integer coordinates at pixel *centers*, so their centered principal point is ``((width - 1) / 2, (height - 1) / 2)``. To convert a principal point from OpenCV/Kalibr to COLMAP, add ``0.5`` to both ``cx`` and ``cy``:: cx_colmap = cx_opencv + 0.5 cy_colmap = cy_opencv + 0.5 For example, an OpenCV calibration of an 800×600 image with a centered principal point ``(399.5, 299.5)`` becomes ``(400.0, 300.0)`` in COLMAP. The focal lengths ``fx``, ``fy`` and the distortion coefficients are unaffected by this shift. **Distortion parameter order.** COLMAP's ``OPENCV`` model uses the same ``k1, k2, p1, p2`` distortion parameters as OpenCV, and ``FULL_OPENCV`` additionally uses ``k3, k4, k5, k6``, in that order (see :doc:`cameras`). A camera line in ``cameras.txt`` for the example above is therefore:: 1 OPENCV 800 600 fx fy 400.0 300.0 k1 k2 p1 p2 Choosing between incremental, global, and hierarchical SfM ---------------------------------------------------------- COLMAP offers three SfM pipelines: - **Incremental mapper** (``mapper``, default): Reconstructs the scene by incrementally adding one image at a time. This is the most robust and well-tested pipeline, but can become slow for large image collections, where repeated bundle adjustment is often the bottleneck. This can be accelerated substantially with the GPU-based Caspar backend (see :ref:`Speedup bundle adjustment `). - **Global mapper** (``global_mapper``): Solves for all camera poses simultaneously using rotation averaging and global positioning. This can be faster for large datasets with good matching graphs, but may be less robust to outliers in the matching. The global mapper depends on good focal length priors. If reliable intrinsics are not available, run ``view_graph_calibrator`` before ``global_mapper`` to estimate them from the view graph (optional but recommended to improve the quality of global SfM). Note that ``view_graph_calibrator`` modifies the database in-place, so it is recommended to work on a copy. - **Hierarchical mapper** (``hierarchical_mapper``): Partitions the scene into overlapping sub-models and reconstructs each independently, then merges them. This is useful for very large-scale datasets where the incremental approach becomes too slow but is usually less robust than the other two pipelines. All three can also be selected via the ``automatic_reconstructor`` using ``--mapper INCREMENTAL``, ``--mapper GLOBAL``, or ``--mapper HIERARCHICAL``. Reconstruction with pose priors (GPS) ------------------------------------- If your images have GPS information in their EXIF metadata, COLMAP automatically extracts and stores it as pose priors in the database during feature extraction. These priors can then be used during reconstruction with the ``pose_prior_mapper``:: colmap feature_extractor \ --database_path $PROJECT_PATH/database.db \ --image_path $PROJECT_PATH/images colmap exhaustive_matcher \ --database_path $PROJECT_PATH/database.db colmap pose_prior_mapper \ --database_path $PROJECT_PATH/database.db \ --image_path $PROJECT_PATH/images \ --output_path $PROJECT_PATH/sparse The ``pose_prior_mapper`` is essentially the incremental mapper with prior position constraints enabled. You can override the priors covariance (uncertainty) using ``--overwrite_priors_covariance``. The new covariance will be built based on the values of ``--prior_position_std_x``, ``--prior_position_std_y``, and ``--prior_position_std_z`` (default: 1.0 meter each). For geo-registration of an already reconstructed model (without using priors during mapping), see the `Geo-registration`_ section. .. _faq-share-intrinsics: Share intrinsics ---------------- COLMAP supports shared intrinsics for arbitrary groups of images and camera models. Images share the same intrinsics, if they refer to the same camera, as specified by the ``camera_id`` property in the database. You can add new cameras and set shared intrinsics in the database management tool. Please, refer to :ref:`Database Management ` for more information. Set known camera intrinsics --------------------------- If the camera calibration is known a priori, the recommended way to provide it is during feature extraction using the ``ImageReader`` options:: colmap feature_extractor \ --database_path $PROJECT_PATH/database.db \ --image_path $PROJECT_PATH/images \ --ImageReader.single_camera 1 \ --ImageReader.camera_model OPENCV \ --ImageReader.camera_params "fx,fy,cx,cy,k1,k2,p1,p2" The parameters must be provided as a comma-separated list in the order defined by the chosen camera model (see :doc:`cameras`). In the GUI, the equivalent settings can be found under ``Processing > Feature extraction > Custom parameters``. Use ``--ImageReader.single_camera 1`` if all images were captured by the same physical camera with identical settings, so that they share one camera in the database (see `Share intrinsics`_). To modify the intrinsics of an existing database, do not edit the SQLite tables by hand (the parameters are stored as binary blobs of doubles), but use pycolmap's database API instead:: import pycolmap with pycolmap.Database.open("path/to/database.db") as db: camera = db.read_camera(1) camera.params = [fx, fy, cx, cy, k1, k2, p1, p2] camera.has_prior_focal_length = True db.update_camera(camera) Note that the provided parameters are still refined during bundle adjustment by default. To keep them fixed during the reconstruction, see :ref:`Fix intrinsics `. .. _faq-fix-intrinsics: Fix intrinsics -------------- By default, COLMAP tries to refine the intrinsic camera parameters (except principal point) automatically during the reconstruction. Usually, if there are enough images in the dataset and you share the intrinsics between multiple images, the estimated intrinsic camera parameters in SfM should be better than parameters manually obtained with a calibration pattern. However, sometimes COLMAP's self-calibration routine might converge in degenerate parameters, especially in case of the more complex camera models with many distortion parameters. If you know the calibration parameters a priori, you can fix different parameter groups during the reconstruction. Choose ``Reconstruction > Reconstruction options > Bundle Adj. > refine_*`` and check which parameter group to refine or to keep constant. Even if you keep the parameters constant during the reconstruction, you can refine the parameters in a final global bundle adjustment by setting ``Reconstruction > Bundle adj. options > refine_*`` and then running ``Reconstruction > Bundle adjustment``. Principal point refinement -------------------------- By default, COLMAP keeps the principal point constant during the reconstruction, as principal point estimation is an ill-posed problem in general. Once all images are reconstructed, the problem is most often constrained enough that you can try to refine the principal point in global bundle adjustment, especially when sharing intrinsic parameters between multiple images. Please, refer to :ref:`Fix intrinsics ` for more information. Increase number of matches / sparse 3D points --------------------------------------------- To increase the number of matches, you should use the more discriminative DSP-SIFT features instead of plain SIFT and also estimate the affine feature shape using the options: ``--SiftExtraction.estimate_affine_shape=true`` and ``--SiftExtraction.domain_size_pooling=true``. In addition, you should enable guided feature matching using: ``--FeatureMatching.guided_matching=true``. By default, COLMAP ignores two-view feature tracks in triangulation, resulting in fewer 3D points than possible. Triangulation of two-view tracks can in rare cases improve the stability of sparse image collections by providing additional constraints in bundle adjustment. To also triangulate two-view tracks, unselect the option ``Reconstruction > Reconstruction options > Triangulation > ignore_two_view_tracks``. If your images are taken from far distance with respect to the scene, you can try to reduce the minimum triangulation angle. Reconstruct sparse/dense model from known camera poses ------------------------------------------------------ If the camera poses are known and you want to reconstruct a sparse or dense model of the scene, you must first manually construct a sparse model by creating a ``cameras.txt``, ``points3D.txt``, and ``images.txt`` under a new folder:: +── path/to/manually/created/sparse/model │   +── cameras.txt │   +── images.txt │   +── points3D.txt The ``points3D.txt`` file should be empty while every other line in the ``images.txt`` should also be empty, since the sparse features are computed, as described below. You can refer to :ref:`this article ` for more information about the structure of a sparse model. Example of images.txt:: 1 0.695104 0.718385 -0.024566 0.012285 -0.046895 0.005253 -0.199664 1 image0001.png # Make sure every other line is left empty 2 0.696445 0.717090 -0.023185 0.014441 -0.041213 0.001928 -0.134851 2 image0002.png 3 0.697457 0.715925 -0.025383 0.018967 -0.054056 0.008579 -0.378221 1 image0003.png 4 0.698777 0.714625 -0.023996 0.021129 -0.048184 0.004529 -0.313427 2 image0004.png Each image above must have the same ``image_id`` (first column) as in the database (next step). This database can be inspected either in the GUI (under ``Database management > Processing``), or, one can create a reconstruction with colmap and later export it as text in order to see the images.txt file it creates. To reconstruct a sparse map, you first have to recompute features from the images of the known camera poses as follows:: colmap feature_extractor \ --database_path $PROJECT_PATH/database.db \ --image_path $PROJECT_PATH/images If your known camera intrinsics have large distortion coefficients, you should now manually copy the parameters from your ``cameras.txt`` to the database, such that the matcher can leverage the intrinsics. Modifying the database is possible in many ways, but an easy option is to use pycolmap's database API. Otherwise, you can skip this step and simply continue as follows:: colmap exhaustive_matcher \ # or alternatively any other matcher --database_path $PROJECT_PATH/database.db colmap point_triangulator \ --database_path $PROJECT_PATH/database.db \ --image_path $PROJECT_PATH/images --input_path path/to/manually/created/sparse/model \ --output_path path/to/triangulated/sparse/model Note that the sparse reconstruction step is not necessary in order to compute a dense model from known camera poses. Assuming you computed a sparse model from the known camera poses, you can compute a dense model as follows:: colmap image_undistorter \ --image_path $PROJECT_PATH/images \ --input_path path/to/triangulated/sparse/model \ --output_path path/to/dense/workspace colmap patch_match_stereo \ --workspace_path path/to/dense/workspace colmap stereo_fusion \ --workspace_path path/to/dense/workspace \ --output_path path/to/dense/workspace/fused.ply Alternatively, you can also produce a dense model without a sparse model as:: colmap image_undistorter \ --image_path $PROJECT_PATH/images \ --input_path path/to/manually/created/sparse/model \ --output_path path/to/dense/workspace Since the sparse point cloud is used to automatically select neighboring images during the dense stereo stage, you have to manually specify the source images, as described :ref:`here `. The dense stereo stage now also requires a manual specification of the depth range. Finally, in this case, fusion will fail to successfully match points if min_num_pixels is left at the default (greater than 1). So also set that parameter, as below:: colmap patch_match_stereo \ --workspace_path path/to/dense/workspace \ --PatchMatchStereo.depth_min $MIN_DEPTH \ --PatchMatchStereo.depth_max $MAX_DEPTH colmap stereo_fusion \ --workspace_path path/to/dense/workspace \ --StereoFusion.min_num_pixels 1 \ --output_path path/to/dense/workspace/fused.ply .. _faq-merge-models: Merge disconnected models ------------------------- Sometimes COLMAP fails to reconstruct all images into the same model and hence produces multiple sub-models. If those sub-models have common registered images, they can be merged into a single model as post-processing step:: colmap model_merger \ --input_path1 /path/to/sub-model1 \ --input_path2 /path/to/sub-model2 \ --output_path /path/to/merged-model To improve the quality of the alignment between the two sub-models, it is recommended to run another global bundle adjustment after the merge:: colmap bundle_adjuster \ --input_path /path/to/merged-model \ --output_path /path/to/refined-merged-model Geo-registration ---------------- Geo-registration of models is possible by providing the 3D locations for the camera centers of a subset or all registered images. The 3D similarity transformation between the reconstructed model and the target coordinate frame of the geo-registration is determined from these correspondences. The geo-registered 3D coordinates can either be extracted from the database (tvec_prior field) or from a user specified text file. For text-files, the geo-registered 3D coordinates of the camera centers for images must be specified with the following format:: image_name1.jpg X1 Y1 Z1 image_name2.jpg X2 Y2 Z2 image_name3.jpg X3 Y3 Z3 ... The coordinates can be either GPS-based (lat/lon/alt) or cartesian-based (x/y/z). In case of GPS coordinates, a conversion will be performed to turn those into cartesian coordinates. The conversion can be done from GPS to ECEF (Earth-Centered-Earth-Fixed) or to ENU (East-North-Up) coordinates. If ENU coordinates are used, the first image GPS coordinates will define the origin of the ENU frame. It is also possible to use ECEF coordinates for alignment and then rotate the aligned reconstruction into the ENU plane. Note that at least 3 images must be specified to estimate a 3D similarity transformation. Then, the model can be geo-registered using:: colmap model_aligner \ --input_path /path/to/model \ --output_path /path/to/geo-registered-model \ --ref_images_path /path/to/text-file (or --database_path /path/to/database.db) \ --ref_is_gps 1 \ --alignment_type ecef \ --alignment_max_error 3.0 (where 3.0 is the error threshold to be used in RANSAC) A 3D similarity transformation will be estimated with a RANSAC estimator to be robust to potential outliers in the data. It is required to provide the error threshold to be used in the RANSAC estimator. Manhattan world alignment ------------------------- COLMAP has functionality to align the coordinate axes of a reconstruction using a Manhattan world assumption, i.e. COLMAP can automatically determine the gravity axis and the major horizontal axis of the Manhattan world through vanishing point detection in the images. Please, refer to the ``model_orientation_aligner`` for more details. Mask image regions ------------------ COLMAP supports masking of keypoints during feature extraction two different ways: 1. Passing ``mask_path`` to a folder with image masks. For a given image, the corresponding mask must have the same sub-path below this root as the image has below ``image_path``. The filename must be equal, aside from the added extension ``.png``. For example, for an image ``image_path/abc/012.jpg``, the mask would be ``mask_path/abc/012.jpg.png``. 2. Passing ``camera_mask_path`` to a single mask image. This single mask is applied to all images. In both cases no features will be extracted in regions, where the mask image is black (pixel intensity value 0 in grayscale). Image orientation and EXIF -------------------------- COLMAP automatically reads the EXIF orientation tag from images during feature extraction. The orientation is converted to a gravity direction vector in sensor coordinates, which is stored as part of the pose prior in the database. This gravity information is used during feature extraction and matching to improve robustness against image rotation. This is crucial for feature extractors/matchers with limited orientation invariance, such as ALIKED, LightGlue, and LoMa. Register/localize new images into an existing reconstruction ------------------------------------------------------------ If you have an existing reconstruction of images and want to register/localize new images within this reconstruction, you can follow these steps:: colmap feature_extractor \ --database_path $PROJECT_PATH/database.db \ --image_path $PROJECT_PATH/images \ --image_list_path /path/to/image-list.txt colmap vocab_tree_matcher \ --database_path $PROJECT_PATH/database.db \ --VocabTreeMatching.match_list_path /path/to/image-list.txt colmap image_registrator \ --database_path $PROJECT_PATH/database.db \ --input_path /path/to/existing-model \ --output_path /path/to/model-with-new-images colmap bundle_adjuster \ --input_path /path/to/model-with-new-images \ --output_path /path/to/model-with-new-images Note that this first extracts features for the new images, then matches them to the existing images in the database, and finally registers them into the model. The image list text file contains a list of images to extract and match, specified as one image file name per line. The bundle adjustment is optional. If you need a more accurate image registration with triangulation, then you should restart or continue the reconstruction process rather than just registering the images to the model. Instead of running the ``image_registrator``, you should run the ``mapper`` to continue the reconstruction process from the existing model:: colmap mapper \ --database_path $PROJECT_PATH/database.db \ --image_path $PROJECT_PATH/images \ --input_path /path/to/existing-model \ --output_path /path/to/model-with-new-images Or, alternatively, you can start the reconstruction from scratch:: colmap mapper \ --database_path $PROJECT_PATH/database.db \ --image_path $PROJECT_PATH/images \ --output_path /path/to/model-with-new-images Note that dense reconstruction must be re-run from scratch after running the ``mapper`` or the ``bundle_adjuster``, as the coordinate frame of the model can change during these steps. Available functionality without GPU/CUDA ---------------------------------------- If you do not have a CUDA-enabled GPU but some other GPU, you can use all COLMAP functionality except the dense reconstruction part. However, you can use external dense reconstruction software as an alternative, as described in the :ref:`Tutorial `. If you have a GPU with low compute power or you want to execute COLMAP on a machine without an attached display and without CUDA support, you can run all steps on the CPU by specifying the appropriate options (e.g., ``--FeatureExtraction.use_gpu=false`` for the feature extraction step). But note that this might result in a significant slow-down of the reconstruction pipeline. Please, also note that feature extraction on the CPU can consume excessive RAM for large images in the default settings, which might require manually reducing the maximum image size using ``--FeatureExtraction.max_image_size`` and/or setting ``--SiftExtraction.first_octave 0`` or by manually limiting the number of threads using ``--FeatureExtraction.num_threads``. Multi-GPU support in feature extraction/matching ------------------------------------------------ You can run feature extraction/matching on multiple GPUs by specifying multiple indices for CUDA-enabled GPUs, e.g., ``--FeatureExtraction.gpu_index=0,1,2,3`` and ``--FeatureMatching.gpu_index=0,1,2,3`` runs the feature extraction/matching on 4 GPUs in parallel. Note that you can only run one thread per GPU and this typically also gives the best performance. By default, COLMAP runs one feature extraction/matching thread per CUDA-enabled GPU and this usually gives the best performance as compared to running multiple threads on the same GPU. Feature matching fails due to illegal memory access --------------------------------------------------- If you encounter the following error message:: MultiplyDescriptor: an illegal memory access was encountered or the following: ERROR: Feature matching failed. This probably caused by insufficient GPU memory. Consider reducing the maximum number of features. during feature matching, your GPU runs out of memory. Try decreasing the option ``--FeatureMatching.max_num_matches`` until the error disappears. Note that this might lead to inferior feature matching results, since the lower-scale input features will be clamped in order to fit them into GPU memory. Alternatively, you could change to CPU-based feature matching, but this can become very slow, or better you buy a GPU with more memory. The maximum required GPU memory can be approximately estimated using the following formula: ``4 * num_matches * num_matches + 4 * num_matches * 256`` for SIFT. For example, if you set ``--FeatureMatching.max_num_matches 10000``, the maximum required GPU memory will be around 400MB, which are only allocated if one of your images actually has that many features. .. _speedup-bundle-adjustment: Speedup bundle adjustment ------------------------- The following describes practical ways to reduce bundle adjustment runtime. - **Reduce the problem size** Limit the number of correspondences so that BA solves a smaller problem: - Reduce features by decreasing ``--SiftExtraction.max_image_size`` and/or ``--SiftExtraction.max_num_features``. - Reduce matching pairs (and avoid ``exhaustive_matcher`` when possible) by decreasing ``--SequentialMatching.overlap``, ``--SpatialMatching.max_num_neighbors``, or ``--VocabTreeMatching.num_images``. - Reduce matches by decreasing ``--FeatureMatching.max_num_matches``. - Enable experimental landmark pruning to drop redundant 3D points using ``--Mapper.ba_global_ignore_redundant_points3D 1``. - **Utilize GPU acceleration** Enable GPU-based Ceres solvers for bundle adjustment by setting ``--Mapper.ba_use_gpu 1`` for the ``mapper`` and ``--BundleAdjustmentCeres.use_gpu 1`` for the standalone ``bundle_adjuster``. Several parameters control when and which GPU solver is used: - The GPU solver is activated only when the number of images exceeds ``--BundleAdjustmentCeres.min_num_images_gpu_solver``. - Select between the direct dense, direct sparse, and iterative sparse GPU solvers using ``--BundleAdjustmentCeres.max_num_images_direct_dense_gpu_solver`` and ``--BundleAdjustmentCeres.max_num_images_direct_sparse_gpu_solver`` .. Attention:: COLMAP's official CUDA-enabled binaries are not distributed with ceres[cuda] until Ceres 2.3 is officially released. To use the GPU solvers you must compile Ceres with the CUDA/cuDSS support and link that build to COLMAP. **Note:** Low GPU utilization for the Schur-based sparse solver (cuDSS) can occur when the Schur-complement matrix becomes less sparse (i.e., exhibits more fill-in). Typical causes include: - High image covisibility - Shared camera intrinsics. - **Use the Caspar GPU bundle adjustment backend** COLMAP includes Caspar [caspar]_, an experimental GPU-accelerated bundle adjustment backend that can be one to two orders of magnitude faster than the Ceres CUDA solver for medium- to large-scale problems, leading to drastic speedups especially for the incremental mapper. Caspar requires CUDA and is disabled by default; it must be enabled at build time by configuring COLMAP with ``-DCASPAR_ENABLED=ON``. Caspar is selected through the bundle adjustment ``backend`` option, which accepts ``CERES`` (default) or ``CASPAR``: - Standalone ``bundle_adjuster``: ``--BundleAdjustment.backend CASPAR``. - Incremental ``mapper``: ``--Mapper.ba_local_backend CASPAR`` and/or ``--Mapper.ba_global_backend CASPAR``. The GPU device is selected via ``--Mapper.ba_gpu_index``. The solver behavior and GPU device of the standalone backend can be tuned via the ``--BundleAdjustmentCaspar.*`` options, e.g. ``--BundleAdjustmentCaspar.gpu_index`` (default ``-1`` auto-selects the best CUDA device). .. Attention:: Caspar is experimental and currently supports only the ``SIMPLE_RADIAL`` and ``PINHOLE`` camera models; observations using other camera models are skipped. It does not support pose priors or refining ``sensor_from_rig`` for non-reference rig sensors, and requires ``refine_focal_length`` and ``refine_extra_params`` to be equal. The ``global_mapper`` does not expose a Caspar backend selector. - **Additional practical tips** - Improve initial conditions by tuning observation-filtering parameters so BA receives more inliers and fewer outliers, or by supplying accurate priors (e.g., intrinsics, poses). - Fix or restrict refinement of parameters when possible (e.g., hold intrinsics fixed if they are known) to reduce the number of optimized variables. - Reduce LM iterations or relax convergence tolerances to trade a small amount of accuracy for runtime: ``--Mapper.ba_global_max_num_iterations``, ``--Mapper.ba_global_function_tolerance``. - Reduce the frequency of expensive global BA passes with mapper options: ``--Mapper.ba_global_frames_freq``, ``--Mapper.ba_global_points_freq``, ``--Mapper.ba_global_frames_ratio`` and ``--Mapper.ba_global_points_ratio``. Trading off completeness and accuracy in dense reconstruction ------------------------------------------------------------- If the dense point cloud contains too many outliers and too much noise, try to increase the value of option ``--StereoFusion.min_num_pixels``. If the reconstructed dense surface mesh model using Poisson reconstruction contains no surface or there are too many outlier surfaces, you should reduce the value of option ``--PoissonMeshing.trim`` to decrease the surface area and vice versa to increase it. Also consider to try the reduce the outliers or increase the completeness in the fusion stage, as described above. If the reconstructed dense surface mesh model using Delaunay reconstruction contains too noisy or incomplete surfaces, you should increase the ``--DelaunayMeshing.quality_regularization`` parameter to obtain a smoother surface. If the resolution of the mesh is too coarse, you should reduce the ``--DelaunayMeshing.max_proj_dist`` option to a lower value. Improving dense reconstruction results for weakly textured surfaces ------------------------------------------------------------------- For scenes with weakly textured surfaces it can help to have a high resolution of the input images (``--PatchMatchStereo.max_image_size``) and a large patch window radius (``--PatchMatchStereo.window_radius``). You may also want to reduce the filtering threshold for the photometric consistency cost (``--PatchMatchStereo.filter_min_ncc``). Surface mesh reconstruction --------------------------- COLMAP supports three surface reconstruction algorithms: - **Poisson surface reconstruction** [kazhdan2013]_ typically requires an almost outlier-free input point cloud and often produces bad surfaces in the presence of outliers or large holes in the input data. - **Delaunay triangulation** based meshing is more robust to outliers and in general more scalable to large datasets than the Poisson algorithm, but it usually produces less smooth surfaces. It can be applied to both sparse and dense reconstruction results. - **Advancing front surface reconstruction** [cohen-steiner2004]_ incrementally grows a surface mesh from a Delaunay triangulation of the input points. It supports visibility-based filtering to remove faces that violate free-space constraints and block-wise parallel processing for large-scale scenes. It uses a float32 CGAL kernel for memory efficiency. To increase the smoothness of the surface as a post-processing step, you could use Laplacian smoothing, as e.g. implemented in Meshlab. Note that Poisson and Delaunay meshing can also be combined by first running the Delaunay meshing to robustly filter outliers from the sparse or dense point cloud and then, in the second step, performing Poisson surface reconstruction to obtain a smooth surface. After meshing, the ``mesh_texturer`` command can be used to produce a textured mesh with a texture atlas [waechter2014]_. This assigns each mesh face to the best-view camera image based on projected area and viewing angle, and bakes the texture into an atlas with per-face UV coordinates. The command requires the undistorted workspace produced by ``image_undistorter`` as input. Speedup dense reconstruction ---------------------------- The dense reconstruction can be speeded up in multiple ways: - Put more GPUs in your system as the dense reconstruction can make use of multiple GPUs during the stereo reconstruction step. Put more RAM into your system and increase the ``--PatchMatchStereo.cache_size``, ``--StereoFusion.cache_size`` to the largest possible value in order to speed up the dense fusion step. - Do not perform geometric dense stereo reconstruction ``--PatchMatchStereo.geom_consistency false``. Make sure to also enable ``--PatchMatchStereo.filter true`` in this case. - Reduce the ``--PatchMatchStereo.max_image_size``, ``--StereoFusion.max_image_size`` values to perform dense reconstruction on a maximum image resolution. - Reduce the number of source images per reference image to be considered, as described :ref:`here `. - Increase the patch windows step ``--PatchMatchStereo.window_step`` to 2. - Reduce the patch window radius ``--PatchMatchStereo.window_radius``. - Reduce the number of patch match iterations ``--PatchMatchStereo.num_iterations``. - Reduce the number of sampled views ``--PatchMatchStereo.num_samples``. - To speedup the dense stereo and fusion step for very large reconstructions, you can use CMVS to partition your scene into multiple clusters and to prune redundant images, as described :ref:`here `. Note that apart from upgrading your hardware, the proposed changes might degrade the quality of the dense reconstruction results. When canceling the stereo reconstruction process and restarting it later, the previous progress is not lost and any already processed views will be skipped. .. _faq-dense-memory: Reduce memory usage during dense reconstruction ----------------------------------------------- If you run out of GPU memory during patch match stereo, you can either reduce the maximum image size by setting the option ``--PatchMatchStereo.max_image_size`` or reduce the number of source images in the ``stereo/patch-match.cfg`` file from e.g. ``__auto__, 30`` to ``__auto__, 10``. Note that enabling the ``geom_consistency`` option increases the required GPU memory. If you run out of CPU memory during stereo or fusion, you can reduce the ``--PatchMatchStereo.cache_size`` or ``--StereoFusion.cache_size`` specified in gigabytes or you can reduce ``--PatchMatchStereo.max_image_size`` or ``--StereoFusion.max_image_size``. Note that a too low value might lead to very slow processing and heavy load on the hard disk. For large-scale reconstructions of several thousands of images, you should consider splitting your sparse reconstruction into more manageable clusters of images using e.g. CMVS [furukawa10]_. In addition, CMVS allows to prune redundant images observing the same scene elements. Note that, for this use case, COLMAP's dense reconstruction pipeline also supports the PMVS/CMVS folder structure when executed from the command-line. Please, refer to the workspace folder for example shell scripts. Note that the example shell scripts for PMVS/CMVS are only generated, if the output type is set to PMVS. Since CMVS produces highly overlapping clusters, it is recommended to increase the default value of 100 images per cluster to as high as possible according to your available system resources and speed requirements. To change the number of images using CMVS, you must modify the shell scripts accordingly. For example, ``cmvs pmvs/ 500`` to limit each cluster to 500 images. If you want to use CMVS to prune redundant images but not to cluster the scene, you can simply set this number to a very large value. .. _faq-dense-manual-source: Manual specification of source images during dense reconstruction ----------------------------------------------------------------- You can change the number of source images in the ``stereo/patch-match.cfg`` file from e.g. ``__auto__, 30`` to ``__auto__, 10``. This selects the images with the most visual overlap automatically as source images. You can also use all other images as source images, by specifying ``__all__``. Alternatively, you can manually specify images with their name, for example:: image1.jpg image2.jpg, image3.jpg image2.jpg image1.jpg, image3.jpg image3.jpg image1.jpg, image2.jpg Here, ``image2.jpg`` and ``image3.jpg`` are used as source images for ``image1.jpg``, etc. Multi-GPU support in dense reconstruction ----------------------------------------- You can run dense reconstruction on multiple GPUs by specifying multiple indices for CUDA-enabled GPUs, e.g., ``--PatchMatchStereo.gpu_index=0,1,2,3`` runs the dense reconstruction on 4 GPUs in parallel. You can also run multiple dense reconstruction threads on the same GPU by specifying the same GPU index twice, e.g., ``--PatchMatchStereo.gpu_index=0,0,1,1,2,3``. By default, COLMAP runs one dense reconstruction thread per CUDA-enabled GPU. .. _faq-dense-timeout: Fix GPU freezes and timeouts during dense reconstruction -------------------------------------------------------- The stereo reconstruction pipeline runs on the GPU using CUDA and puts the GPU under heavy load. You might experience a display freeze or even a program crash during the reconstruction. As a solution to this problem, you could use a secondary GPU in your system, that is not connected to your display by setting the GPU indices explicitly (usually index 0 corresponds to the card that the display is attached to). Alternatively, you can increase the GPU timeouts of your system, as detailed in the following. By default, the Windows operating system detects response problems from the GPU, and recovers to a functional desktop by resetting the card and aborting the stereo reconstruction process. The solution is to increase the so-called "Timeout Detection & Recovery" (TDR) delay to a larger value. Please, refer to the `NVIDIA Nsight documentation `_ or to the `Microsoft documentation `_ on how to increase the delay time under Windows. You can increase the delay using the following Windows Registry entries:: [HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\GraphicsDrivers] "TdrLevel"=dword:00000001 "TdrDelay"=dword:00000120 To set the registry entries, execute the following commands using administrator privileges (e.g., in ``cmd.exe`` or ``powershell.exe``):: reg add HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\GraphicsDrivers /v TdrLevel /t REG_DWORD /d 00000001 reg add HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\GraphicsDrivers /v TdrDelay /t REG_DWORD /d 00000120 and restart your machine afterwards to make the changes effective. The X window system under Linux/Unix has a similar feature and detects response problems of the GPU. The easiest solution to avoid timeout problems under the X window system is to shut it down and run the stereo reconstruction from the command-line. Under Ubuntu, you could first stop X using:: sudo service lightdm stop And then run the dense reconstruction code from the command-line:: colmap patch_match_stereo ... Finally, you can restart your desktop environment with the following command:: sudo service lightdm start If the dense reconstruction still crashes after these changes, the reason is probably insufficient GPU memory, as discussed in a separate item in this list. colmap-4.2.0/doc/features.rst000066400000000000000000000170011524536416500161170ustar00rootroot00000000000000.. _features: Feature Extraction and Matching =============================== COLMAP supports multiple feature extraction and matching algorithms. This page describes how to switch between them using the command-line interface or the graphical user interface. Feature Extractor Types ----------------------- The following feature extractor types are available: - ``SIFT``: Scale-Invariant Feature Transform (default). The classic and most widely tested feature extractor. Produces 128-dimensional uint8 descriptors. - ``ALIKED``: A Lighter Keypoint and Descriptor Extractor. A learned feature extractor that produces floating-point descriptors. Requires ONNX support to be enabled at build time (``-DONNX_ENABLED=ON``). - ``LOMA``: A learned feature extractor, introduced in LoMa: Local Feature Matching Revisited, ECCV26, that uses the DeDoDe architecture. Two descriptor variants are available: ``LOMA_B`` (256-dim, frozen DINOv2 features combined with trained convolutional features) and ``LOMA_B128`` (128-dim, more lightweight). Requires ONNX support to be enabled at build time (``-DONNX_ENABLED=ON``). To select a feature extractor type via the command-line:: $ colmap feature_extractor \ --database_path $DATASET_PATH/database.db \ --image_path $DATASET_PATH/images \ --FeatureExtraction.type ALIKED_N16ROT \ --AlikedExtraction.max_num_features 2048 For SIFT (the default), you can omit the type or explicitly set it:: $ colmap feature_extractor \ --database_path $DATASET_PATH/database.db \ --image_path $DATASET_PATH/images \ --FeatureExtraction.type SIFT \ --SiftExtraction.max_num_features 8192 In the GUI, open ``Processing > Feature extraction`` and select the desired tab (SIFT, ALIKED, LoMa, etc.) before clicking Extract. Feature Matcher Types --------------------- The following feature matcher types are available: - ``SIFT_BRUTEFORCE``: Brute-force matching optimized for SIFT descriptors (default). Uses L2 distance with ratio test. - ``ALIKED_BRUTEFORCE``: Brute-force matching for ALIKED descriptors. Uses cosine similarity. Requires ONNX support to be enabled at build time. - ``SIFT_LIGHTGLUE``: Neural network-based matching using the LightGlue model for SIFT descriptors. This typically produces more matches and higher inlier ratios than brute-force matching, especially for challenging image pairs with large viewpoint or illumination changes. Requires ONNX support to be enabled at build time. - ``ALIKED_LIGHTGLUE``: Neural network-based matching using the LightGlue model for ALIKED descriptors. Requires ONNX support to be enabled at build time. - ``LOMA_BRUTEFORCE``: Brute-force matching for either of the LoMa descriptors. Uses cosine similarity. Requires ONNX support to be enabled at build time. - ``LOMA_B``: Dedicated neural network matcher for the 256-dim ``LOMA_B`` descriptor, comparable in size to LightGlue. Requires ONNX support to be enabled at build time. - ``LOMA_R``: Same size as ``LOMA_B``, but trained with rotation augmentation for better robustness to rotated image pairs. Also matches the 256-dim ``LOMA_B`` descriptor. Requires ONNX support to be enabled at build time. - ``LOMA_L``, ``LOMA_G``: Larger dedicated matchers for the 256-dim ``LOMA_B`` descriptor, in increasing order of model size and matching quality. Requires ONNX support to be enabled at build time. - ``LOMA_B128``: Dedicated matcher for the lightweight 128-dim ``LOMA_B128`` descriptor. Requires ONNX support to be enabled at build time. To select a feature matcher type via the command-line:: $ colmap exhaustive_matcher \ --database_path $DATASET_PATH/database.db \ --FeatureMatching.type ALIKED_BRUTEFORCE \ --AlikedMatching.min_cossim 0.85 For SIFT matching (the default):: $ colmap exhaustive_matcher \ --database_path $DATASET_PATH/database.db \ --FeatureMatching.type SIFT_BRUTEFORCE \ --SiftMatching.max_ratio 0.8 In the GUI, open ``Processing > Feature matching``, select any matching tab (Exhaustive, Sequential, etc.), and choose the matcher type from the "Type" dropdown in the shared options section. Compatible Extractor and Matcher Types -------------------------------------- The feature extractor and matcher types should be compatible: - Use ``SIFT`` extraction with ``SIFT_BRUTEFORCE`` or ``SIFT_LIGHTGLUE`` matching. - Use ``ALIKED_*`` extraction with ``ALIKED_BRUTEFORCE`` or ``ALIKED_LIGHTGLUE`` matching. - Use ``LOMA_B`` extraction with ``LOMA_BRUTEFORCE``, ``LOMA_B``, ``LOMA_R``, ``LOMA_L``, or ``LOMA_G`` matching. - Use ``LOMA_B128`` extraction with ``LOMA_BRUTEFORCE`` or ``LOMA_B128`` matching. Mixing incompatible types (e.g., SIFT features with ALIKED matcher, or ``LOMA_B128`` features with the ``LOMA_B`` matcher) will result in a runtime error. Do not mix different feature extractor types (e.g., SIFT and ALIKED) in the same database. ALIKED Model Variants --------------------- ALIKED requires an ONNX model file. Several model variants are available with different trade-offs between speed and accuracy: - ``aliked-n16rot``: Faster and trained for some viewpoint invariance. 128-dim descriptors. - ``aliked-n32``: More expensive but not explicitly trained for viewpoint invariance, 128-dim descriptors. Specify the model path using ``--AlikedExtraction.*_model_path``. If the path is a URL, COLMAP will automatically download and cache the model. You can download different ALIKED models from the release page at https://github.com/colmap/colmap/releases/ LoMa Model Variants -------------------- LoMa, similar to ALIKED, uses a detect+describe+match framework. The descriptor comes in two variants: - ``LOMA_B``: 256-dim descriptor combining frozen DINOv2 features with trained convolutional features. - ``LOMA_B128``: A more lightweight 128-dim descriptor. Each descriptor variant has its own dedicated matcher(s) with different trade-offs between speed and accuracy: - ``LOMA_B`` descriptors can be matched with ``LOMA_B`` (smallest, same size as LightGlue), ``LOMA_R`` (same size as ``LOMA_B`` but trained with rotation augmentation), ``LOMA_L``, or ``LOMA_G`` (progressively larger, slower, and more accurate), as well as ``LOMA_BRUTEFORCE``. - ``LOMA_B128`` descriptors can only be matched with ``LOMA_B128`` or ``LOMA_BRUTEFORCE``. Fastest option but least accurate out of the LoMa matchers. By default, all LoMa model weights are downloaded automatically and cached locally the first time they are used, so no manual setup is required to get started. To use a different model, specify its path using ``--LomaExtraction.*_model_path`` for extraction and ``--LomaMatching.*_model_path`` for matching. As with ALIKED, if the path is a URL, COLMAP will automatically download and cache it. Extraction and matching also support an opt-in ``use_bf16`` mode (``--LomaExtraction.use_bf16`` / ``--LomaMatching.use_bf16``) for faster inference. Iterative Runs --------------------- When re-running matching on an existing database, image pairs that already have a two-view geometry are skipped, so changing ``--FeatureMatching.type`` on such a database leaves previously matched pairs untouched. To force re-matching, first clear the existing results with ``database_cleaner``:: $ colmap database_cleaner \ --database_path $DATASET_PATH/database.db \ --type two_view_geometries \ --type matches When re-extracting with a different ``--FeatureExtraction.type``, use ``--type features`` to additionally clear the extracted keypoints and descriptors. colmap-4.2.0/doc/format.rst000066400000000000000000000271321524536416500155770ustar00rootroot00000000000000.. _output-format: Output Format ============= ================== Binary File Format ================== Note that all binary data is stored using little endian byte ordering. All x86 processors are little endian and thus no special care has to be taken when reading COLMAP binary data on most platforms. The data can be most conveniently parsed using the C++ reconstruction API under ``src/colmap/scene/reconstruction_io.h`` or using the Python API provided by pycolmap. ======================= Indices and Identifiers ======================= Any variable name ending with ``*_idx`` should be considered as an ordered, contiguous zero-based index. In general, any variable name ending with ``*_id`` should be considered as an unordered, non-contiguous identifier. For example, the unique identifiers of cameras (``CAMERA_ID``), images (``IMAGE_ID``), and 3D points (``POINT3D_ID``) are unordered and are most likely not contiguous. This also means that the maximum ``POINT3D_ID`` does not necessarily correspond to the number 3D points, since some ``POINT3D_ID``'s are missing due to filtering during the reconstruction, etc. ===================== Sparse Reconstruction ===================== By default, COLMAP uses a binary file format (machine-readable, fast) for storing sparse models. In addition, COLMAP provides the option to store the sparse models as text files (human-readable, slow). In both cases, the information is split into multiples files for the information about ``rigs``, ``cameras``, ``frames``, ``images``, and ``points``. Any directory containing these files constitutes a sparse model. The binary files have the file extension ``.bin`` and the text files the file extension ``.txt``. Note that when loading a model from a directory which contains both binary and text files, COLMAP prefers the binary format. Note that older versions of COLMAP had no rig support and thus the ``rigs`` and ``frames`` files may be missing. The reconstruction I/O routines in COLMAP are fully backwards compatible in that models without these files can be read and trivial rigs and frames will be automatically initialized. Furthermore, newer output reconstructions' ``cameras`` and ``images`` files are fully compatible with old outputs. To export the currently selected model in the GUI, choose ``File > Export model``. To export all reconstructed models in the current dataset, choose ``File > Export all``. The selected folder then contains the model files, and for convenience, the current project configuration for importing the model to COLMAP. To import the exported models, e.g., for visualization or to resume the reconstruction, choose ``File > Import model`` and select the folder containing the ``rigs``, ``cameras``, ``frames``, ``images``, and ``points3D`` files. To convert between the binary and text format in the GUI, you can load the model using ``File > Import model`` and then export the model in the desired output format using ``File > Export model`` (binary) or ``File > Export model as text`` (text). In addition, you can export sparse models to other formats, such as VisualSfM's NVM, Bundler files, PLY, VRML, etc., using ``File > Export as...``. To convert between various formats from the CLI, use the ``model_converter`` executable. There are two source files to conveniently read the sparse reconstructions using Python (pycolmap) and Matlab (``scripts/matlab/read_model.m`` supporting text). ----------- Text Format ----------- COLMAP exports the following text files for every reconstructed model: ``rigs.txt``, ``cameras.txt``, ``frames.txt``, ``images.txt``, and ``points3D.txt``. Comments start with a leading "#" character and are ignored. The first comment lines briefly describe the format of the text files, as described in more detailed on this page. rigs.txt ----------- This file contains the configured rigs and sensors, e.g.:: # Rig calib list with one line of data per calib: # RIG_ID, NUM_SENSORS, REF_SENSOR_TYPE, REF_SENSOR_ID, SENSORS[] as (SENSOR_TYPE, SENSOR_ID, HAS_POSE, [QW, QX, QY, QZ, TX, TY, TZ]) # Number of rigs: 1 1 2 CAMERA 1 CAMERA 2 1 -0.9999701516465348 -0.0011120266840749639 -0.0075347911527510894 0.0012985125893421306 -0.19316906391350164 0.00085222218993398979 0.0070758955539026785 2 1 CAMERA 3 Here, the dataset contains two rigs: the first rig has two cameras and the second one has 1 camera. cameras.txt ----------- This file contains the intrinsic parameters of all reconstructed cameras in the dataset using one line per camera, e.g.:: # Camera list with one line of data per camera: # CAMERA_ID, MODEL, WIDTH, HEIGHT, PARAMS[] # Number of cameras: 3 1 SIMPLE_PINHOLE 3072 2304 2559.81 1536 1152 2 PINHOLE 3072 2304 2560.56 2560.56 1536 1152 3 SIMPLE_RADIAL 3072 2304 2559.69 1536 1152 -0.0218531 Here, the dataset contains 3 cameras based on different distortion models with the same sensor dimensions (width: 3072, height: 2304). The length of parameters is variable and depends on the camera model. For the first camera, there are 3 parameters with a single focal length of 2559.81 pixels and a principal point at pixel location ``(1536, 1152)``. The intrinsic parameters of a camera can be shared by multiple images, which refer to cameras using the unique identifier ``CAMERA_ID``. frames.txt ---------- This file contains the frames, where a frame defines a specific instance of a rig with all or a subset of sensors exposed at the same time, e.g.:: # Frame list with one line of data per frame: # FRAME_ID, RIG_ID, RIG_FROM_WORLD[QW, QX, QY, QZ, TX, TY, TZ], NUM_DATA_IDS, DATA_IDS[] as (SENSOR_TYPE, SENSOR_ID, DATA_ID) # Number of frames: 151 1 1 0.99801363919752195 0.040985139360073107 0.041890917712361225 -0.023111584553400576 -5.2666546897987896 -0.17120007823690631 0.12300519697527648 2 CAMERA 1 1 CAMERA 2 2 2 2 0.99816472047267968 0.037605501383281774 0.043101511724657163 -0.019881568259519072 -5.1956060695789192 -0.20794508616745555 0.14967533910764824 1 CAMERA 3 3 Here, the dataset contains two frames, where frame 1 is an instance of rig 1 and frame 2 an instance of rig 2. images.txt ---------- This file contains the pose and keypoints of all reconstructed images in the dataset using two lines per image, e.g.:: # Image list with two lines of data per image: # IMAGE_ID, QW, QX, QY, QZ, TX, TY, TZ, CAMERA_ID, NAME # POINTS2D[] as (X, Y, POINT3D_ID) # Number of images: 2, mean observations per image: 2 1 0.851773 0.0165051 0.503764 -0.142941 -0.737434 1.02973 3.74354 1 P1180141.JPG 2362.39 248.498 58396 1784.7 268.254 59027 1784.7 268.254 -1 2 0.851773 0.0165051 0.503764 -0.142941 -0.737434 1.02973 3.74354 1 P1180142.JPG 1190.83 663.957 23056 1258.77 640.354 59070 Here, the first two lines define the information of the first image, and so on. The reconstructed pose of an image is specified as the projection from world to the camera coordinate system of an image using a quaternion ``(QW, QX, QY, QZ)`` and a translation vector ``(TX, TY, TZ)``. The quaternion is defined using the Hamilton convention, which is, for example, also used by the Eigen library. The coordinates of the projection/camera center are given by ``-R^t * T``, where ``R^t`` is the inverse/transpose of the 3x3 rotation matrix composed from the quaternion and ``T`` is the translation vector. The local camera coordinate system of an image is defined in a way that the X axis points to the right, the Y axis to the bottom, and the Z axis to the front as seen from the image. Both images in the example above use the same camera model and share intrinsics (``CAMERA_ID = 1``). The image name is relative to the selected base image folder of the project. The first image has 3 keypoints and the second image has 2 keypoints, while the location of the keypoints is specified in pixel coordinates. Both images observe 2 3D points and note that the last keypoint of the first image does not observe a 3D point in the reconstruction as the 3D point identifier is -1. points3D.txt ------------ This file contains the information of all reconstructed 3D points in the dataset using one line per point, e.g.:: # 3D point list with one line of data per point: # POINT3D_ID, X, Y, Z, R, G, B, ERROR, TRACK[] as (IMAGE_ID, POINT2D_IDX) # Number of points: 3, mean track length: 3.3334 63390 1.67241 0.292931 0.609726 115 121 122 1.33927 16 6542 15 7345 6 6714 14 7227 63376 2.01848 0.108877 -0.0260841 102 209 250 1.73449 16 6519 15 7322 14 7212 8 3991 63371 1.71102 0.28566 0.53475 245 251 249 0.612829 118 4140 117 4473 Here, there are three reconstructed 3D points, where ``POINT2D_IDX`` defines the zero-based index of the keypoint in the ``images.txt`` file. The error is given in pixels of reprojection error and is only updated after global bundle adjustment. ==================== Dense Reconstruction ==================== COLMAP uses the following workspace folder structure:: +── images │   +── image1.jpg │   +── image2.jpg │   +── ... +── sparse │   +── cameras.txt │   +── images.txt │   +── points3D.txt +── stereo │   +── consistency_graphs │   │   +── image1.jpg.photometric.bin │   │   +── image2.jpg.photometric.bin │   │   +── ... │   +── depth_maps │   │   +── image1.jpg.photometric.bin │   │   +── image2.jpg.photometric.bin │   │   +── ... │   +── normal_maps │   │   +── image1.jpg.photometric.bin │   │   +── image2.jpg.photometric.bin │   │   +── ... │   +── patch-match.cfg │   +── fusion.cfg +── fused.ply +── meshed-poisson.ply +── meshed-delaunay.ply +── textured │ +── mesh.ply │ +── texture.png +── run-colmap-geometric.sh +── run-colmap-photometric.sh Here, the ``images`` folder contains the undistorted images, the ``sparse`` folder contains the sparse reconstruction with undistorted cameras, the ``stereo`` folder contains the stereo reconstruction results, ``fused.ply``, ``meshed-poisson.ply``, and ``meshed-delaunay.ply`` are the results of the fusion and meshing procedure, the ``textured`` folder contains the textured mesh (``mesh.ply`` with per-face UV coordinates and ``texture.png`` with the texture atlas) produced by ``mesh_texturer``, and ``run-colmap-geometric.sh`` and ``run-colmap-photometric.sh`` contain example command-line usage to perform the dense reconstruction. --------------------- Depth and Normal Maps --------------------- The depth maps are stored as mixed text and binary files. The text header defines the dimensions of the image in the format ``width&height&channels&`` followed by row-major ``float32`` binary data. For depth maps ``channels=1`` and for normal maps ``channels=3``. The depth and normal maps can be conveniently read with Python using pycolmap and with Matlab using the functions in ``scripts/matlab/read_depth_map.m`` and ``scripts/matlab/read_normal_map.m``. ------------------ Consistency Graphs ------------------ The consistency graph defines, for all pixels in an image, the source images a pixel is consistent with. The graph is stored as a mixed text and binary file, while the text part is equivalent to the depth and normal maps and the binary part is a continuous list of ``int32`` values in the format ``...``. Here, ``(row, col)`` defines the location of the pixel in the image followed by a list of ``N`` image indices. The indices are specified w.r.t. the ordering in the ``images.txt`` file. colmap-4.2.0/doc/gui.rst000066400000000000000000000064771524536416500151040ustar00rootroot00000000000000.. _gui: Graphical User Interface ======================== The graphical user interface of COLMAP provides access to most of the available functionality and visualizes the reconstruction process in "real-time". To start the GUI, you can run the pre-built packages (Windows: ``COLMAP.bat``, Mac: ``COLMAP.app``), execute ``colmap gui`` if you installed COLMAP or execute ``./src/colmap/exe/colmap gui`` from the CMake build folder. The GUI application requires an attached display with at least OpenGL 3.2 support. Registered images are visualized in red and reconstructed points in their average point color extracted from the images. The viewer can also visualize dense point clouds produced from Multi-View Stereo. For lightweight, read-only inspection of binary sparse models in a web browser, see the :doc:`3D Viewer `. It provides the same core model-view controls and visual conventions as the native viewer, but not the GUI's reconstruction, editing, dense visualization, or advanced rendering functionality. Model Viewer Controls --------------------- - **Rotate model**: Left-click and drag. - **Shift model**: Right-click or -click (-click) and drag. - **Zoom model**: Scroll. - **Change point size**: -scroll (-scroll). - **Change camera size**: -scroll. - **Adjust clipping plane**: -scroll. - **Select point**: Double-left-click point (change point size if too small). The green lines visualize the projections into the images that see the point. The opening window shows the projected locations of the point in all images. - **Select camera**: Double-left-click camera (change camera size if too small). The purple lines visualize images that see at least one common point with the selected image. The opening window shows a few statistics of the image. - **Reset view**: To reset all viewing settings, choose ``Render > Reset view``. Render Options -------------- The model viewer allows you to render the model with different settings, projections, colormaps, etc. Please, choose ``Render > Render options``. Create Screenshots ------------------ To create screenshots of the current viewpoint (without coordinate axes), choose ``Extras > Grab image`` and save the image in the format of your choice. Create Screencast ----------------- To create a video screen capture of the reconstructed model, choose ``Extras > Grab movie``. This dialog allows you to set individual control viewpoints by choosing ``Add``. COLMAP generates a fixed number of frames per second between each control viewpoint by smoothly interpolating the linear trajectory, and to interpolate the configured point and the camera sizes at the time of clicking ``Add``. To change the number of frames between two viewpoints or to reorder individual viewpoints, modify the time of the viewpoint by double-clicking the respective cell in the table. Note that the video capture requires to set the perspective projection model in the render options. You can review the trajectory in the viewer, which is rendered in light blue. Choose ``Assemble movie``, if you are done creating the trajectory. The output directory then contains the individual frames of the video capture, which can be assembled to a movie using `FFMPEG `_ with the following command:: ffmpeg -i frame%06d.png -r 30 -vf scale=1680:1050 movie.mp4 colmap-4.2.0/doc/images/000077500000000000000000000000001524536416500150155ustar00rootroot00000000000000colmap-4.2.0/doc/images/dense.png000066400000000000000000034071551524536416500166400ustar00rootroot00000000000000‰PNG  IHDRÐ#jîšôgAMA± üa cHRMz&€„ú€èu0ê`:˜pœºQ< pHYs"š"š¾Ýû*YiTXtXML:com.adobe.xmp 1 LÂ'Y@IDATxì½W·Ùu¦7wÎù䀜P@¡PÅP¤DQjSÒh·G»/zxxøøÎ?Ä¿À—ö¥}å¡vÕ¶F7Õ’%Cå€xrÜ9'?ïÜÉ–È"KU…B×Î9;|ß ïZëÛß\ï|çŒìŸÖç€@@ ð\   rä¹hIhD@ €@@ €@@ ðeE`>Ÿ["‘°Ë‹sûŸÿ§ÿѪ•eëŽ'öúßµõ•»þÒ+–HÅmmcÝV–«ÖﵬÓ<²û¼cãþØ¢ñ˜½ñÆ»–ŠçìÎkhkW¶ì+w®ÛêÊ’½÷á;xüÐRÓ®%b#›²ãÙi7­ÝéØÞá¡ýÝ÷¾oýþȲٜEÙ UY³ÙÌŒ6Ígs›ú&iÔ"}µÉhds‹Z,ÆO”cx>Žø(nãùÔbüÓ¹ãÙÄâј%Ói΋plÔÆ“?ÎsÌÌŠ¥’uº]ÎY±˜³élê8Ähc¿×ç§KýsK&á˜Éhb]Ú½²ºd¹t¼.-–HY«Û·r®b¿ÿW-OXg0°N§gÓ±Úe–¥ åBÎr¹ mY´/—ÍZ„ö¦qKSÖ˜.Çiçt:³J1o ι·wh³y„c’–ŒGm4èY2•¶ápl#p–‘XŠzÆ´­o Ú9rèf6ôm>y}3ÊŒG„!Ýæœ íÒ³D2iiÊŽØŒr)g2Žå2 ‡6aV–kŒÏ:‡üí 4šÄ¹)+å 6£Ž9}ŒóºGûl£ïk1ÆæÚ+ s¹¬mllØÚú ÇÌíôôÌËXûÑ?µÿõÿøsÛ®®€EÆv¶¯ØöÖ†ÅèÏ,‘¶dµl–œÚ£{-OÙæÎŽ]¶ê¶\®Ù 7´GÚÊÚª¥Àl4؈6Eé_ÏÔ¯\>M•sk5ZÖ<>±ÖÙ© ÇC»¨×msyÅjÕª5ÏëöÕ»w¬ÙîÙ`е»/½Ì13+äS|^bNN,qðÒ¬cúùO*S°ý£ ;½¸°1e&â«•«|&°Á³Ýµƒã3ÃÈ|b0ÖZö4Éf´7FqÞŽÀ|2漈噚S>Ÿ±’à;æsá“Îd­Ýjñzì}Ž'˜›Ì•3Ïv»íý­U+–Ã.s@¼¬XŒ5A4ƌ֑&F>ŸµJ9Ë|ÓÚ‰X³Ù²no`¥J•ã¦6ö­ÕìØˆs–——}>˜1ÖÝ,±k»;Œ1sˆsOÎ쯾÷c;<9·L:aå¥eÊÏ;ÙÑn¶­ßi2—‡–É,_(xŸµ¾ôÐZ›RÇLó3žd¹€#sZëˆÙîýK‚^õ™«Í:kl8Üû,<µ¶ãœ›X$7ífÝÍè‡pžàgN¦4?èƒÞ‹ÓŽ(mךÔÖy\˜iµƒ»XjŒIo€¹uZmÖÇÔ2´Sk"ΚO¥SŽáhÔãºÔ£ïœ@ÿEÈë¡ëþÉáFsYÎ$šÙiÚ<æ\­«g­k.©@wøÂ¯€@@ €@@ ð)" ;MÖ_x€@@ |"t[%Eª6ÓµÙ®ß÷ZŸ×pr@à)R[ФׂŒC^VªK¬;ˆ+H§r¥ 1Üá5dm£nHÕÄk·×ƒà„T-V!°²P‹5*63 =€ŒµYR4 A qÆ_‘ô"ŒWQ÷¦!½ ËxÏ,ŸIQ¦ñ(,êe2lC>ìC2ꥹÙ$žøF©ÒG–"û¥¨•Ê\?"7EÞF!Ø'œ˜¦Ü>jõ,jh‘‡|HY3Èÿ¾¥!ÉÒ™¤—=¤ÿr˜ó™¬CÊ—ºWvŽ_Ô1uX¤'¼]ß^µeí”Oƒ Ä9²Neô9?ÒŸY‚QD¼DJÅ=“zü qGmÍÅ ,‡Ö…œ—j\¤i§)qSù²“â"òEz'!³rA0&cÓr6 Í:gJ;zƒ¨“ÀÃ.dóˆ¿Ã1 o‘—\VÅãCƒCšº{åôü;[žÇÁÐÉbæH%Wôq9;¿ÐE™ÿR{÷-CýAþŽ HEfJ­K´ÆQľ_Áù•C‘¬1¶“™Õ2+vzôÊÌ±Š«ª'`×¥Þ ¨öQJµŒÝ{ïˆê–­olÙáÞýý{«mnÒvæ8?í§0¡:Ÿ‹ž¡nF ´Î1é5}<±è4j­ËŽ%W Ê9‡QùCó¤ùŒ‚›>D#"\!xåàæ"sçR¤s4‡{?´sD{Ç8œœžs^Ö‰YE  ŒëˆÏÈB*€…æ‘æUÇ tÕŽ™æR‚ˆ1ˆì!ãÏРàÎp¾ NSÎIŸfkŒº8®L˜¯sÊ”â?ÊI)æ4s)Åq®„g2Š€O¡ÈûmÒÎøkµ{àŽîÀçHŽ÷¯ß½aµZÙº(±›®¯¿xçYÎ.!ØÓé¨åÌé4²8Äpn±.s·g?ýñvyïmK¬m‚2x¯àjÀ9r|‰°PÙ3/ä@¡ë"HÌqкP{S¬C9ØH¥îã)§ž¹Æ )D4‹¸å¬ÌzKfØ„ñèr}cÉ”ëN„52Äá$&§¢"øg\å £ë‹æò„c„C‚ñœâŒ"gÕŸ`­G47yø1"÷ýUø€@@ Ÿ Øn<´!iíÖõ/ŠÕgÿè­_|ž}*hÃÁñ׿zx€@@ ð…EÀï›~þuŽv A¤–¸öºÃ# øÔ€<‚À+¨æ ^‚Ð,â:G¸n‘†ãab2…µTÉ"Võ7 ©Z zr ¶T°gçç|ÖuU'…Yµ«“Òdz¤/ªâ¥rÉIS…´á¬PîR»æsiq“ZP<™²Ö¥F2^œâÇÀˆñÞÐ 6‘s²²†ׄ`¹X]Zug›N‡¶RG*?¥üŒy‚M!µ“‘¥„ÌNF5¯Ú(ò?«,¢_ä«òMW½;% W,~š°îÉdÚ–V+…˜Ë@DW!¥xWÈèX, IN›/!jEüK=‡ ö±¯°õÔÄYÔâS@š B‚+uJä!„´.vÞ“R~¯Mhî¡¶¥ä—b]½œ¦¨„/ê=+B§ÀI!«uÍÌÈ!E¹.žEìN☺°þ ¹- ú¨ŽeF‰ÈŽxÙÏӜÂÈ_‘›z(ä»ÂdëQ†¿¼¼pÂR£¡0öÚ$å±ÈRE ˜ÅJd©ï¯noØŸ|ç[Ndž¶>®²ãr`àüäïG‹sî1¡×û¼µåU;?9±B©J„]Nö­´¾aãˆÎA=Ï8LDâ3Gã)NCDàÏ&ÑyLó¨Ü[ŒçTênˆ`a$ռƒÏpP(ðn·ÅcÎ0frJNrÞ˜á¢z4ÆRö«¥R‘y²žÃ8F´ˆ|ÐïkmÈ’–q”²YaÚ•B@8ö{„៷Pª£¾|žy;™ì:FêlÍU=DªÇÁ¢ß’sã örj‘½›'=@"Áü£Ÿiú»PP+ÒÃdžóYQäðÁü\á;¡ËKÂík}*ºƒ`Â15÷úæŠ]¿¾ÉxË£o›Û[ªUi³ÂÿY[DŸ`Ž ûmæ'‡á :2Ñ6é±î/-‘­@D£àöu­Q'ŵ–SÇ©¡ 'zCÈ{Í …Á-Ç 9Åè‘bªÍ³YÁ祮 šKz!Õ½B³ËÙA¸È)D2š?Q9ppŒPD”‹pwÔÞ—3Šœè9ïyo¡è³®#ô7Y8é݇!ü €@@ ÿ<d é¡‚ÅcaÒêõâGGWg©;"qE>BHˆQ7„;dlŸ¼Öª3ÚÐlBÐA´IaS8kŽ¢ž Ry˜",·Èç!jbå‡qóvµ ‹¥”ÕµEaÊaýPt_²ºàêÕ‰ ™GÙꇜÒ©¼mÒV‘ÖRþæh³ÔßÊu®÷:8´iÛy½á9 ›í>ï É}ž·Z±`EȽ}tu>¤ºHpµU¤_B²€”±µ2 Ô³¶ºîdk³qé¤o„>.T󄌒Dlõr”~é+4"d#ùÌq`˜ÏÀˆëeÌDÎËIà²8Cù9œ„oƒððÊ7-u´‡½ç==ר+‡yBM·‡;OB„Š8–<™Y¨ù”T‰\rU4uJý­ðá]HåÍVè{ÑÏÎÛ€xß¾rÅŽŽöÝACº>WÎóùå57f3µ¨õ9KaÎ5Ï\ùLtåxW˜p…L—‰4W›„Èi<Ó\eŒÆ†e˱DĬòµÇbñ"·§#æÁ‚¨wõ¾·{¡ÆŽ@üÊ¡C œ9’œ/U{Ÿù["D|’yè<ºÁˆ¹Ø¤†‚säø‘fΉ¬¦9†NèËá"IT»´¼æáϵsá¡û™gÖÍŒÐóŠ*ÐnA°“w¯4¡z[ÿ"ÓUBÐG!¥ànµâÍ·>°7Þ=°å 0àDÍàÃëO$¾&ƒSâNä3çx­z½nŽK‚YŒ×¸YøÐT_ÒK‡ÏuPªöÁTi˜´Í›#Rœ‡!4'åÜ£i(uzÄÏ9ÎsµzDà+Ò„Ú¾ˆX (´ŽúUo„9¥µ¡×@whï€@@ €@@ ðñÀ&[<ü ¿0Äd$öß&ƒ+Ñ«azO†¡ŒH}29ý!;ïç/ž¼þüZó/ä‚\¶±ÿu_|^Œµ3Ç»2ŒÙÇðï,ÎWz€@@ <¿è»ZÊ´›ô‡{÷¬ŒPù•ÏZ¶´¾é€:ìéÍÀóÛв€Àá—í6D“Y‰¼Ö ­|Ø"{CH¸ÔçkºÏž²þ” ¼LøvƒpŒÆæ¨‹( ›v~Q·Ø¸!&’‹rHVÚr% ­²2äWnnå%–*»Åyï²›¤Ú–óŒB‘Kª°ì"ºDl‰)~K¥È{åržC´*÷·òSgP`+<»Hx©Ï;´[äz2Rí jöPÜ\h¤bíC„7! ¥¬Æ„³F‹PÕäyÔé…B5rÖòˆK‘šsˆ·•Õ%+èÊV’Е¶Tªоhg(œ9FFGLŠ„¦n‹œ&Ïw–üçggÔ…:Nï£KC‚J £ò&ÊòN[á¹q‡tNá¯d¢òT+Guu%Žê—RPw:(¸³œ/û‡þ‹°ä)ä.aùQÿ“ ÜzÈP°U(ë9ž%@jë8íÈ—BËÇÁs …9ù襜îñÜCÚC`+d¿Â•K.•´ˆHõ›šPd/¬«l&Gù Ŷ¸+4¹4¾RÛKÕ-%7µû¹®–ž ^²:¬÷ýõñ¥l93ˆ¸—AÝG²šìô5n]œD(gótÂVªä(Žnwó|/Õ´€P¾oEPþua UþúʦQ€7ëäh×¹´«C?EÖ C‘³¬œ7 Âé¯r¤S íH2'òË\ä¸>ΧnGe/œæà”Eõï¡ê©ß•Úà$Â]á• ÍÚê²Þú=ˆk°¾ÞVÂë»O}Ö:Q*Ï™Îø*w¾(Ò€ˆð)Î&-" h­¨ú^”#¹öŒÖBéëyü„"=ÈabJdaVIÖ`F©4?˜»ë>Î GÇçÖ¡-b=MnôñÀ’ô T|M+ ÄÑiÝÞ»·Ï:g®‘² GÈu…‰/e"‡Cƒò”kŠô†{í—Œø«ñÙŸ Mr‘Š\óPQ4­´vã úL; `c D`lä`çs‘ùª¿ZŸ æ¸ÈFu 3ï›}Í#ÖkŒs˜UŽ»ëZ°ÂJ×)©æE п0_a¡¡€@@ €Àó€€ 9ì<ÿyú\¤­ 2‘¸c ÛÆl.ÅFÇÊO¡m¶8IÕýä©^†ÇG" ¬€Ó omÚic@…œÚ¥L‘«zú„TjaÃ¥–šCNæBåsíqÊKÑWT¾½‡ u]Φ¹òò_%d!H¥ôU¹CÆI¡ã¥PŽBDŠoC¾Šè%„º+ÚQ…ËnË‚©‡í¡ËxË)CŽ œ ¡“£Á¸º²ÌühÛùã=ÛÿàCëåU+*´8¤ï'€)x¸³Ä!'z¯ &×ô«G{´FN:vÚÚε []®2®8Œ°æ¥^ßÜØt‡õYN4m¢Kh.+zƒÚ&2?Î÷|'S 'E³ðh”ßbmèx9TªÖ ýä:‘ãœã> ²€"%ÈÉA{rFмc¼sí Ìf¬yÚ¬k]6‘ó9©ññ±å% µ~DÀ+A&7Rø€@@ €@@ ð[" Ń,27‚1Beà åÙŒ‘Á‹>>'DF]w¡Žá;g*†A.Õ u~a»ë–¦£Œe¿áñO6z`ë:\2z]¢5ŒéþPĹ4ÊÙGØB6w|Ó ,d)liØÞ‰h3 ƒüµyð^à~€@@àyC`ñÝ/ÚJÕ*Õè2‚¯ö ›æ®2\ܨSD!eu!ˆ=Ÿ:„9–š¸°xl0Ðß.¹µÇ<ñ™l ©rò< #w˜V?"~ RýŹÔÍ]ú(û$‚õöúuÂd×(“í®úåzÆÅíB]äå%Äß96Z„üVxw){gÂæF»ˆ#B&¥0Óœ€™Î±±)9Ny¾gRyóW ð9½‡éSØ}‘¬"e<és…É—3±ÚR47Ê…®ÖSÆJ䲆‡“±Oå,Q®*T»¨­!hçæIê-âR$¼æF¢2)Üe›1n2‘uÞñ“â\¤§ÂÜ+T½ˆÉä|©˜u‚t€“ÔyÔ…È¿l6 Q!Ö)Se)÷zkxjIìç1õÉA@ÄçøìÌvQé_íU;BÁ ~«»»8®œtVžó rŒÆfÄø2Ü´•°Ýà2%G·ú=d^5pæ˜wGvJ™Ê?ûÁéœ9Óƒ|î)4'…½" P?1»lâ÷ôâ ò6êyå¥L—FŸ¨ ÊÃ\ì°7)]].yîp'Œµ?À?á3bž ã®°ô"’5~ K€1D®°”[ýþ‹ðûÅÌU…÷W})Ð¥ ÖçZ+;)º‡8I´ˆfÀSæ D5mŸ2÷Fà¯hy¢Ö¥R×|6Ræ+¾ÛÒÌómB½Š Åz¿ßur:†SÅèIXyÐ}ÖT„¶/¯Ôl}c‰( 8—<çW¯^ÁWjÈsHN‰| úå(ÐãµÖti{‹–N6-¿)„ë ó>f>ˆ ßÞZw"þŒèMHõ$×—s$Âm·[˜ý1ÛÚ\÷¹v|zFùäFç<åœo“ï½7è2³³–cÒ-œà ÄþÇ(4 Úc¨BÒ—+Uæ Ì£@ ûÐ|~¿üúôŒª–u=£.}æÕ|Ì>αŸyÃ?¥ ¾Œ}ú” ùÌŠù¸˜Üã?³†‡‚¿Cüªu§›_áÁ=> >ç9¾ÉFÃ4k}Ã3?ÁFu …EÒÇA{‹Í•¯•gùø¨þ~ÔgŸ´Ÿ¤ì/ãwÝ'Á㓎Åg}þ³ìÛoS×osÌgI(ÿã!ðE^ó¿i¾ý¦Ï?RÏ×ÑÏ[ß>I{ôͬÍüz«i=´•TÛ‰›“öÔV_X²åê2Ürûdß៤Ï×è‡Öü:>¯1þ¼êýu8üÚ÷i¨–‘HÖ>äg¥\µt¾ˆzÂ5˜‚<ÎIU Q­0ÑQH¸÷â=L©“Eèr¶Sña¢¡åª¬Ï|%ø7ŸÈ°<êåÅ%dä—B¯SnšÐèQ”¤=ˆä£ãcH,ˆn”ÒR‘Ï!Èëû'–¦ôÕ’uN›„«æùõ]«mlAò§!Ç;ž[Äð}ÜëËKv—œÚÕÕ2JaÂ_þüÎçÞ°ÿäC+ ^Í#__ƒ ­Uò„ÙÎZ>-Âx†â2m‘ßE•Û…¸“Z·ÙáuacÕ•öÒ G! c„"øÔöx‚Ù(›ó–…9 |¶}p¹¹ÀW9¸•¯{EÁ<ƒ4¥]Ô¸)ˆÀ2*ëtŠððôUDhšåÔð1Ôð9©ˎL$2´ïÉõŽzKáïÇØ>¥¥‚«Í}êHÌ N!TE®V4F%-›“÷DÓ:8y¾å Qxꓵ5Ç®@æFh›œ)"¨šÇC弇 îE¬RÊYe­ê‘¿bÔyI¤ÖÙ‰–@? flådñ”b]äŠWþq…Ÿó~&ÃQ%Ÿøô/À¬Mü„SäŸ àçUïÇn0 Õq•&t™U*+P¬ÜMC"ÖO."TJa‘à HA‘u ßÜ ­Tv!Q£CDŽ B/Q*—öÒRÍFsºpˆÂ c‘d-H2…iÌV„<ëÕQÚbé³,¹ÌÛ½3ˆÚ’Í’Y·nÚÎwâô.„|•¨Sä`Gå]*W¸÷ØÉÉ‘½tûŽXL{ÿ§¯Û÷ÿüϬ²½ãŠñ!DâupÚª‚Rý¿xÏ º Ök„[©&us]!À3ù²Éz(´â<…cT°ÕQ*·Qáî£.ÿÉÛïâ ñªzeë vGÞð¾uçÔ«ˆcQ'F•Ë]¤œTâê¿æB§‚bçŸZ§ëž]ž¹“P«Û´Ã“}'w5%r¦oï\µÇGôsfw¯­[³qŠ¢÷-;C½>@Û#Ÿ¸TÃâcoߨµW^ýªýðíG6îuì^»cݳþôMBç·É5/Rt¡ø•·TáR‘‹Ü•Â\aã§8LÁ¶M~øV§ŽR·äò€qUû¿úò-ûö·¿m‡õ¶^4ì•íUÈàýìg¯Ûë¯ÿØö2Öžx0‘è,¯ÿÞ+öÕ×~Ïš½ íœÕPŸÿäÇ)ÖÈ-_ö65÷¢µ/NªWÿ 5ó„¹8fn®/á,Qqzf æ—HõÕe»}ëáÁWìÝ÷Ùo¿gp+UË®ŠWd8·ó úÏöö¬X©Ðó(Ê謕É]¿½¶f×ÖWìßýïf>X´WÚÊQ¯ï©pÖøÿé¿üŽep(‰2w6—Êö·ñWvFt¼Ød9€Ì¨­œ.Ø¿ø·ÿÊ®@èÞ»gÿñÏþ†××çþàæ"¾ذQ»s÷¦}íîmKå ¬µ¬Ý½qÅþæoÿÁö÷ŽìÞÃÇ8e”!œ!»ƒ¥r†t#;´˜Çe+å¶¹YÁF&_zƒ0æ8uôû(õ9¾DYßüÖKöí?zÍɇ“k»Wìû_þõ뫬@:3ÿý=¬5ËÔe]+cýâ0†“€ˆp¥ ³ØË„@§1ŽU´ å`ÏY!–ÊÛÇ8#0^1° ¿CöNoìIam¹h|§Ë!GáóU‡§: zB†²&Ôc]]{aWÞvQoúzË2N1EÂã7..Ü)äöK/ºâ\ju}ƒËd}kË4W‹¬É¯¾òí` 5>ÔYNWluzž5ªh•¥ ©'–‰v@ô.~Rùç‹Dt9 M¿ÍŽ ë¾˜&áw@ x.øÂÜ\?h…F€@@àÙ àßÏVÚXÂîã žüXZ cm?°ìì±¥c줇rg3Ã5„¼ê§˜\ãé>Šõ…W¸Âü§i[yáÛ¶öB“£NóóTPðlzõ«EÀ 'Œx¶€ìäð>éï[.-e±Á—)ŽÓB‘°} =çu*6%!!ÇRœã%¿níùª‘¨ÎCÀÅPe°OösçUáãýƒ'47 _*äUÈw¿H¹ ôÝ[–AØ v‹×íþq0¯]ËBŠˆ$yÖŽp_*¬Cg~çxbÞ ÐDI ÿ‡öÝrÊg}|x A*U-οÈPWC,—*¨a‰¼U*’÷%mšÜÇ%Ô¦CœŒïŸØ@ªÒ'j^…ûV¸u©Å¥ªÙ>Æ^JP‰wQû_ýkÛ$œs"óëßü«-/£T-@äŬ|r´ï¡á(Ùû¨·ûØ\)ìƒmúÖwþÄ꼞¢ò–êtÔoBà‘C²=K»·ÖD®&Q½ç 5½jÝ󛢄—Š<…@Æáy’ÌY“ÜÇr$'!Ú Ÿ*(tz”pßuËn>‹âu™ÞãyO gêKâÐ#eý˜º¤â—:\9ÚÒq)Ì +¿¾s…²Ê®ÐnLPÈn¬Y¶¢Rï NŸ¡Þ¯Aì& c)B§–„Ÿá´0¥üõÔó¹ ;9=· *Zô+붺Ñ7£ÏXžÂk†ÃpquËþôOVíøàÐsÏkŒK¨¸eRy¾t.±e0ÖxH5ž/•Qf§<ŸôÉáÑ"D6FV:Cë+W ˽âJê‹£sKr>‚ÌìÒ·ßÿÖ·ìî+¯Ò‡Cë¢0Ö8Š`ÍKvõÚ+æ vpx*š“È[^]CÝÏ$ÃJäì*˜(_õÞýûÓh·o;„¯î,[â:„g!‹šøÒÊ…Y{óú9Ý ÌC²•£^`@¿|{B½Â½€Ü¿´c¦ÌáåõUˆwÂáCª+„xa¹Š- J8ôsùÅ—_´ÿ~û°¡»{ áJ¸ˆ\‘¼ Ïž¥ë[›öÕ;·ìáÞ$×¶{eÃ6w_@å|n]r»Ë‘DÊyÍçsé•»7lig›ˆ»uýªíÜx燶ø¾’3ø˜ò}Îsle¥j›kV¢?Á•Äï¶±{Ýþ5Í-¾÷ˆ|ïǧ˜¿ÀUœJp4ÙÞ^·4DþÁ£G`S°2ó% Ýaz¼ÈzÉÛÚÆ²­®á<ŽCÂ÷¿÷¶}í÷îÚ7þà÷pþHÚ%ý”Ê\!Õ:¿@dd 'æ8]D]ŽÛx)ÚÞ„¼áI"6LF}/Kã{ÊüÓÜKေ¼ºN»ÓVÜØ$Á©§L¸ ¼~ Œ‹(³\?äx#å¼Bþ—øÎÎ_œ¡ë!ØIÖ#?„Ô¿$t~–¨9YŽONíŒãÒ\3ŠÅŠÅIÝ¢hggçža=·b—ç {p`m‰|W>øÆÅ)÷ C{á…]k¢BÿéïøÞ@my…6‘–‚1Pû2¤*xawËÎŽÏì½÷?Ä pàyšw¾ÛiƒeÁ–j„ÿÿ†€@@ ߀ÎËn¨KUБ—Œ ƒHû}BÊÝèCqŽAWLjëÂl¢ v'âñ 23BŽ›H7È¥˜Š$PC³I2À»^ƤHÛðøh´é£‡þöÁþðá=ËÇ” uF.AáÍ> ^î:¢ÞkX*[f³0€Ó¾åÈ!X¨ÊhîX´C^5¼âGñ Ô&IÎCšÂà*|]!€@@ ø|Ð÷±qå`ÕýV«Í=ÓlÍ.F8ÇÅŠV‘žexŒ ‘T95ås|¾ µ¾ °ð¦Ó'Ê[R­Rc·±Y¶pÛäªVîaUÈNå'Ïb\ Ï,Ì©¡P^†ìëuÛ¨P!&!´ñ*vÕ¶òc+²È:åtq®pßÊÃÝfϦ1[ÝÚ¶Õõ Û½qRåaÚ¢~óR™*ÌúoÛá»oRv„:!¨7¯Øß~÷ÿEIŠ™ëÃÅùý ÿ8Dw BúÆkßp…qž°ÐÊ©]ݽmCbÂCO[ºR#2˜Ùß¼þºýÕIƒ‹N¶»óƒ6Ž|Êá•«,Y¢œ³þ¥¦{NŒ‹HŸv>Fžò8aÖ3…Exï(Äg' êµmË¢\nB°[÷Ò}dÇ„>¯¡À¯'N¤ÔwZoÙ¤Þ£Çû¨Œ•ŽjbÏì˜0òUœrŒ‹0“*9†Úûûó̓sÈö!þÁ1;¡ÏNŽígˆ2 çÄ`Œ6I­®¼écT¹>v˜@ýAÒ”!M£ ÂУÎM+:šò}£ö0å”YÇfÝxlÃ[‚ÜWT÷z5ùcû”éÛ(¹3ú)Èr©¡=·=Ψ¢?<<³{ö³·–{…è5†î aAg€×ë˶ iŽ6™qY9qtðȪ8gܽûŠm_!‚[>ƒGèzpéC¶BöŸCܦ •¯í¬ØÖF¹7²Ç‡'v†}ìÀÄi‚îñœÆ£Ï„”‡ƒû{¶¾»kk[6­dÜ1ÏÒ(òÔ‘]ßÁ™¡h} ’ǶØc’ØØ²ë8* /qäb>*gû·¸¶ÎZ0Žì²=´:dp„uQb½ÌZýì ¯pìÀìÎïãx¿FÔ´!!É߸ÏáûND< ÿUœ˜ùTj›qìÊ?ʾ¬øìI(û1!ösØÀ9ú—'Ïz–9½Á‡[ #¹ÌûÌ)Í­1ó,»dÖn",RÌ)9 LPŽÇpîHÓ×ù°ANG¦8o@ w{mÈsœg[Í!× ’§þ¿l{Íû§NÄãF`Öo’üáÝS¢+Ð_ö?yëC{¼w?ÇI‡ít¾¼ºÊ|[„öï£öÎC¶ç‰,‘„lWZ‚jm‰¾0§Þãäx'ƵÜ.¤Àéc鲌± ù>Î,ž'Qçë>" Á—ù)u¹'&\×h{7yÈò”G±È«ýëSý룾—3Í&:FÜ/Úô/Ã\èC@ €@@ ðÙ"€6{*„DcCÒ6F®íäø“çÚˆh¡°˜bø.-áO˜¼$F¬6v›„k²ñsŽb¡Õ™²Y‚’¡\³-¼¦±zÝ(Kº Z$®èßðøÇ(Ÿšvñ"l)Ï\4FØ»T ¥Ã¨ð'd{¹ŒwzA^íU>à%å‹dH¾¾6N ]Œë*Wé(¡Þ(n4/°qÉŽEÌ?= <€@@ ø|xz/$çD)Ð{„R> ùþë?òMú?øÆ7=„¯ÍQêAßßOOú|›j|¡(J;‰òzŒªs‚óoUn«Y‡ì#¤6dk§q‰=Ó‡}SH縭F»¹AAzi· ÿ rEHOZÇt¨j±Dx*·¸r¡w!³µyˆIôz${kCø-olÛï¼cï¿ñ3{óǯv»„Bô'r‚C¸¯Bº% ‡$Ü@u œ0Ì«žyB¾ôûG6àšäø^«a‡MÑ6…¼úòWìÕÿú¿ñÓÊ‹-â8‘Í[sÿ‘‡ëNR¼T­ÙO:¡¾kʉ@fËKî ÆSDþ€þŸœ@òÙÚÕ +`_ÌÁçÂ6‡0”"}ɧœâ#À§{‡6xð’UôœëÚ˜ßß#Ê¡-‚[¤q»0AhìÊÛ* uQ•-B¦Ç`÷»—=ú6¡ßCDÂvº¸N¬J˜ë„üœPì—Jw1µÎùN˜]¬éZFÕ~qQ³S"$@ðw ýžDñ÷Î5»5^„߃Äo]¢r?…èE­-Çë‡Æ$JcÚ]1蓬å>ó¨Z«1o£ž Æ`Àx†³É€¹C½Þ¨wQð_ú¹©„=d}2š@5ß&wúÌ ô'Íy°”cÌ §¸$Ķœàåhs޳ɽÏXŸ¤?`ž_*”;Áÿ rÓ“þáøèÐŽ›ìŰPæŸÕæoŽ3ê¢Pß—Q:Š*áÛgÌÓGZ³r–‡8Q'RŒÇññ1׸&õVß¼‹ÆÏ¯RáW@ <»ï¹†Ðˆ€@@ ügÈSÙEŒWöv,I&±äè±%c ð¤î¹¡¹}ý¡ï”÷n*ð`Ï?>'üêôÆ”ÐíøÓO²¶ýâË–©nšR¢ç3Ξ’?žüùÏê/ûâ !(匓gqóê-ûñl燇V-G¬ZŠÚÖVÞ®\²¡'\j‹xŽvŒï" „ûv¾ß²Êê pM%‡äCϲù…'?›R¡/j ˆ€@@ ðy# ½‘9©S¥ÖT˜Û1DÄ££#k³áÿê­Û®Ju] CxŸl%¶”£­K){÷!Q{CV**D,"õ¬ÄœIòj£"Oò¶uyL®KˆÏ¾û³ˆDL J¤­ÈÌ„p²K!ŸÓ8_B¾Ž°‹R¨@ET¥Bθo'ª÷úÚ©²:û¨³iËxŠš÷ø2ëÈÏ™Bo_¹ÁºÌq² âV'ܳÈJļ6=¿tÙx<½5·Úæ®uh¸œpJµek_B¨âài;à‚#Uk‚ûœ0ÒIÖ„µˆÂ ÿþ{J®ô‘Åh äÊ\TÙR@ú{„‚¯;¹›€€¡T‡:Çn‰ñCxøL Ò½csÈÆ6Á²vwÖ­ÚÃêJÉóg‹Ñ…o¤"b‰@Da¯\ï1©a! “(û_¡Ñ'|fÑ<¸É~™ÙÒ´êªrEX[_£;#WÖ¦Š(¥i‹c·º¦Æh·¢t•/G‘¹Ê=]„œ–+C2F¸zKå‚Wš1å‡âi<ìmâ ‘ÀqAã™&,z’~!·söÇ·oâ'“¡íó±µl†sÂÅ}b3ƒU†T­Ag ä?ÏC˜B¤ÎphÓ9&œl…0Û7_¸IXðÄxв  Á?âÔAÙ1œbØÛyr« ¸XãäP²íkWíFóåœÙ!ã/˜Èà"¸Ýº¾éŽíVŸï’º¿ýŽíƒ‰ò²g±3µ"|s¹"aãúNšÎ±7›ûôŸ±LBº/%û>±öþ!c?€à_&’¯ÌZƒ‹ Á–#BzÜXœñÌð=VZŹ‚ñÓ8(„»æÔÂ)%>jí«»;|0µóÇÑKÓ¶%HŠè(éÕ5ß:>'3r<³)8ì‹|/¡Þ–³F¡€~›²•6¡š]Î|‰â<Þ·žÂÁSí d¹Ï 9¶@.—远þQÆ(ËufœuBä<þ­T+N2;“Ï:šð=<Æae„³B\¯i[Z)ÆDJèARÇ´î•j ÒÝ£@°+-ÄXiX˜‡*צüåYúúàÝ÷} õ …ãNŽpýkD¢è¶›Œ'Ž8ôzCÔð]R8•œSvÄv¯_‡Ú9N-²å›‘!Úë~]i’“=Í5fcs“°ô3O ëÉå9ãÅ:êp1áÚRäÞb‰¹§‹›ÂàOHk0¢rPâìt  €@@ €@@ ðëОlSÿbllL >k`Iòái"Š—ôÚÖŒyì@¶:(ÞëM{¸Ox>„çç6"V8æ»vûUÛ¾qÓCü0Üâ(6ü!#:lþþÊ!pç…'*q…Žë±–©­Ù×ÿ迲¿þ˸íñÓ@UÐÀÀØ‹7—íö’åð(Ÿ°ù•A1±‰ZâŒÍœ)IΟ%±áÃsFM7# S><€@@ øœÐ÷±È)ËDܽê¯È¦þˆí·ï}h¯îÞtB=çŸó@…ê¿ø=°äÜpcפ’1ˆMTÏ7Z‡s~¤äHq Á•Eu®œÃ"_ ¶ òN¹À±–°iÖȬÛHÓ 9Ä¥õ´U„ŸLG„ZF¥®h]Êm¾x@ær.5wDÄ3$Y Ò]9©u~Em22Q¬\ÅGõºí™¢ Á¥(U Í 蟋LÎCÊM ?-‚0 !Üž%ɇü†«»E¼‰˜©_FU|Ñh¢ÂnÙ›½7-Ʊœ¡?üà>í'„:öÚ˜cB>‰#€ˆo™mÊ×~ÐÒIì{o¿…“¹žQG¨[!ª=r·ÎSk©øÇ§'„Þr•ô1Êú:$£ÈN¬K‹Ð–(SÈÙ¥;Fz(RU¬‡¶¦¬¡ÔÅ”'ò°ÜÉ[ÄŸ"o­mA S÷)!ÜSÄSÞW*Œc·x(šÖ*uÊ a ™)iD¾íF¼Ð–¢ÄZÛœ° z'®*Wxù.”Ïsìsœ:ü¼ú•»–­²Ÿ90AÕÜã˜rd'§çVeì_B¡¯|íårÞUÿuœ4·ÆCÈzÂÄ— Ý«(°×Ö–!ãQÐS³Òrœ D€³T‡ðø]ì·D§[krzWE1È@Òg˜ʱ­°ëˆôÍ]òŽCª6 O/iÇ ŠwWi{éQ«ÞÜr¸Z­®íA´ñy”°ô…¥š;D™ûqú1œ €‰(ÇüêÔ1è{‘Ì"eÕŽ(ªôXŒôà.åø1†`—SÀˆùx%~ü§žß>Gù“'Žî9¯5#þè™ú3„—Åéñ!óžùÏ<’“„æšö!fâÊÏ0ï~b鵯ç•^ '‘1iPåCÌkÜ´e?O™»r>8cþDX["ÕyÛ&íóG·Ïܘ2ßDÐS $=©Q˜‹ZŠŠ Ey»‡?šKSêP¥«ÓcÈîr®¢-(Šœ?z„€s®œ01@œs¼"Qxx=+9ϼpç¶;²LçÊôוõmœ)j\S†”ÙÀQ¾DºBéCj'!À5€+ô_Ѭ™6s¥Æ1"Çåô2‡¨ï°Ž6·6‰^@ êîAÄç˜Z* œ=òDO`1|sê"œ<ç]’þ Ƙ%q"ȧâŒꬣ@ ûP‡_€@@àù@`ñõó|´%´" ú~v#+› ìÝ£JXÀ!ùÐñš/ÕVØÈ!,Æä,Z»¹kµì¸NDT/ýe»Šê¼¶±nÉbƒÒP`ø¢>W¸3y­‡ÇG#€½í^àRhgD¸¹µë/Ú—­þøƒwìðÑ»¶‘O‘#­pa»7˾Y7±ȆV:WB!sÈç¨C"„näýq´È†œ¼à}»ÁG"Ü}ôX„O€@@ ðY!àwDb xh#|„šRyZ"ÃDLñ™”yÚìãÁ‘"·üdCßO¿ß§÷¾"È"r˜'¨_•Ï ²½µ¯µþ€5IÎg©J| òQá¾–5ÈqJM• µB·K½UneŽ—ª¶Ó% äV–|äQr•=éÈqIòu+W±HBÕ/rxÂg*Wå(ü»ÈÒ$¤«ì$%Ô´@IDAT‚ äÚœós¼§FÌC«¼¸×ÉNÈí$9¢Ex+‡²Èüþ0åäb‚ºF‹RXË) CxmøE'ÀE$fèÛΕm'Å8Šü­ãjŸê/QiC7Éõ^­Tè}|BFαSD²Jq«cÇ”…`Bøž=k阇¨WŽeÏ‹ i§>‹BÈ&°Uæô_!ó•s:)ûGÄ9ÇÀN'éE¸S¶ÚE-öøý÷ýZèð㨠—s>÷k$Ñ¿9ùžá‰Qÿ¦mŽƒ´,JÝD¬®¾cŒU‘ôIŽ—ƒƒpö¾3¾gØ 2u™(/¾PÊAˆw<”wówIq6¶eÈæeÔõ5Hs©®‡¨•Z±ÔCHí­­mœËó—Ì,úÙB©¾÷Sò†_6íŒä]Èä©úSœ4˜D Ë“]}Ph|9#0[xÞ£BCPË¡²´ ±ºÊxH¹žµe”ÓKÛë¶²cA¿‹cAÝNÏÎèîÐò̉—o毑K”Ö÷÷ì=ÔÇë[›¶LníÒÊ nÈsrÝÇhããû°ßÁ¹!ò×-xÚ2g½Äg¨hÆœÀu„²ùßüÒ¶mÖT|Â÷–Æl'žK|ÁÕInÎ×÷Úþû÷éå‰Ç`LnsÍkÍ#>f<˜Hœ£y˜¤¬D·ÂôÓ41Ÿ&8—p„Öšogw”`MõÈSžÂn]b'ÿœ˜ŸûØrg-ˆm9/è¼ ÙA!äå 5 ¶Î¼mÌkú$bÜçï©Ì äö<úÝ!¶5k›9¥h Šf!!¤ÿiæƒW3Ç8x{ ð×ä@?»ô%ªë8˜óJÛ#B‚\@Ú(Þ¤( º~IÝv~Lt”ú·n]GÔ0³#"ÔB× " ¤p´tZ¶¾¹ É~Ó£×<:À!…ÈCÚ˜âo’k®víê–m¯/[£Ùò|èj‡†!耀@@ €@@à7! /l,Q6^Ø<‰“£‹Ð‚Smì`ÐúOŒÜmƒ6OlXdªVެÛW¿ý_Z åùÇrŒá¨sˆZg%š٭Á úç^Ü¿© ‹Ïe~k»˜Âx,~ûÓßú—JøÇçÉ8Ç6vãÞ7Y8B¯?ÕÇ“Šµ ÿ¿i³[‡ë¡¶-zŒ±«ÐqS6vjä\^]²ŒæÆÉköðÝŸYçìB¿Z޵D‚!™Ü„p”=Üï[¶À&S•ʼGfl¾‹Mûïá€À玀î¤nÓwÿ€MùDWšÜ¬K+Kúõ Ua‡0®cîÉ”§W›ú"üÆâŸÜÝ|îÝ <÷<½ßÖzŠEÉÓ,EoZJ\HA~Dîj½‰ v)¤\”¤Ëž ™µ:ž ¸ç†(„Ø“ît b‘pïN²AMiuö!Þ;[ T¥ä3 \*ë á¨Ebë'‡êU9#1BŽKžK~dåNBþ¥ˆ*¥å-[Êw…zÎ’w[d¡H¾””ÈnRÊ«©m?‹pßÏóî|ËÛRÈ*'òÛl¤Ðä­sÈóˆ@Äýìí·qp-’‚Â2Q'u±Èm™EcH7øaÊã4]÷¼â ¢ŠRQ¤¡lF9(„½Ú©ëØ„F ³“ƒC®pЭû#Ú®ô_3]ïT¸ŠWݺÖÏS-e:6êS¹"9@×G©ƒ¥rŽEˆ@½Çû‡bÖ!³tr¬¢ØŸœ¶Kµ®ó"œ£Ït¾ÂËO)WDžºñçã̸c¤Š$•í*rw‰üÓ¯¼ö5Ú ‰Ió¥ ¢\ù¯€@@ ð|  /MÝ…G@ Ï2Ʊ©Ü°ÎØHŠ“w‚³²1"à £oæÞÙ„#ÄÌ¡ŒÚÜÞ¶—®ü©í¼øŠ5Gs6Ø ÂxËæ}žbÃc]ÄúÞ—G·ÊzõäfÀïøåö¥lLÁñäã§Ä¹Þ’áýë}gñ?¥^½ú¬ìÚö‹:þññ¿¾Þ_|¢rž–ëïªÞcï€Í)ðéû“‚ùèW>üc?oAèg±b³H+z÷ʉ>Áèg‹è%ÛÝݶ{?[Býð!ذ9¦-ÂEzÇØ(‹{8C66p¦O >H¥p~då:üuõÿÊF…7€@@ øL³‰_‡LÉ ˆTnÜË ò˜r¯µ².2ef-ÔaqԆʅ:ä =¹”cs_ßäºg€@@àã! {`­­ýG÷=J–Ô®"~Eè)t¸ˆd˜-±…ÚYaÉ'üD` E»ÂZç@¶ ­D²‹`óœÝJîU‘IÅz†Ss¢Yá¥uL†0Ù"ÏeèèŸÈ^‘ë²t¦Âs‹˜£D–Â]¶–BeK½-¢¿§ÐÏ~0VmÔSýZ˜RÙR*ÇŠ(LA–)÷qåu›@ŠÕdªÚÔ|ôîÛ\[‚üC…Žã­ú®>¦ —H¶eƒE!×”#YÎÆ}ò +TºrzKE?¥i®GÂO*g…ƒ†Çµ"¤¡ÞS¿æ"Ã)IŠPtë8“?2zˆj|&LjJõiº2w2TøËfâ}Ê|J‚Ž;"=9H½å³,v© VŽîAr“1Y©úôÏI}oÇ‚äÕûRÍsª¯*GXñ˜±âƒ¯Gô¥_?´ÿÍkT¿=0«ŸÙ½#ôýùî7×j¤Õ(Zb²I:³k;×°‡·P…W=ÌþÁá„ùc{øð<¼ï9³sÅ’ePÔ§“¨Å—~[„´°õîzÛÕ»‰”ñ8PˆÌ÷¡fŒ^^‘Ü"D:€fÆÆ#¿:sQaÝ DÐ\éßiôìã·ìûý÷V\®Ø;wl“ï’Í5B½_±Æ$-ŠäfëÒs®ë/¡‚oÚƒÇ{vyZ¾ºmUœ3V‰$·}ý¡á›öøÝ÷ìÞÿ ðà#m´0–#‰Ú„ó óI¹ç©žPâ}>]Üz-9Lp2éñ™Ö˜Î¡Ëô#é«Èí,sT^õYÎúš"§“¯ý õ|êµk¬–]{¾x_ëVËAsO6±Èt­Ý„œ%X+ªS…{D<þzýÌ/êî˜B[R¨ÄU‡ÈfÍ%­#­A•«(‹6È¡ƒÒžÌM=ÒN•§²Ô'="r\á߈ùÉÉØòcá=ÛÙÝÂÁá–‰R@^u°SÔ§tÂXót‰q¸p®Ö²"#d Ë>îQ?› SæHŠõ•§ß*CíÖ>Kš>ÔÏI÷@Îxäõž·]s8Y(ùIãÄŸ¥ìg k~Ô‡„?‚Ìg^Wp º_øžty€@@ žd°Ê”©M…÷bKƒP›CŠ›¦P‡„›DÈ™aC&—°ÚZŠ›Û~4)´¬’ÁHÅŽ¡¤Ð_/WÆ(›$ÚœÐ=€Ûy¼Æöö'…:s‚¢»× VV“1(X†© ’÷½¨6êS=T¾¨ï§›&zõäMž<9Rçp®6oT” tÕ­Ö(Lº6#’½Ú¬RÉ‹2yÊC†±Œs•¤‡¦òôÜßY쳉Àæ»Vîýα«EÔ7)fô åJ’Б4BøªÞð€@@àÙ# ¯`m†ë ùäôÔþêû?´k„PÞ"Œ®ÂóŽø’£Õ=@GE¨ÛCáwßv·¶íÕ›7üÞäw!Ͼ¡Æ€Àîµ¥n>=ÜsE¦ÒR‰`V$‘`ºŸ¢ —C‹ž§ =FyŠÊÔ 1>Ó_‘ݺw™§Ð亹æ©Û錔áqÖñ˜XÏ®NU½ ‚Ov…L…ü–2\¤µ—ÅqRáJ9.U­H`DEÁ"ÉÔ>O…n›È®¢BUáDŸÈ3•#âPDâcEªËq&[ickËËJ£ .— ¨Ÿ3Ô!‹ò¥S”×-Õ°·SV$lŒ&ẗȳ¬¶I -k1©:±=\eL¿d³©Ý²‡CrŠÓ·¶"+ÕÿE[›Í&N¾(§QòJñ빯iŸÚ(BV¤§ˆzõCNÜRùjœäP üÐ:G6Í€:œP•}·¯§«üÖŒ‹Ôæ:³Ì¯»®D–CýTù"iûýùÁmggËjKK(yÛ¶³Q³JìSò€×÷Žl Äè?|ïöw8lîlÛ×ìö­íkßúªµ o¾÷øÈ„/£<ÿêÝ]é=ûðÞ}kllÚúµK•ÊV¤ž^~Ùþ¸û/í­}ß=Þ÷y¯I§t#šoîP¡1P“ÆK*r9oȆNðžÂÁ7¤5ÀÙ@s)ÍØ¥™oš3)þæä”Aû5âL׊®Ö%e€æzâØC¾Sžœ܆ç¹Óš»ÂTèŠ@öáe-ðÖÚÓzÖ\þøwx[e”ÇqAÇ‹~:‘Ê?È|­g9Ux{Ÿ8š³¬UrÁkôQßÇ!ÈËœ |²6!Ôg»P¡cŠ«` ôÜìÌÙKÈs©ÐåD±²ºnËäg§'Öê´!Ö[®'9©¤éÓÊuœ2¶6loïÀê¤Ñ“¢>WÌ1Žßä=ˆVóÒ+·l»½e9æè¢áŽ,ë›+vã…m] ¾òõ"¡ü/ìB}ÈxŠL¿²¹n/ݾ Q_?;Û„ö¿À!BŒÂ# €@@ >7Øeü:i./r€lž`ÀʺbˆG£]6"†l¶ÈÛÍ~Ë:q‡M“–‚°Uè¹9ç/¼À²㟷1*DµlÉsÊ—É«_ 9¨ÏEvODóž¸{b7lµ¡L¾A##VÆî"i·>×Cv+´:¿†øÏà2”)TEòÔd¢ *mêhGËÉn™ÿ”§ÐsXô~¬ÂžicñXú2ÖUgDT| ²âäDTÞ:•“äsÕ7ÆS<ÁûRR¨æEûÔòch*}‘J<Îm:äê—6µ’Ê¡ÆáÚøë±1"b½RÉS¦r¶±¹û\›ê(m‘Š„­@ߌÇr÷ú-Xô"ü€@@àÙ!ÀW½îœTÒÆ¿B¹.W«–¿Q°«×wí°ÕðЬcÈ4äV‚û¯:Jµ.al‹„w–JR÷?ª˜û‘\á| ¸—–zW!ÛÝÆàµnÇE(I9Þg½±²ÜÎÉ1 a»Pž“Oö. §ûr‘Ùº—íÇþá)ryÞU¾ñ±ë VŒÙ6 .’Wäš9Uc}w ¦ø¦ jñþš6ÈnPrøUÓ¹ê %÷÷ e.%0Õ;Q G‘ U­®qÚ)BNaÈ:|D?Ö67pô_ÈŠ¼Æ2࿈C·h׌œéR×+§³ˆÑ4‘¬”¯›“±-¤Ç>‚ä~Jô«/"Õ?ÙN"<“ò¬ÖÛJf$Ð,HV‘¨ÊËÌ9"òužj³l*·é“ì]++lEÏ/M[’ª²ÉDªKùO1núÚ¼ø·S©ëÕFϹîeSa>@ؾ ³ ýÕÒ«wíÅ»¯Ú4ƒ=Kér.eïÝß·ãwîÙÕÕŠm. ^“œÑà{åîK¤ÊÊ:Áù·8A½÷Ö›ŒQÄ (}7HiF‹4ƒœH‰œÐØÉneli™ÿÈÞ‹@~»ŠÙ/ãúµˆ< ˘ýÐsMö¡ÈY+ƒS¥/À7éýä¯J÷TÜjC§Ù·¿ùî÷ìÿöÊ×^¦íwìÕ×^E¹ß³½{¬N¤“¥ZÉ–ª/Úã½S{÷‡¯[šüØ+•š¥pœ¿¾Âs³ïí=@ˆ^PÍÌÑ8Šò ϯ4´õ4{£Óä×i¾2Q«æ–¬•-ªålZ7^knˆ°Žáœïs7"Ì¿vƒõfr*%®1Ô9šZœª3ÁøÉ×ZÐ瞟óZ"[ºß'L=vøˆctŽÚ•C­¹'$5/Õna¥¾yÄ b"h̘}^–¾«³¨ðuL·»pÊ(áü±Ø'PhwÔá´Uûjcœ6)Oü„ºäPÒ2y(CŽ1ö4â(¾3¬Á0rŸT(ùSÌ=#"ýüò’ºäH1f^Aæ“6Bùâ;8ôû|BYº6LØ'ª_×V°`ëó¾pã?Ø¢R'ZB‹èó9¹ä%Œàš¤/j‡ÆF{=ºn4{D¤ÿá€@@ ‰ÀÂX‹Èú‹’WB[ùèÆÀÚXI¦rx…³‚Á…4O'×y߉ÛHõ‡G@ Ÿ)ºð(Ïú>†4*T+výåQ˜uìþÁžß‡(×±B¸öz=¿×:&7pEáµí+–+6˜÷¥Î+‰F #ª*Þí}áÖ«¤ÇºcôílÿĪä†~€¢üâÁžÝÝݰ"ÑÕú½¦5s»õªß kCdþÕ_üµ}ÿ‡? ãYÔ*µíÒ¸¸I§Öx›t]VŸa¶U/ý¥=4÷ô›–ó:Å8y›y©cÝVÓsÍCœ&4;œ<ç=³lí*h·[Áˆ‚}ܤöW}žCžcpLc“¯¹3õOð¦½÷æöÕo~žñõ¯Ø×ˆfF„“{ ˜’ã,ª°gä-ÃÒKÌš†ïÛ°‡ñÇË/ëË!!³ö2ØØ!Dš6•"Ž2ä2x}'1¾åÞE¡!nXaà´¹â$8L»6³Ä?'ð¶Æ2åùbÃCƶÚËvŽУñbÓ8…ñ­vK­Í^&&."Ðé”úÕç87U'ðºNò£M$ÓÚMq£™Âå‘MD@2¯1Nù\ê‹!›lÚߢDÊÐf‡<ÞåuN˜Dö.䕞f7A^î2Fù˜ (¼ÊåOÚHÒ†ÍtõŠJâ$0†`OB¸kK݉oÈy áºp>PÔsKëV!1úd2°”f-Þ§Ÿ="âŽó\ÍO ò¨)T Ô Œÿ8?¹"3Õª›fù/÷¿¬\óá¥ÏÃ# OÝ·è^¤…²õ1dÅ%!‚3é,©UFVGm¶thM…tÎWÊ|WsïÚæ ר׭³¶bïïC¾qÅ=Ñå‰LÈçrä2U_Ñ áüS°Pȗݳ—È­{óh\¿zZCº¹+‡UÙ>"ÔË•D×"§¹È5…”Îe.[F÷ý±"ȤX¥ÊÀ"à8Ý— «Dr.HQÑbÛØ"²mÚ]>ƒ°ö0ꜲDÔ £µîE>ö10Ô¦ Ÿk‰g!îR(gå+D!¯¥¦.ðžÔ¦²­r^í’WŠVåÕn´!ÕèÓÍ›×lÿøœv.ˆMO3Åu¨ŠÚ¸ˆ¢ZW‘×zÈNF"eåXÜD©œJe ý^¡o„‡H” Vïy˜{µ™DÖ‰ V[’tn7ñ™ç©–‘Fÿû8ˆ ›PmÕ1jŸò©ç3yqá¯÷!Ç鿜¦EÊrð‚¨ ÷k¥«€9N6¦"w‰°ƒ…Ι)Âýê˯P>dðÉ©“’$mçdB×Gû¶³¹‰Í±Ëóºm¯/ÙÁÞ¡õí+77Á$c§Ç'V€`üö}È)ûÙ[oÙwÿÓߨÙÁ™U—W8W]ª.Rü{ä6I»4ß<‚Ÿt\ùj»+Ñ…7}JbÏŠ4÷¨Ì!/Èæ`)‡)†ûnBþkHgáÅ`Ü––í,Ää@Uï¶¡æ#Ï…gŽízõƒïýÈÞ}û]û“?ý޽òÒuû½Õ{|‰s[Y.¡ÎNÙOìtÿÐJ7w­´¾n76Èß}ç®}øö[®¸à´1Á‹Cv¾Ê®^Ú o/æ:øË¡ƒ€îŒQ˜k>ø¹,åxTÏ5w™cQðоƒºÞíÉWžôÁýt<ÕG}âóLEó–?¤÷¹Âß9ÞjêåÜ¡9Æ“öŠtÇ|ÁlwÌ嬡ù¢¡‘cKûÞI(KNóªFöµR,LpšÐ¾ƒÖG’t qú-ç8ÞrÌålãƒÅo­ç~¿ò~b_ã[®ÖüúrQo‘^Ø,ó8„½ÖŸ=¼§çê_ò;•YYÖúù÷ 숌ïã\cóc/O)rôR½kJ!G9%¨Ÿ¾žø­ý–©ðáý eF“¨ß/‰®à(†_€@@ €@@àwÙ’2˜ÿéCÑ ïmè2› ÃJ¸2~gƒSBƒ+< %ªlâ`xûžŒèk GŒ.ý$ t#ñ.F¿6y&ÖhŒíõ> ).“ú‰Á*ÏkBöe0¸c„yÃæucZÆ¡Ìü8›<ù!y­‡¡„-SþA‚nŒr’ç3gs(K¡| \9mÑF•B*v0yê›0Ú@ÉadŠ à1®:r„CUT…6­´“ÁÏg2êµy¡‡ bm ñ›kÚ¤`s‚Íy}‹P—aí¡Ó0¢•g|„ÚAj mnÈ U¯ÕÞyõ´.‡üØîãàÅ{2fÕ6åL02ÈõZãÑí¶ü|!1t£gw¶äšË ò»º_žæÓõòŽŽx(ÃélhÆSõ’_ãÄÖšÚprŸ „½oÐ'Ñ©—ÿÞ_דÿz'<€@@ ø$ð+òFßéH¨û{{ä:ŸZƒü§G{¨UÉOÌ=D¾T´<›ûº§Ð¦¿“1Ê[ÚlÛ~â¡æO=ŸëvuÅŽŽŽ ‹[°Wnßb÷ÿI8\¾¾Ã# ø(ün»"Ïý?n«Ü"s+Ì=1N¬=ñx–(ˆ*ÈFݸC@ÏQl‹(\5F’î¬ç² °QR„C–m!ÂØ•ÍðÆ"±¸×WâÖÿQ†}!»"¹§{ó1yع*É}}O¼zZʺWOr^rKÄ¢Þ‡Ó·(mŠ}‚ÚvÊAÊ#-\Ýñ‹ÌNZ޾sŽmµë(Ɖ F9í¶×]‹ôa-¢;tõê¶e±1¶ ý•j‡0‘cð1לJeÉ*ä WŠ 7Ôè¢iˆ”í£~Íè¨c±€š÷T™”ÂØ~:жw¹öMéL–Hê§·’p¡\—’[$êÂ&s›”s¤„WØv]=¬;Å.œxÂCöŒÔ×Cœ’Üiñ•ÃvšPÙ¹bU•Ø ¡ÊEZ¶qX*äsV\Z¶ÊÚ*x’«T²ÇûGV†è}ñÎ îPpvqa×v¯ÚÝWnã„жÿóÏÿƒýðï~dÙbͪ+ë´CHµ3f”/rVöŸl¿”¨á¤6‹†UT7éåÕgM<Ù“ýõ<ݲÏåX-³Ž£ù\Ž ïïyeOp]`.Ü5ït®>]„Áw²à4&úÑ)ú±/œ²™‚¦µýûÿëÿ³‡ÚŸþñ¿°›¯¼d5œöíY­”±üKWíÍwÚ[o¾k[wnaïÏ­†£Å«ßþCÛXY!œ=s«\v{Vm2~‚Ÿò8s+šìzõEób,‡ Í)>—M¬9¢¶ÉÆW …ɈÝš¸Âi¹Zrg­!ÍZ¿oÊR_år8×\•]«æ4/|¼™M{i¢h_HiƒÖ8˜ƒµåBHã¦vé¹Ï=Ía柊Ñ\Ó¼T¹rHÐüÊãø¡ãu¬GNàs)ÔµW §E}+½³œö (ŽqdG˜Õ¥šzBXc”­öù|ÑUùr´Qza#ç •'Ýœy0`?`6I²ß‘眅“Ê€¶¨ß)U¦>Þˆ(/Ë^‹:;€\×<òq§­ºöxô”ëJ£ `þÿ³÷¦ï\Yšß@Á}KîÉÜ3¥’jí®jw÷Œ§Æ_¼ü“ãÏ~æiÛcýÌãéž^ËU’J¥=•ûÆ}_@,éßïY­òH-e•*•)EHL‚@ĽçÞÜ÷¼ç¼Ç2Ú&'Ж|Ë-[ ·ÀËb¿ìüú˷ܹr äÈ-[à뱀ο_Qîiyí–ý ¤X ®=¸l@_¼œ’ `‚wˆFVªÜ0êöiÚ]ã™laïYåÍt¸ …À-’u^|•vFxŸžXÓÁt„ƒaéÑS2ª7«€ É6š2d;£©u„T¨ ®ãGI2fuèüäVɲ0[—5Ê%ë£ÑÄ÷ãpiR3¬àìB<·ø¼çkD/Kô€Ùj/‘Ø8„ôÞ½Ò~8hx_ø­T*ð•Èp>iºÊ§qí¡‘Aœ+Ô$ĉaöù)×WO’[ Ç€Î2#fôѧCSSeДeôÃÖéÛÝÙ C+g?h{Á‚c7£ÆwqŒWˆ¯5n¶‹Î’‹sµ¸o¦é ƒéúÄ(ÝWÚN9G0YõÇÇåëá èp|©š‚d©PŸ°]\·p¼ @þÇØ}Ž£‚O—oR^‚ÿû[g€› Ú¹þ ÿæ'{Çwó-·@nܹr äøÊà TR¥Ÿz¼ dò™ÉÙ@é§µ·Ÿ._˜†€*’Þà+ßL9¾÷ <à=e¢uÐÏÍͣ؃3~s'ÕY{ @8Œ³ž¸vi1Ö,fƒJrå[nÜÿ²‚$b-ÿãÿÜ 1!ÞC n‰Lm\-Hˆ€Vï(É- 3ÉÏX“ÉÚl*1.Ýéí=+±&VÙÛßM-î_‰ê6ÙÔÇŽ®¥%Ô\I›!ê&'Ñ,¹%xLÀð0õ³U¥ê«ŒŽM°öö³dÈ÷øÉç9\ª[³¸IÍâV«‘FÈf5Õ…ýLu‹ dµ²×b´ +­¾±±Štôpúèîƒhƒ¼öÉ bëÁ//­ÅsJrQŒdÛ´WfÓ‹E´•™¿‡Àg^CÒÒ,_ë@÷ð,®§qH~I]1]ý«Ôi j­ÓÈ*_Ý@Ö¼(mu‹éÙÒjº2IP6ªR|aa"°%Ï/ÎϦ‹—æÉÆ~˜þ¯ÿû?D°“†ÆÉ:·Íüç¸g…ÀÌtVm@‹JÈú.-æ}qx‘@‰ Îy-‰,¡,º23Z|Ùã|–ÃÙÇgØç³¿ˆ*›óÊIä',Ñåß=­—±Q&.),ZÆ>àO å‘}Îë Q¶ ØÈœçü^‡yïÜè!äÎG÷ÓÞÎ~úéŸýIzýÆ•t±¸V—WÁÏ•ôý7®¥>~6?½—Æf¦ÓÃÍ­ô£ëWÒ!éÝ'X> PŒmsN8ð÷ÈØ0×­2¦Êµ[[Ûq @…1²ë=¾b^mä´ŽÀûM#›HÇ£Zϼ…<¦ÃÆï9›GšØ,0L(½‰ïµK(8µ¿ß‰þÏ-g {á|B[˜í®ý³²œÇÜ?esÌœðP¹.#æ¡»iC'ˆw®CßNlL–8mò¼H±×ð9xŽŠ¥ð`hÈsTáh«­móe^X“|o›¶rÏðÎüÜû'ÒãÇ÷i3ÁñŒûy0÷ˆm¤;ü †G€Ä<¦ë%ˆs|*}àzîù%›5¤Ùi‡A4›ö)¯d9A7úEoUYX×/S 3A6±Þ©àĬ¦«—!ŠFd”‚áôù–[ ·À—XÀ{PâøÊÍkÜ—È7ƒ'̶ö¾4[¸!m°K|dЯëxׯçY´fÆ˲,ùÉ:<ÂT¹W%(·¶¶¢Þ¶õ…%­Å®ÓÍôöI` ôn—L]ï[Î%9æIb×Ü–´€ixj” }~H”ù ©“Yjöi…ÇÖgôò~Œ2@‘Xp|Àä~¨B²ƒ!{{ÓÀð{³»rðž´0¨!eí‡?æyD_9ïñÑ^´_¢7j{“®,Ž ….Ú+yÁ€£i}´)ˆ»"ÙÍ|ñ¼ƒl䔯’uDîº0X r²B0±A UÒóU0SøTHþz®·Ê” G´Ä/âüE‚?MêOÛN1ìÀà Áߺ" ÉíÛib¬ŒSH+ë›Q2«‡¾ŽLLAïùݤ@Y7Ý÷¬¬¤[çý¨—mí쥅٠izn2ýÝ/ÞJÿð÷ÿÈQ•À¹Y°RFÜ;Wޱ·Ö62H|¡±ó¹¦jZ"ð'GÐF'°A™:_b*ÛmY1?îv^Gý|ækÉn´eŒ5ûªüø·»¿Ãn¼Ö†HêSðª ÍÀ*猤²s^µ©\íâ~ÿ1kºòukc'ý—ÿü·»;HºßJ ‹—Ò&5á{F*éG?¸™îÜšî./C¨¿–F˜ƒÑ‰433O¬8õæQ>ÏJôž€Íë¡ Ê%§±Á:>…ÔÖö£ïÖïQ!¡©ˆ‰‹ôÿÄ Ú¬ô~] '?ź] {†$¹óŽ6«ðàMX¡ÏÞ¿f·7QaëÁaÙÕÄõú PZñ´­K{+3­½ Ü/•)§b;¸×-!n€Jÿ÷k¡ÂüéE朱òok¶ÛnIî2½Æ9$¢-s§â>Fú¾ÙÚŒ±ó¼ú1 t‰2vµ¾4¿x%­­®q1qL–ÄrˆElš9˜NÓ0™åt„C¯R£ý]k$–SÃÏÕofFÔk§'f¾sZe$1qL“˜EŒ‘ó‹Ñe'î8_ÇßìÁ.™&ûÅ_ù–[ ·@nܹr |®,%ÓdÚÛ~˜F§ k X.¥]ÖG¬qPÍQòµYhëòž²PhéÈg=¡óû°“ÆÈTíƒð+°&ʷܹž×b‚#Ö°*;±>gñkûšRßlÝX£›y YÎq³ŠO\÷KBF +þ‚­¶ë°Žެu%àÍ åX7+µ,!Z†|S:Z’Z,ëfÕÇ`%ß r% l@Ñ>qA£‰ÆçAo\+»žY»’v4U#3¹÷3z”u>åú•¼ßÞZÌï¾þþ5>ò9Û$A=66šæ/Φ°NLwÚÜÚÉöUR±E g•m£ @ w ½óuÏ5ItŸaRÅfÜ{¬8->ÃyTï’ÜT–Z»— ù;måÄ%ò³ ƒe© p<—`V1¯ç“Í”Q“´ç™Ø&л 6…t:E$ŇP÷(§gëÛéÁƒö×xžÞ¾w/äê¯^¾”ºŒÇù}d¤{ÝM×g'Pè…øwãV:9ØõÒ\Çs[NœÍ¸W i äè9m(6AßO"¼AÜfDæ¾óƒ{«`­è{ÖOƒ½|Ø+þ—$ç}‰h1u—ñ´Ýnò 6 ›¿ÄÌYÆ5A4Ìû5›§(G¨6§Z çÑWà˜‰cým0‹÷´¶<&ˆFDÌQÚWª >EcßlÌC Ï  =B…‚ïiNÀ|®¥¥¥ÍÔÀ§âx7 P10Þˆ!‚Þn½ñ£ÀþË$LÏ’}±¾ ™~Ô8d^ÓnÆÔy¡ß£âƒ¶1»½D„Áúr $9lbÞ ¥<&‘¾ Ò Ÿ¾zò{„I§¯Åz*1ô öA¨÷c'Ÿ7C¡N±ƒ^EK1ä:ý&·³ûò›lB~íܹ^" <ï3áy‰ºš7å[hïÊ|ü¶ôóÕÏ;ïçíûÞy—¾A 8ÇÜÀ}Ù¿³?$Ö%Ï}Ok†²S^+Ù-˜7JÝhÿ‘ùJè5G€³£¨¿·Ðh ÿç>ïïoÁºYÞFh'knÜZ€å*uåšDÒìP¿à NM-œ>½}ÇiB^`¬ìÞ²æ‚_Qv‰u®QÔ’õmˆó¬S€LNÇá ÀEmÅa Ã#Äw&§Fÿ¸¶Ÿ3+\àè¹t´ÉžV*ÍKÐÆ¹¡ü¢Rƒ=OA¸eÁ­e(×yãyèm3«¤§©v¯?m®c rºÑûF¼—ËöK¹E3@Ü7ŒãH€n´»Î ,'-¥èq’‘º/q®ä}8<¸ŽY5>s„ýpVÕÉUý:b:Ȱ“)x×á"XîOO¶ÒÖf‹öÄX ã‹am@PÀ.™÷wóZɹžp†yñ dc©“j„: }8àªÔ‚cv$ª«¡>:Ø…FáTÀÙÀç8B›0t/èp‹gÓ˽瓒ýìŠwÝçŒãbßçÿóeïþ§ò½¹~×/r½Èkýn/ó¿r ü~Èçìïg·ßçS_jk¾Ít0>>ÆÚ¢…ªÏߥtDvgÓµ ÿ"Y\!{ÕŸ½õuˆ¯Öl‡ijv–Ìs¾¯÷ˆ¶Nlö½ë7söýûû´:ÿÌ·Í_:¿m~Þþp³H`P\bÚµlUrLä:]La6§$–xD\Ð%˼B&·•õÊ›d+‹©2™gÖϦ¶Û„ä6Ó5#>YÏŸ@´BzÏì*Fh©9μË:_Ì%ñø…çƒQ¬í¦e© õYËÛΨ«l ö³ëIÎ5!ƒOÀ b ãi œKöê`Ų™×|ÞhW±çP­joo/ÚÞ?8ktÍæ9U¸jƒWÄ‹–Íê7T¤=>!•¿Í¬•ôïVÈÌå·xIœR¦?A~BÖ‰§ŽÈ„õ*{ÅÆ~q¡Ñb‰^T7 §à'³j X[”è?BíØ•óƒµ=¶‹9´¥gdÛsü<(£2òË2n¶>tÉbs--?‰€ðS2ý`ÞoD ɹϸ \¯#]šOOÈB¿ûñZºÆë™É¡ 1GÉ »0‘~õ›÷Ó?ýâ×!õžÍ ÆšvµSŸ¹ñÜ5°Ø×ô7ÈXÚÙÁARó] \åјÛ‚Ñ7ñøo±Ÿ |Ë1–Uó„Î/,ækÄ¥¾).¬b3í]ä’;}ü¨¥ºB&ÝØHK…mÀ¦`8æz—‘xâ×¶#ÚÂ{m‚>xï½kdžÿ MLϦ52óëõjº¾8›Þ¾}?}È5{ùœA ãã£i˜öTÈê?ä:< ¾<¤ù8'*ܾ}/p©%׆©1?9lþú2÷ß!vHƒƒdvƒÅ-›æ|öÞó»ÏùOi/ýå>´ÝÚÞ^9ÞüËŸª;pXU;gö̲±½ßàýîÍ^Jจ9&F7`¦b¶<íî2 ^$å±!ÿµ/i+æK%ƒ*õT§D‚A –"0ðÁ¹ׯv§Ü‹âõ6ÏïmK¹U°Ÿí.qýGøU¦Q4¨QÞòmJ­’¥ÿìÙÓôÞoÞE¹b¿ôØ@©{ƒô?3äCƒyäùª´S¿ˆÒþ«Åôö÷áC¨…¯C%;ƒ%Zf§Ó:ÛˆÉbÜ{ hðÞöç,Á±¶™ös&9¸¹øœRoUÀ/àPëyìSÔãó(Àc[w_{8#³¯mœi‚ü~€·‘ùY-v f4xm56bÜñÈÞ$ûâôt+­¯o‘‘®$™öHêÐ0SÄ,xk‡HÍÙ_ë"–Éh³ž]‹ ˆ.&;j3¯ès™vX×]3ÖqTh ~öóÜñâ<õ|öY¯BÔ¬cÏmù3ó‹,“ï ¼Èyô"¯õ<6ÈÍ-ðEÈçìYæëßÿe¶ö}õf.Îͧëü|x÷aꙂh"ðÐlUkÚ–b‘DV%É1k¢&DÃÚÑ#Þ#«ìb:ÆÛéÖü"™j5 ÙWï×ßüŒ¯¨¾l¾¢Ýúz›ÍâUÒ×€b±à¤žYsCýižlsK‡õ1Æ”ÖxçýÓþOÁX‚ÚÀÛ$Ft³mAî2g¢Ø‹—1>–7³Ïn~Fœ&îtœ[¶ØÜ@ð^‚Й-Áh>4œÆG – S—I‚&«š¬h‚ÒOhsÔßvÜ "qî—Jª7°‚=TZ,7³¼LVµ÷€ª —d%¸O™v€aŠ÷ |9á¸Je ÈåJÁÎ'ä-Î%ÍñüÓƒ¿B»zÏ3¿)ƒ¦@¼]B‘Áñ6P¦C=4•alƒö5Xÿ¿Š¥z9¿óå¤X#Ó{(p·YíŽg‹çÃê·nÞâºÕ°—Ï“ÝÝÝ´ýè÷¾~ }Ðè/€È§­’àÎé6Ä^úkvvvh¾žKËki˜ó3Ç Ðk„Çs†ó‰õëÜÓ¶Õà˜¾*ôOPõŸÀÍà—⇔{N ;?ò-·@nÜ/‰ÎÖ/IkòfäÈ-[ ·@n—Ãà+PœÿŽñK€“=Yd¸Ž 1Ÿî °$¿oß%›ù\dõ“Jeõ:¿6Ž™¯mdê d}OGÙÐM"¡w NܽËþ.ŽØ>9-²¡8y$£%ÓCWFŽÜuõ~¤F!xÉd¥Ò®*u¶ú¨.8Ûçz}ë¶×Œ…¢¶[È(ÿ<Ú[Y1Iø&d·„·Ù×½Ng1Ùñe"$Þu_TâñêˆhRŸ¼G í4| ^ pêgt~d¶@ž6ÉÌïÚiöˆ [¹À¨ÿŽ£ \Ñ9Õ¥^Û ²–¶w¶Ól¶Tx_lŽÑAdŸ¬&Ám&{ð© šö»{»\ÿ(Ú!ç†m*8¬SV« ¨i+g›ÅYó¶ P¬£HGà^ N¼£€ÙLýªrnŒ‘’lÃcý€|ë¶{,õäýÊÓÑî¹t˜‰`í?ÇPÉ6ÉõÝ}ìÏ\€/!_‚ôï'ÀwyÃ:5ðÚwš½Q3Íàa:)L‡dÑ”ùÌ1µÚJ8ò”£+2aM¨RÇϵ]æîÑòÙNØ‚ÞÞ<ŸÛ”o¹r äÈ-[à;iø®ä QyÕ…Ù¹tŸ:©Q«—ïÒ:V ¥œ}ˆ ñÆÇS™ì¿ÖgÖCum¥òÍìÔdº¶x)¾wýÎ×9žo¹r <À¬Á­ßlƨa-'¬íW©ÊÅ{’ŠÙÛ’vbˆ&ØGÒÊ5­X î#l¯'ÝD€KïSJ8°;Ž“äs³<Ä>ÁÓÓÈS›Ùn¦²äþ1ׄöcý.qoh¥¶Áœ¶,å°ØŸÐ>gým NÏá•=ÞÀhñ’XÍM’½Ö0«·JËE"~9Fü >3ÝãÅ%bÝ ‚1„5¡Uÿ:%S9»¦u­ù<ÇB}F?•So~_˜¤ÍÈ_“u?Œ<ö™ÎçììTšœI[{¨¬µvÒc0NfÆm,/ Ý>“î=~’þæ¿üØu0‚ Ú.HhŸÿ ežÍ¼¤må4رÖn>—¥¤ƒ<[pzŽóoƒì³’ì}`+3›Û`MIbq¾×s”Ý$¸g¶ßßg°Šó|Î>Æ#ûŒÁàllê{^_Üéy•è Ž·›¾g"Ûsg*x˜W’Áfx&JŸ|x;2Äøý3]”å¶ÓäHºN°ÁÇkkirn&Uèšåwnšþæþ6U 1h`s{;‚¶>x¤9ÒíÖÝ´09šÆþÉòÁ¸‡*Ü bñãªô»¥àÌÌ›Ö dÑoa›CZœ’e1xo¨¨Pæf+ g½ç$»þx}m(Q­¾ãd€/×ðµ¡(@Ã,Wà¸zóóéÌÀ*:0¿²ŒÞsÌxV*ÏÅ2›YÎMÒ¹Àý§¬|µ§Î÷:joŒÆsedt$õq?oíPzí¥¹>Ët‚;H»û{éá½{$2P:lômJ³ðÂà‘ O¹²àñt¤ã^%è;$´"€brªžö™Ó[»¨epÍê'§ä¨*ˆÏÆ{¯B]óÏ…•¥eÎi?̸–Ò.×hA®ð|ÛÝIŒÁpŒ½vóY¸»{Ê 9ÎÍòMnÞ¬Þè/b{‘×zýy×x›=ϱ/¢í_Ç5¾}ú:ìòÇ<ÇóÚüyÿc¶=?wnïŠ>ï¾(H‹ä[D^¤%>o<òqxu,àø¹}vÎ&ÅkF/Ÿ;?ÀîËÑê'¨ soñÀ±Ë‡u>¸¼Í{d/#I¾½³…ÓAIv2Ì›dL ÷È8o ”Ó!tˆóçPR½S…zW–ëòz·¥|;d2 ¯mSv½¨7C<¢Ÿ¹FºK¾Û¦5€óÙÌ“ÔíZßÀ‘SMS8,ÖVVÓ­ÀQ©ÂcSX%H $›Û6´+D£mÖ{gMÇðÚj›ßHŒA#ê pæonV ´ DyO{)-í1±qψ+n2x5d͹¶YÃb ‰ÆkœÓv³t›amð“Ø¬ÂÜÀdß—Ì6[Ö~IXwÀwÊÎKNÚ¯w¾ßXÒkôÚ(Ø ‘–Ÿr>Öôâ?Õ§ ”6ÛV¢°Ç`YˆÅ¬>3¤5Ÿµ/þŠ7Ä0fˆÛW×ü»¡.ˆ0ƒØ‹{œm8÷ fÆyØëHzJº{Œ™éfj#‚ÍÛ”ñR5‹ {ìƒݶ¥xœ)t¨; >¢]–Æz¸õ”úÛ³`£1ˆÈã4ŒÜøäy?²×’·ÍÕõ´p 5¡U@›»8—öéÿù›äøB ^tÍ&ì'VÏðd6K„;̱f\<„ºýà °mÌʉÑnÚ¤ |6û^©u?.¦öœž'æD¶3Ôg¼çrv¾8qŒ_döó1Ë–Å\äZnY“´£Ÿq<ܲ6™ ]C›: MÏ>•íã˜lÜNÓúÆNúÕ¯~LûdšæÇÀüžR'ÍÏOCøž¤gÛ»©~q>mpí±«WÒÏ>x÷=æa9­<[…´Ýcog¾1‡ Ε9Æ:‚?ø\à}Úf yÕühM%@•„¾*ãÇœS’_rÛrr®ß½{‡Ð¾ç77ÒG}€={PÜŽÄ„>jÌ¡s¦Óh§mæª×í@=ŽëíCŒŸ`"ÝCU`p¬ëÐ{Õçhc—ñ6›Þ{}k¯‘6vRïÚfØÎù_ h…ÆD{v÷Rƒ² %sœ®\^Lo½óŽârºyýJúä“;䲯¦Âqú'W1ÍÏÍ"g·äöüìtºwÿQêåº#ÔûøãÓç»09™Þûàc¤ñÆÓ4äµ×5``|lŒk}š&‰Ø_Äžo½ýêŸVÒ$ù/ÜOó si¾ýÖoÒÐÈIše<ÖWW˜/å4޽Bn(®à2£øR÷·Î°:nt>{ò,²,v6·yý”vÏ„c®ÁøK̘%ã<ÃqÕK°Clƒ‘ÿœÅBóHgŽ æn_…(¦nçŠÑûµÈƔ󿂷ÇêÄÉ&¼ÀýüÕoo•³=ï­òÏGœ½ñ%¿þÿçù’ÃçíoãwÝbß1ÎKøÇ‹ìÛW¹ÖW9æ%4ãwºI¯ò=ÿeóíËÞ•þeëÛWiÏù1’f¡±ž1«U2D‚Hت¥ZpˆÏ°.¹yåj|nH @øŒŽdõ‹ ô«(ç›ççü*cù<Ç~•óåÇ|½p­óªŽÑ+ÕnÛOýáŒt’DÊÈb‰Y1Y/k\ÉPÿ–”S¾½ ™&\ëM½× Ì O’ÝÖ&‹Iü+ñ¦=$Ý­UîZ[²í„šÜ˜yþ-a0ʳ³ˆîv ®¹÷Í —Ж+sN HßòòÑsV¨÷]B*Ú÷\ï«2U„qìkBüCô‹õ"@’®Õ"`Ø@_Új–¹›ç³4„Ï!KHÁÍþ\¢R‰mƒš•Öö·È:¿ ¶Ÿ8/‚É»>B‹ay­ò˜¶Ì”Àh66è¹ç›Ä¾Y»A~Œ ég°o«Af­™éØÜ,iq9çÑN\KL¦0»ß ç68¶p¢Ú˜%ÄÌ>&«jí;>Ь58g|üBšžšIăg«ÂN:`ìöVÖÓ­©q”ð Ä丧—,á¿þ‡HO-¥QÈöLJ^û"Nv°8<Ã$fsÛ2¶¸®}Å4PÉxíå½õ³ xçrÙÆ1̰È?!ArUBØ,hk_ãˆãàL™3’Π+ì†ù; ìW9Ÿ*ì”û>ÆH¾¯t»sV¢];9¯$ì#H_♎‘ëç ?nÊœ‡íøÛ¶kCÉk»c»7×wÀ™o¥Ÿÿ›Ÿ§ ³séÙãÞåtãÊ|ÚûànZ&ཻò}õãŸÿ+pýfÚ ˜^Lk`‚„øîÞ>ão9·“´ ¡Ûá{n…º¦þl?ál²3Ò¹°×6Þ2ì2š%ìdxÌQæ˜mŒ€0l7Èkæ¾Ò,‰rÊ\òsÞ¿Ž•Áã’èQò;—˜Gfq+¯ƒ#Q6“#E’_¿Kà zîQ%ÚõäôÖÂÎÅ"Ï>§Ì¼*1m0³í;)4CåOÿü·™áó‹‹éÒâ%”vÒý»·±í*×g.î’‰N ¾c¢ê¶¬7èŸ N&{€;Ôõx_¥çœ–yqœãù…ÍÎÇÞ¾8îÞïÎ#K¸yL¦Z§!™ð‡ªâ1Î(ü9ë|Ž8;4w¾åÈ-[ ·ÀKbžíù–[ ·@nܹ¾uðû-¢˜“f0 V0I€ëˆ˜÷O+ûª‰Ç6 JÐc L‰ok¡ fBœ:^Žp4f6+Åå~ u3ÃW×Ö‰ªÖÉÓb‹Zoëi°˜sîÈDYnQ6×êt”ÿÒ‘A¬3ÒvùA ’±YÇLGQàöðÁÃtãòå …7VÖÒ"õdj?™Ø.Là!#™ãff.R ¥t ¼NtŽˆùùêêQs›ˆî¹¹9ˆj¢Ï”óó³éŽ‹ú^öÏBrAJ >J&Vºtc³“Ùó*çâø)AœÒx~n:=zò8]Z\H—/¦••edÈGÓ ïM?G:~rzrší)Hî‡Ô›K >Ø~_ºt‘û ú4šnܸŒ„Ùfd__^œ#›~™>ô¥×n]#‚ž`ÆêïÝDùø¡tåê%ýuê‹ÕÓEÉ}"Èk8._YL[ÔÙSZP¢_i4³8Þxó5F‚lþ¾uóZHGvp‚]»~™¬ýœEÅtéâlÚÙ\ ËÅ…)@ûV*É? ù¾±¾ŽÃ¡Æñ‹i•`†6Ÿ½HÿwÈ‚¸ÛW%áÖÉøÑÒ3œ]¤ë¦&/àŒs¬³Ì³Mö÷ÈÊíQ¯£JÙ{É Gî­ $çÈvÓÙb6K¹¡\åÃÔlõ6° N~ÍÎלëðbÎv ÈÆãkáÌCO-³ðÂTq†)!˜ÕÔÑà Àff‰N<¯Ž(öùwLUfo¶q¬/³œíËåÈ-[ ·@nWÈñUæ:ŒŒ½±ÑtýÚµ4Ïë*k‡{÷n§åG÷ÈZk§CÖ ÿñ?þUHæþ7úçàæ:¥Ãzk‹23,]T²x|…úž7õK-ðu,q¾Žs|iC_ñ$%sK¬Ó‹¨q±À#‘µJðð1D3·d³€çž§A›-krµR'[*b)}m`4TÊe²À% Ù –k±N6ãVBœ/碄Ÿ¢Œus&éMmoÖᮇ]‡×ƃô²!»ë!Ã%Z•ÝF‚šLÛ“c®'Q* ~SªX¦Äû|4¨%ÙßC¦ª-ëò¹"¤|­™h‚lÀnAÞÒ–t€ ?âOHCð…×Ò.=t=düJB–«ý©ÔçØb /9 Y ‰äÇHÆgÏÖïýö{‰cO ÊÖ¾´² ¶8„ì68W»ˆS×V7ÓAÕQ. ²4H`lìûbWÛƒà49édÓ÷“Sð*ÏDI@¬~Ðä0’Tø‹¿üWé|?mî¤åÍÔ×®¦åº>üÕoÒ"8qdÐàj©#;~af&½ÿñ'é×ï|ž‰Ìà  9³×y‹Š™ ’ôb: ´›ýŽÀ®/Iˇ0‘û!ßé«çòo8[ˆsÆ ò’Ɀ ^:ÅÆnY°…D;¯9Ï)ïIÒVè¯ÂÞB!ñ¶¿='ÿCôC†r]· ^i“©mçü´Ï^ÁËõ ¦¶-6U¹KŸ8„?ÜÇIé“v›Ý¥æùüÂýô“ÿ2w mƒ}Uª»8I0øÒV*ƒÉ‘tKÈ€›>†<ø÷¿ÿF\ï1Ák+H‡oÏýÕ¾˜îÝ}ÄýÕ$8{$Ú=<<ŠOýS K¶´÷Çce™kÜkOíà|k£@i¢÷í¥­ÞƒÎ!ý§øPºøElÍ\ƶbJ³Í Zˆ9Î{e®Á§¸¯™k¾â:GHª÷äQ‡Ôï¨&®­ò¹÷! ©ÉßGݯÿŠãéfίݤdžsb€¤„²óß{ï}úðIL¦.¾vP¤k¢ÖwJ{÷”V÷9Áýä<.°ïÞØð™±w˜ÊÔŸ7ó“‡_‹k‡úñY¨ç¼óùà8w¸ç|¦iSÇØ6yJç„7ž3ã}³7 ’Ï tížo¹r äxI,àƒûìûý%iQތܹr äø—,kxðÙ-H`íý­ß>û]õÙ×_Ôq©àK¨’½ÐHݯƒÅsÔ 1xg>‡ð²Æ¸/j}ðŽˆœ>œ{–2ÌÍ ßÝÞJ«¹˜Fëw ÔCœóKto‘5¬¼çRi'8"eÛ û8L¨#<ô ’ù¬ü9­ gmUæû0ØIïìît×Ó_þùϸY ÔéºlTö8àmœÕ×.¤!ò™ï^oBY"Ý ¢SxßLgú"h•¬¶Î›Ä+°Qó 2· ø6:_)t% ͨ÷uD•Ó×HÛ*6ÓQ\æ<Ú­hô: Ôàâÿp9‡À»†Ü»2ïÊ®I´+ƒ.>5WÙoÆ9 ÄÖ\R?²üÙc@@HòÞ QÞʦ÷׫izz"ìWÅá3` ‚Ü ƒ)vÈ:$þ­afÛ†û!×÷¬/§9ûgKÏÂ6ÖßÛßçJ'itl$"®ÓNÉû»÷?¥=)-BØß¹G=úv#¢ö—V—ħd×_H÷ þu\"Pàν;ick‹¬ô12üß!ªà²å?ø€±hp-’Ù^'xá¸3˜Þ~ç]¤òëix`(=yò4õÑFm¢ýGŽG°Ž8ƬZ©‡c¢!xftt^àô3bkk3­.-Ef[8¡zqŽå®$㥳lhhX\ŸŽø\ƒ}ÊM–pصNüØ­°¤Ÿ cõ2^ø{b<´µ#gÛâ$Q¡A7QdNh90¾óÌí«ÜÙ‘ù¿¹r äÈ-[à%³ë@I²Í5Öi|_®.?I?~7Ö]£££¬ÉXÓí¬¥ÿý¯ÿ(Ûó£×¿º‹äÏH0Ë•Ïé¯HÈМ}?¾d=Í›ó- VÈê"Cò°:0üŠÃ\¹@råºê|ýô<çøN‹} Vvkð²¶gIš*ˆŠv7QöâÞr]+~ðþ2#TÛJèJÜ•Q0{Ó¬ÍbÂR¸[7Ëœû’ßfŽWÈn5‹Õõåì3ik3XkÖº2ñb¾Jf™+wÞ<؂ģF1çÊÖ¾yX(@lö{N±A‰ÚÐÒ®šƒƒô<ÖaÇùh° ’›6—¸¶çnÊ¡ÛO1“dú±¥¦ Ïšïoí#YlmfÏ/ÎÍj¶gA³’ö<…ØG v‰lYœJF›]«‘õÍ{{{HI O)æ9蘄¥ iÎw‰]·¼ ê¶Ô1äqŒ'\à ¡¶æµu š=³í6›û< ÙgýpNøºÙh¥O?½“ :Ÿ˜L;Y£t7:Ô—ôÉ£'éÊ•‹©Hp¾rùß{íuJ¼MÔ˜®^»B›*éù«Ÿ^'`| Ò|ß$CÜro4/rñ£ûÃWœl °º¾ìÚFè«$ï1¾mdÛ#ŸùkÛík/øÞ$ý*Ö)ÇôÌ•lF°ijýòsMü)ô2ÚãV´Þ})J¤¨¾¶‡?¦X¬0§ëØÞû¨¶–WÃ3>YçÜqpÞ,ôpOžÙ°Íø”>>¶ŠeÉ0gîÑ@ÚÞâ{ƒkã!#Ýûn`˜¤e×xßòif¬›uï¨tH˜Ø#À~9=77“êøv¼?$Ýpr9ø6#ĈtÁ³ïk Iâ$Äá£s#‹R§–8Ž‘:DºA‡ØÈëY×ܬrÛ¦Cë¤1Ïû}€çUj½á/^Z!3œ~n÷ž> ‡”û·!˜uŒ ãZyö,^CdÚ?xô0t&l˜Uбsïþ!ÓGSÏÞpòô, i½¹µMÃ@zøøcM-y2ækHáä0 áþðშ'?ßÃÐþW Ö{û¥åæâó»ŒÅåE2êÉ@0†ï¿÷^Z^YJßûÞëiƒ vk¿—Kc? „C-‚;tb¯6 ”Ö  6p;e®ù?jyÿvê¡$Ù߸Ž7dló ݸõ:}«¦Gw§¥§ËšÎc`»ð±Ìë×oÞùö$ß'ƆÀŒC!ã.™kÉåÔUapÎö¨êÀ<1€Â¹y¢úû”¹/ƒ+UÿG¨>íTæûÓÀ%Ü-%F× µGû%¾ôüØŸöx/Ey† À?®WæÜìuÁ¤ »(½xo2ðøÚ¨(ˆK{¸Gõ[œèß)çä^ÃàÀ\Úç€26ÊÔú ÇñpR’TgN|øþûàõ%|>™¬½¾eÜW¿÷=WûìÙcÈþ&k‚‚:>köš÷òûßç•sKŸc‚GÚŒ}µ[ BÝ åß Æ1X'SB_óìÒ+ePAæ_á>´o1Iòr äÈ-[ ·@nܹr ¼‚¹½hGˆ×€ö"j9™´󆔖€òÛæ¸ÓÚöÑ_ëðÔeÌΨÇ>¹r0ö1úü5Ž´òÕ‚ÐjÕØfßóƒžLpƒ;Àoo{¬æ-€˜ãèýp,x~ANƒlf£•%Ì×!iwý ÈgÁ®5²­µ¥3Bi@wd”Úë­"UFz qTJƒYolIn í `óÉÃgéOÿä‡éÒãÀ—gg‘7Iëë+È _˜*¿‘ÆGÆ['ÝA<{¾=ˆoÛ@+ÙÒJ­í>õ˜~±'Yê8K¸Nò´ÑØÂ‰aý­"AÈ}ìê8bŽ èÁ#84ö‘Îgû’¹fpÞ­÷¥|Û>`³£Ã  2öð“öš:8uüŒ;nátbgØìf»Òõ[þ:h{ ‚[="î9™Á ‚K¥òݧœ ŽŠLÂPG×âï^¤é$·ýƒƒƒœý”>î@à×qÕÂÁ­D]pn¶øꯛý®ÄÝ,6ïCò€èòéi^óí6=M”9ªkkéÂ÷¦B>}rüÆëd÷#ŸŽ3ixq‘:óWÓ㇒™é3ó³iii9êµ_Àq±±±Žcƒªd°üé§éâÂtHÑ[›½—ýu¢ÞïÞ±–û>ÂvºÿA½p:tuRYæÌ>A]¤ãްsTçÞ Î¬ŠcR—pêé(jãL'eO0ǵ‡‘ûÛ(#Œ‡£Ý*öïaL‰ÃǶŽÃÑ ã£§Ž€ìnMÈØ_0#ÇÄy‰ûA'^‰Ì ç²7¦c)®ËÿÙæ‹ó÷?óò·ïŸömýå¼×ß¶çò·u¼ò~}w,àš)nϸC]_e÷êwÇßíž:ÚÖJUºvcc•R*Ûéþƒƒ$Óm­ãÛ5b‹D¥] Lóû¸Êz`céaÚxöb­•þ»óßÜx«”YéÃù¤îo'ÖwÛįdïâ¼ÖR®™ ¬¯%ÏÎ1ØWí”xÍíT½`1çôùöY h Ïc‚WÀCµ¸w\Ãïí¬³N…ìâµ6TúY5¤.µÄO»½à)Aù[%*å’(ùä:ß`_3ו77£[ÙmPŒ‡r<ªX²Ê¬wà0q¡d—d5 ‚Óõµmª‚Ý–~NÂNBº’×ÿ\àJ¤º¾>dm^’8ç¿ß©¯n7m3¸Ê@UeÝÅE­#T²XƒG@5×÷d’ôˆÏŸ¤I‚]G§HX“­ 1h¬ÈeðåÓ'OƒØá8Û¤Ò—ßc1Ï$íi‡†á3âéøÐï7‰{‰JUƔɳ–¨%-ñ'žêb‹ÈŒ¦]=Ì};/Îk’Ñïu‚¨‡ä,"m}Ô4œó‚ó6wÁ"ª´€-:Ô‹j@²Kè7öZi|XŸ[LG´k@ãMd²ß}´’fÁl·^»éÈø£òqë{7 }§!ÜÛéÑÇàäƒb²’Ѷ-dÒùmØŒùáúZ{º;Þ¸à›ÈìåÞ ~‹“ƒpÕîü„(Ò~~6»Gy£Å@ƒ®k6ºÙûâ”ü·ßf8%IŒ‰À^øÀÄzpÝ,¹Š]%bµ!ŸŽó‹á{µ7ç·¶ÊsúŒñú^ÃÀ{^1¯$»3|Ÿù  ÁÀÎÏ.6þä“O#P{š ‹ùJ ­¯aÿVº8;š~ýÉãôîúF~}JùÕ……4Oýùý]ÚB¹²+ ¸¡ÒB«Ÿ{‰…VS…2®™ûÞ'G``ƒ-¼Çăfhë Pþß¾Z:¬÷A ;(½xÒ2(VwÀªGÎlV*1Ï%à§ Hî 0&(nš7îeÇûô”ûÛ}]‚P¬'_2@ì.ÖUI0ð?sr–’uMlÑ‚À>!;ÑæüÛœÓ;Ÿs®€Ý( A@\•{ñ˜9céºù¹yưJiµ‰ôôá½´JÐ}•q©¢öæx÷IÅq³Q–;À.·öR?×·Ä_’UÖµÞ"öܧ`åÆ »|›ìcƒ½÷ƒÚ¤OkƒR#ƒLd *Ÿcp§™ÜEHk€S¬™¹rÎJµL9¥¹ÆÈö\â] 8‡ÙGÎJêÓ¼ýÝ-N›‘ÙfþŠqÄeRn ÈØÜ$7ïÖÀnJmg¸­HÆnokÔ$©ÿl3k÷¨eM¿ 0ðÛúñ§’“Ê ºýžT…Êqªƒ­ú©]¾æjY>  G°x!ðîµ¢™ûMdÅô·HœÂ3dÀŸ’9}mj"]½H­n1Ûæ~úÑßïŽYjMë°˜ŽL”4U‚ê·Kìb{¯'ej`·Fé2^Cˆ=ýl†Y ]ksŒ· YÓ¾ÈvþˆSxïùŽ}Cy‚Ïè#`"Ñ>cé÷ùOœ¤› ëlyŽc_¡ÈœÄì^« îµ­Î?Ã9;˜Ðψ1¢œ×1èòÌãr%ÎgP4Áä4ÁcøXôÏ9eÍóõMê¡ßOÓ“Si‰vý âÍøzqj8í~ú82ïGƆ)7¶™G\oü lN—/Ï1~*•‘YÎùÌòoãw¨¢à`°D—ÌroÛì ã(ÚBK´/A(* ð6T~Ý$‰d•ô7ðm{û ˆûjôœë½Øâ^ëìho ¸÷’>ÆŽë¶PV;Dñ¡ŠòA…èûï½l)½ÞßÅgþ}Ö$ׂªÀ…ÊßÙ~ûÝAâÍPpó3õzo¨¼yx¯]·h³½þ,-Î ,wSó9Úøí½n» ÐñÞÐç¶EPÇ1óyfl ݼ9E)»jZz„*áÆaš\èK—oLDrÄ>úí÷–¹oð-Œ@?ׯá4ݺ›¶YC”hƒ!F ÄØ2¼ÇM8 CRοyèT¾åÈ-[ ·@nܹr äxù,pž5®Š…¹$¡àZb¨ pй"¤z‘—  %IÕÐè»D·"¥ÕWÚ€0ж]Àãq/Ïfë?c1™«ì¸‰ŽºGA¯G¥+h¥—Ô´·C`4¶`ㄈ_?#h”J*b >/ñª¼µŸqXg¼Å:¬]nvq­€£®›-À×.Ž  ×cÖˆÚY.Û9\Öárõ¼F$KZjc³ºû©gm/†ÉB¯‘1l¶Ã©Î "Š7—6ÃÑ:@Ýâœ#óÚÍëéu´Ç©=>6˜î=x˜f§ÇÓPoºû` 5ˆwˆïÇOŸAØãœÅIûŒlf·Ã#CQóÜHtåÇt`ò¬}m¼YísSèN^2¬gÉ~6;]Ò{óšåp@–z?d:$+݃Pµn¶5±©s>@V8í—@ »™ .ðlb'ç–‹!&ö5j~Ñ÷p2àt1A=Q­@ù=A¡™ÕAÄ3TÚÐ}!!ϵü€W3È@[  cÁìžó’Y{›éHÆýÄ8øKëŒ+‡¯sKBÛŒ~,;;ûÈáO“?ã«´Û0„úÒûÎ¥þþÁpš{ ³ô×7Ö®n³ût tœ8ðÃH½ÛöÈçG3hß)ñ'O–pX,‘Á>Ç5'ÂXKSß{-äá•€Ÿ™žÆöƒAš›¥vBýc;Îg­a¿E6@²zÓÔNòô)rý³d§_M}zKtÓ(óäɃ{i†>÷3>kœßŒˆq$çͼÐCÃõ˜ãÅ„7¶p¼Ãɇœó¦§Iï°ê æáˆZÚÙÂaÁŽíý«²O™Fnöõ†]ztžî“å¢Ã ç û ¥’ŒŽ9Î æŽ ™?ÖQd z{3Õ9½;ßí¤ˆ}ìáµïÛÆÞcüøµ¹Ó7_ÍÍ9kûuÈê¬sÎk#>kŽq@e޲—·ö!FaxuGâåµo޲糀ßú~GŜԱç;G}þј×>çxq¶®ø=Ζä³Ày“Ï_ƒ'&¦Ryo'u]û˜™Éàø”ïM¾ÛXfà°> àrŸŒtžå¾ÜüÜ€k@'$ÿd’ß¾ŒúŠY%oî¹ÌÈu ÷7¤¡± d“GY“JÌÄãü°ÿê·£.se£²A·,0ä»kËxvÅ'8G¾ýŽ¿r5YÓwÁ XrKÍÃÖœ¬aëÇz_™ÈúòàBV› ~ éç=éº×€\ƒbˬ¹"cšõ«kb4ÛSÖ]mBBÙ'V9!+TB¾ÕBåŠ1‚ùûð›MùeÇ“ªF¬Ýø8ØR"M, .„亶~“üœkk?cF©äv•ó9:\_:jª³Ž®A–wh³Ù·Y dÈH- €^¸8æÞ2 %*l£Z™kG³Ó%OO Å»ÝS2oÁœ%É6ë™#œExÏ leÆÅnÊlG¶6ý¬qªÊFäi˜–â­*™îx- ¾joö‚%8.ä ö8=!K—¬ÞSüÚÝ€Ü>T˜c®û+”N;NÞßÀO!l¥ûŸ¤ÂÞQzì335™èÊÜÿÙOÿ„Àñ Œ…$b!¸÷PózQ·H:0cYò›N†mì¯Ø)“ ÷ÖçY.…Íø…š>Þ×Fî¾æ¨ÝÈàü|Â4ð­Ä·Í;b~ S‹Üo0r—â~÷8=ó4;Ÿ'dÆðêz¼'Þ£›1ϳëfýó˜Òøkk›qa0þSÇ2ÁÃÌ7®_Lï|p7- Ñu!-¤ßG@Ãøü˜\Ï÷èq~úÔ%k\? !òôÉ ‰&¤òÍ$À£ÖŸFû-]@` ú,Opz‚ì?vîï'ãû È£€ ÜÏa¼#ÂEƒß…lt[ÊàÒC®k[{!È‹5Z@RÂ>#Âñõþ<>Q9ŽsòãsÀñìòn¡˜çðù›Ó;'NÅSØÑL~‹x…î{î£s´}@À Ç4??;í×gR*dsĽKRÄÐ$ ûÌûžð¯8ÿœ3â·ÏñÝ}Ê50¦hõ¥KWæÓÅë2¼ˆÊÛþ“T¢ÛÖA—ŸV”Á«÷[»idž€þ)J.4ºiòŸË }øHðÝYöÏûÓëdJzé`KÚåº3'ßr äÈ-[ ·@nܹ^Z ÄRŸÄGpª©ÑÈðwÝBJ,rƒÈå%ëuþ~1Ýð2‚%3¡#VänÝì­Í­42:Aö4Õ+¨„á =og¶?û÷|ß×÷ûü¼š ΢Ù?‚Û¤#ÂÍZfš03£.ÿ€Pøº(ø6/|¯ŠýʪË)M/>ا±EV€D¼Uð¦Ã¢ÒáÑ úBÔA¤+«Ý šùâIŠ&A?ÙE=%ˆi²¶•ekà@Uf¼Y/Isg/¿ƒ¤dL$Ÿu–íC¾šm¾8;Pî"¶–þäGo†$Z“öܼq>=¿Ÿ®]½‚‘Š`ç¯,†£À,m‰QA[ÔèæµÀzLx÷+éõôÙv›££#Ô/C⌠Ð&ä3M‰úyÎS³.-­ãÅkÏ"WîTh@F_˜§½ÈÖ“nF²Ž"¯m–G‡±Ò~½i ¿ÎÑ1*d5ãj¦­Ýe}t3½m[?€QgNÔ‹Ä!`ͱÄY¡F’=#8W P­ ¾m2KZb\'‚™â4™9ƒtõÔJ´Ã¶zÞ&ÛúìåÊ·éÌ0{]r܉aûûq8–%²øÇÖSnuòYY~ë´Od×?Zq1ò|“€pS<{ú°ÜMÓ¼ÞTžŸúöfh›õ±±¾ž.âlÀ©´‰´üÕ+×ÙC]ìáº@öYuëW‘ƒ»È{—ÓG·?I·n]‡ŸMß¾ƒ³}b}<ýæý÷¹ÖtšE9àÙÒFÔœšžáõjóãdc¬q-³Af˜?ËÏVÓ*Yë7˜'-úÖlìÑŽé´ÎÜ{FöÀøÕÅ´ Aÿ”¶OŒOÄ4HæGDæãÄèàÄ Ûp¼ãkô<."l2B¶‰ –@¡€{耈zÕÌtèE2ÓàŒÎù2Ž>•tP˜5£]t2!õg uçdS‚q,áà8ÆÃ´‹´£FýÔ\4£"ò…t|Ýó:Äâ1À9ϳ?ü;”$÷¸¿\ï{7v}fÓ¹tFºfïËøR˜mõÑn2úïóIw˜ÙSî5èG óýwûùâz¤½¶G;ÂIxÖ&÷}Sm{qVȯô²[ÀyzêýÃ÷“µMÍbò{ã÷Ûø óúÇéÑöRª â¼Ç _*¿ß óO½B0›1žx¬Ó ~|=}ôÁÛ^È­6 cÊ8»qÐ46D6žÀ|™¥¹Ù~”jFÀƒ˜ª °þ‰ï-ƒÇ'.ÄôqÝãÜŠçiþä|…fEÖÔx¦0~½Vl?ÛI‡[Ry†õ&û²çÿüm(îRíªÓ¦DRë Uëµú¬a–…úÊ™äÛ`ì¢Ìq讆šà­öAÌà‹ÓA±•¾xλn"7—ïžxn+å¬äñ1‚‡%Ü,±à ËPV¥ARwÀ®9•oŽ,l°¢ºí£¸$ ÿQ –͈#JºÖmXS€Üót%­!U%I3¹vöq»‹yz #ˇ¼GFrÙµ2˜¡ éÚOòØõ^6· ¹¶òÕÖ–“„mrþ~Ú84<óŵ£jhuÖéÖã,ùq–©¥œt ÷ bÁ“¶×L|±•}‘W±Ëìý ms*pQV:ËÀ„ƒÝuÖì´ƒq_ Õ8/;—kUúÏÏÏ:ÉhÛ¬òÿÕx&¨8|i°ú ˜ ™V· $¹^‹1j‚@ŠQ¿zçéJ…¼^¼> N)¥Õµ pÕ@úéŸþ|6F«% ³Rc;`–}N%€›á#w;O´¥ÄµÓ@ÆDÂ’çϰ“ØSßÉ9ÎÀ>§cPkܜǀ— ¹9¶ÅúÂóxnÿN\ˆS±¨í³=ÇÔãv 2›‰kc^1ÿÄÐ1œÄ62ò1nbš#TMíè5Ä© [àëvÑgïñ)Uq”ô–¤.Ð&ç³µÓ%vm‡ý‰“°o“ò\~ |ž™Žv¯>X¯¤×¯Í¦Oî8Û@lK¶YÖ{¼ð^E çêðÐ×DÖü×˃ßcü<-ü =`Në¯GÀýå%ψl`+Í ö`æ=¿=·6Ž€ îÙP`ì\+Ö ¢ë Ò<êDpû(÷X™gÇèøT<#Zd·y¨àà HñÜŽ·ýå%Ì¡ñQ¤áüt)mnp^”wZi;îï¤ÉQî{ìÒa¼GÅ´´²™¶ZHé?â>Ä/Òmù,Q¶s[‚¡TÙ­K0 fÜ;7Ã>»ò tlžo¹r äÈ-[ ·@nÜ/§bYÎbÙšKG€Â­]$£m*ÒO.n›Fº»Àuáë:ÞÏü1·hò?VóáÌ[]_M¿üå[釯¿žoÝà%9# ³Mfq{¼„–®s ñ8ˆ¢€ë40‹Ú¶hO÷=Á.°ç%gn€Ù­HfN¿ƒÊ~LÐâw§EÒQ¢Ü×!-ø4²X°º‡óÃý ³•Jž ÊÌvÆ]¸>ÆÒéi½.åÂkÔõÚ§v¶ž½€ã Èk38 ‡=i”J·oAbáh› ~õƒÇ‘R›N‹Òã'Ò›¯]KW./uÿpÛG ê± h%á§!S×6”¨ëÄþ#§²ë‹‹‹AnY~qaÒúˆƒ4O†²}7«Y0«cEÉwí'á¹OF´Ò’þ;´K9ðÀV"_'€$³Î»}ÈøËH†›å®ócll R#Í1Gü´ë„™µ.1oô¹d´6 ûã˜);¯¨å®|{H,2öÖ9‹{„ñl*7Èg=²‚½¦ä­`T'ƒÙÿ:¢tz˜•®Ãʱ1š]A?×´Ÿ.Ž^ÜoF¸í“ÜV-€ÁˆŒ k²;ŽfŽ0´«ÒªA Ü`ÿ½? pPb°‹“Ì>O!}N,>?qqgHodL]!»l,æO©çRÔå{º² Þ¬ò§KC&}`h8=@?äšcÔ­ßÿåAZ!°a”ýÎk­ÏÍíOË*¤ýuqÖ ¬/1Öd­/-­0Æù<íÏVø=™.]ZL|òIº|iH÷‹éíwÞ%@ãɽÅèw•¬õ+W/¥§ý,m¥52ݧ҃»÷€Žü±È jB`´¿Î&†(6 æ—A’Ömæçe‰qk`ÍtJj¬ 3ÁZðUÒK°¥sP¥ƒ=îXjÈyÏ(¨ƒ‡dêûy_ã0"«SÃ~jÑîÃÜðù Ž4* Ÿ:,#ã€}: •[t"fÏgågž7¾ÇÜÐiâ–ý/ŸûŸìÌÏý±¯üûb?œï:éð…¤–Îž$ÌÛ’?ØÃøƒoÒI¢uµ…Ï`íêsÙï7m*^»ùÜÏ·Üߤœ‚>Û%=ü>((£ûœ Êæº kˆýej²B ÷C~rr¿ëòyþœ}©wÝyÖ@'ðã¡Â÷•O5¥Ÿ%&¦çSÂÀuH¬sùŒë?ê÷1¬ {(Ù2™êC¯’ñÆ÷\yÓb½éæ³Þ¯©næoŸ«nù¼ 3¼üÿ0vÎ ƒ9+dkîoÜMýc `‘Qöfß‘¾ÿE[uHuw WN‹¬[YK»¹ßµ@¾}ޏGÚ` ï³Ì{û‡YDBÖ1’âÞs]©žT¾”vmë~»Î|úìiºwûvàÞŒ&x’÷è÷EJS5Զ̸ ò›óZKÛ‘>w”YÏ“…~ †›TÀ4ªH ŠñüŒÁ©%°’êJ½{§G&:ØVÕ«ßS3ég?ý ó‡ZÏôËLsÞ8»^ö8=µ&:te’|w‰î6Aù§Aª>|Né‡à_Ní:šµ1ØYRO2ñ/dâ¹n}ôE–_²»½|ƹÀ¬XÜÅ~oÆ.IfÇw߀Ú*Wù¬3sÕiØ¢H`¬ïìµKVøð…qŽ9M¼ îõ”çB‡@IDATYi;à ¶ÓÚn+n즕´¾´–úȾ13¡+¶/DæùÜÌlúÞkWQø2Ëž‘ÆŽQËž@÷­-Æã*”‹¢Ñ<›y,‹]à1a›îÃNÒÿó=iß ‚ ø‡z–‰N_¸}»ÖvîÿÄ?q‹“mCðü·²+îYÉô,HœŒ 6Ë[Ñ8N>b>¸Ñ™`²³P­Ä`eõ ´õ1Á˧’áØ×-0óåäÔ±2“žër1ÇÊ S•pxmÿhŸ°Õô›vú*ÚÆgT^ðGŸ†>Úõ«”õY0Ft‘ îÛ´·”~ð½ËéÁý¥tç÷Òø+~¡ÎFV¶êxm|öÏ€gm¨ê]KÇØGÌîœî¡ŸbxýVx €Ïûñ}¨^ÖD¾¿ÝB…¥ŽcfVµ81˜Ñ7çžmäž:íJÎ[*OìÇLƒ@7Ȩ¨]¸ž> Kdó‚1+v¸?›$7”Ét¯hâ½iâ„÷Xœk‡ã:ôÁ6ëOò™«²ƒjv>Ï;(‚ØWËà ßjÂú.Áô¢˜x¢ÁguÚGÕ!zè#ŽnÆ|ì·‘`/#õp}qœûµ×$÷ÔÞBÙ¢÷$ýÙŸ-òܱ^=Ýùp%Ý}{-ýð×Òo.Æsä郃tçöž¬9¤½jÚ‡ùAû½º<÷1Z „ó5ßr äÈ-[ ·@nܹr ¼tT¹x5§‚¶•´üè „ÖÕ4¿pñ ä^L¬ûY Û ¿ü}ö*ûýÏh.þþ:þ¡i\ 8 è5C´9üþ·ÿÂïߦë·ne$¡‹~ð? Ò*È”X˽Á8€Œó¹Ë½:ü²Wþ>k<¿ÜÇh›x+£[6‚CAîÙ;AŒy £¹Cv àd;¼€á„ýeÖ‘2Š_P¢ýNÕ ™!Í%K»›‡¬¶Ç€$ÉðVó ˆb>޽!ß9¿™ÏG‹JxK¢îá$èXO>J¶™ â¹2Ñí|VÐl­ËpMXƒHd¿Ž³ŸuŒS[§ãE€ÓÁ¡a6ó`Ó[á8 s%Îóæë·èÇ1Rà«d!_¤ïÊÂ亂×oŽÛiÙì1d÷”\_Û@ÊÝsWŠ¡ûYö5–]g¿äŽÙú–YÓ•pflomZ"³ÚúÒf4_^¼D›Sëf\gÑÞ1+àÝà3Ž•6ÚÃ9ÃH9áécÞX'ݶH&¡` P¸)?ˆÛ€K½9l¤} ^Сãr ½G$8 `V:þ¸ÏŒ‹Ìqcƒ@ßcÌÚòø o9?S…>“MM<·r-ÆVplF¡m4[‡G‡ÈqVÌ„7 ÛóèÈ8àšÖëÓ¡¦$¹ûÌ,Ù ;\'ˆr¬fŠÛŸ¬úQÚ&«| ÎA:‰†F!ä+dŽ/A #EsHE‚Å+WÒø…É´¶ºÎ(℘JwïÜæ:H´Ïà0z„Íw ¨ –¸7dÙÇ «ë«‘Í?7;Á©‹>ùã1‚²LõK—«éÒ•KéÎýûéÚ•ËéÒåKéƒ?$‹m'MCÆ?yò"Ix®·²Bæùú:„øLêòŒ,µôúÍÌŸQ®ÿ8ýàûßù÷¥§OÓ›ß3T žÒ¹¹jé¿|›Ìø‰PHøä£OR}îCš½§Ã2ö%#R˜óuæsR]ç¢ö1Øaj‡sAu³+¸kÉ|çÞBáA…5ZÜWfÖŒãèÓi¤¬7_@ÞQ—›5æ[f „?NL¢üO vè™AÐG›¹a ÃIP—à!Ú!“¦ÈóÉ穵Թ5bþ:œÃn>+|$sË+¨’½ŸÍ³ócyë+mg§þJÇ>ÏAöÍöÙ ºÊ+úȽës®àêò¯Cdjå_ÁRgÇGßžçB_Ó±<ÂΞã8Íx¸zïµ2'â5  ý˜ù–[à›¶€ß/ÊZvš»(sl§Be8ˆ ï¶çÙÌzꮥÕÇïápžçzdÙýö!òRÙŠ~7TèíOKdå*Ûn¿Ü}’ÏP횟 œ˜ jÅÀ¹Ã”¼øoÿâ§p,ñ*W`0ë^‹qÅ–‡Uµ†\·%,IÅ ?hweµÅÌ>â•w÷Þ, òv Ó#’ÞZŒñ²fµ~ƒ Ë ‘ÎÆR˜õíc¾leoñðp.÷ÛÎsyvF#îcý‹3XY`øÝöjOƒ:Jމë öÑ4>%žUÅÔC9Þ7`@{«-xl=ñ3,ìTV²ÙéïymóVRš|þ Ei. -[æ÷Ìy’A…9raÖ’\§éÓ»÷£týÆ9ˆ:!ãËÍ}Œ3ÉrÇ® ¸§±Ÿ *vì×Ì t†4ßr äÈ-[ ·@nܹ^. °Ì²œm¼0úôàT‡äX]~œ6VVKEjÏÅÛ,J³—×rÉ^:Çl,æYn„yNþüƒ6Ï!X`e €Éœ“ããéßþüçé¯þý_¥÷ïþçô?þOÿCzóß©k‚LhÖ€°øwà$ð?[æÊ1H@ŒÑ+Â;ÛâR^ðñˆê)3·Ÿ€Ïslà„ó„S`ã¢ß>e$Ú¼~€rZ-¡îµ`F§waÚX0df¬²}zQwŽ÷f$‰ïç$¨­Ynf¹@Zàª<´¬Dt“´W#’Í,÷øÚ#˜•(m¶Ž`:8$ôRáˆìfI["ðÉNÐÆFö{M³Šv‘‘Û¦>úÎöNºvér˜Ë€¥[W ¦¸8—îßf Ç'†Óû¢’ê½¥© ]´IK«+‘é=ÿæð úg!iÍ$_ÇÙzùmÉìUˆÓ:D°‘ÔÌUd¼FÈ|W¢Ûˆi幚:åŽ!,G‚TWBmòâ$Ö­´Bó0Ò¢¾QÚƒJ«ƒבÁÓ©¢œ·„­#ÐÉif¹N]åÈÍä—¤HQ®³ÁkJÜ›±žMæã䜙uÁ(óE€gmH³Â™€]œ´'%ê¤ñm¿µ×Ä:Žu ñßѯÝ%Þ­Óí5”´³Îy‚Vyù‘ÎÃ|ÚÃ1íx)•¶ÝtØnÛf­C ]%ï|ýDÔ7‰’߇ôW¿’öv±ùÌ|DÄoooS‡|<¤W·W¸èOŸ=ËèÿÝOï¥m‚%ɼni.!?3=Nƒu”¦_ƒ±Ã´žæçf!Ág¢œÂµk×Ó-‚)~õ+¤`éç<µ ß~çýh£™%wîÜÅy¿ÍõƘûÕ´¼´”n\¿!>ž––WÒ­›7Cv™¶X/}~n.Ý}ðiZ\XH‹—.¦·Þ~Ws¤ï½ùzú›¿þ[‚p‚ܼÁk´åÊ•ËPS[ÏáÙ$þî½ûéû”0{ãÞ§wp @¤37|èL<Á®] C(ß2è„ñÓÉa`Ây††sÉyZ$ ¢ÁجpL'٠ΰe¤ëë::t¤¨ÐÏs“§bjó9•¬[sÎ\Ηc¥'dâYá~&h©Ë\àü>?}Ö¢nÏLjÇI8¼Çu$ÙGƒ–|Vo>ÁüùÌ®xßóÅvþû³œþ9.ç`:~¥ÍSj;uä¹/²Íh_'ìðƒkÏþ?öÞûI²3;ÓûÊfyï½í®¶h 1˜gVC»KjW ­B¡_õÏmHb(´$Á]R$—C´wå½÷•Y™YFÏsà(»”¸œ0ÚL ºª²nÞûÝÏÝsÎûž÷¬¤?ü8ýð·3µµSn€¬÷Jç÷7ýúºíc¢ífÌ.++gH âÅYR 0û#úæ›oæ7Ý-åë}‡{À˜t%{ˆ«®˜?æY˜Â³ê«ðj¼“—•ÈÞ‘¸6_¦c€—Ñ;oó^)“¸ô¬û‡ÎQþÛ¯ChË ,i7æ±?”}ÕÖpÞÝßIú§ÿgdOݺ›º{ÒúÑAØg{;im˜”=lµêŽlhæuz/NÓñÚ*(üyZ^¨JLÆ<ÏÐbî“Ô­s¾¿•æ_¿Nïü³?Hí]ÿ `òöCÅ,ÐjlOm<åz}ÅVúÿõAóëÐéÿ?kã5c–iÀ>FÙ)w¸”ê[SEMOé9ˆíüÛ+|ŒWTb\]ðª=p²õ:eš;>[cÔôaôËCÿÕ„a9h'5·´!­ŽHÇ\@–Vþ< ÁŸ+çXËú/Öw}Kpp:ÆG“lyÿÁƒðƒ´ µ[ÂÌlõ» ¦ö©þ¦`¤ÄÑ6T¢ íÚjú ä%ü›þŠç¿æ}KM•²¾‘R§F²Ù†-øQ»˜ßó,¥/ÚO©¡ÑAHÇ-´4†;f},š¼žÀùùñþYªÜW&ÓLi,¾8¼Õ,ÏïôÒwŠ:ÙzÍØ¿Ú¾>º$¡_BÒV5 vm5{€r´6±q å·•‹ÖgÒηô•þU={’J*MU3¯½}|ýtËxy¯µ0XõÏýÊå®ñ£è{Úòɇ³iêÞ[X²×é€{mJ‹}6öR+¥¢ÚØ_õÅß¼šMÝN€×3#Ü_ÂWÙJ- Íifz:JTé‡ègV± rka`:Nßõß4ôWõóÝ+½w‰Ö¶ñ ;ÕWÈï;âÆRdžߣ߰gí«çÔW~…þgØäÌ%}Û`HXW  "öiÈëØäž«È¸IºrÇ6~à\r´æ´q<üø¿Æ†*¸/Nþò¥ióœÁv Ä:¿%Jfðõ™TÉ1×€©’®™œ)bLon¿šÏz ÛbûheNxN± ~nmnÆ»`[ð˜f¤NÈÞSi4­QNîœÌþv|Ž·ê&ÒæÖnšü’gáqjÁ—m…$ßÎxÕ1Íöâ”y»®k–ëÓ¨Ú&™Þø åÙvóKðW2Šk @%?ãAÞß~v}ãE¬g›ë\týyNשýƒX_UE ‰Œ‰e®®UIÃïd \ûÆ®$ޏ¶™Ó’®C¹¦}¿š±31Á}[¥@É!*8¸>$é;†Ž¯ ÷ï߇4€¥ $0ë‚”£ßi¢‡¶ÃµÏ}÷ •ÙìæKï‰Tsç!}à ¢OªÓòR–¸I ÈÝì£hM«+Ô[ç<¬jΕ¥vúQ6Ñç8=¼ÝˆÝÊpAÿ2çŒ]8VWÌAý@®Êµ!@Ðqù{ùUîr”{ Üå(÷@¹Ê=PîïTà'„Á«‘ë S˵µ½;Ýyð0ýÕ¿ûÓô'ü¤þ{¿›&¦¦p¸””.ÝB©SøEüãçÃÆÆ¨/ýw«ä8•~ýGÿkÛtÜžtò°iuX&&'Ò¿þŸþ‡ô‡ÿëÿ–þÝŸüQ:ÜßM÷îÝI=ȃ“îðH‰£–©e]§„ΆàzÁsq^ë9]q3:qlà/‚ã:Lú{:S|ào©šà%’l*UÃÐ.ù¬:«Z‹óRàMYÛž£;]&¼Ž¢À­q08ƒ$ Á÷•À>&k[¶à&H.j¬Ùß:bYKßËñ{-Žj%×À•¥Ì¥ptð¸åôt¶étžtú‡%°PMF¥Ù ÄèŒ  .ë<Ùf3}‡É6ëx~a6õ’1>==޼öZ"»÷ÆÍÉ`ã_ µ54H-j>{†8`*ø~ à;ØÛ¤2é}Èt[{…zcõŒI'õÇ!•ë!ËÉ ‡mì"kAY3‰=ül­îEî[©1å÷ ©<`Æô ΰÒp0¥u\ƒéwdµÉ¬°V¹c)Èî5%èZWLyöS‚/¸™P1ûÁ åH•9W¦­'Z"†¼Nt0ãuîtž ]P¶F¶ ¨°ªF` 2ñR6-s‚þ½4 @0ApµÖóØð?‰ µf”0Ot$/¬>Æ–;mÄ–=o¹6îõää(έ<»YÓö·Yø¡6Àg:Íü MôA'$“F2R²(8Ï›pœ·!444”+©î¼lG`‰1?Üv ²Hnèƒè |ù!²…££5tï1Gi6 y_zñf.ú×ôkkëH±¯1fd)ÑŽ5ë¢3Þ÷ÊÊJºwû6Œ6@óÉûzÓâÂbáÜCéo?øû(uÒûp¾Wpla£b.¥¥•åP3x_\ZNo±ÆÇ‘á_Z\dþõS}0}ùôi|›K_|ù8uyà<Ï:³N{/óyiþog'ÓýÁ}›i—9£Ìžck=ERC5u'•¶s 9¶þݹq|b`‰G‚’2” ´–·’›gd¨¸ŽÍ>pœ«j@)AÇZ9Ë6ˆ|8öÏ×ÞÞ²¹Î³Jö§JÿÕôý%{B‘ì‡,{Œ‘®Ò»ŸõÎk ZQTÛåÂwO’4c¹Š óðŽó¨¬âŒ—!8%úbÎ1]Cv×?ÄË3øþc=ÞcK/b«‰y[ ~û¾{a阿9Xe ?ä\&‡â«“±ÿÑvçé ?ÿÛŸ"ËgVÊ,ËF|}µ¯¯çÙ¿‘7EWpÏî÷f<Ñç‘æfW xU¥Aæ¥kš¢ýÆ[÷tAù"¿>=3ÐéÈ"«$£é:‹´nr%¬%­ÚGÿÐëïÖ6 Cöp9íoJ(NMmݬd×AyŽÿCý÷ëð·ÈÄd.°÷–ç!±}Ê·îò Æ6Þ@U*{zvgÓéÞFj€¨xQë~ r~œ(6L[E.åw—±A•n…´h`R^ŽçS+˜Õ{sdéaÑ1ÛײÈyÞíl¼Iž|œ°—ß¼HO>ù0 NÜM¿óßü÷Ad »@’gyÆ}7g•ãöä>3#‹ì3ùÓÔØ èzÉñÅAÚÇÚZ‚ñö²ÂÊðb¥\çÓÑÖlÊ`dZ‡±«Kd´8°üOì¹f­ªHV… <{ û¹6 ¾NY±ÈkßaW³ªèb…œYçs2€«RÙÛ×ú/ø > $†jÛÄù}>0FØ´ú’î÷×ø]l%›‘ìùEªkÒ=&wV…ªh (©p•€8ë;Üc¼†°}iršMã½Â±ªØ3œÚÇX¶| ŸMð×Ì^A@íãñÓ©E*%ß%ˆ{ g”þûÕjôÁþd–zïfÞkËóïckr­ãÓýðãðm¿òÖ5tT‘Ç/ÔÖGƒ¸•É!]Ük…qî5S‹bYÐf¤WÕá•C*Pê_¿ 6·L_1§øû9ãg„mohhÂocM÷Áï©gLÏ™O‚û¾ÀÏñåÉv熌Åà`Ð^ú ò%×wMøß5ç¶Ã `ôŹü› ~)nÁ¹8™×­‡ðÛˆ?ëüPAÍq­£*þH®dîÔb7Ÿ¥nâ(’1Ì8¿¢?ôÍr÷\ Qdyæ;.®!û J:0ŸTÉ0æÄh¡6s”†&›Òèp;s¶Õ_§ãÎÇ}Œ÷«Q€ù[a©³\¿9J<¸L ¤/p`±™f²á%¯—”iðÁ ÝI€hi‘,PÔ£ÓÊÿ”{ Üå(÷À·ß<#ÂPøö[RnA¹Ê=Pîo¿þnOÄèÆ¤çLC¶c ?Ýzç^úìo?${ðƒ`~ ¤ÖÎnld»pütt•tSfJ@ÞM¶äñ#ç ˆ[ý»k}uÛÿ÷¿ßºZ:Rt6³8¿G'Ô ;TmÈ„c:ûê`î.rÎÖqlŒ,Û&¤¼* N(…-`­L·À—N·þ› {\2´q q<¿a C{:÷En¿Š R‘ÌDƒÊ©ÉÊÆiÂá‰÷Íã0àÐóe¶‹s†#brß+âx$Eà€ßsd°šå\I[¬‡mFq.$ÄÌJ/ß‚¹Þ8­ 0.:”@- °VgÅŒÜ#œl:žßü46 *Øj À~”U2ë€õy>k]mll¤vjîÝ~÷ÙÆ;½ºÒ{ï= 'í€l¥bþm 'Sæù*`X[ksHd/¿Z O€tÏ,oƹ‘Lè]²¥Í^êë¤ÏG–³Hf9ìAv°®º‹ êa 6š¡½ìëO¥ÚŽ&;ïl—Éíxéì…D¹RcÊ¿;7º[ûÍ>î¤þµLnû¯1¯'C GàE™;Ç'%g_'\v÷EK®qÆ¿¶Ëºáf5ÔÕ!iJ¿éášíÇsœ`¹Á·pPézAp>ƒ ’%Ìs~ °_NϨ_HÆ™î÷ȸàoâ:—²Û•—×mn¡×­õãÛŒ~kš;–¶C/ÜÏ(ÛîK<è4Wr^`ÜMd…åÕÕÈb¯ã3¶ïŒs×áä7®;ÿÐ ¶™Íž!€e†ö)Äï}ßW7Öø=ǹ{Ò&rü‡Ž uÍÜ2îG‘Q.h¿E@ib|RDwZ˜[ˆ¹2:2ñb=]Ì\¤ñ‰QÀígQ³®—lÿõU@wä׆R›€þÖéÓd t“I³ñ¢Å‹‘4h>Jùˆ²[>ûüÙôýx&cÞJöõ˜ù…ôúù«433ƒ„lkzùâyúÑo|?ݘžHÏȨWšÞºŒ_|ñˆûª¥ìÀÍô³¿ù@"ÎxZ^\०!×–4g\ͦðg×Lž`‹}d°FKyöX“DBŠ’¿djk yLpÍy`‡m$­­®ÅœV ¾ÙÜ-î½ö2º+0. Ë™ƒPC§ž¹zÅ<Ëq¾, ®ïµå..È@p™3—Šld‚®Ø~˜ãîìÌ)aWìW.ø&Ѱ®!ÙǽŒkñÝ`PâÜ98OÔ/ç_¸¢Mþä¿߆Ì}6x}&dÌo¢>ìXœÐ#­=äæÕüì\úË?ÿ‹ nüþ¿þ ¶”2ª f¹ç–ö,¾}¯¿ Xr-Cº¥ 2œç ßÂÏWê÷´€¿ëg|ñ$ŠlûlYUà¶ðánÜžAN¾)üÒ û–2ÔÖ׫¿Æ×pÍç°©sé³F:áµ3îý”X€sRrkñ±q ìqk€7סFÆ>#ðçC.dÃécKhÁ@–žÌuZÔ\W"ôÔp’^Ïéæ!€À,÷úêÍ2Õ#i/WLK€èÕèvlìàœ§õÙ¥´;¿™&G{)+uŸµ3=6×vÒÍ›7ÒÃßa,ÓDäu2¥U3CùˆÏºŠŒCwâ+ BbBõ¤ ®| —6XîÉiî}yN×À±÷ ñ¶×µá˜ûù c3Ä\”§—ì*‘DŸÝÞàDUš_‡Ü-ɾŽ6Þd|Û›ëÉF…Ÿ¼™~øƒé¼• H¿Wôw-@}%d 3¢—VwS3kÊû¼bþ)_CûÇ̬ýs²Ïõ*êꈿà?q¶ª:ŒY¸¾ˆЯ–Vãvb{{8=É%b‹xºqŒˆQEl%>ÒÔˆ‚}ã¸]ð÷J€élîð™ qÞËSò¡¡b·í„'2ÄúÔw‘8R’Œoj©£ÿ2iŸ¼ÿÔî¶R’Ž~](8Ÿ˜‹Eö•:žÿyÆ<ÆàÑ÷î’óòØ5µíéô²#MÎt§ýÝ“´‹ ûu}{ÚØ«æy?ϵU2ö{GÓ~¡*7 ¥ÞVÔñŽ Úu¤ÂÁ±™KÚ£²`¿>4@º2ñ9â{YÖô)Jοò«Üå(÷@¹¾#= qP~•{ Üå(÷À/z@'K§$¼’LÔ5?}œVÈmikN»H8òчi~n>ÍܹEf'µŠqÍnÕ¨ˆÉ]àXp®ZKìníþøî•txt ¢Æ/΃…/ õo¾ãí××?‡ƒÅ/ux½'gù´°0O[~–N¨al=©cœ¢S2“uRtܰÊÑ©Ñ  ØÀ—AƒFÚYGƒ‚õ5ἊN*ßÌPK'³tq×8‘’|KÇ'"@Y‹ƒz‚„—Žo8¯¸k:¢X|ˆÀP2]GU©oïÕºr‚í‚Â2ê½P)ËG§ð«»¦=JnVsmp©è³ç•(ô-È–ÇëªCÆ/wI†NT ±òÓQ;ŒëaÙˆ0ÃÛº–Àô8»É0_Afk?ýWßPž,xŽžš„ \Ÿx;=@"ÛY¯ÈÄŒLZ"+++Q'¬•«µÐó¯ÝããÑ®3ú`d˜lcœWÁUX¡pì:è6+ÂÌû€qeê•(ïD6["‚5· (øµOV´ŽnÇȲ¥I®c á)ò暎±Áÿ XÙ§d>™ùÜÌW˜lm´Óƒ’zÍ}‘eB=ô`ÜÓ‡Êøéä+õ. lIÇ\çXÔ¹h{uzÍ:VÀzç ïì¢ÿcÎeYýÊâlðAguÌ'~.‘KÌøAp‘¿y¬ÄE<ÜÓƒ RŠœG÷ -†Ž Ö5d‘R-÷’2YÕÜ/Á¦ìÙIº¦›Éˆ8Ø;H=ȱë [Ó\9¹X—ôƒ%Z‘ˆ4(!)¡—LoÛ Êf­·q.ëÖKœQ:~ž¿_PþêõÆü ¼+f®ßœžÀ™gc£#d•÷¦ ²Û%ܸ9ÜúçôýY yö ‚½=}!OXqãF|faa¬óP8xÉu!Oô¦W/_Åf¾®¥u$þGSd Ûóýï½—FÇPE€È180”n|ûø£OGìDŸNû“Ÿ¦ÙùÅt“y½J–Áù{ï¾YöoÞ¼I?&Ø÷ÖÛ÷¨Åþœàn+ä‹é¯ÿòoboHŸ~þ8mCéïï¥oXþfT!S—á z ®ØÉB6H‘(vx`DÄ=É>T)¡Àœr­ jˆFœŸ!ÓÈܬã½-q̇æ¯sM•df;HJ9c}†¢Œ¤r/ê¼±)H1+ÁùU`²YöÀÈUÞùG@†ub0ǽǽSÕ ÷×<ß×­¹èô^”´tϲÝëØB# â\öÚ‰ z ˜[ÓÒ`jdý;¥9žG0ä$ 劽‰¯È0;(«KKivviÏáô[¿ÿ/! 0·É´à:n Bþ)/N÷ûõ÷8—§ä oË{¶J¡'fuò>ÛPì­ªNOsi¢È«'ÏÓÚæj¹5NÛhPÑý§ü*÷À·ÓÎWvföš Â1Ÿ $WÔ42‰QR¹Ò†ðYXš×1¹cIÅ?Sžc¦"Ý~¶7›v7 a ˆµ»LئÊsüÛÛ_ÎUCÇ]8$‡ñÉÏ?H¼ÿ“tÒ9­í-Ô3N[ o !n§jñ&lW(×HÍ´u¦Ë&Ÿ1؉ì˘SÈ«V7[@Åç‚å6®xf\qÌÚödF‰}µÈüýìä*ía´ÖS¢0  p–GJ¶µ6-¼ys.Ïó¡ý¿ûŸ±Qy&¡Þô Âç/§Êgùåô€óI»Ã6ôJp³üÙ>·¨H1Ÿ.®%iÆú{G5öAÈ€¬`€`ñ´Åþ©ªÂ6·lv›¾‹@llH¿œfþÚŸå«:îC`ªº¶5öùRy!ì02Ì%3šùéÞo&ª`z1IìM Ô¾´G±/·÷O .¡òSϺtì³8[§ŸÐócbQ{w‹Q<[$EêÇâ¡j5†}'p­*—×Ä«àYÃÇð#x^h+ò¥ŠPDß|UÐ$¨_â“°s+ñO3kŠ´ÿúš²dØÉµ4Êvg(¹ØžC±LP»§»ûµ5¶4×,b™Cf3K*®áú5Yå¦1ø¼boá}³žJ ½„€ÈǼ|ÇÏ@+BL˜m.ýgßg¢¦Jæ¦$OÏ'ùߘDœ›ù\K#õEŸ¾\Lÿþ¯Þ'£¶IýkîºÒâòVzùìu:ZßNƒ¨¨ýð÷h¯ ‚A–Äq‘ï¿û_ª?Ÿ~òóÓ—‡õ9%æÒ¡±÷ ”Ò»±.ìÿ:Ê:uwõÆÚs­„2šCǸi‡_ã/jÏjƒG;]OŒ] Äš ƪ²–³xžR=ík®cÝpûȹæß"VïpAŒF¹u³Ø+±ãƒLKߟâ7jËk;+"›ÙkÒGUÎ~V’ÿ‚ÏxœÙÖúÝú>’ÏÇNïU$ÀsÆO˜÷±cˆ¥ØŸWq/_ùÛÆ>ô+¼WÕÂçá¸XŒYØ>ÌCIaÑã#‚®£ÏÎe= ¥9BðØ÷U-$’!¾…6ágAº×W~óz>ã» õ£G¹³WoVÒ_ýÑŸ¥…·n¥Q÷Û[ÿfˆ)ÕÄ®›-–Z{õUšÇ!ƒ<˳Á£=®9wDÇ&Ê[Æg£™òÚ6ͱ,\CRá~ÝW}ßÚëJv×V1¾€öÀã$`ÓáÛI€«cMY¯>GâC I•t®±CE×%Ì£ª¤ u\óÚì{V©ýÙê+ù¢†X\1O ÄdâNÛøá-ÜŸã(U€ýÜ[¦Ò¹Ã+ˆ¬Z|Œu6ÛÁq|´ÍvÅgq00æë— "}Ûûè-ÎK NÈ1`ŸÎÞö&LÛ*kK *¿-&à%l6´Ž"Þ’uGáü+^7 c¦¸Ú‚‚[f© R›åcv± «YáÖ–\Í ¬á1…sž÷ÒàÊháÁèüêÌ–œõðqdŠjÊéÑOÙïü¬cªóXƒãhÀ±Y¿júº çG‡Ö,â÷ã6t5ý½6{’ëÛÈÔÉ ž¹‘êÉ ?!`aV¸ll¥Ï'''ÛÒ*ÜW8w-Jm“é,!ar|"ï#ÀÄáÁÁpx÷ÈÈ3°±²²ÌøUP½3ä«­Ñ>1:ÆXTxï˜ÛH_.ÀÃoެóäÓ%9ôö˜™^ ±ƒ,h‰'`uêì#ï£.Ðêh³NVGçH8Ùfë7|6[Zf¸’t8…f ×Ò>ÉÖ#DðÖ íCJ^âÁÕ‚éJür®ZOkîL‘y-C^çXiûz‚ÎAÁçï)+ç<@ôšÎègÁ#H8Æœ[ðQ@Þ ƒ!ûÍgJ’m¥ Þ€ôûëù7i|r@vŽÛ'C©+»YÚëi||,²g^¾žMï¾ó.䌡ôêÅk$åûÒÍ[7ÓÇŸ|ŠçX¡-s´5j  ¤×oæÓøƒtçþÝôüÅ«ÈTàüƒŸ}@ uö«2 Vh÷ótk ÉÈííôâ%€<Ùéo¨/ö6CúÞÃéÏÿì/ÉralÿçÏ^¥,$ ëǷчwÜ3œ •Ì/æ“;g[8wÝì1sÃàƒAI•̬è£.¼Á«‚4ÌáH¾ÜwÜø ’šI%¨Ç˜aN5PKÝ –z@‰FÝC¢Î#óE0»w]¤”Ø¢ÂÆ9{ékqi~>öZÕ*F'ÆmÒùœ‡@¡º–Y_þœ÷®³Ì+Ž÷Ÿx†D,Ña16÷H÷ÄùcMñ^©Õ\€’:%ÄIz„2ÁG¼Ï¸§ßýýßKÓÓôk™L¯¯nõÓ¬ò±åøåö{Á’ >¬ë«‚r'¡ P¤üJ5g¯.xž8ùÿ¥Œ°ÒÚÒn¨bís›H·¿&Ø àÒÞ¦®Ÿgåׯo8zî£þÛÄüèE>ýŠ,ôÿëÿMjã9mYu[_jã^à™³mt‰±Š`üÁ«Ãtz©“gT{w“ÃLÁËs€¬}‚ÛØèÚŸÚ”Md«º‡_’=i>˳‚G@˜²Çd®“¡¹Â d‡F ñäj à~5³§ö^žÌ5ðD8¿¿ï—_ßp$¾TKJd’GÖ* höh=5µñ eL6¬>ÎeAPU`b²²EìnêåVU×3·LõXU˜äò^óŸbý_Á)—Á5ö^5µ«µçj. ãëéo¨4öuư¥¨Ì~mjé`= ´QJF£eÌT%1«T Ì‚¤Ì<ÉLÃØŽgËÎIŸ+K³MÌUÕRîœUêL`³ºaQ¶9/$CwAC°ú{(Õ†íŠÊ=øFûm“6q‘ëé› rÖàs J¶;—ůÏrs|šß±Ë°¹2€yÚ”‚á•ØË•|†¦˜-_@QC€¶N{“‡„â†ÈD××ñJOkêÖzÛŸîMÎ?mqvB>úÇ䨝T¡r?ºdß–T­ÙÆçñ[ÿÍ¿ý ÔÔ É^’±ŽzÕ.„½4:Бn=¸ÄWNN»PÀ/Ÿ™ÃϪO;û‡é¯ßÿ }öÅ3b§á¯VWž^C´¥Qö½än*£ù¼ÞÉïE|chh I ßguÑÖhwr8œ¬'ÆÔ±ÈHüæ÷Š @^È+õM­ÜûWÒì\Ã{•taé2»\+¸Û¡DôåX³Æfè7|ýÖÆFHÃô«íSËöF=v®m_Ö#ùš”ªªê€6‡ý+¡Ã²Tîÿ-Ä]B¡ñrp|¬­.yJÒ¹Ïåä½¶~Œ±!˧EŠPœ2j.ã%š £’À‚õ>{Œy®-$Ú³¬“ . ÎésK°ÝN`Ô™‹þp‚¯Ö÷ÖƒÛ”.ÛÄÆO&ÞpkfóðŒÎ[ÞØ¥´ÖYúÑ;i°§.­®¢¦ÇZb­It÷þÏ%¥¡¬`_½îs8 ´SXÛ9f,ìÊ5N›"VÀ}]^””cô½”6Ïs×"³„X óCÀŸD Þ12ĤßgÉ4L17/fê;?¬en&~5$–è߸ûR¦·ëW•;U!ª=ç’PmÿÏ1fåX¸xb½:~\бRB’÷³/>Ooß©Eî¾ßê]ø¯W(valnà#S¾8EO' Yö¬ºqH{ÍU•ß$p‰B^sÄCl/î熀ÀüÈq1¿3\“·Ë¯r”{ Üå(÷@¹Ê=Pîr|gz@]'#ìf~Ðf$¹Àð_]˜ Gogh3©Í>XÇy×IÌà@µâdèôutöà­ùk%µ­m&›U2‚p4:ÿ—°n/0š[2ÿ<3`ƒÍ.ÀYÄÑ×Y«¼¡>kF»à¦lø>¹zÀäç+q*›pêkÍ‚.ã8Å8¶—´UP}Oç¹ri¤ËÞ„ÖiÔa¶þÓ9àd€\8ÊŠYo\àKGEIt :0èf\ë Êž÷{ÿ²ñ­‹n0S°Ji1"ƒ…öŸŽÎ–]®£Ÿ#h©ƒi{¤ÔàðA?`<ø-Y™ò²Æ¹6ç\ Øk½1ëTQ5¤¢·ßZ‘…¾uûÎW†€åYåSáĸ˜™ž yïù…ùîl÷‘A×q²¯ö8^Öóðà@8¨; Zp¢•\_ÛÜäœ |¾  )Ž×í™›8~5Ô¾^‹¬gÚýý+åôÛp€¯ÏS?ÑÊ·ïìíE6{%àÞ*ÁT0Ú¹Ho_t!—ìKÕ‚v>Ÿ58Kë€ o"­l†@ ¥ÆÍâ!€@]<2]¿í§Îg0Åytˆi6„ Ì®é`þ Æ}8Cú¹aŽªpÌßzSw 7 ºuâe‡;F•L5¨£“,{æhÈÂ3f¢¢„AçŠïü1“ßútÎCIÊù ÀËÄw~ÔàšµlÛ›z@i³m4»Â …à³Ùóžãäø0Ö¨’õ®_û¥‹¬Ž²ÕÍfçÃÊqt³ôŸÙËÈ£Khh$hä½p¾æ‘ú°™±Ý€4ÓM¶zo% JõŒW6]âE뻎@„Š!Àkk”Ûž €R ÞÌ/@¬éMd®oBÈ8ïôžà(‰ø¹7séÞÝ»iphÐ}3å‘hïfÞ¼~ó¥„m‚sý!]¿¾ºœÆ'\¿|™>|'ê¦?~þ‚,ô¾tëÖLúÛŸ~(OñàíGŽ›,ô»·ï¤þ!2@ž¤·¼çxÎç%mŒOM¦Ï?ý<-,.¥qdâwwöÒ“ÇOÓm2Úß{裸Ô!àö­éôìésÀóÆÈJŸ¨ñd ‚ýŽ1•aàРIUåeô­A& Σ»ØQ "m8öZÁ D»SEc Ï”’ÊI–ã†Úb£]ݳm–ójb^4ÒÏØ¥qÚo¼¾Kù iZUÀKþ.ð/0~M¿^éh §1$€kU¿œéBŒyŽèôd1—9&ÁXOf˜_²_#C¯BalT°]öCk¸Šu]YCAæÝ 5Ä3:)aPe­râ"ƪªõG#ëÜóÉ/ýà ~wáâÄ+ˆç *Âñ$دxæ77ˆ.¤ãSÀoŽñþI‚èì¨')CR DæEumk²Ä…¢¬!^öQ¦‘ri¨çA `¬™Ì•Å×e=z¨üO¹Ê=PîïHð¨*¿Ê=Pîrüݚþ4¸£î­Ös-ÎñÔèP:Â!ÙüðC“Ýt„A™²8fbû©àI5FýÎæzZ[Y ™çj õ>2RÍFïèh‹Ìm¥¤šp*­zuÐ œéüå)6æï²n*x¬ë[$›J@ªV­à»c#ÿT>EAÂ;ÞN_~ñEªÄ1ÑQÔÙP¾ÌlðλÀc°fi»íõß+ Æ3*¯£a/È)#ÚzÃ\ãò`ŸeþçaÄS¿Îåbº¯á€äj\ÒfÉœ‡‚ÌfM3Ôm{ mÌkêÝ› o¿€ðå½ð°:±²Õ¯¹—Sˆ A> 6:f®åõ­ƒ>88DMôÎt²0m£—¦@IDATGÀì8îC‡Y©u‰Îû³‡qkC¢\bˆ7€"ñâˆì³’œ ²ù¨ 4|U sÉ16SÛþè!kee9d÷ÛXÓÊ®I¸è@—$cvyoO`y72xsH¹o0%:äÈßMãHwלŸ_JÞºŸ”e__]|&#}h }þÙgÜ+`?àúÎöµÐÉ.§>›rô+««é­ûo¥12Þ_¾~ ðÀ@ûÖ_Ûi EÛòjzôä)íï¦áá¡4K$ ô3?yFÖ¥võ¹…Åt’‰kJàþÎÛi˜}nanG¿#Mߺ™>úùG¼ÏLM°æÎ‘‰_NcããÓü[‘y?scЬå/…R$ƒ7ŒÉÜü.ŸŸI“¶w¶clÍpÓÈPŽIADI:l ˜Ä ð€9åºwÎ z1é{=®1ƒ/Ì‹öÄ Ÿ°¶îý1ßÙ¦bï°¤ ¸ä *Êîº/5%´M¿:æ’7âÝŸ\îËä2Àþ`ù;€\â~è~*±CÒ‰Û°ëÌ œ2‰'Ô©<·®C³i$ôÑ÷'̱ZÚàº]Z¢Þ=ãe=É©{÷Ó]À›ƒz¬ãFžW Ðûˆ]:~/u!]Fû ÇÅS*öSW\d¬¸þü±Á#÷ÿ(™ €/š(Gˆ”0A©®sÕ²Èîm¦õå$ô7Ș?d¿$‹’@ÒϲLƒÏ(.i¡Ñ:ÀÞ˜¿òåÛåW¹¾‰ˆùÆ„«À»& ¯R„ÁO'§ÏŒ«Kl&ž•WEˆhÁ~²ºŸÎ^§Ê­åTS¨H{Ü#0rêíjÇÇíŒxÀ¶o[s'äÝlû:Ô8Šéñû÷‰×-€ŠúSÚ¤áÿùq(íq’U}6KÐnåþ´f ô%.Tq;?3®¡¬¾eè{ìeÁ~Ÿý ‚+k Ÿ£;>wÉ"{ãp ŠœU2·jŽ»í‰X 縶=—J}55d!«X@k˜G¶Û>¦y¡0çøw ë·Hü×LeÚwI̦[_œfÆsI…9R…Ï®½_à Äç¸FÉîwõ±©µÍi“HL¸âïfW{Mã*üÊ1^Óvó‹ïsn~ÀÏ)BäÞá^ô—P&Èó7hÈl+ýB›}FJð˜}½y|ÒvåÃ&ÒðX_jÚoàÙ ¹~Ÿé…|Þ‘V–ÖÒ§þ~ªEaOÒÀåU ;ʦþ^ÁhUº¼³Ã ­11‘kè 5‡0nsÊóÕöPÌ3ÓÛ6\¢¤4zm†ûæ}©Pv`ëwF™+ï‘»P"½º†®UÃ89MŒ1pý§ïv‚Ïvähc*µô“„hçT%ÒìôQ5îÖiÀšc$ND}ã˜Ážˆ}Úõ©oèü”ôTÁø¸?ÔÍî.DˆvO¯¥Ÿ¿XCÉm€D‡îD ƒ3$æmM ó\ŽùWÚ‡ð͸x çWuÃø_;?3Ê)·KüŽf•_å(÷@¹Ê=Pîr”{ ÜåøÖ{Àà›8¦lÙ¸vát`×âäâpÔܾÿYå¦E,ö+€ÛMlìJ2Q¨%EÍm8YíÖrRúÉÚË:Lî²’;:0ð5¶uÖÌè¤Í ±mV³€°ìV=4cËR¶úuÚƒñ­‘]‹³av¹ˆ`“­Ådç38l|þ݇ï’a~”vaÐñ½c͹jD$* seçÊÌKÿžë–$Ïí‡ZÚ¥¼0e8f‘†ô™>}T“ž ¿VЂùgÐY‚’Þ¯ì~AJÁÏbARg8EÍn;@'S´6ºŽ”N¡õât 3W¾9þ€Öó´Xߨ¦V]& àÔ7RŸw›úÏíi” è€ig[; v;Î6µ•¹‘[ȳ×ô(im rÍÀóCd½oLOh½ €ÖŽR€Á‰m˜ê4¤gøNÒD;ãi]vÙÜ16ÔAߨ܆½^G0¦=ØÄÖ5t×a]X4£|ø!€TNV/²Ôrs]Iïñ0Q…Õ6̽i³—e0^~Æó)ÿmp#ê§r|ÌÌVr<x§üw$³‚•¶6ÃwÀ\iÂ.ÀVb§³µÁeÆ;²Á›ë!™Î\iCºíŒ"Ÿ§»†ƒ[¸ç’€q[A_¿Î·¯Á½˜»´K⃮£o@°ä¼ë¸ð(eÝêX†Œ!Χޡ™Â‚ìdà‚fb¸.$QxYïç’@µ¤õ ²cä•_ we{ºé« È % S€´™æÍüÍ@“ÙämHð×@†0;ùiFþë׳) ÜC àÅ Ê pîNÆÔzpÇÇgil &:@ùúÆý–xï¥Î™æHØö#ÝN?I|¸L+kËã#R?zô„qØg>v1~+öÒäøD LÀÇ@ÞØž ™Åáѱ´AMô••UäßF!Û p>ø` C˜˜_X€ð1DóÑôèµÈwKYè³€ßssód‘? ð1œ6™ÖL¿}ûvúéûï3/6Ó@» ÷ìì\zëÞ4qH&ü«7©ë7¾ÏïwÔcˆ¶Ðîå•5Ú:Î|È’1ò$=|÷aºÏq/ÞP÷ìò±É‰Ê·zN9²/æùÛ;¦[woS«ýE*BºûöÛ)ûñg(LMüÏC÷rÀWÛî/³ ËçúéŸqÚ ”{ ôQ¿©x¶ØF­nüŸâjR-øN–ŸÞ_ϦŠf”¯jK6O`È€d>úú>Cãò?ÑÎrUF ø¤ÂoÀÆ)ù x¦Úg'Xçûq<´þ’_§ØkGØ}•|¾ïìq“¯µ?%ëoAûÓ­Ã’Xf„«ÎU‰ïfY›–Ö>K9ü"_õ/•pl¿¾PIÍYÁ±È3eoû 2;þœå°\®²sî"Rëác玱ռ'2sñST)ò~²g½ .öb{µt§ žmEj¾Ÿ3¯.˜`ã„÷•Mø™ørʉ›ñ,ˆ¯ïÉ.Á}( OßÐÞJ€<‰Õ•¨±Ä}Ù‡úÖ9²®)7ărÞPWd~[ë’ÏöþOÓÑærª>ʤþ†¶4úÞo¦¦¹¥ôþ§‚„*¹}ŸµŽûŸž ŒU_G”›§ÄÓÂÜžÉô7*O-Øö‚¤ž\Л–~Hš$ \¼mמTÍÉý͵¦ßjÌB@Ù/ ’0I¸Þ»`·Ïúø®ú“è‚uMر ïÕ%$H –ÒÓϯæ~Èçú›_—’ò×ø¼êÒçÖо€ ã1ú‘aμ2ÛŸf3œfuc‹ðÑlÁ·—H¥Ÿ 0®Âá#ÚÎ9Oµ_"&B[ôŒÅ¸¯;¾q?t@-sX@×¾ðÚU‚Þž çÚëé7ésHÀh¨£_yfññ xx.Édßíw`}‰>#k˜ªÞ îûRÁ54ûz)-.m¤™[S¨«ôAüíÃw;Çç<ŒgåÔÔ% Ûˆul¤£Õ´ÚZ=>ón¶;?zn ö1nĘˆ)ègùüÄ£þ2¦ãsÔ~Ñ/BVÑ…©­Á÷!. !Ú¬|!r“.ÎñÔ™™1‡ë!œñKÜuA¾$lAî/öevV•/!ׯÊs>§÷uã™C]†ø—äe;)Ï<¹¼bN ‰îü3kÿõI,xMÌÁlnlEÞ‘˜SM¿Kº0ñø„ãoüìŠx‡1$X7ioϬ9Àø<7'IÁšì–ÔËvÓÜ’$ï\j­WÈ9Ú­ o†}Þdæ†ëÃq¶=ÕÆkÕkøc@[þ§Üå(÷@¹Ê=Pîr”{à[ëìàxaˆq­Ü’õ½5𱓋1\1¤É¹Æø¿G–÷³§¹ý‚¿Õþu·"¹6E}²êôæõ›p<]r`Øâd+àtá$-SÛëYoÚºä:G: -ȆëØëø  @XC;x„†Ð†,@^A΀r‘Í|VGI#_gVubr,îp¾LZÂa’¥î5[ýŽOpT øOç„6˜q€&ïé(j¸ ú’mLÒ€?ëÁÈÛVŽJ&¶çµö–Ai™ãuÙ:™Aeàƒñ›y/Æ/ü/c;ØØü¢Óx ¼·¿‡ì:™é8v«+˩魛33ÈvòT#Á>ÝIVõnzøö[‘ñ»‰ü6Kã““ÁFŸŸG&›,ƒ&¤ÍßÌÍEæòÀx,7C  9½˜Å!¬ìáSJH+›gvÁê*Yꀩݨ«ÈϘý##ÜYÐ8¬]Ô§Ö^l!¸"è|@–“Ìî‘¡ÁpTWq.›é£=@mÇHùnûÙ€IdÑYÆÆìWAÞ=@rºp¾¯JÖu.ÀYeÅNäë™3e¶ÉlÏâ Êì¶ïu²¬õm ÆìñšL~ÙÝ| ¬„€6ÙöS*”H<¡f³×µNzü€g¨™Á„"c. ØÈ¸˜ %c\§•™‹ì<税ÚdLkÐ 0ˆ££,˜éß:ü~é¨Û³Ñ >0)"‹ N'”vàQÞɉ­3LÀƒ‹^1§”ëW’Ýùå9öe|‡ÔkÑ6K¼èêê¢`ÐC°þ©`àáJ—1NÉ(î®×Ù¥?”B4‹}ui…kƒL±²´L~ò6ÝCüÜÁq¯gçÓøD6$ÌWWVbýuín0ÆLóŽ™Nõ¾ÈðuOLL¤O>þë‘u>²íKK éá;Éx'‹œ:æãȸ¤ÏŸ§}Àz³å¯g/äw¸î0Ùp­invòDºsk&}üé±nFÉj/’©²B;<ÇôÔ$KôCG𙹙=~hzœnÝ™òïdZ1çÌBö” ÿË—ÈÎßKãS´ýUzïûï¥wß}'=þòË´ÃÜ~øîƒ`î›%$Ñ Ž}ჟ˜ÆÆ ȽµE&ÎÒ2ŠÌOçMUѱdO¡ãì;çP(Ô8#ãÁ¬ç;Ó…  A ç°óÄ`†kµÈš­ä| @œ–¬-TνF‚¡t$š˜c\®´ÿ•öD•(ª ŠäèWƒ]f04ÖgOÍ›¡(í~Ø4[c}md½G Úç¾fvzÁ)³³È÷¹çïnïÐ!±­+ý‹ÿö_¥;ô¡™´1÷ ÚºËæ ¼xï¶K2oEzŸn$e{ͪÈò`§®³5”ö>Ý[ë‰"È;9Ú °\ÀÿŒ#23Ï!|U³–UÄ8fŽd2©§§ƒ=†4 k‹ì5µ¼× ®$~©6f<#€l{ʯrüê{ Ö“Í€yu}Gšñ”̨dÜo¤ü5¶$Çêºvž'G¬í]‚›{<ãVB9ÚxÒ¹•gNwߤ½õ7ì-©£÷NêºIÐCÇÅŒµU~}÷{ ö7Cv÷AÀØßGnváMZáë`we–²¾(©ÒÙDÖ÷dÚ€TésW[Àý_Ûµ”mŽÝÓT‘Z©SªõÛy ©µ© »«ÈgñxFÔ œbïù<¨¬ÉQÎ¥Òv9ÁøkHV•—'€lP-©¸MÜI‘g`ÛûÜÒv¨÷L •2ÍÿÅü>j/ãism!ìob§Sòƒö»/<û8-¾yn&õ1®‰çUÏ2÷àx± x<”_ß@hoèÃXëð§šgÿÏԹ穾ý,5÷¿GvúX:Úý45v¥üñA:B%£ïîCæÏâì~ÚÙXNç—ølìÆ©W~ý½ÐÖ)Õ²¶Ž´ !~-ï]áSàÐa_©:R²Õ\µ©áÇâwð`OG:™þ¶æð)¾ oâ P† Ÿ™Ÿ&¥lù&lé¯m#m•xT4ó)`;*=6î«Âr*iª ©6¤l»>¯òÙúåù"çÀ¿– ÜŒj… §@®ÒÙ‚zfx‡,5?k'*C. gíí8WM#vìAd:‡¯Cå£k•¹¸a÷8ýi}/ûD8JéÄgŠ×>ĽÌÓnüBAu>À~½Ëç­!}}M†>ÆLF} [Î>ó7l[mlߨY0:U£«½¦TYçxºÿöx  ×öõuÏR [+«[‘ªÓÛSé”÷>]‰nß ‚þšcèeT:‹qäo*ð B†z í0´ÍÚâÞ“€¨¾aUU‰Ø1 ýKžÑ®Cç‡äK×ñÁð3ý^QS„K…9aÿ³YÓ[Øøì­fR³ð°°!YàëÓzïöIdŠÓ/¥zéú­ô=m—˜g{\ë‚ßú·ú™”óØÙÙOkø„7§Ç#TC¿é‹0ã¸}Ê=Ë©%k>žQ|þ;%ú˜¶KÎõù#¹A•8¼–{¼ättL¼žs¤gÉ5uµ™}ß=¤ÁØ~«á‘9Ï5½–¾w¨#@“lkæt<7°qôaèĸwcÆl}:ý„çO_ãŸn¥ññá4:<@’GkZ]Û‰1¶ß†»c½@ >Bî}ƒÜ_üÂÙ!Cw¥+Ö]¾“ÊŒ—<³. }œ¡¼b›ôß+)Ð ±‡6BIQÃäåÕ•úºðC(ňêðQ\?ÖµoiAu Ÿ>‹" %âY~i¬€}VßÏeˆU!·eÖâ·9o¢ŸX{™|†ÛO*›9¦Æè„˃ÜB’D-ãâÞâ\¾¦ýúu¡ÌȺ²\ƒcã8Å‹¹W¡ecç8íR^ ¿= ‰Y¢E5æ™ôoC†C†ÿ脽€þ·Ÿ‹Ä¡‚¤iéŠåË=Pîr”{à»Ð>ÐËŽØwa$Êm(÷@¹¾ÑÀ ‰6Y‹PæL6l5Fø1×ï`ƒ/,‘åÙ×IæóDÚ&ãs”ÌO³©{ÈÙß>Y±‹sÑ8(ZÙf|+ûëUtuŒŠ€ž2‚K“YáHÒ!ÙHV²ïéøÈÜÖ°Wz]žªÙÒÆv•–AUÏ Kµœ?ãue%ëö±»±±ŽcDÆMK8]‚›‚:eáh㔜RYÖ:42ÚuRq3h—¾Yï’ t½ ,<ª–Ù €@*÷( ,0.p{cWjNAAkn"èDGlo*s±1œ-Ú¼ =AVîô­8j骯?“qkj{;Ÿ~ô?@~¯“lÜÅÔ1`°¿‡]dùŽn{ÏËK«‘]Ü ˆ)XhPE¹ìžo­S584RÌgK†ëk¤wR7ÙëžßÌÚ3‚ ãwî„Ãr€Ô(¸ºF?šù]?0@f3ÁäÍ‘w|6 ÒšÙÜOVøñf2t !߈ƒníéfXÄJn’*¡ßXUÀù2û©ynk¦“!Ûa`‡öÚ_ |&dÉ dH|h‡¤¡Ã­¬šrÔ~Yo['þöM¥ë+ŒeŠWHκÐÿȔӇJ¨™ÁÝM<^,Ù°8ž—1o!•f7 $pß#£k€EGN§ù„{ºfŽeè3öQÓœsd0 è|17˜õÌÇÞëç˜C%év@æ¾àõ5óÖzá‘5‘úvu~*ä˜;ʼëÔg˜_Mdi†›lUYpœÌÌ\atÞ›o? ÚFK¬¬®EûÌ4ß$›\yw¥Ò•Ü?P7°>ŠDãÒò }|ëôœL’=€™æyMÚÛÙŽR ­d¯Í/Î1ç¶Û¹v²úû€ÕSÚÒ2s¬1êï…x±¾ìó^úQ©ðµõUæ{CA'€múÉHýæU:™žJƒ¬SŽÙf~ Pµ`¹€µu͇†‘C_à~ÚÉ\¤n65Êç#sÙ=a•¶Ô+Ñ.í÷©Siû§ÛÌK‚ €ä^KbOÛ÷ÞM7oͤŸôI(_ŒOŽd%`îu|.YÞŒû¾¶~ÖÁƸ庙õ€Ä<Á‚¥»ÛH:ÞL5D B–žÜ/mpïfbýùc´Ëʯrü*{€¹ÞÙÑ“ÉôùÏÒ»D:F; õU7QŽ`)]ç4`˜¤ØRõdk®¦“Ycè)»óŠ=Œ½f³IyþB$3¸Z(~• /ŸûŸÚs"UŒÜ²Ø;ëØÏŸ=FY`žl=J(ïô÷¶E@\¢ ûhkk#ij©(ûrtØ£ýÞDо–à¶Ï¡š Ál@RxuH°±§sA>ï ¨…ŸK­Sömt!Á=Á·JÀ¶†úÎÕH© Α´«½`ÿ»ÌÀ}!MC^ëíĆkæ×ËVŽMð&€^K›ZxÖ+[»»ò"­.­¢TdÒg ‚6ýäÞ. ]Ë1>ÎßNÄrøÏ¾5f¢ºM½Ù!Ù÷úÇOž¾„LÕ‡ÖËéÆ!ÊóLjäÙRQg&6mË冿çÃ×?ñ^kÔ2WYÊyÄ¡Œ+€16Šý¡L»ñ$ã:–ÜS5 _F‚~”¼c~…5ÎE´í}ÙÎ3¦U|N»½ç!ÈCmnŽ¡‰»cž0ÇBŒ9n\(w–‡(ý4Í-®2×{ i­â9»õ•¢b=ñ„–ð¿V^Ì¥Ú£–ôI3¥Ç!*ã{÷òÈm¢¼I-`´÷ÞI¢I$TÐ6³¼k”Q§MW—%‰>»m5"ÜÈdiþ`z$‡p´þß×ûp” ˆžbÝÄü1F…q‰X¢rF?‰q°\`Uå¾d âx’ýUé³6Ì~¨Cd‡†#ÆØ¾®…ìÑŽD}ýï­aN¹Æ3÷Ç×Õøx—Ì=×§ké5eËþ÷û×|–ëA0®âM©"SÅ—Ä€Ÿüå#æ­3Š9L;ÁÙSU½µç{ˆù•äîg9¯X(¶R{ë®…öù{ùUîr”{ Ü߈Mú»Ñ”r+Ê=Pîr|c= ]zÉ”Uι˜Þ¼y‘ dɶuõE`ë‰Ç3Ø®;dœ“©{…sŒ~ (g°K¹¦52)u5}é éÜb ÇѺœ2« €uÖp“q–+ ð¾’T×8IY@T;³ddÀ ÜTãèVÆ«@¦¡Î10R|/âJ"-…³aýaëuï’µÝFf© æ“#Œy š2cÔiÒ™ :mÕAѸâšY€+ß;'S&®Î«`SÂA=ç»ØÊc)Sf`ò€`a ¯µßts§³û‘øZERò:™²c‘4È|ëÞ=þ½Š´£&½÷ÞCßKÀÿm¤á&^´DÍ`ïw =Ë5—WV©5Ùàå&µ¢ÏGgnˆ%Pdvqg¯÷¾“r´ihp˜æ9z¥ÛÛƒ `ÍdTHë[¯CŠè P¢\ç–òd8©­XŽé¨Ý°/˜»Žôz}lÝëÀ)35oNOEÛmiÄaÎà”`»$aF´rÒÖE—|`ðÇZè@› ˆnó™Cú¨ WgQ7H 8ñÊÆ;”ûóo9æaÀ©»òÜá€sœÙâYkÙÆ j·˜q0-8lÿ褞ÈKøÐ±sèJðµÈvLæL[{3Î!ÇXï—öˆ1å±®ëÖÅçq/¿rD}_` ´Afçq‘ ²,tåÕu\#󀫣Ωã‹{ ȇ¬4²s‚Ó: œD9ëAàßzuJÀ©¤`mwE†4gµ÷¸?kS» .nÊHWrß IÐ] U‰÷££îª €¸pùuŒ¯õÒ%£±îxD‡MkBÓïýLÚõÕ¥øÛøèòÆm;@àèæ¸€ç,óDv?sœŒ¹°Â¾100Äü$(ñe\»¯ÂÆf`ø|šžœŠ±”îÜïîç¸'¡0Æ:Y]]ä^bÍL¤±Ñ!@èEj¤÷¤ñéÉôâésæízŒíâ^”ŸØž¢–Ý«7³Ô ïŸN?{ÿg€Ó-@îÚ1?ËßzºÒäÔríO ä·¦áq2â>ÚIŸ>£ÄÃÈšùäejþA3’타Ï1d‘çn¾zù•ˆ›çS€÷¯CIá2íÊæ¾zõ2}ÿ?`¤¯ÒgŸ™Þ}ï{¢òñÇi9ü´É ˆíxðÎÛé·û·¸$à©ß8*±†ñ]„lÐã¾Å˜<"ë}“5s’Ùàô ñˆÈ81{Á½É¬pçj±X×Í(Q¸‡:~VÌtD>öà,„%¶ÝXƒ¶CÕ °f™µ±¾Üg%7$) ´[c‘!™=ì 2®¢$¢eéÝÿ/˜£ìãÈè¿xö’=›ûº1šfPhlhF­âÕî:ïï³öl 4}[„TcÖ’µø"`Iû]ãc\[Îã _Å{f(ýÄgœ×y‚Æ{;ì…(TäÝ«¿Z¿ÖJdIÐ.–=÷pI…!œ6”A BÐñ™bÐöèøyY›ÎÞž°vÞ};Ý¿}7²i àÕ‘ÑK( "ÏúÂýÁq ¦ûR~•{àWÖ®:m”Jl§É[Ò‡³žþüOþ0ý³ÿNê¿òdbåÎ=‘Æn˜8å¹}]8Kýï¦óÃyžqó¬‚ÿÔku ,öõ ;0~-ÿóê-¯íFŠ}Œ=ˆŒÎClïµÕTO§YžEµH·NŒöbAv‚¤r|FMUl€"ûU„ØìáN€¡cƒåØÇÊëjÿŽ tó÷„m´‹=ƒýö ×! «VÛ<^€1±?³ÿ²§%"’U¨Ú »+’îeûØ«%­bÑäkXÍìÓØPH'·w@ÎÃVq>hinUú– Gˆˆ ”31c5‹ý r“öõöÞVúòã d‘iF&!þÇ3ʬ4mCíè#®VžÇ1J¿’´w/ÏÓæì ÀÖ Æe©ÑQ”-°/‹k(ë ¢…ORŸ©@aÂ+6z]5Ê3'€‹[O êpüä½ð÷xrÆþã“ïߕ׷Ü'1¶“$ÉrìúG¥ šÝ«ý.̉²~™¥q+ó¾Y¢rüå=lú³Ô ñµ‰ñq_c÷ܾ1 Ar-@ j\¬%vK@Ïb+ílQo‰ñ¦ ío<ÆEÖÑJI,üèFlNɶecï=2S¬ÙÆ_5ü ö sJ9äx^É~W‰L 5ö”Õ®+ëyuYØö»žqñö– ­¯}B?(«®}Å!(§(‘'½z©r}œ#ìqÛ/Øm_…‚Çš©¬ì|%öm=ÙÛÜ/êÙìØÂ¸MJþ° ¶WâŸ}MÂÖ™lÚ£-œÇ÷¾$&ÐÎýwc“«Ìt)µ¿ g$1;^áãùhíè Ú+hn[|O_Õ¾ œñViê‚›S­DD5ó\_‘Ñ¥_ùDÁmý¶·5Á{c(*Ò˜í]Gÿ^ñl(’^a»jŸ×ž ÜVCÔÆJÅ(Í)IÛ°iv\ˆ»wªFgû½ömðYla•ü$¡¾ž_ ÂXK+Rúýøá{gø|›i—M+þÛ}|”:úÜ{¦q[ðœúÎoc^[0WEmã85 þÕÏÚ"ÙÂîn¼”ötVR^y~Ááø}zÁ8xަj~%Rx"3Û½¦¾±O ÇÞçbd^KpRÑ¿úë¾ôkì)ã0ú1Gû'ø{”4ëu´âž÷)$éVJÀ©Ôhôÿàq‘½ôxéãô;ßIÖAcß(³e z|uÇ_þÑc@†zq’«*iÇþ˜eª¬ NßÔ°†¯Í¶ q Œo%Ïösæ8ä^_ž‹Ûö™YìW{MÛ˜D Ä€wSįS=“ðüåîX÷’ôåø?özb\9žã®MU.¶ˆ™©J$iڞѓØcß` @x /9N5IoÆLºy®ÿì'/ÒöìvêiM]ìv«}ÞÂÚ¨Gù€„: ×q¾™G£oõA\&ŽßÖ )…tšcs©zì…ŠJIÞ#x~”t ü*÷@¹Ê=Pîr”{ ÜåøÖ{@SÝ—Ž¥õÿìßÿyÈÚ¶;ÁÁ<Ô”¥»0K&7ÎÅ5ŽÏòÂ|ó:ç8‚J-*׬b¼Ù¤Ë‚‘:g:V50\‹ÅãV#Zã¼ãZHF‡¯V§‡c««Ïæïá8GÖ¬È @%œoœgP3•ÇÊb+É%ØY‹áoNi)Wë±ë+?­3–Åñïá8ûyÎmÝ3¾…³(;X§Où´<™ŠQ RðÕìqjV¶N¹«3vaM(Lö¦µ¡uUkjŽÓƒû÷SGW;±ÔŒ$ºýÎýÒ´¼º`¦õË÷Ò8÷€S‡ÄìaAÌþ¡d8·È¶Þ‹k p¾ž›9ëÁáadŲ´79ApI fûZë{eÕd¼eËÏ/.Åýy=e˜­w­\¹Ž› g{{g)«§s—L7}¥“g†²ûƒÜS©Î=ÙÜd ד崣eÍ8³ÊÏè§m±Ž¹ÒëÇëHÝ·à˜ Š“849í!rfI›ù°ÆµeŒ«:`¦¸ ÷ lvIeIÍŒ³æ›dÚ¥ó|À'ê¡ym³Î,޹¦ÔÜ^Œ³µÅ×ÌÊp3Ã`@#×VÀ©fPÂÚòû´Ñ9h¦¼`¢×”Ä@¬%‚ ÉZÉdì2÷ H”ñdÿÒîZæ°AwÛ-AÄ ·ëL°ÑÌà ^ƒ< ‚èŒ*kègN¸%{{™dÌÏS~ ¶OdáÛV3¹ëX{g®\S县ó]p½óäµÏÔ¶…jƒ5æ=‡™%[ Tg0“¨*@Ö,×å­eü Œ//ÎÌÞgì‡`Öx§_ ®«ðòõëXW’æçÒàeÛtsº-2xûÈv"Ãýñ“gAžèƒÐá|Úg.M/^½œ!»|8-®¬’­¾I)€a‚³-HÍ.¥Îo%Õ^¼x 8?àúÅÖvZ[^²³æ_ÏFÖK7õÓ_<}šÖ›ìÉÜn„,"°ïî4sóFzöò}Wgný|'-²îß»çÃ9¾ûNºÃ±Ÿ# ¿Áœ†ô²±CøÖí;€×w ½ŽÀþÀ¹c·ÇüŸ‚äÒ<{æ{µéÎ[÷Ò§ý359 ð? ý;ïÜO¸Ÿ/¾ø’þYO“dÊ,}©à-ÞŸæ÷/ÛOÉ~¸E;$²lB>˜¹s#ýàßãüÏØ6®è·þ¨3÷%uÛ;ZFXûCi~n.½Äï‚´ÀÜÞGÀ}³ƒŸ/Xë• ĵ³ö›•É#XÃÞìþçþ{Íúdzò»Y"¥ ‡sø UcºXƒhå¾  ºçrd<±..ØãÐ=o À½dµîçË ËéÁû© ¥S®INf€»Þ¬Ý.QÄ€Î)ó³€b ×°m6CÝ{p}ùò:>$¨¦É„ã%=#!xa0‰ÏI¤l´f-Ï +챂=ºpqkˆª$rŠÌµª+ ³ÆN؆§§ÒïýÁ¿„HÒËóN9TR,GºWô‰m÷ÅR‰ “ßcÇ»åÊ=ð«êƒŸd8±Ï¾÷£ßIüU!}þÑOÒ[((t  0ÂóïjI ‘­‚gC-ò®ÇEyÎ^^ÜfíÔP›XpÁ \°î_U“ËçýÏê¶±°Ùü3!žÇ”BÚÞ\£ÜÊl:ÜYÇ68‚°YË3š¬É.€K>ÓIIÒ^ìרygc ¯sÆ&CrÙàúÅê9€µ¨¨ØäPöOÁî"ÇÔS¯ôš½Î,Ò+~g±Ça§“ zž·¬’ŠMØDÌ'òPù óäŠ,¿(mƨ¬k¶ˆ%g:ð'òÙý´±ò& ߘäZÔæ>ŠØö–ô Õ'€í*ëàvålÉ™£Ãýôúчi‚ïààhg®wsd›²Ûv÷û2ˆþŸ5ÕþÁ…ŸèR݈ÕÛ;‘ö>y ˜Nígä«[šûÜ 6 6éÍQFÀ®ßM§(å³Ç”ØKC7~3u1V€•LRg Ìòë«øª3¢WÅ3Ø/Å?ÎQ†Ò_nÏ´C”ÞŠuZÄ&V1ªb„5¶;¹ï›ƒ­ál#ƒÎõ÷(ßU‰Š–c#ñ •¸ƒq!×:o1çCæª)Úó>WT…ÒöîÉìf•Û|O[>Çu¥¨P¤Í’ÇÏ…í#NÉ\Ã/ç=k¯×óܳ†·Ï‚*ì|ý+ýyÇAÿ¦ ŒïaºÓ ×Ldõ—ÎDÓ÷Œ‰>…~nçm¢²¬/6¿L÷ðíŒeñIj{}”›Óixd¿l8Ý9=L5—Gi‘¬ôLj$‰Œ>åÁóÁÏ |ãy°Nm¿$äÒz±Œ½ÙÙYí4é3ß¹¡ÊÝñáYÜ·ªt®ÿZ:Üq°.aÂUU™ÍÏìàjÆ·óPßR\µš®¯œƒÜ¯Þ }|YÄ>d *+”°¯IÓƒcéñŸ¤Jæ³bÈ~؉ÆÂ$e—HÌ?ÚkÌE@ÝóÕrÚnÊÚ êôZ™YaN pCÿIÊ>!îSÁZ˘xøÖ#ÝÞL\®ŽØMsG'6j3{D™$t|~\ÑFKCTևнXÐé„ò«Üå(÷@¹Ê=Pîr”{àÛí j eÀ3wD†ó—HW­Sß§I° çä€TÉF³K«††~÷pxÂÐnÌÄhÖh—-o–xlÒ3œT¯c=2ÝXd E/ lü e%£Èî ÆluÖüAf-ŸôPFJ6ºu¤eÏ øä *éõ¤pxø綔ƒt:$KT‰53ÃuLwwM”×¢60í‘E]‡CÑ@Ày ®+vtl˜šÐ]iem@s@ÞÁ ˜×Èf*YÝOMfïcÐj„>i4Þ?8 g†¿-%V×VÉÔ>J“ã[¿wã<ôr­ Ô@ã!ä§•g^!£ÖìønÀà˜øËdÕq@Áå•å`pLLà\×È h‰šÕÖ?!ˆ1:Œ<&K¥´ÍÂVÖ}gw7Æe€ ¤ Žø2 ¸€ÑÿÍÞ›ýFšfé}/ƒŒ Áà¾ï{.ÕUÕÕÕ=íÖX£È0`@7†. _ùÒ7öàÿÇ€/|a@èBm’ÝU]•™d&÷} nÁà$ƒúýNtŽeš™wmêˆî¬d2"¾ïýÞõœó<ç9Ö:7€!à5Y íïWVpX@k‚€Wc´ ~7ç^p†šòÝ€N×Ô4>M33³ô_.Q3] ÊÌNK'ÁUAðKˆŽW7^‰~ÐñW²Ëë_‘É4ˆã%°{ÃõœE®o¦µÕEÆB¯„C§ìt+ÒŸ~çâ¼Ì½z#0{ ûÝï´ÓG$Rô“å,˜®l_½¦s]ŠOO^IBcå2Y€y®ˆ6 #;Þ:ëM´Ñ@ˆßXÓ¡fFéx›- aÂìXÕ ê¿ìøzÖx“Lpþ(‹V¯÷Gm1úÉàŽdA:úÎmç¹ý™ô¡Á_’N|ûÒ`ø°ÍÒq}8­m8ÌÊx+Å-Éãˆ9K#‘äïOÙÔ §Ï$Ltátû^€EãÀ-c 0æfz]š„è„—ÈqDÀ3…®¯q2Ëû™;ëëê”ßB¶ýŒì‹®©‚*®€kæ³õ¢¿úr²ÌG×ÚuÕìU:˜››%£|32|‡ÆÆÒÊÚZŒ×ÂâË4==Y #f¦g`ù5\•{µ&ÝëïÅ€ûD¨ Œòý¹ù9Ê\¥Í4ç{¬¿µÕµ(ß Œú¿þWÿ:˜…€^B¦îÃû÷é—¿úUúü äÕß¾MëÅÍX¿*'lnn§Ÿ½zuÓW߯ĜyMFù׿PgNM‘muYÁ xK L1ï?¼ÿð¿Ov=räŒë›·K鳟ÿ<} 0ÿ[ÀpÙôÓ33KîÈDÿ6}NFøeÚa¦Ì‹O^§"Äk»¿xõ*Í¿|Š­íûiznžù›ç~;Ôo•þÑ?þsj®¿¥ÿö&ÓŸ³—I2òÏÓæ`fÿ@š`\ß½}“Nê'è“kÊýk›9nÿ‘<¢ˆŠ"ÝìQêüc–‚Á'³;Ìù2x"aEäc<ȬπŽ;Þ3Û@ÙÝ=FÖX{Ë.÷Tmbln’3ä‘`&®÷P³ž"“]6±³ÎÜÝÿÙéS™µ¥Ì«™M÷U³s”Up'GƉŠk²àè{ëø‘AO°£€”¡9‚;÷Ž={ „ÞÜØ@å1y0îâZo'°{M6×ûlY’=ÍŠ (ñ<¿ùõ¯Ù÷F!›œÀ¼……’†å=7×X•À•k×?žIǬgÙx5zà»ìW³Ì©ÖÙ=˜þü¿ü¯ÓÆÒWìuzò·é‹_ÿ& `—Ü]+iÚËùDàû¡„íR¦>&VΤ‡‡ gJòÝuðE°¡ñúqõ€ãüQêúŽ=éyŸ³s}å©ç”óÉDÙ›)ˆ^×A¢C!ÇÀ6C©‚†gî=FÎ4¶âË×_`G?¦¯¿ú?S…=Tõ£Ê%ô@66äj˜·ð݇Dz¾¨ŸŠ ÃǰS°á ¨?°JT5‹Ô}×=ÏŒ7øØK rkÙ XðÿØŸ!jïóËÚ,“ÑLFÙéÉaú°ô[ì3$Ù»û˜‹wØ(ƒØ’œëسȹ ¤ƒ´qïGΦ~ÈC|î–>(a«¸»’¶×—Ò0Aÿ©ilPGÊA¨µ55öûxÑÞÆ¬®wÅã¿úoMÔª"‹üæò(­/ÿ_œÉX!ox·´Qwä@>›9]í®1eNx|8ÎÌZ ŠÌÊ#óÒ h¼þ?=àäuÂÒÏ‚_þ©Ô NëÅ›ú T|“ òŒ‹É>œv›þJû¼\F͡ЉYÓEH3Íøûø¸ÚO*£E0÷<Àצb<+H;ÌŒÝvJ1h“…{÷n;É3)uÞ<×::.ñ7™º(Zeè:‘~öû³½”¡wCˆÌ`€Ôø„m3Þ¤Ü[ØkÌÌù…ì{;$Û™¹9Hûø+ªRG ¿“¼üľ€mªT7·á\ãšÌ¥l¬ýLÄZ ԚƷ ß­–eiGªþ–¾Ømå"|aËV°a¦¦²“íKú]…¡Ú3™äÜCre;UisŸ¿¥Æº{`ûuIï½£‹ô¨nÖ¯ Læ… hÆ^Éë6q]ÏêÈ~e§Ô§Ô÷ðÖ6¶ñ`¡C-˜NnØÔŒ 0Ãý[øŽ=WÛ[ŸR"vØÎü­ ,!V[Ùñ“lßD»UrTÖßÔ¶YHóOOþìóÔ¥ý‚òîäEÊäð=Ìè/r–XbC2Cw‘¹ÇZÿ«ß̦³ýé¼RMÿfý*èVËŒiŸçñò|OÒ€!š˜Ó´¥leÛ©mÏðrÈ‹B©Ûþ–A¶?@g¥þ!ër/ÇÚïK¤ÐÏæëÌcüÞ¯‘¡,àmŸçUÝ!rGMz3ò}yŽ ŠûrNPÏ€8OãÚüÞŸý ï%™=~à= È’ÏÞ¼}Ov4%¦ð5ŠÏô/s\¢®$ø DˆHçÍ(ö3®YÖÇ9sƒx×ÌCy6nÀ½ŸPV¸¥¾·Dç‚° ª1J|÷|5NŸ›a¶·1ƒ6H$ƒ¬dl#æí4aö}•µÆ”d,:øb ¤}É ôzô—eµ$´ò¾ÿˆßdŸ‡|»²ð9ˆ6’MìÉ Í<_Ø5Ö Äw[è'ïmÓ´kÿÓÌs•(ÓÖ¢¯û\ª˜ù¬–q‰LxîmÜ„4|öŸz©´WºG8>wÜËÏ»N:S¥ñjô@£=ÐèF4z Ñøa{ œš€ë€qOP–ÌâÀ²3˜ÝÐÑ©y]ÐÖ:NÀÁˆVœËnêB)E݆saݧ.õK€`˜"Ÿ3;TÏq©m³•}–Íû€ãµiqˆ;pòûúBúüèè4œ‹.dœ¬ÃtI¦ öt8~J²iP `™õ=Dv¬ÙîÇÔhÞÙÞŽŒlce‹+»×©\ºÌäžg€#f5O<ãLfÒ. ×`Oo0‡•.°’©=ð؉C|zФ8€Ißâ¢{‚Ñ/ìîqѺ:ºµ‘iˆ2SµÓ¿HÁlk³‚•[—õ¿³»õÂfgçq ª€^Û!•n=ê2ìkûbÐq`h mò,’&äÌ‚¶¾x‘ç4kÈlW3àgòÌ53\çÔÔÖG'H çp^·÷ö|´¼ºÈ—Ù–@IDAT¾&Ëû`‰zX84§§§Â·ô$¯³CmBœ2€àÖV×á>,ìèŒlgÁü ¿g`uÕ÷èAqò+˜þö7‰qøÀ¾Ã  ¦?ü÷Y v6ÞÝ0sÌïEyÀó{:?§iÀ1‘i>Á÷¬Ózɽ›f+•¯cf}vS3)l¿ò…Ö€×ñR¾ú‚Ìí 2 :‹]<+û€º²³íS%Át]£&#ýð1ƒIùÁ¨“Œã–§½Œ-ÈÏë¸*Ŧ“-«=J èpó¼:†uç› Ïmö¯±cAEã/fÜÃôW]ÁL ïkp 6œðf‚@‘õÅzÊÓ¾[Þ»#P¦¬Y€Á'³z[¨«fM@::>g‰kŸwb{7kÆ€–°à½ øXËþ‚Ç%Lo3Õ» oÔIÆyøau=È1=¬ñ¶Ã“Ñe“CÜØa.•o| ˆ°GFô íó¾òÂ1Á·1˜÷“S³éÝÒRæúRì óÿŒ€B/q²Äwö¿k2ÍÇÉî>$;{“úésÞÓOë§‘± æøE¬Ÿ²Õ¥ßÚÞ‹ ûÔÜ\zón)OȪB œ,]ðjq!I&y¿J}v®ÑÏ÷ÙGüÞj³V6È€¶¦û‹×/"‹[é~×ßúÊ:s¡32Ïß/¿OëÛ»!]_&X¸¾¾^“M1»°À5Dfx/U –—WâÚfÎoóFÆÆ·÷Ü¿„_¸˜Šv8—Gyî;æ÷ß{ùêïÍó3µˆ 4ޱÖ,Þ“Ñ?AÆþkÀò¥%væÅ"}d&È7ß.¥™$Ð_¿Žìûí-2ð‘ãü’‹õÝ­/713ƒn"5ÝçÓ—öKö©­Ä@xPQ`•ç5Óop{ÂÅòûé„~[€è£tûÆúFì§„ü·JWåëȨü. º³ÿ1㣫› 륉eh #‚`¬ ƒž‚û‡Ì<5ñŒxñ8VjGò2? ü™ƒ2DõõDPT‰yS--dŽ“â`YƒfR~$ŸHª)xް¦rÌsI'’€”%Œ ]¬²8*¨80ŽAŠ1*Æ«@@Ï`›ADeOhO,€dGØœ¿_EŒŸ±ÚÂw¬å¿W¡"êõlrýHMPÏæ°¥‘ó–˜í=%Ñ!®»ÑÆV_ÀÔïäU,€ôÈwOô3|5®aB„`wÂiÏû¶ßmáwúÿ¡®f¿Ñç* t3&[‡—i¢ŸDƒìÿ®¦47Õ¦Þž…*I/þ€ÏhœÁìf& }ʸ‘áߎoþôÌ5™ÛY:©ªz Ÿ ²…Ÿ³Ãìaæ%Ó‘6™q^bo÷¯kO¹Flk”tbü[ Îêó[:ÌD ûTâ¬>¿pdý³Kyپ׈ìsþ}Ìï]Wìï ·óqçž~¸××'¶Î#nìÅù’e ¨jØ–ïbn ]><{bõÕ½Û‹xžgÆ_© a±®ù®ßó U9!æ8s7ÇlGà™1k–LÁ³© a?Öž­ƒ›Ä÷üþŒ%Ú! ›½ÎÀSÒGd•›3ÆálÎà ˜0aI–6|3ŽsÚh ‚½%í‡ýí ú,…/TÄ€ ÃQE{3|_Õééu]^ˆî˜ŒJm Ûâ • pTMà1ƒ\ Ó·¨G=F€öô÷’œ£‡]s_à¹3Õíal££ßx5z Ñhô@£=ðíí0 ‡Æ«ÑÿIôsY§£€1~K náÅ«¨áû/þ—ÿ±I[Ú½êÄ| ÀÛŽ”ÛFt‰`–õsƒÅÎÚ-m6³Ž“@Þà°À§€¢N¦ŒmåÜóþ:Gá„eÈ$Q’±-‚êd*Ó.Hó€e/ªS7 ÈÔê÷yF¿ü’ëºâ •éÌŽïyM¶3ÑñÉIdaÿæÏ¾\–øÑÁµ"'ÈRí:ËG€Ô¢/_,ðþ-ÔQ*’É<µ0íXtl·†¹²Ífª ¦‰é:øw 7Ü 0i¦ù5@ò,²Ófž ŠYókvf:‚›«[Qº`Q`ÿY÷~ÀÌn€öM§s2×' „ËêßçZÍ™\ ȬñúgÍ .EA^€Ôlóؼ=d%Gv²¼½‡Žõ)€xžç8µ®»„ÈÖÂÞçY•z_œ_àwƒL^Á|ëC„ÑÉé¥eßqÍ$ßÍpµ6ãaíiƒŸ‡d;¹!F9Žœ fзÑ&keë”J¾°º }ȼãtVnq¨™w%Ê|ï‚ç¸5£]ðKF»Yí§d±š¥ßC¶´óL•6% ï9îCý½1?trŽunµuÈž þÈ™h‚ùÏ|ÓQÌÖé¶½ÎÁA¥ñ”ݧ#y 8xe榶YÈ^SG[×13Ž!:nv1—ˆg ÀÎÏÐ?–AX`M=ûÔ)i3>o­ønSC+×HIgqT­gªç,BÙveå+ô!CÃZɧ£ÇúÑÁ¼bÇSòвôGôwHÝsœuéÐÍö÷:frëf,šÉr.SR_§9îç;>> ù‚`5÷ž!ëY0‡ hU †û¥÷«Þ\“¾WRøèäBE_êc­míìA`¨¤‘‰ÉÈŒ6sºpxl±¥•µ PLpÍÓS‚ÝôbqžŒõCH5ý×/Òo¿ú2ÈA¬×aÚ½,»m0sÞõÒ1¢ÒFÐÜv½$_@æý›7ïØ·riÞ5Mÿ.2ÿü³OC.þ=÷ngîÌ/ÌG_mAZùâ知Y¾÷-ß3 6ÉúÝX_Ga-}òúU%2±±¾™1fô½{·Döü:™æH­CîXY]çš…È _z÷>-¯l¦_}ùyÞ¼yrð¯_/òœ{iPÛ6z]eä p›5¸»½Ë½'ÒÏÉT_áÞëdÅOÑGfhl’Éÿ<Ò8ïÛ—Ëô½2ó¯öIÔ`»´ÏÑO|ûö-}Ù2ñ{ÜÓ:ñÊ ¾¤½§€ÆÔ¹#[þS.¶Ð=¾Q.g‹Ì% û[c¯Ý¢½[»dÁ¿~™Æ¸ÆúêJZ“”Àþ9 AÅ÷ÛßÏþÓÞÚ r“ÞÓ†62¼­(Èc¶Ú#k ÏÚì¢,¯AnË|¸‡»OÔÈÜhnqM)-löQ=ˆ¨JH3ÁX~Íš!(l† Ë•Fp¥.‘hÄ'7Ü€q!€êõXÿ ¾E ˜÷|Yo½Äú¨@øª)uAýsïž £ÔÒÚÛߥoߥ֎z ®ˆŒýäÄT¨®l£Àð‹ôŸ#CHÝOÎHŽD–8;® l¼=ð÷€³,‚½Ì9~J}#3AºØû-™Ê_sŽvÇÜ?áü•8Ö×aVñ5ç+j ·fÉ=ÚFVùÓXkN[ϬÆìýŽî?¸¼û¿ÿwßÐrO¬`§ž¦½J£ìn@2¼ %™‘OÉŸ±m`®”ÏÓ=îb× » Áp=K±µÌøÒ&‰éÁ=Ý/¯‘Võ~]2sH»_]’‰~pŒ3Xòž¶áûÔ78ÌÏ-¨ÔC°AÁ›D’a=CÎ̸®­%’íaY‡–ý›=7skÍóΖiì±6ì¿Cì™ôŠç8±Yrf˜f–ŒÍ`_V•u q´½ÝçÏpžtàWô`14ÚÃM]éë1ƒ,¿ç|€„ÅŸàË)ø·Í¿˜¡| Jp´½Ù°]ü™µ)èùqov-eA«ô[\Cwë$hÈé"ÏQWkËbOá?iãœ@6­¦æð­YláÕ3<뀩6RÿY°íhg¨`Ÿ° s %/'ð,IQÁli 5Àê|Õ~|8³Ö}ŸwðѬE]·×¯õ{@f¹V@Ík>ò¹ À¢öó# ÿÍù!~Ïß5>ã3çšû~ôgÁ@Á?Àêi½ú#w-ÉÞ°4Ãntï µ . )HúÌÞYìê£ï:Q®’d¨ë÷èO¯©Ú†OÕBÜᦂz¿«=Óÿ\‹!¦ óé°dÍs@bÆJ?Q0×F}ü+úGEªG@¿Vö`Ç\À\ÿ÷5¡”É R“аäÇÀµå9à9,˜û„Ÿhf{v´²ÖH l_÷bÕF|™ÎNË1›7ö|ÈTóU!èèªÜëÏ€J ¤Sç¡Ïª´¿$Aum³ÐŸ™kƼ¿}ï³<Ó¿ª öàWö[Ó@¡eåjUJzAÉúÙ<mH2ÅbO$ðÕØsõÝ[ùóT»`®ÏáÞÚØ‚¹ª¬)%ïܶßìV|‡G¥¯8üsÔýÏúá’V]ªœ„Í´ä=3ø9‘mÍ`…Š>ƒ¿£òB$bÆJ6 [ǹá3Û!ÜQÕËCåhŸý @¯ÿ^"ÖaÿJæVø8-”’+ 7ßí÷"żmåŒ eHïí¹ºc|©ö‘ €eË@ddlͬî郦¢>“2ìúFfз37Ÿ!xÞ7±.k<¿>¿ç·s«ƒyì™nòÄ]K#a ¤>Ç:dÒ†lÛÝŸ%»9ÏUzf¹wxEã!ÍÏ%b(”xÀ=*« ™vÆÑÑ å>)©Ã~ Iu¤ØUí—æl$sOÙ/“&³[ü*o|_’t|Ÿ鋵 гŸÜ0o˜-4ƒk 5Ï¢åÓܘW;ýÑDy­ ÏÚУ[ÿiô@£=ÐèFü´z@CË?uün`i¼5^øi÷ŽóXã8@ÓÞ¡ôçÿä/ÓíÅIZùæÿNs£Ô³}TÎ6yå)]àL’J8¡7Ÿ2˜[ø®,jÞ­¬ÈŠðÆ—圞QpbÌÔY Ùtîwt\ö@?™ÓdòéTpÌ$7›Yà\‡"²ÃZÍ·n¶µ²^.Ìáà´„¤³Aå×{gû" ùø¤ÀñÄÄŽÇ#ÀÜ~°’g swvv’®Lö—dÆ(‡-(?Bós˜ø»€wfN(ò?<8I“d‡ íìï¨8 àÏLè=$Ý­‘½8?Ãõ;¨q¾“®0)ÛŽ÷6\Š ''G¸W¾ɯÀÇ“³ÓÈö¶Öš€W&îÄÄ®ò3R`'‘ ÝÏgÍ7[ßL_I ‡«ôGiWöõ ™ÛfïH Yw_3ñExÒIîå::>2¶G4 rî’¹ÚŽ“âóWp¼Ìï¬lÅé='˜¢£Ö ý„cff¼N—rm:Ifìön°=/#ÕÉ͆Q«PŽKÈ›E¿¹Q"ˆ–§ç™ÌŠî!k^'éŠv¼°-÷–ʦ+Q^ ÖZÈ‘Ò&Ÿ7²$â³m_p?4r瓵æ%pæW…ݯ ˘6»?2ɹŽD3ñ;udo 8øžÁ©@~=ÕL&úVÀ>æ!c!È}OVB… Õ%í° „Qk†3ÏÏ t?e§ÓœjÖ˜QÝCúIð/†ƒ.iÄë›lð@ÇøŽ ºŽodcðý[2fÍÀ7âs™¡T¡Ùï7áô¿¥Êdè¶26ft™QmÆX¯ê¼wÍóÚTåùwö£ÔÀ `¸Î²êÖ×î¸-16EŠ€‰fè>1_TtؼÕñ ×±éëíb¼Ó»Ôe=§4ÀªsÔìrëÍ[§{ ØÒ–-¸qˆ¤üðÏú(90š6¶Y;Þ§‹½žY|Ac˜v¥a®¡Tíóì’ › ~_á9× ?Ä3Í{ïÞ,Qó|‹=a> »þ•®ƒøòîýZ죶´¤ë3”8·T9=¡¤ÌgIà Ápè1éꬒÆ^õõÐÊ5<¿~Нzð›–óî£?…—[Et9¦h¤`ÏSáÃ4Ú¡—¥ƒt[.¥™™yHiŒ]kÍ›±  ³RBÞéoO£aWh:ü‡Ð%yPÀÈ{8¶þ¬¼ì—_þ%û©åt¨•NVa û¾š?Jmƒdw±wv(ùLÆ›ûº*5ª¬¬.‘©j&ºàÀïÕ°“²yJ^`G(¹L^úô‹ß°ýeP³Y‚°ö;ò6‡ßk£ý^sqp$½~õ96Y“ì§þζ5â´´ø’ˆ÷€ý§ý$˜!ðg=Ž+]œLÖxèsö Bè;¡M>o-m¬Râdõ 5Ö×Ùˇ8Ûæ9ÿFÄ ß=l²óü§1W‰l{Ýþ´Í? Iëã|mr®eP;º/º°w„J ™±Œ¥þc9q°ƒ}w°ƒ2Ë=àîyšü⟦þÑyÎaÚNýf>cý§öŸ}ø{n×j”­¹FöÛß=ApY@Sb`Îlcü–NX$‹¬]öqíœSl¥Œë”¬×²º-/d¶¸$Lç’2ú®/v…ø·YÌÍÏUì¨ %™ Ë ÁߨWÍØk¸Ÿ¿s¿qJ0´½¦*CÜÝâ£sbwäV³âͨ¤ÅU®YðZ9ôfÀQçŠkÕu-P¨Ú×l¶cˆrL·Ø[T‰gò¹y—Ÿë¦U(dððî9|œöAKI’1cY™k÷>á×JÅgTzš} ßE[¢Çc¿ŠÏ[bN´5¹ÝBr»O»1KÛô{Ÿè+ÿ¶.´™òúbEˆž[rõYÍÌ–rË‚ ÀïDùÜ»"kžþ¶FwHWG ëÒ`¬î¹w&ƒÏÇ)ÐŽ}ïªÐ߬tžY‘ä÷\þqG ÆØ«ìýÜåù)jNìÃ<;Á˜Ò×Ü3Ó1æxï6öJ}gýI.}ã¸ÞCXmÍ™­nÝmË#A¤àsªè/Kö¥þ­-ÿŒÙÙñ>>5Yõ´uiýT…û4>4 ÈÙÅ÷êä"í±¿î‰ÝHd2k^ß™-‚yœ·Â#1cÚÜöŸcÄh‹?³¯ó?ýk3­m¾¥þ<ÿL¦€Êß7–ÑŽo3)â†~‚¸àû¬ýw‰ ^Ï›Kx÷9½¦×prù¿PÏ BŠe$è¶1G!¥1Ÿ”ƒ¿åwY"úÓ]ȦsKÛˆL{¾}‰?Í•ç<´ÿ›‰'àâs×XCYÚ_ Þwæå9æ þ{Ö³¨)0Î’‚UÓÖŸw þK<Äà|{  ŽMyUpoÉJÈ—ÌP!ŽÀÌ:b²²&èÓ2~™c­ô:Ïâ9cÿÄçÜ!pž2ã|tÞߨðÀu2´é™…q˜®'!Ãû÷ôáLz¦ýÎM–J¬]?óÈ^õL{¼ªº$OŽ)ý« ‚çW}Èö;מY7’ç>ÎG÷>ÕLž‘Š—èâ3g‰ÅÐÁ´ÅµAŒ‘O7^hô@£=ÐèFü„z@CÈlAkÅj 茴P‡N ñjôÀO¹4^ešê¨êVÉ@|ÆQššOëïßñh8ñ¹°`"j#â8'g°_«yþ Q}}BF²Û¬®’öwÊ‘éi}íÖ‹u{ñZ"{ÖzŽÞ#–‹OûkWlçßfN, qE¦1Àªà¬Ùލ£€@ß”ý=Û?ç`||œ•H›Îɺ4è@¡ Þ!àŽàð8@Þ†¾ÀX;‡W¯q0ŸkÞ 6ùìô4ŽB†ÌÝ“p­¹œŒ>”µ†ù U€^Ç€³È* öoî"s ¿8?8ݰ¶I_ÜHÖ H³³³A£Q²Éu–Ì<í&{v˜ŒûËk¤ÚØffÎÐ㻀¥ ss„ H¦‡b†¬ÙÛ›¼/°=¨(lÖö@]'`á Ù¡f¼Ä;˜‚JÊ›Ñk­_Á^ÛH´ ÀÆ ëY_@P¶ÞŒR맯’ujPd”çÓ!¶.v7×ÑJg›åìguH ø*É®*ÀÙî§¼ßoÆ7c]‚Њ£ÔÅ{e€0³=ÍøV"lgIRA`Ax@Ís2”SW*Î÷·f$ O†#%©BÕ‚^€¹Xýw:i³gÃÉ¥ÝåòuìͨÈ¿º>Šei½8kêL÷äÑÙˆ8óù$D)ù¦óîv®œ˜sÏ}^àÐï Àëp 0ĉö#›0qœtüt,íÖ¨IF;Y`ÌN¥Q ,ñ?å+yï™ÅiUÍÁw®9oS_ŸÙµÐ¨¨´¶’…¹\³ÓÖkÖ!¿d® ¢·Ñ¯ãª&ŒF‚Pp€<»²›Ž•+3û-[ ~w÷~x¢®(ÒæÌOÁNû[²É6Òíô­™Òík›€Óg1¬y¸ ™Å,wÕ#¶¶wY+'€´ƒ(žEÞü-Àp@yhd$²Ü¨×ízš%ëœ ƒ€æ3A²Ø£¾ú«óij|ŒÏÚB<îOû¬Á5Ú³07;a`/æóëT"É*ï FKDÙ\ß [ ÃPorÀ‡•U@úÅ4ˆ½ÄÏ’§­«§ìûüÜlš›GjÌn•5†xÏZ³f¢¿|±È3±þn)Ö—å"†˜¯G€Îf~KXã~LŸ4ÃõOÞ¯¸Îð½é€7c|a²î?ÖIDZݹcFûüŸ™DR} ÷oøyiø2À7¡o£ÞûËÖ²Á7 áPç½j—}èí›oÃQ€àTNãwõÛk[ŒÁQÔ„2m£‚øY”>ú!lºßM NB¼Ã–ÇÖ¿P;‚PØFæçÄä„·ml<䕹ï=¶iŽÀ·’éÁ›ØWo Øm“}>?>ËçgÓY³íØŠìËUlKÚx†Þsþnw9­Ÿl¥‘ž‘4;1‹=ÈÞŽ}­M6ЮMªè˜ÚM=dÚ=pß2}ÓŒ¤µ ]+™öz,BõzËøŽ´ß$–^œsl§e@å݇Æ8ÓFð9´{œçÌúÒÙ¢ïóc{Å,þý\–¤éDŒªgð ÂIføþÛmÙ( dPëêÁçjôa^>C¤&SWUN°=T“Éþ:uœéÁ¶ü±õö¯=ú@f[6G"µóÜìq3ÀÉbÍ‘åÚŒïå< â/ÓúÇ·¬écì/él;êL}b±—ÌWîY² ß\_æð² ­P´ŒÔ.ßÍRª¿÷Ízl¯¢‚!`k6í#c{[ ;ëà`3ü8}AÅfA<æ®@µöš2ô-¬g}Î{Èõ¾šé,5Ácpýøä½(ÅèŽ=+ è-ÑR?¨‰û7ÑúÄÖ:ÌeŸçÈê)c ÀJìyYöEs÷²ÀÎúšáùñŸîø”5äf¶A°³.ìÁ6ü@È@ć;A<²°ðÏ ”º¿š1mu¨†ÑxAoI=ר€fûìú’›a»éÓnÙÛ>‚×,dѱà?üÀet?ßÑ>~]c\<l¯`®ÄkËañ%KXç¼§ŸBœ^‹"H uÛ™¶°Ï7Ó'MŒ¿©$ï­k†µ’Þ‚ýJØç°ʃ Á9¢òÿä,R­ …ú©•ñ8=|f/¯<¤wÄ^N.yƒ{tsŽð$®oÁëPÿp¿à‹f={6ÛBý,÷‰P˜cþY+=k–ù$°ìw=³¼¾Ï(È[[µo$Sð0ôd‡|Ä—Éù¸á‰+û×çUÀ>pòmüm¾·´$œà®ã¤?›!æœ6á@Å…{;sŒà#ßkåyËŒ—@¾qýÓ[¤Ø=g$1(;-`ëxéX*ïç:È€8=1¸f¦ºuF‘mäsžÌ–Ð7‹Í@²Ä•g `[¢à ðx€ Z7ýw†”·R€ä÷wÔ¿Þ݈Ž1³>½u¢ûT'@‰Aùý)€hëiK‘°¡¤Ÿï9G†˜¿—™ß§€×½ý}1Vf›¿&Óq@|ƒŒcz1Ú v(g>Ϻ_DöYÍ4?aM¬®®‡”ù×4{ý˜kŽ˜…ܲ<Æú™ŸäçMð”\àsWå›´ 0ÿùç?K/Ü—–>D¦û(ä ‚Ï(PLÏÌ ½Åó OŽS¯ûyÝ‚,:µË×7¦·ÒÜÜlò»Öwœˆ0þú›7´s<n3» \ô@w1oŽ )ÌÍL§Å…ù¨¾¹ yˆŒqo‡ ~ù‹ÏÒûÊڲͺ_¾šö¤ÌÍβöóiçdVjÝ ¨ö‹÷J¶oBÐy·üPýÕ @öýØw;•”—woóZ§E2çÙé¯Y@y!¶éO*ÚU–Xþð>²ì'x¾#È ;¼ßIÉï×åë ¸7ö_BPRÖ½ r…„ H'ŒÿS' 8{só¿ÄXûbobL/ 3ýììü\€ÐGç’“†ÇFÒxf<ÊWHé £³s"‚zu”òa &! 9Ï\yöYçÍèPÔ)÷Y-™!iã¾:Eð– U(òÜ_]0Ýï;Ÿ=Ÿ.·wSYÅV.\ûeúቀ¢õåOÏPYAa‚ue§ÂÞ‘%ˆs®Ž,ñóOþŠýh0²Ÿ”iŒ`7ë6Ãú4Ùx5zàûêg›MÏÉ»J ›ë0@Žž1ÀÉBGÊ_¥ìÃlÚ[å<¹„XØøHðù¹0H=cÎP˜~?¦î÷Õèÿ÷Ñ_÷­¿¨¿‹:Ž€E?$AÁdÏÅ8˜?~äGò·gwä¨×&.•NØ—NC‚ù®r È;”ú'¶—€€£=Á}Aþ´4çÃðÌ/q¾ß¢¬!¸]Fµæ†Ÿ»©Nä:ml¥c6­gl©è2öDf jRŠþ"‘Š-ÔÆ\á%2…É9KÌm–ì‚æfjýrnM@ü4SM@^ô@ð)ƒ¼ó-÷¬"5]ä,Q è_þî_¥N²-I”…õÈÞY£½‚7fGb7‚Ž¥;äãOOVR題D¸3H™wåÔ}Ö I²ZgjMØ=ÓÛÊë‹ôöÒúÁ½›\6ÚÃÿxF»Õ¾5s·£›L}d„G!ò·‹÷$Ñ+eÞìžÂüÒ—õóÆ}Ì@מáô#"ñ´Ëø„þ™¾¸g—>u¨ðÝ››røËMøuYqÎnD÷¸Áǽ=/ü÷u·?¹î={’€¯*tÖ ×²Ãͼ¯¶ êJŠˆ–Å|ñ»Æ<{ÄóÌÁÈ–fδ2×}Þ3ȹÕ;úÈãµ1?í-Í¢z¤± b ‚ÜAx(nv\«–šbm2–*+¨Pe^\ãó˜ˆa ¡Š«ü{–þ¤¾ç¼—,ïóÞpm׊Dïóôȵ8k3”i¡;é~†â³ú|—M\ˆ H8‰’ŠÎŽ÷é—&×sB{êðÝ þ¸{;ß­M.9ƹjÌ¥bÍmÍÒ~÷‹'j¶cx°yF²ñ=A”©âgÙG7ì–`1þp]Ê%"¾ë_ÎtÜŠûØ\›£ 35¯F4z Ñ?–àŒn¼=ðwö€óDô N#¢ú QŒó‚1¢aÖ˜Gg6>ð#ïpܘÉǽ8Ö½]‹€Õ´·¾œ²Ô'˶b¤c\›%y Žó…ăô b¢#b]0LìÈJnQn‹5"°ÙA8wót&ö–qÄeŽ_Ô& Âè,ÊÆ"KT€ò=³€·™¾®½ó3$qòõæqH•¨*XzT5«½GìÃü ÐCƒbbG'K-E¤á0ð§³4àOâßU@åáP)|vVâ™û‚Ì”üÓšœCrí.²J AÎÌL‡s±¹¹ ê…/æÈÈ Y‡äÅ‹…ú=È–í¦.›Ù™70º7þú{úhÓÄçløw7à°Yï;Û»¦^ ¼+=73Ã3‘PhðÃkWé³=Þ³´™±R‘H6C~` ?mno°o0ýyß÷ R´5˜Q€pÑNÀ÷¤D¶­ïsÛeÀ5¢\´í{ ™Uƒ›®ÊfP·Œ@†™ ^Ë{ù»ºÄ)<\[Ý÷ ÌØ~×àâ( ¨€¬A™é(¡m¬cÖ xx@–ñ9€¦ך²þºÖ¨?`6 ŒµÒðG€«®=ÿœ H`f»eЬ£í½P=À"€.¨»87À¹ òùÄ%¤ßSªý< ,ÌrSCýð€¹Ž<@éà¬YèC?NÓ¬¡woßAÔH3s3dÊ•’Ê dÃÏ’Qþ»oßRa? #ϽÖW×Sǧ¯¢íoÞ.§+ˆMT›PÁÁ¬é¹ù™ôí·o `s²‹7•ºŸ!«Æ Ͳî-ýðŠìów<¿cjɃü[džgçÓôì ™ÜïPßà:3Ì›¦X›í'&è»{æ™™Ü>“ ñ mWF\•  ¬Ý!Èý¬¿ííô,ÎÍÅ~´ºº–2ÜñÅ‹´Ùf}}|!ýâ¿àžoƒÜò’÷:ŽÍR/£Î°¸°I¦Àg©?6žÆù›v‘‰_YOãÓãd¦Ï#‡¾Ãþ´MßÞ/. ™~Vè'!ÌÒ¿û|^ddt,d(ÝÛ.¯*ô[_jgíîŠ 6 Ž 0QQ–8>a^‘FP^i}ç‘%'†™§Œ‰%*TÜö!PÝ‘9r€šA0¼‹5㹂Úuj'ø38PL…™AöïZÚ;¹Jÿü¿ûoÓ¿üA1‚FœtfþºÆUÜà¯Æ«Ñß{è<˜atŠÈîVšg2ZK·âG a ûí`ûiðãÔΞE–z/3˜—޾÷¦ÿA7´‰±Æ8`/Âæ{J»û;ØFkég¯ M-¾À,Ô3ÿ#X4ØÀ}™Ù/€•o8/Ÿ’q^Žóm”3îТøÛÑ5ð7àƒŸ÷™µ5| \\SÒâô Ýóó5 Æñàs…+Ø ac@ﱜ÷»¦ìRç\Œ/yå®é~¥â 4@Äç#ä:ì°,DÌGöÂj*=Øí\ ‰}’Ç®,Uìõ­€ÛØèì¯=}ƒ(úR ¶G;{bȰò5I©vì|˜Q6ÙÒ÷(•S ­H¾T:乪iíh3È-€ ~?È<ìM½y¤Šùž¿1æ9Ìn# >ì½øïå˜óöcïà óž,?öñ+ü‹£ÃÝ´²ôU:AÞ}ÉaígÁ$Ëo˜ÈCE;^?ôËñ«U뙣5²sKØî‚P]øCFµ5¿×Ç»d\WŒ»6S!O.ç¯RÏ­œ£ž÷Ìí*g·öu×ÐtêŸcœÆxïßïÓzïzÕ¾Ÿ¿þûû߃þ`EŸÃ>*{ÿ-Ÿ£¨…¾¹{œò äê[TßTmhÁFÓ¾R)ͬÙ"Äòvˆ±g-Ùeùë’ß¶’mÎÚÖ!Ñ¿ó³Í¬©ê% ¶V/„3P»ß‹ø`èšÄ^¢DºÙ¤ÚÐÍÍØ¼ØÑ%€½ {˜YÄí{™4ÿŸyÉcÄ<‘ˆ¬‚DøOHX?±V MÜ__5âi‚Õ|u«f¤“e[ÁŸºƒ„ݧÐI&.בÈ#aZ€9²VÙkô«ô—rÚ‰‚ìúµ™¯¯ Æòû<}ñx_WCgšï˜Eï³`WfF¹ {›}ζ¯b³èó ÒuWÒà9¾»€§ýcF¹¤0xMÉñžWx‡ü›Ïð¢…AìÀ µÖœ ÏôW•gõgÉlÅAŠûûE¾ŸË¬²çÑ,Æ” ^b3íìáΫ™ÅÞÛïÒ…|¦Ôs;ú¿.¯j“5¸•Ú®ÒGÏÜÛg±ü‹Ñùü0>vSE¡Ðß=µRâzõBÏu¾~úJ&÷OÔzp÷Ùo )Kïža"@Ìæœ>l²f)»ç¹gøžà¾g›~·}¬åóľ“|ŽNüEû_?9dóƒÌ¯£é×xñÝ+;&~®?S ²Š$ÉüÞÐ>·?ùPœ‚¸õ1â=æ¥5Ô-´f;é=ˆ,ø'fÕ!hU‘O—ðÒÜÌzà'%ß3<Ë- º§‹óÞ‘7 +Ts8Ÿl 7dýzÎ{}úõ±Î'ï/©D¿?€~ƾ™>¬qVÚ§ÎCýBq¬3Ö6$µ¦g¤äùœ$zphç­ñ,I:’VT“ð^—´í’ò„žŸÞƒ„rHP´™ñȲv²Ü_b‚ûŽ Ñ}þÍ88.$ƒ´æèÖ¢kCÕÇŠ+9äþú*’Î.®SÿYUÁ+’8LV¨@À±? øm–‰Seϲˆ–û“ÈÝÇïŠ<{ܘK6^hô@£=ÐèFüDzCŬ@ »úK§§nüMãïFü{€)ÎmƒØšƒ²À§¦fÓ¯ÿ⟦ÿý¯ÿWÈ"ù`ª_"O~vN­)³mâ”2ÃVÇ`ÄÈîì${cÚEÒÎlXeýŽL[3WºróºÉ¢¿'€Ø¢óptOPðòì0œ³ƒÍ«¼u‰ìl3h‡ÉT¶ž³@ì°6.f|;Tƒ­Œ1¿OVw °V'¤Ÿ¬ÜKÊÞ î ËÔ.œë¬‹^Yje·¬™læå%Ù5»»»¯ÓM ‰‰ÇSWgÎŒ 6ë>›ñ,@-mUàý/óŽ÷ œ˜!_—|SΚj±qÊñ0­ï^o3ȉCÏweã`6¸, ª£Ýlž3$Ö¸´Î¥¿A&úÏì oƒ ÌT÷Ÿõ:w€!f-ÀБ}àþÖü“H |‹­*€A#uï×㹆B‚ý8¸+x}Ój&}ß3@NÀMùwkÖ+]n@¤H€VÀݺÐýÌ+³{ ]”Jid 7 ÊÞOŒ ½µ½ rßâ"êHÚÜHèKã2œû»{©ïõ+È Ói‰zã‚•®­£d÷÷£ßGY'ËÈœ¯Óña@ü=€ð" Ö óv‰l÷#ÖÀslŒÚèvÚnÇϲ ŸP+|jj:½gí©*!8ábûw2îÓÌùwïÞÑenvy÷9Úºý6…\ºÙÖ«ÖÒg?ÿYzýÉ«ô ì>$˜9€\ƒÛ;ûi„11óymu3­¯­§ÖäÄø8óvÏÐG î |æAžirœ¬î£ý´Â^8ñD5‰ÕÕ äÑ4O`NÒÀ›oÞ‘‰ŽœüÂñÔr]KSdÊ¿~õ*H6;Û[ôåx}D?‡ð°XtqÑ•¶yn·íqž­yç øCô÷Q-âÛo—ò{É&c¬Ïëª$'á/  `K™äóÊ©¯Bò‘˜1áçQG°.»J’ˆÐ׿n@ì ”.$t(ÙîÞÐGÀµ%C2ðï«ûц)Æê‚ï°N Š)oôEBŒãb-Þ~ö“SÚ%8å:¸—k£LÀåBÉAÃdv¸ÖÝ뤖æÓC÷9ö•÷(°Ì J`FÅ»7ËšÙ[¬ç~F?ï‘5‘ÆÀ÷6›k~&ø PÄ\ùt¢= ô(áNVIç²£=¬G‚@œ…fÛ@¦È÷ºFºö²¹|œ#,Ù†¡ñjôÀ÷Ú,§83$=^_cÛªÔ»ÊBjÛ3¬µÐ“òÝgî[!êUÙ¿ÇR÷à¸c]Ò×ýÃyÌbÿñ#}EÓXl7؃fa îð(ÉÊÒ We$‹±ñŒÿÍóüÏB_šáé¦d8ÎYUvn°µ+ Ž÷r®ÍN*aéÔ´g¶`®ÿ»ã/ÆsàœÅÇ—§i‚Ä.µÑK()±E†ÑnÁ>0ó3ll‰*×¹Æf­H±%Z‰¬ HFE5»:¹Øí*™E©íÔD¦«J<Êõæù|æUz÷k͘ Áví{í![Så[ØkoÉl ²–»Ëzö¡{'÷­',Ï5àÅnjzLùVöw³Ê}KêÄNåá/@®ßÁ50Ÿ°Á(ǃÝtÊ?É _>ØL#½CiœLôÁB_êiè¨`ÙÆ€£$Ѻ Axà=ãMîk øNìÆƒÝUάmJ¬q®ôôBô [ê™yS¥ëóçß}¿Ïi$yËøë'hó=6hi/K*\^»EUk*ˆÅÚvìv Š¸ÖœÂNbÿòw‚ì12ûUviåܽƆ¨Þ]†Ï%HÓlk@Å» æ0ó"hš˜[Ú¬^ÏuY¿lü×4^{€. õ*ÖTk{Ä>T!°G\“fP#‡CÿBV`^³º‚â|µ”• ò16ú->P~z{¡›=ÌbÁc|†Ë2€;°=ì-9|ô¼2Í\_ü ÿÐZêÖ¨W)IàMÿ_5+%¥%AºŠõWòÔq“)iû ;÷¢´~­6¤™¯Ž®¤{$×ÛnkK^|F :#|wžî)Ñ¡:ÑÖ,ís¿:†Ô)IœGŠXÀÝ Ïd¹ÞÓÆ~j Û;ö–ˆ¿Ùv2îó¨=@ÒTÎâÿ+·… ™Ç¾Ì4£¼Âžy‡ÿw ¸©bZ<_žñ—”.'#–¾1ÓYÐýÙL_íT»æ°W^Ç¿%ès  fFy ÁÊ-ñAöy×cø}|®¾fÂú½ì–ëê;Æx²‰ÇgéÛĤDMÉÔ<(ë?¿ÎŸÛÙïTxtsäÕoí¹!ài­rð û§à±µ¤õWU„°¤‚û‰€©JuuYøúÞl–¼±“<$rGÉ0úSûвˆÍ@IDATøîþ  U ýK÷oíåñÑîÔۑşϰÿ6¡trÃó“ìhmâ±]‘EÌ|x¦_Y%ø·sVRS™ùä~À¯¢ýnöµ¾VU›Î—ôá;&f£›Ý,Àù :Î9‰¼<×°f¹í{bíx.db_¬æfê;NÚö®/ù§[[Ü›/ÍQÔw¯¢!ÆZ™»ªôŽùB•„•\Ö W 1Þã»®‰¿/?ý9„ô¤bØÇï=ËKÇ<˜dKÎùŽâŒ¤ŠPB±~ÀÇlCæÝvøsH÷3Ïx4Új¹9k×o¼!˜§öAUxï‰,û6Úá\Ô2fd¶»ÀÿÍïûµÃý›µ®²E"Ù%díbªC8Ö–Eðžô}îù,i€LqâDA†á~OÄ /ËÔ2|˜ß©Zg›\c1ÿi[éì*ÊÔ”ˆåSx$3o‡m‡|Õ‰âŒk[EÉKH3§Ç܃=€}ÎyÛ7Ôñ º:ºÇV5^hô@£=ð÷€@Ý$ùÒ¸ýº"¬Ád‘™ˆ‚ÏiÞ˜8?ê1k4îí&tó°¿1Žq?È .¤Ï>ÿ"m¯-§ÒÁ&Ä©{Φ ä×óK Y­VœrkEé´ήð®­ VýAOÎãúå9ŒòB–å)ÙFfªO²™-. Y¸È0ŽPoúèø˜,ïÀóÑÈßÞÝ Op]Àiï`/dÝggfpŠZC–þiB§H¹eÜø4@zè´¶º*fkž“A|¸: p‡÷µ¥u¬'fÆpnj€úÈËãY‹])T³JÇÉV‚sÀêŠÚçÓ’Z¶¶¶Â—¬`Íâ Þ‹šÂ€yfÓ ®õ!'o¼ >«||?ÙðºX2’ÛèsåÜ­[_xïëÀL Tf¹×2ƒÂŒsý$3MÇmœ:ÙUvT%DmzÆÌç³Þš²_fS˜1;C?˜½nû ^ î+v‹ó×NС ÄÅÝóã!$¤ÃáÇyÓ©7›;dBÿv‚‚èW€„ tü ‚èô:_•TÓáðvÔƒÃd¿ã”v0ozº‘)§Jtö¨0nà€Ë0 EÆzëÔŒÇI6@iö{½¶^.È:ï^瘌pïNÖ£à°õÑ .>iÃ(¹®ú€*Ö¾6À%H.ÈpM‡ ¿ãq„Ĺ€ó0Û’.”x¿!¨of¹ŠªR;'¬=nŸ hïÎ:ÏGz˜“é½—((>Äu”.7SdBË6㿳³›¾øü³ØWVWÉà½KÓ\wŸÌfçÒsaø·_C}ô,ëcEÊ Gd ¾OŽ¥òÛ÷¼·—çgO¤õ-H,fz›i`{­‹> ˜k[·ˆŸž‰@×ëLRƒ¥–—V’ߥ×ì‚ñf¾GÙÖ”Λ¬ƒ&Ú6Í}–ÉJÿða%2Ô_¿F~ù}Ú‚­2H69 !m( Ä›5¾´´}½¸¸Èüy\^cíV¿§R'kb@ZÂÉìÌlÈÙ›µðòå j¶Ï¤÷+«Ì¥Ûº!¥çÍh_|1O_ï¥7ß.q߃óôå·€ìÛódȱF––—bÿˆßGF_…nc¿ÛcÏ:d§ù¾ëc—2‡ÿGæ×ûQ{À’ƒëÕò¬%Ï›ØWU ¨ö 2žÖ wÏÛã­¬kÖ+˧¢ƒ™ŒË%{Á1„™>æÁìÂ\HnBª0@7Â|p]ñ™2à^Ù‘mØ«›Ìbº"0ÙÅú53cuuyRùåûë묋f²üû¶’ÓÖ8¯V`i„u¦mŒ`²ºª0œ^×5y q©5(³G%âHþÄ´ý—Œ»‘í;Ϩ§gÊIÑÞ,~g¬vI7ŒÎ¸€ø³[ìö °¦Û‘¹æ{xãGáRP.¬í©‚Ðdþ«²Ju‚Æìdœ;·´{÷è7‘Ò³c “Ô §m-Y@{и†1ÿT’¼&¾´‰d©gè·öŸV÷Döð„_WBøv…¦xÞ¹¡›=¬r# Âö)¥¸ ºº§»†\3÷Ü×s¨ÊV¢Âž'm–=4Î(çº~܃™èìw‚øó9öh}õkGü~ §ªx jøÆ0¼(§ªd‚¤¡¢Áõ"3™¿Ý}Ù_¶ÇÒMô·aMzîÓVöoë‹;ïóýܳªœú«ÆKøtØíA ãr´ÿùÓLÂÇošË?aÛs–ÐçíÍÀÏž0œåßf:7Óžú}H’ ˜fv{š¯/-Ðëœsþðÿ#~ÓÛs­ª‹”3×[#!lK÷¸¥ËêÀ??gPȰ¯Oùyû¾‰çsŸ‘ÐðÀ8µÐŽ…óÑ~½Ä§~xrí¹aQvä÷¾¿óÉgòcúé&gH.±Tž{[ž¸”{šmç/â(Ç\Å-â$þŽÃ‘¹R‚ÇÑQ‰½ÛxÄ8l° ðpžãÍø™~ÏqP!Ïoro¥½*3z6{$=pIÎ-žžßpܸ—%bî¡ ñ¤Ï)¼.¸ï:”,bLBEKƒt¢îR„„ØÁþrŒè³JN̵¸·c5AŸ“˜A[]CÕ&²õ™MŒÿa„T¨U…þŠõÊ3x~^Ÿ9==¢?J€âÄäTD¢Ãúº•»Ï3~œ!ø£¶Íñ.[E‡ˆƒÔã)&ÚxÖUÖoð‰SK,6ŒÆ«Ñhô@£~ø`ßn¼=ðw÷†‘¤@ 6Y€…|ñúø÷ß}•Æ'=ð£ì¦w8,a|c¤ût3ˆ$húÅ/ÿ,ýõÿ¶Ò¼2Ê[ 40òÍØÕ3Hö„äà-ñéÅÑ€e£©s†Qn]u¥ÔZqXÚuœ4…[¨³¨³k³Øâ:èfNçYtn¼gÞ¿qDu à`Xwâôéè=>p>Üâ¬càsÛÆ·ò‡:…rdJTnîè`áÈ)oV"ƒ¶€qrz:²0ß¾ù…À`e€ü@J ÖÏšœ-ñåµsÈ €Tf~/}@C’¸ W/`”2Ç*’yêw/qR”9Ÿœ¨gê*e}~€°¨,iäÔN#V YßÞÆ ¹Gªt>ØÏ«kÄÒ €ÎÔ@šA³†u㻣C€¡‡”ºžŸ™F«ƒ,Ý÷¼üìÓOÂ9RZYy.¥°%'Xë9YSë—Ÿô+íg8ºYÚÓÔ%nÁ›ÚYÝ'Hû ÛC›€ë¥ß;èƒCœÉ32—ÍÒÕÓÚ•o^Ó?:Vf0tñùSž·Â½Gi¯;«ýcÀ14ø£ì²¸}«ô›Œjeëe¥ï‘‰Ëp‡3®LªYÀJ޶¬µ–±ýÔ?Lv+ÃX1Ü xxÄ}$KD67ý-"ÏœW:ßl÷y³c—ñ_¦ëÙ:öd,¤°7äç^™EQÑhí³-Îu|ÏW=¨ã³È´1¯$›ØNYö>£`»ëųGpÞ,\ç•A´< ±‘k‰01È#Pk½ÀQ2¥Í13ÅZëö£A±S€Ë~ƒªÖ˾"ûCó~楄¥üÂ%,DÖ9Rûf][Ã\pÞïY7Ýl(åûÍ^þíW¿CÂ{;½\\ˆìc•ÎŽ [›Þ q3Ûgg¦ÓW_} ®$•ˆ"§þ=¦É¿' ]€]ÂÈÄøXd“°„+©xˆovû0Rã—eÍY{]Pà×Z÷ÓsÓ±öɸw=ŒOŒ¥e€ðuꬿz¹@¦ú‹Û·TxOë¶­“ymð…Å9ôU@ö¥ô³×¯øìëôöÝ2û@ŠçgbîÓ/£´í5ï»æÞ½ìÄw]®SŸ|}}ZñfCB, kÚ’‹ sì9§éëß}EÿLƳ"oOafv6Æfkc-äøÆ˜ÓŽó6ýi¾ý{uu¶¾Ù æYS§ÿAÚx?DR^¢‚F££c”i8^šõ³þ‚ùªqXÓ}zv"ÖšµÝ{!é ²_ìѯ'¬×–õê 2íîîÅ^2€4¿²õ»;»ÔõîëÇÌ£lö†{BJ" bÖø#ëÝù[Œ6(ùau-‚C=;övöéŸm2Á»R{­{ÅÚöNš ýlVW׃Ðtr^B!â.lj%Ë‹tÄÞe­òN²î×~*ül`©Ì5ˆ[’ ùаÚtp†ôÖë l%ã1ÛBÆë.²J¸‹¦þ3kКê¥+ä# øßòH°Ê³wôûÏ";30¥4q;ç“g[ãÕèï²<3Ì.3Êh ×—.‡g$'Kìuþ®ÀBÈtQn¥H¼„óý(k~_ %Á°•’2wwW¨§H¹OaC<'À^älçªÞ‚/IsýÔýïöül¿Mœ*Ë(÷[A9H¢™Å°Ü³Vw¶#ð?ùí{i°v¦/míS÷=ÏÔ½Ýmö.•†FRÁp¥·»º!7T÷Å×n çœ^œ¦-²¢wKGéÚŒ:úÜ댔µUH²*o‘1mÃFkIÌ6.lo5W!–e°K Zß¾wàVêÜQ(»Å¶xf_3Óð ZÐF›:Ã>܄ͬöP ¢ ðà##É9W£oyÂŒ f’I£%|“y‰ Ò¾ª ´{c޾°m–6 9r‚ÿîÍÝ%61ïºÙÛŸÉ2U¦9‡-ŽA„ Cf!¶l¸çŽ|§ƒ=º¾ÂÄ„¸{‡Í|H?Q™çè¸,8/Ñ`9?~†RLW/ufG¸G1öh}wGÉÌùΞaú® ã”3 géÙÑn:ÚãlêHŒ—ç®Òù °Ðo­ÚAìŠæ¼ëÁITŸ~´îÐ6e±kÈ„?1GšÈ„ŒÜl2úíYÀÙ&Ë¡ØÄ؃ÖrF¶³Ÿ˜­Ë|bNéW[¥e-dÁ¯–vzÑs’¶©øüù»¶Ö¡´¦M Ä<û“~1vŒdds¶á[»æZ)YPÏ8gÓGú æ¶}äüÉŒãÄúŒ©šXVÕüâ<>že´åì$ °™îºg%²ÖÈÎÆßr¯)C¤ÍÌ»VÝ0•âšÊ¢»Î”õV&Z Z»¾*ŠO¥6:8Â<0[TÙo×.>Ï> B!)M9;g€m2óü±æ½X¿Ì çKŽy”îÀG1F è˜àL“Ä!a™™ë‚ÁÆ XËì1$k‡Ù? ¢,ƒýY_øùEæçmøYîgÞ0j3»¯ñ³„}³ˆ;°Yy4›!|‡ý*ÁbˆYÙú¬ú Ä: 'llo†Íî8äZ 1³×=°/ó„7ýe½sNש2Õ~_…0³Íy(ž±â U2æëdÃï]D3¢m‚–Öoæü(2¦ @Œ}­ZŸ=®=ëÝ}~•Hõ#‚äÚ¼ž#|9ÅuãÞ‹f½Ó÷´B;øo|&H¼ç¨xfÉ4wOcC5Ÿ‡¹ãþä|R޽ƒëv±¿ë»–õ«+çCóÀùæ<¶÷ìß–›±¿ý£ZŸÄ.mŒkúÎáL ˜+îi×øïÚ-¾jøÙ>g³¾xS!b5Ä$óã“?fk[–{Ú8Ëyjiù»\[gš^xAöÆ'雯þÀ[_œ͉¬ƒV‚wCfÈ<ÍhÑûîæû²™Y1|§rÿ”ÎI]Çωõ8 fY±úq²‰;˘Y»I–¼Ît€…޶޴~5÷üœ ¨†»2—!£…£cFéG¢Ë 9 E¤,«©“S–íl¿ŒÔ7ì}ز™AN±d{:ö>Y_ýp]æíÄ’½dG^àílgÈá¹[ÞÞDʘ ß9‡}€:ëò pf p Vmá<ÈÖ©TZk}{ ó˜ïÌê)U®Ôºä„66jðf…ô€”‘à6óÝl„}gyÇé0جsç·N©²êÖ ˆ÷9Îyo`Öùþ†Lh³™+]Ô—$½â9Gfy2ö}€Ýª ¼8> ÛѺ÷òÇÌWÁuË8w7™WŸýüç€ècé믾J+ŒåKÀ_Áæ k³‘¬+€ïf,þÙ'dv/¦7oÞYbœŒë± µÓ¹§2âf~/Qÿü«¯¾N?ûäUÌýoùì$ŒÙ™éXÇïÉ 7˜eûíû[êŠ/‘õ=Ç\Ÿ¥føuÍW¹Ç‹4É´L½ã ¹Úë+k!¥n}tÕÞ¯®EýñEÖÉÛwï"02Ã}|­¼ÿëźï5Öò>5%Û_¾XdMì³f6#Û}¼m<™µîÞØCŸIü8È–805=C á42ÞgæfþçëÙû»H ÓgÖI_‡$#azv&öíí´ Á@Ùý©I”!Üg Kô#W/QãˆõY”Wò]µ³5Ϙ_" í3.ÊûI†ðP^Ý¢s¶L‰ñ¾d¿ëg\]‡gÜÿÈòt ¶vëàž¥ÏÌšØäÙ¶÷öÒí±,Ã2Yù'Ô'^X˜ÔNÔ™_N-ìÕî{ÛÔðE)¹ö /‘ã†=¹¤ ùDÒ‡ÒÇfH ©Ž`Uê&Öîí`m¶³Ww¬îåâ¬ïå8”ûsovìÍ|oâ¼1ËVà±¹Êì½{‚Ožhgd<4£é‘}Å’ ×9†‹=Oéòq÷÷#%¯Ï(ñEúò¿ˆç½å³ É¿áJÔX˜üÝx5zà;ë¦W€e€‹žNÎ6ƒŸMÎyÏl¨Ù}î1ýÒ&sÞ›ÅÓDf¯eY8êÒöûß,ΦÈh탯™÷ÖÂ$DÎwª•³T戭ԉ}ÕÇœËó¿+  ¾þèÐøÝû?‚žúP‚MÊI3³ÈË+Ì…’A¨às T˜É¨]ë—þ‰õÿ7ŒQæ:w·ézÿߤ&2Ÿ²}©kö3·:S™‡'ËióÍ¿H×ü=ºð:ÀÁÚÆÏÒMdP>?ÛÏ‚UÔ¹½ZOí½3ø@C€2µÕRj~Dʘ,ä@÷Zn$µwO“áèØÿ‰¿\_L)÷0írIŠJ¡ëÃ4€é›ÜhITÐçhf¬#Ûšyr­tŽÊ~B$k ç;ÉfL‚8ˆoêü? ”@¬Ü¸À˜Dk¯¯?ì¼²Œ– úr5ü,å®#›Ù;;÷Ïb¯k‡À±µ»Eræã‹WZ³ÁÄ^øAK¿Eßʽ±™R >˶Mf>¹¾ÕYŠmø#n+û[¡(–ñ <êtOb›²ÔDO_žlè®÷õ㕾ö¹´ãŠôYì‚õ-£f57ó\Ì@ÖœgÐO;°Î$Џ df9W̮޻—‘!Ï}n™§úxì+·déKÆoc?8¾GòÞ=’®‰}ÁûcºÒçõ:å~Ư¯O.P) ®]jû¢68ãìXû³ ÀNþv݃#S›ö:î¿®ï|IËìeH2ð}Õ£r<³j,D&cŸu_7K]YöYõ÷|^_§çr~™ìž*¨j?z.ØUš Ñ·ÄÕ“ºì:ãÀþíx97ŒM¨.˜EA‚‡ë\²£à¹e-‹˜CõQpÞøãèÀ¾½Íw$ÈØní,gªï{ígâ•è{ÏbÇE’½±3 *<Ñמ۞ÇÏüî*Hô¬=ž»e‰Ú#²ùP/0yöKÒ0¹†D©&Ôô%s<¨q¥ !%_Hüƒ°ÖVìÅGƒœ€º£Šv9îïÙ–cxd bGìN Ìü×'» ¿L‰|eñ{{Š©ù¿ÿþÇÿ©ÎÒÐ(qH¯F4z ÑxÔŽºóæ·ûÉÞ‡o4zàïÝÊ÷J‚3[ÃŒ©‚0„ÿÎñÆþò÷ž)?é 0€%„C§Ó#üކ¸lWchÜ·:ƒ(äºtXp6¢­F½Ž¢NœLq3Au¤d¨ë°„CÅçkõ\ ûZဦeØ«—wÔ-¿M{ÇeÀêìž_óoÅ+2{È6¹†Í}K=³+ÿÆÙõz5œ÷ÁΦg5ïIû`pNzºÚxƒÐv ùª6žâ¡LGêiŠö KxCFJ3Nwæ™kã0_RC÷«B¶t™À¨YÐûZWü¿&¤¼¬Õ¬Ìžu~e,ïP˾7ȺX¶À¦S``õ0rçÂŒÒ NÿY%‚ÛWåKžú÷«’eû§ôœ`¡ŽIV'Ÿ€Ù-NÔÖÖfzú‡Fp¨kÜs).j¿ãH]à¨Èˆ6«Úu»¾±!•Pm2œ³MÍ4›C¶¬ŒüñnÔŽž Üð>Ãñ™[H}€lÿ޽7}Ž4»îô.+2û^@U¡ÖÞÈ^I‘Í#;fŠqø‹¿ú²#ìð'ûƒ#q È\%²@çÓ[ ëPa½÷}2tð^ô Æàófí»ÑË*Â~oôù@Ÿý7û€Jªú ׺ÏÖxŽ™¦·sœcþm”Æ4‘Æ Ö£ÙÆGMÿ¾ øk„ÉiUõþ~LÔ³ÎöÙu x«ãˆ‘ÿG¬U³$Hçqú¼EtºÎ(:Zè±n´±†!#¤ø0‚ÚèfÇkòì^Ò„ãÄñ˜¹×`3?˜áÊúâ~k£³ö]KSd&0Mù´°æž ñ0×x/¦ƒHC/YamT0Ì™^^Ãâ´2›Â ýÜ"Õü6㛄nFÖÜ¿ÿCA;èf¶ tÊpü:llX‡]CúЕy1¢Üÿ‹¬gùÍÂÕð.´z‚ƒŠN+óKš¬%²\0Ÿ^¯‡Öý¶dÄÖµÃYÏ3D»¡¡Î7:ì¸6t¦a? ´›®ß4ÀFZ^°ßcî¬;9‚‚FBëëê¼`D¹¼J|ëM©èº<¤0œÀ´L 0²ó Ap÷e°­ßÄ`¢aþvÌt±ÏØ:[ì³§üpˈ‚×)a \ÃŒµÚ?üùÇ‘‚Ø î=Hptqï–oÀ uZ…îl¥&é×[¤X?i`]ª§ÒH±>R åa™ºµ”í Þ`f¤˜^º6žn]‡¯LduË1éà1`Ô-F÷„LF2à ”ŽqÖªs6Ô[´±‡#gÇêkŸ'‡ü¥e~?^ÙOK;D»sδ‰Ð´>Ÿ†Ñ6Æ(©ŠµñôÏÿè¿ô¿ÌYe œ£…󂨨p²ù:HÁ6{ÿûPàBî6z÷x÷q:=xHD.éf;üôÁàÉÒÏÓêsÎŽ§1x†èã£ök~e+QwV~Þak4=(;­¯!T‘ëÆ…+RsïiZÿüORcóÓt°µÂ¹K¹…A@ä´¿(÷7#˜Ûøyö|}ÌR#2Ϭ%:aÉ5´GÔ&rÏ gô—­©“ËWû²OY‹ÚrX´_–-ùðƒÒÚÒ£g²Ä$N„3ð~KX˜¾UYâéöRzÿÑ'é£ÇŸ“úw51v“8ê•—‰ëÀÇöáqz9'NÐ[ €’ù6²5æÌ´Ø'ð?e½ÔßÞ‹´ê:¬j¼6³é™E®–qtj § ²7 –¯°Ðæ™Êìô’‡¬÷о€7¯í7êŒûä‡Ñ=cðÏãaô]9œŒÏÄH®ìeD¤`…Ž®ê6§DÙ¡>ó,Jãp曞œ£ 0T¯ðVAXA](ž}ï`(ÛÕgćfæÜȵx7¥·*ÈHF… âXF}Hgò‹¤ÇdÚZºó #³¤}?N+þqZ¿÷ýdúCþ.Ì“c&cç¯ûKž¥ìׂo»>Õ'}9ߥÏ÷Fô»wÔÓ•1uJEæÜAFþéøZÂÿ(E ¨§cÍ Ž9Ö©o²ž•ÉàÔïŽÑ7Ô}šì5e¼åÇÙW8 ³®ÎÂ˲SìäÆ}&OáQò9 ÃQýáÝÏÒ>²à[¯¿šfæ(›ÁYa&96'ý|§ß]úÎŒèä ù žÉžu/©. k$óÑΔȘxâ°®­ñlút  \ŠïÑáÎÂñ‚ùOè¤"­\³çŒMÓÒÒ¶‹NéBYl8œ`ÿtN ýtœQʹ_YYÏä¢Ë…ðÝ ƒ8ÓXÎ,xÚ¦~w¯¹´‰xö(ÏFm"öa [’äì×gvífÆégŸ›Å¯€î%¸O?l†ÚÑ9’µºþ, £·vm}:§äMUàU5¾ÏÙê½zèQ G/IÓ„é-¥"t!h_@_²©¯õåÙ²ìÞ«GßDÏkÁ@•Œ~où0 ü¦›^ ïBÈÕªÂÉÞëÅ¢ÀÅŒªÈ!Ç¢\Æ ïY|u1Љúü&Bú“?þ7ik0Œ(EëØšRSåHC˜êPŽ6úc‡ hÁàVÇ›½ˆñKåß”L*7\ެb‚âÂûøÎ´‰î+•gÔœVP‘ûO1«Lz½Š…^èìGAÖZmƒúi(w±7ñ*ç:=Ô­û6À9û•gåñÈÕY&’½ßgÑYNá™®"+CÚÄP0Txà–êÔÎÖ€…ZŽ2O›‹wPn0Š"àïv¨ ŽSï~ÓzE*«•"§QŽQ`5P+¬Òo hÁÕ5Àóq”k ›ösyiZÅãi–ïMSy÷‹Ï ó8àáljî6Óû?û RºKWSV«¬˜]õû¡Ôc@'’´  oÔ¬QÖzOï¡ô›¶^ömÚPŠPÂèÇ 8Ÿ×$¢Kµë)QôÖs?FÁÖˆzŸˆZ×)ÔN‹UPÈPÖ5¸.í3I¦Œ·–å²É6…&ŠÖ0@ä1óz¸´H#³óX?j(虜ÆÐ/àm4Ù&Qãƒô­_Z¡¤ €Oò]ãÄ ^¬,z£_]£Qû #ë†Î(Ž*…]ÖÐ2`°Æ•4£'¶â£zÖ70¨” „ë`°»¿Åh"š#ŠÀªýÐxiÊ@ÁR7×Q¸Õê k&3¸4p~ˆT¥.$–¸†k-dû~o>,8|F:2€â64d¶~½ ¯†,SuZ§NåVÀ¾LÄ® t2øÔxRàïJ©uî]ÿvÂX€¡D…]…v93[`}“uµ`i$± ,YPŸd%û ËKKiaáJ©MÖ¿uëfšÃÉàþ½G¶[éÆÍ›‘î\€Ý¹»<ǽ+‘ ÁúÙ/ãåôÞ{À.âPñ*€ö¥tçÞýˆr6eyˆø‡쎥ۤN?†ö®ÑWp‚ˆHsÖ§5ÆV~õ›¯Ð'"ò ½}^LOpb™Ÿ›Oo¿õ&}|*šK/q­ ¹©éÜ_¸z)ž+x=M„÷Í—n’? ÝÌ£ì³Gü]ÂaÃèl½ê7Øc¦­Õ9C‡ˆ}æoŒ=x™ó›[ìÿÃÈŒp ðúÉÓ¥¸vŠèî2ëÜ{­_<:1Â~&e:À¿€ùèÑÛ«[ic‡2 ý8I`P³–ücöÿUœFÇ# ïÐÑæéâ ð8A×ëºÉÞ­²F¥G=ÿÐÇ1|áÑ£§ih|8Öì!ûúÁ£ÇiìˆhtöÃÆúv¤¶‰*ÝØÜÜãº6û‚š‡ÌÑ:†—{d›¸}|¯£O? Dç`ƒ¿‰g/t;Ô¹%út”¢8+±Xƒ'ÉW "†Üo ö³<_#‘†ëã…ñ~jÍö*õ›Ô“§\È)S ñ-"-±6a¨7ÁMsØÄ`eMÙ·†«0ŠÑ_Sêæß<5 jqÏ”ùQΫÃÏsÅ.séþÑ?ÅPvÆ%ŒKp J8f‘6±}þbÉ@ÿ¹ŽÆóÇÔ«Æwþ8í<XMÓ/ÿW©<¾€qpêÞ‡éüxàáçŠQ¾'Ô§Ýp>$3È~šˆ#ëçÃ(À®Žnw>ø1|¬ ÿä`Ö8Ù6SßÑvTdÏÎ\gϰ‰~Š2[ƒ5`<Ä:©¦üÔYhÇÀÎõ ÆôÀrÏÐøÊÌ€ü!öòW5§ò I ¤¡ñÝHaÏâ Î\KL¬./ÑŸ<|!@ó!¢²¬½í]uÎmÓÖÒ¡ôÙÒ½ôé“»Dt!"Œ²uʘL #Ѐ·òÝèOXHýDi–‘A¨ÙmYKõ“É´„¾}4bp¾·¡¹'GÊd(kœ š{ЀÁ¢˜ê÷~¨gv<2©µ–»ÈHF¤QsŽ`¸>Ù¶H”£œ'ðÕ 2”Qzé¾”¡äÙ›‡;éHƒ=r ‡ wÉÈYðmAòARQ÷´y†€„ K ‡ÎkfólZpÃçqVkqOôºÉœ#Ì·6u÷™ÞÍš€©(Û#·l“q…~UxÚдŸë£Èœ5ÆÖG:Zçf iëh?QÝse:Ï>ŠóY®J=Øë/‹l.d”ÚZ%µá"ú ýü*^6Ik¡‡¨o¨ð'/JH±ÖÍêôvá œ§‘¨áŽÛõ7cÍ7ѳ,‰Eç¾TWlÞ³òd²V)Íbù”.‘·ùs2-õ!Wé8s#åsŒÄ´Ð5ó‹@úñþtÛFvôQO@þÛ]¾—†.}ƒÌQ·ÒÙÜ«ií.sð©½&ž¯Z5þÑíK ø?Ë1u\ÿAªûºŽ"Ê@‚gÅÁà!Ú|w÷œY îÃMœJÛDjW)]`ÔjD«¢3 Îu¤!´à0ˆQ©ð%åµ#äÃ\Gedã>¢oìñ¬,”`uŸÍìCTr“Ln{82Q"­ÄJ`½Ñ¬¢]ܧSª°Åéèy†[l`FÈZF>–Ÿ Ë[ªèL:× žg:¾Îùð5ö˜³myþYcY^`v0y½kÜR ÚL—.°wt؈lJ¦ï˜Ó[y™WO »àY|ÈgxfßCÖ“WºŸ¡¹× ¤u}‘YL‡}J<Ësh‚Hø6t¬UÙêðUeJ£’-Uç(íÿ ¼Î1ú·v G÷²¿ÃÉ…Ïå‡:}æ˜gûe m/~o»ê‰ÌâÜ/_=:à<àe4¸öÈú9TCïØÜæ<ªG?tp$ö¹Y›F^1œ9 :;6÷€²@¿ÙæC ]µ(o÷ ¥ŽÆèpõÓ.}5êܶåKòÏ ë˜ZP:M›œœª”§uì2ƒžÑòÚ-üÑ!";pN¢=ndÂO8ï¬Óî3\³2…xV)û„1:ïeMx΄`Í\mÇmÓ{¥±×HOõë³—9_ì¿€¼ö÷Ñ…‚¸f©2º¼„ží2¥™X_>«3¯Ës?ø±m;7úc©„ ÎÇÌ‚pÂYpPgpÆaî|[®D`]›–öé´½·ãSÏ‘¾:N¸Ö•otæ2@«C6DtzÔ&1Äz³ÿöÛl 1¿Ð&Ç5ÒÙ(}ùÁ9û} bì>8{ède&`t–8¥¯–(1ãž›v™p ýuŸÒ/ñôZ3ýX‚qûŽN§A/ìsôÝùsÍM¯ìáßf¿ÛG_ÜE‡5óã % ]{frÏŽò ËMp „mPÙA¾âšöåZÓž¢£3ívÙ3ÊHA—|C‘7H'¿÷êQ Gž—™€%_ïbè£ I釠U?—‘ö^ÏOµžŸV_÷+>Ô;ˆ‚_ ’ÈW‡dCèñšjþ=_Tã¬U~Æ™’­w”(|À|Œ©¿Kêä×7ÒQÌÖžâЉsÈô”ç z[£mpñ‹tRhG^ Gæ-‹ÀŒ2gÔ‡¶ð¼7„z”³Ž>”šè Ê?¦²R±QÈ÷ÜS›Øn¡˜´ILÔ:oQ"0À!  &Ù›&^eHƒMñ Õ ¢´0`¢+2>ˆ/ ¯¡N¥ Œ‚Q*Q[TobV‹:Õìý0rð\ÛЋü¤*"Y„žnUbÔÏh{ m¡ ŸáŒzù¿ÂçõÕû¡ÜÕCèÍ»û(]x  D4öÏÒ£ã½OõÀ=@)Y[!jpKoõ}kß±÷PXŒ¶ßáû.  µ2ÛõîXÚ#µº)á‡G‰2 ÕKÝtÔýŒù>)´Ï0¶]pÔf:î*<í·^‡¦ý¤¾þÏ÷IÒq/ ž=xL-ÏI­qÚn¦Ÿýô'¤ˆ'j`ñg?ÿ€üVºqc!”á‡Ôšl¾|ežÑJÿ°s˜¹} scÄ“'‹QÿrŽê?þÉ»iui…ºïÔ'šì?üóðr¾2Gú|žõÙw¡Ýi¤Ã6¥ùŸÿèéÖÍÛ¡4ß¹{7­|Ÿ4÷» aLì½Ì½5êm½ÿÑÇ¡ÐÎ^šG îOïþ¿ïFýº ¢‚¼?ø¿>‰Ÿ”ľ÷=Fj•b ÿ蟒êžÈüùË(Š•ô‹Ïî²<0xÓ'#ß{ÿƒPfõ²®c¼~ïCˆØbM=f<B¿ ”ýëðñÓÀѵ4áUÅ÷ǀ·D˜ûeóÿŸËu¢ÏÇñá€e#œM¡ûƒÖÊêRˆ|?gÌ› J<ë]úQÇyÁµ½ƒLø“÷‰f}˜Ö} pó“;÷0ˆœ~ïAD»VÝ?ÿèæ¶‘|‚–OV7#‹Àëg}‹(gôU$“8:<~ÊúÁH¥qÅõm _%®-bÌyHó½û÷Qº+ß;)=&=$ûiŒ5²´¼’ꓤˆ#*†œ±mgŒ:µl3@f5Ž1®0æCÖZ—ñµØ—®\‡.ì6xÁ‘Ò©]ÂÁ;Œï-öE Pv•ôî®›)²+°%18Á“ž˜%nCíTG‰¦§.ðÆž{ X%åž)éhDZT‰ÚÃ!ÀtâmÓÒ·cŒ!mx™Ù8ªÃçÙç‡Gà(å`ŸvIU®³Çë³ Ïa †¡Æt—¦]ß$½¥ŒÓþíáhôx‰Ôíé:Ž<ÀqàŸßOßø&Ž­Ìb±F-xk@6g¤›¿Ÿ58a ?&ú`ñÉfŸ…ט&xšÂK0JÀ0‚±¯¦/MØ Ê~—{Ž0¬êõ¿„SÎ)49¹2<îΤt´%,ØŸÛ[û©„ÃÄøÂþΑöÔå £žò{ gS¥—Ysö Óþcj(Ã1•§‰#óHJªá×ÉkÎÖ(Ù97Å,¼~U jûì¼ m­;wÙOÀ_£WóD°L‡)SŒÊou6ê»/Ý£ô†Ïö®‘:ýœ&9Äi4Ó(ÆrãZÀy ¦®góõø-ÏÄÆAS$ ‚ÍRÆÐ“f­qqX9ÃìÍñGÿ*½úÚë´W‹4ñ¦6§Æ®¢Ô­óé¥y6Cé½zøJ)ëŠuåÚv•*iû¸’JU"þÈ."¸BZwm‰ƒ€gi–õhô‘\€Ê8fµ¬Iv íÊU ;‚*QNâ`’÷îWÖ>ŸWIù{66‹|±ÂÙòVžB6 öãwñR‹B ÿfÌÐÑÌÈäš,§edµŽrèßO œhD¿š©ìjK|H;øËz++Ô,_^Lôe|âRzëÍ7!Ô Nf×á÷ȼçœeôk}1=ĹÈZ³êQ—¦2²k Þ©Ñ_×gÛ‰6CQ w~ÄZ¹~‹1‡º:Šh’Ï”Å.ˆRœ†é†yÎY1°9‰ìâüžbp3<š«Ò>§ÌÌÓxªÏå…ógWã> VÇÌjð¶~RÅr´òécMNIïÏ}§ð»“&ŠÔ‡0ÆW“4ŽŸóy©o(M¨¼¯l!ù} ¤†/²¸ÆÊ–Z2ƒ[V’`?‡s€§R¶íZœUþô¼UFÑ)×õ§¨à‚ó Qݱ”ùÝnCgÕ d”)æ5qŠìgüÞSàì+ÐV Ÿ#=8_Dî(#í#áÄÊ<½vùvºyé©ä‘kÐ=Œl%å{mx’¹]M;›O¡c?²7¥PNp†<ÜMåå4AÝç‰ A§Löät~}ýÍmp™ϱÁØDF«"sªé4ríêûïLüú[om=zó|§BuÏCÏØç}y­QÄç…+œ­ÇéÚu²õo¥Öúû8XlQµÀÒÑÐß/0ʘ̈́À‡®/AÃÊ ÀGÌem^í[‘»s8Û–ÛiêÊh*²†nœºO‹Õ÷ž·—/èuÎËG'’rÍZÁr}–ë7á2þ‘èKç?A;A:e%3T 0çEøÂÈØtºvëfðF³˜Vû`#Uc…2Ñ®W Tb#CÑ6@÷Ìü¥ì|P‰æ¥¼×'ÈÌŸF»Êò]'óvºó1{¢:hªiGùµNYZtϧþˆÏU§ro(¶)urÏAKeeö….¼î=ü”ö•ùbÁ©|äqÈDõ èÔÁ@Z;ݧo¬ëŽ7Æ óÜÿÌÈš)ø{¾¸³{ˆžfzmÀ`~,«¤¤oPG¦r°Žšq `¨YÕÂÒVþëÐáÒ‚Ê: ±ÏuæV~· jƪ9ò9²vÀ° Þ@gë£måoÇîD œÔy"âž'z–rCûý¼‰3u™³Ôñh‰û§yöy¿²{6‘]4:X.¯­‡õÈ-å!øií&å‰HÛÏ}:Èú¬;8»Í}FæáýÌVĸtV—.®w¯Ñ Åü£CἤƵå‚<á|‰’gÜg—¥=â~Ö—|Ü÷RÓ3ÊÆýþ΂0²(tõ éäuq¾0æÌnÄÚe1m~íáðÁ•môõDç‘as¿ÏqœÝÈv„>[(¦k£ÓìÅñXS‚Ø^WtOU}¼Y í›kúœ9°®{™ózšk£ÂŠtè‹3Þ½aÆAeÖ¼ú?ó{€î{&*tÐÇ=/tqÎN8oÏŸp++› `æ„\ž>r¦š¥ÐÔ÷êjFÀײsvÞ=ŒÓ²Ž#9d‚ÒÓ«à ö+Oœ¡ó[RÀÔóÊifÓ™PˆKÓc¬ös5@šûc¢£”Ð9{ ²‰Ù¢D€¬ÆÈ{þUJ:hXãžÒd£qÖZ´/¨®Í=Á¢b 8FP¸A{¯zèQàËP@×`…Ƨ8Äelq|™–z×JIÏÞ«GßD÷VfÌŨ€ dÍ!=a_ôWÆk0#à èõî½^H ¸šU0Œþx–¾ ùŨ˜óE…0ß•Ç@IDAT’yëÚôÙèXúüɧðNUãƒ1¢pÝzÁª ËWOQnTŒƒËòG”ìþÖsÜϱ„B&åѦÂB:zšŠ±w«Ü©tUä|rtƹ1Ò†ÿŒ8×À§qIX½m£¾Ïo6égƒ{P¸T>TÄÎ;‡¡èù<–¨IÛ**CöeêÁ•Ї(6'PTŒUvªöyîÑØÇ£CÙ*$P¤qO¡?”Q£…hSغo*=*æ!€ÜH«X§÷ûù9#„Q¡U™ìŒ©M4¤ŸI=ÊÞ3z¦TFÙ#º`|’äž „'Ç+aè˜Ê·wx6‘JÐv‚(Êþ©²‰ €ˆ–’ß_}‘ÌýôAãôÚâã¬MŒÖ¾ÿÙ/0yƒ’¸eë¦tlVµ TNvFÓ½/>‹Hø¶ Ýú#ͪJðÓÇ#‘&Pg¿aj¿ïòýÆÎ¥GÒµÛ·ÓÏ?ä]‚¯ö§­Ë—Â𻼼žjÐqo mèøðÁƒÎ.d«0o“ú{ºë#¤H7Åò ól$üQ9O——CÉmbØDûâî½P6g/-C?ëίƺ4µ·éºï=xµ»oÞ¸ÁZ9¾—n\»=B”2i«IÓf=°A€J££5ðÎ_ž‹´±Fl«ˆ š[üÞ½{Ô«'¢ûòåH¿ï÷/¿üR¤c7EÿÒR–)@ú%úiîwÞ~ÇÍ•>ùø#ú7^ûÆ«±'ÞýÉO#u÷UÚÓXiJm 4ž­‘Vtññ£H~ij1JX+úeœÍWp´0åù,׳ ÓéÇ_9»‘¥Å½‹¤/ŸL³€À:,|¿tû&Î7‰˜ÞK_|ñEºŽ¶ØmoSû}÷»ß c×çŸß‹”n:NÀ€Ó—¾ó{o-¾÷½?M×o.¤wxö2ÀôŸþà½ôþá;Ì_>½ÿÓIé}€#Ð{5Ÿ>ÿ”4º”xû7ƒ¿Ý½óÀ¼–Þ|ãÒ•o¦wßûÇëéM¢í?Æ©â΃§é»¿ÿmæx=}€³BƼÈ@aðóÏ>GñÀqäJ”#xððiY–èwÀ/>½“^}ã›Dko¤Ï>ù$öM›(ƒ!èed÷>D{D–`º‹¤>7ÚÌLò¡‡wÎÒ</'ìG#òÛ$B½¾_'â~ ÞWL#€(¦ƒ?`ŒËÌm•kê¬,¶–cРÞ%ž.àËÊþ22 Ѭ‹öF;®‡×ŒñƒIŽè³ñaäœqžðHX]!O$ü›ý.6ª"œ•0rihl©­SRåMÈ4´7á‹ä­uêx:è!snÁoð:3BDºC¾3ýÿ‰FEÓvã$‡¡RÃgq+EF 80*ïålàœ`£³_è;´Ó¸f¤G[œ%‘FX¶­q㑦j—¯\»¼«:>žþññ‡iáÆmœeäÉ*ðY';8éb|Ñ ]‘½W¿5 ¸öØgùâ`žùfjož>ŽÁ‘¬6,êîéN`€£WãŒhfpÖ¶]1~rdGAȆQHð.£D'/_%ã Ž>’æ®5‚½Ro¤ù[oQzã12{ÿw¹È­y:€¼gf„KøÌ³ˆJö¸)B• o]»My9ÎQÓÿÍû{¡÷ÈëN‘ßÌ ²Ì9þäуt´ Íɬòê+¯qF[¶K– +œ›£á€óts)Ý]{œù-¿)‘} ^EN&“d¹¾ÂMàc¥a¸:ðÁúFš®÷3Æ3Ë„áTJ:Ûˆˆ„‡E”:^Uº ™¢œDH!/žÑ‡ñYÒmC3 ÁôBöÉWÍFÕ96¯RÎhî>œ÷öéö#V‘éWÛ{õ´ÝÌã @½k x¢‘áÊ(e@²­zZ&îÆµkÈ€ 2¢¡^Þ‡¬|ެ™GžÌÚ$¸~îÚT~o¶IÃrhQKuQ0êÚ:Ũ^GVUÞ=- ›ÃÜ¿HC ß/!M N’ÁŠTÃdGrî#í1÷Ê¿kœY4Í›œ[ƒÔ}¦Íúm /³f<㵬1r(H‡+²w…{É ðÓ¥Çåo\y%]¿|] ÄTÏ#—Óêu÷!j¤ YäÇ”×Ë‹dvXNSÐ|GÀ¢Ñ–ÌiWôÛÙýëÚã.îs š'‹ÔÓ¸æ¯D ³+©“_[¸ó,¸8Š# `\€£PŸ)~®—}õbOík?I§È7²2 !·^z)õ½Œ£¢‘£žÿÏ䉋Þñ.þøšý† Î_š«#ëô¢eoâTm½y=³r¨ã*:è6òžë3ŸÇÁ…l"‚®Êe‘êŸßÕŽÀ.€ à³ÁV‚Á¦x.£3â0)Ð>5;Ï/r AvCßÕ»3ÝOmä u‘uªþÅû蓮q"Y‘)ª-?Q' † hQ‰5xF?°Nìã<<‰“ŒÊFÁRZ ‡b×ørv‡ˆ]y—@ºé­Í²azyyŠ5ìUõnAyÏk—›‘Kr½Ôÿ›Ã¿}¶g嘔q¥£¥K|žõš-Cgú{ù“²­©Îc̶3µcV7=¸í†=ƒ{•_Ê‚dÕC^œÖ‘%²Q°‡”%Æg%ú¶FmÛÆ„K+KG ªógç‹ÑéY„¿2­À¸e­ TÑÉÚÌg(`¡ÓN„C›5 k^äù!߬sD™ŒÎ >qÙok‹9 ʇì ïÓ!Ÿ1J~fš,Wã–;«át¡íƒèlîq_ZÎÃó#œupspí8'\ÂZ!’ÝyUöF§䎿y†rw‰5æçU% ÐÚ¬wyM‘›¡OŸýc.]3Êê¾ticß–Ž øÊÔ,g+¼Ø5žã¬=gM×µað½ç?Lû%ÓXo‚ÕŽÛvÅÕ)Œ¶V¶éÇÉŠ¿¥‘NÒ‚½.+×Bd>äç ‚Aè«GµA%kXSý„3Ñ€çQ~¿¹²B&Áµˆ² œþ Æ>³äJuÑÝ«ž¹9Úa _•>ø Ê"@»ˆgžÝ³þx¶i'ê¤#l¢£ê|¢Ž×Ïyj zÎt`mSÁ¤ÄºÌãèaô¿ëÛŒhö±Á­Í¦Ì\•¸ß5m_” ]ÙÚµ)"Õù^ÓYùº§Î)$î1^XO)Ï~j+cù./Ò–vHæ‡:æ(6-5µÕ ±ž¤©ëL½@zûmÙEmp‚äÝ ºÎuì®ß±—lOGIe3ÔhK‹´ü?üé‡]Ó è5i '©÷êQ Gž‡2|Å…€uR¶XWD} ¾ _‰4:ÏÓPïšzøÒØ#­ ƒBsH;kÎj”b;ƾüÒ þ¹AHü€‡à’ –4¸PôÿŽ £×Íç @ ™? ×U„Aôx±ŽE¬ŸõÁû?Kòoþ]Z|ô‚/µÒ-Z3¥Å­C<„{|ñ‹}¢Ò6J Ê Ó@×ðM¡ªôeQƒvô…-n5VªXeë8³°m\%YãÚQœ™÷1 ¢Šk!˜»KUæÐhâ{•p•ÄøœûûŸçk¼ÐY@Þâ0îÑ×½´0Ò¦JmJ£Ý¦Î g# $—ªôÞý:Êê8›cŒaD4­7{§ 03‚²ÑÁƒ?‡5<à5h8°]Ó×oîe…Âb½_ÁyºÎs£Ç\“yå«°¨x T©„èÁlú<-ŸÝB!R±(PÚשÀaŸQ«‹¡0ö6«ö›®A/þÇÜIC#½L g_ü²‚’–ÝëÜdƇ6îø¤¤i¸4öH•ÅP¾ðúwè>¿&Bá…v^¯ç´slÔié2i)”&•:¥Ì㤯̇+2GÔpk§…QÙhkìÑÛpRh3^Û/àu0N¹j_TÎ5Û'éê}*l±Ã9F)=ë/RÎ…W<íòaÐJ#ˆŠ¶ÉŬ½§×·†cÓ:÷Ò!¢¸NyLzŸ`ÌШ¡ñßµg Ò ‘º®óSèÑ(‡®­cwNM³fFS¼;ÛÕ~݆ö¦N´,€Y(Ç*Ö¦¡;2b¾hpÑp¡s†‹@c•Fÿ&ŽFÕë-¯b*h)0YÅÑ$²'°NÊDr릢šX9Œ ñl­‹ÐÓˆq(FnM“åàŒµj`é«—¹†#ßÙ{¦9´¶z“3Ãɳ~¼5ùt qnTž5Æ5h«F?Œ®"¬i»„G¥ü€Ôïýì»qÒ’›¢o{‹ˆ}Öí †¿ó³\Dê×jE GÓdWxø˜ºvÔ ªÞ×!_?ÑVã̶Πœ÷X'¬‚â¿OmÏ~j¼ñÜ&)Ã¥IýfdÀ6ÀµÆ‡1R<MŸ ­‘Ьq ¢AMa7ij5tZ`Qàyоå¼e-s¯eó]*O+ñBðMç–9ШâB45¡÷×1ò™Ù@CŸ‘ƒ~&?ež£NÛî}üÒ=Èe¤O'¢ð}.§£cŒF1j(ò ¸ï]_\ Ít Ï—O…Q‡v‚ÿ3.£–¤±FS×¹Î?ž}ž›Y½È³#»Ç½Ïx?ëQÃ^\Çgñ›[ü܈G×Ÿëæ¥[·Ó&Ž󯽚þå¿ü#æ‰5A_‰ÆðUÇ ËuîùPe-º–Að͸ ÷¿~ p½†S¢¿1@®/~”Nv?„¯tÒ )ÌÇGÉÀh”²Î\¦m_~BÔ(@Q¾]dŒ+—&Å_ǘz€‘sÕµ¼Eiˆ…k—ÓµW_â0ÖX °ÚžLCs¿—úŒb·\¶ C{î&ÙÚðÚ&eJVH×~e0š8ÎUÈ&b ;S©z¦z{­¬ì¯ó’‡ø çø¦òŒÙ8žâ0¶ÄÏ ©ÀaÜÐ|§…i2,ÀhøéÃ0é“—wVÒÇ>%cÆbÈŠcÔá—u"þú˜ d¢j%“OÃxT‘G¦h’*]g©âéaš&‹‘õ|u˜Ð<"ú¾Þ /¼xfj^5’Öw×(£C­dÒ:ïà¼ÖG—Û×^BNÒ© Bv I2¤ì3ßÇÐë€ 4—àÕ§èp{!# OŸ²0BXÅÌ7¤a‡É§îÛ!ëÈêê<Þ `¡ø¦óVùÉL1〠Êüǜ߂ÑòÏ:ÎVÊ/ø¼ò¥€;"S:ä|5‚´€Ž˜Ì¡ ‹2¥²?Rg¦²;¼5JÖƒ!Ú's³¼÷ÊÌBº<}9=\y®>ˆ³A€Á³QhQ^|Jté!²ŠNeÎÎ6™bˆ”µ‰Ñ=Ä ù\צüÆ9}1ÃO´ñì=ãL,ãPòÑè·¦¯0Gè"žM,¢úƒÆÿœ\ëì—g»)²Ÿ<º‹|W¦ôPVÿy‚”êf/òu‘_®¥ß¼³çmœßq¾d C,Ägÿ³?Fõµ'/P6$u½g¡Ú’OPnäá·ís¿âRûL»Fãæºdh/¦ÎÎÏÓúç–Î&ÒÌÕkiÿé=èpBI¥“42w-xÏîæZ*"_Â<Ò㻋icéòdp}.ÊFôŸ1ÿ6©0„LÇ:\À©ôêßO'ùëð—Öcuìê.žëþ¤îþ¿:÷î#eº#xˆDࣘkþüZ!bÕ,'ˆ=y²˜ºì%,Ž‘Ÿþ—ÿõÿHý^£Ô•ÿæïÿÃ4¿Ÿ ôÏ0zr•r©ÎÞêDÊ£îÑmœ™•ÙA¦®rÿõÛ·) ”›AfCÞÁaØ]hÓ?Ÿ²t~Ì#[olo¤ïýŸÿ{úÖK/¥o÷µ4‚\>BÖ§¥{Z¤èÏ!Ãç9?B…`4êÃÊ›F%›ÅÇçŽL^ ¹lóéC@uJMÀW•çò8ÐŽ¶¡2(à¨Ä°‚vê§Fb//¯¤C"_•‡ÈP5h©£‘r¯ò¢4•gšÒ^ÝÇZÐF|+ïš!!úÂ8‚78D]IàÒ„l™Eõ"w¢Ÿ÷SzÈÌtföx°²“þõÿýïÓ$`£:Ô1v1#’MõŽœz{É—ý¨¢«ñ˜˜ƒ©‰Qþ†Ô-ƒÄy_U&ÖÙß>¸‡'9Ï'¹nh°ÎÚUOt7͸ú¥Áò kêð¡úëò:Ìï’ Ž6|fк0”àeòKù˜?î19†×©›~çÛo¦?ø{‚µ»Ýê­–5Rñ°\¬üÆ l®#×£¶u8uK‡R]gý诌šs˜…ãzãw¾¹ºº’~ø£“&œLRò^úüŠ5NGÞíìŸ: ‹…kÐ+´»°Æß~ëmxvÞ2ÀùC”PŽ+ÒÑs¿e]>þèCt.ö í{¦hQ¿¨Â+Õ 2€œóŒç” ©%»Ìüç9"”2®›2^%x­Ž‹ÚÆ8“Õa( 2„ƒõçëé¿ûoÿ⯢“ó92‰6õ½ AÓ³3””Ãù¶<se qõ S¶ˆÐïC§<#ƒ£g¨ze”©AŸÐJ'—à…ôQÛO[‚º¤NÀG1xöØW¾æ¬—–ê—d¬c½¨‹5лÚOé-¨Ô‘OVWž„|àZ–F¦‡] ^NÚI´QDnpôaÏ›}€÷&g=ÛúP¦‹½‘eÀ&„¬Ñ¤MÞ´ ¬¹üÊÒÓtíæmÖ‡?[`c÷^= ô(УÀóPÀÃЗ‡¤‡XAD%A†ªÁ—¾ë½ž“Ò­G±ç$Ö×ø²‹u’)|î·Øq¬D.„Šyß)КÒJ!O:<>¿ÆkáE:G Š‘Ê‹JÆ/y£«=€U–z¹õÍ·¾ÍßÝô?ÿŸ$ H³lôLBûöz…b!8’Ü8²âÅéÅgÔ:ÀBq¶~[…YodžHïîÅh†¨³QÒ⌣U#Q|˜†¤F¯©ÑFeB_ñ[ÐÛ}ÊŸè¯âÊ(˜Ïg«< úKðÈ—Ù&Š\ïµ70pJà˜©Q'9‡õô6MšJ¸`Ñq\ãø5B« ‹^™ºr= t4ÀP”bÆ.Ð¥q¦x @ÐYßWšê\F”7úDRñÞF0Ú§ˆb¦«Q¶ )¨êó4X¯JºÂ½SSDQšLq§Ü 3/…˜¡5 í«¤«Ød©PÓº‚w¬Úv¬öbRK¹Â˜¹”‰ˆÏŒ26U~½„{ÇÉ©žÅ¨Ý쓵¼Œð7š_ƒä Úésίh™¶TÈWÈ<Òø&<¨K´¾8ãîFŒÐB£€©ì4€Ø_3˜.̹tÞÍ Ç}77Àäª9OôÃÈzy›Î1ÎΠкp}/ñ ¢,ŒÈߨ¡À§îÞ§€öIæéâ*Дb^ô¨×ÛÝyϲ, Ò)•Më7;GFÇr7ýUI&]´vz‘è°Ü$uë1‚s;Qö{¾ŒôfÅþ:éÖ©Y:9L»îÑnå¹Öµ×ñb‡ˆ°ÓhÅZÓû¿ÑBéÆë£ Œ¾A#¶K‡õíß.ä"iVÏ0Tt1Lélq‚Qå”~²>Èz êÃV)‡AÃA©4ÍzËΉïµ)Èxx?®"5¾ðŒÌyÁºÑF. ÔŸSS“e›Î™³ÏÐÖ_£O±fXÏÒ·Z ¨wvï¥Aöý7gX—ÀûÚëixŒ±3>r½§îávº5†×¼<‡g‘X`åžõÜi“¡ÃÙÄ”û ¬>gŒÓ3(õìÙ“ÝÇ ñöuª¬yC·‘f§4:1GÝM"ÂSºã€æ}‡©4Š£ÁHƒ•Qw8ä ÂÛÒãaÑþ<ч¦4"Ï5—#¨VùÇ Ás0Œ®“×ÈãZ²Îá©Æ0ÇJ”Ž n˜Àh#O»]Ç4ðø…†–#Ÿ¹Þ÷ÈáWÏØ·F¤ F#êìcÃ?—ÄOS ›ÏŒÖvËr¿N ]èSbþŒ‘ǹ§4šèàr/Ò dä Qâ¼¥ ïg ò xŒmeÎMÄÁX4ðhŒŒßq$oBÛ'E>O‰=`[î+yhŒ“½åsä´ÌÜ] ÚM‘}¡=w)Wàko‡²:N1Ö:iö5LúÞTûA Ö³Ïï0o*߈Pd,¦*½43#òd5oߺåŒÀ¬•u£È^eÖùN1¿Ì]¥Íg_ö~õ(ðSÀ½êòrŸ{.»n5€^ºz-휯¥;?û2µl¥›¯¿®_ ž°¿ºœî~ògÚqÑÁG?£–º‚§;[ìx3¼Ã”áG{[”C™O—_º ¯«§]@Ù @Ñ)R¹h0œˆÿŽº7]±@õ! ¶üÁò0Ö^8 ç™g·„F0¹çœù¤5‰:~ mJý¨#wêx4Lyš‚ý…÷+óÊïò8g· Üp]Q¨‹Œ{ÖA>æ0háŒÑ$’sÇ#à‡„;-ൔ9W:<£ˆìÔæŒn9>xºg™un•Í<òèF®Í,ªŸC§ƒ´ªÈkFí™N¹ O/2çam@”~øp ºöãl׼ϰ48›*¬q¦þ=0FÄìåƒL[Œ0²q‡}4Ø}€ÑÏYX*ž¥Ç–©i¦Wço’Ý¿çç›)q3YQ Ò5°Àš››C^fky‘tir’hNh <éÞ ÆÍˈéö¯}ùµ€šÎ¦k×Á1ÛýüâKÁ.Ï|ÁAå£:•¯\‹^£líþøõÏ}öå¶XÿÈ>Åt˜úN7ÒþÆeõT›¨GF4ÿÑgAÇgàzÚzt?­,>HWßüVšº4ºKº Ìío¦&:i‡§çÒáÎaÚ£l•àËÐ¥×_˜Kzª<^ ='äÞ+s~_!±ÞÚDQ«3Ed.ó,ÿÏœ‘¡Ù/û{;8&7YÛ8Ü¢¿m®­#KµÓÎDU2Y¸~Ê8³TйLk.¯4«—ëk÷@¹šÈtœM}wáUêŠSd_È)²žLÇ?ˆNªnvŒN¯^›kÒpFf­àpÝôÐD¿02ØoÔêÐdȪ¦b>ôtŽhfÌ€k™£‹ìæÞ\5½»zR… [aO"z†N¥Œß\<…OÕ"•9ÎË0!eN´-d¶IÊb XèºW¯PÔÙ¸Í}òy»òŸû£?uÕùÞýйØOÇÇÈ‚8¼p*§â!¦"@¨( ítÞÞ%ÛÓæ1<@Y]A1\¬÷ þñ˜àßfP?ã\‡¿Û·3ö…¡êêÃŽi›yÀ6 ;ê9-߯ ìBèÀë´%˜1ÏÏ•‰•Ïå–5R‡Ž¬ŽÓÆý~'B®æoù­ÏTÝÅGÇK'|Ï{ù<É\sæ t27-2ŽuÅŒyC¬©2™QäàÚúr¤•ç:Ï4VH&¯ó¶à3ÉuÎ! Îyû€«}pu÷¾ï;‘*Y™*ô'žÑjèPÁ3hZx¶«§ «›i›Ð9@=Ëlös'qç¹Û%C :ÜÅY ]Ö–$hå:rvlǹ/Ä<û\qh2°îë§8b,EFŠÛW§ÉbFËu0VÖ¢ý9äæ¡H{ÒÆ²Œ;Û›aÖA N&å1´ÃByBx(Øs8gxîKãmlû8žXRÍ}èüáüO »’y}NçØt6Èßùü3ê§Í…×P,oïû2§ýé½zèQàëMY†âõg§³´±ñY™|é…Ñc¿_šd_ËÐB` £wæå*løbâÊ4 f¾ôêE=( Á,ëþ ²õöNç…úŸ‚6öÖ¨›TÑS–Ñe«À3G)¬NÃ8xˆ½…ë7ÒÎÚÂ4)™PJM%h+ ÃîÁo…m% 4 6à')·lÝèX£zêöUü­¬/:= 5(ئ‘ã}ýzÞrýå(Q(éÜ×§‡6m{>ª°ðËúDÕžc ŒHiY;×aDqœÖ(e°‡ ¯ÕKPˆð¤VI¥¶­RHGB)2¥U&ÿgÚBŽhPÇ"èc°Ï÷AþÓ°ÛífѺÄm̺Ë8®G[*ÙzÅ \©$ ›r9Rº[SºÚ»€¦u,%B‰Tì«í ÃÈýŒ•1Ç8£ß\O_‹ùU® íöÙœ ¤¢ Ûû•7†¦²«±×¨Çf´@¤ÍT¹„O£t6BY凇…‚)Œþq¿}7º¾Jm.OqŒÊ¹†>*û®?ó{çÓçÉY™Næ$3®m­×½kAƒŽ†×Ô.(§Š¾éôCq¥\ÝtRP±ô/•=Ú¥ß-ìÔÿŠÔjÏ5μþ#²J´Ng§›e·v©X‘¬+æ)¼Ù¹Þšy- ËÎŽ³ý¬*‹‚ŸJJŒOEѬlO‰hˆëø»ãú‚NF9‡æt°X’6èoSÇ€ÔSúÛ\ÖYÁ§DvæŽF1HBÛá×VŽë¹‹ï0Rh!á9ŠdíÚ7‚F?ãÁÂ%¦‡éÞLÝç`Œ>Žt®1Z·Æ<Ö¶)\®3€ ôæg>0À™iB#@‘(m#t+ÏRµî4èƒi3è°â”uJs (bݘòm‹tݦü ð˜¿uˆ g ö 3®q­ûþú˜[½õPŒ7²_`¨`¼p¿š Î5åÚq}žÓŽ‘÷ŽYïzù‡ŽR0Ö$.ó¹sšcU ‚²u÷ŸÀ†´p-ÔÙÀ[·Tg×Çá!iJÙÌÚ\˜cÜÇX5çœp½@µQ_®%Ç'][žú¬[Ì:Ñû>ö=sçñ3×§2Î/òSHR{Tç%æX£F4y´´>F/„a ‚é`äºv-ºV½Î“\Þ. Î6ôKv'yuègýÊ“!Ÿdüϵ«'ö:Ÿédä^q-YßPÞ¤¡ß—Æ_##­ágZAK}˜±äöí—ÒO—žFMFÛÓÃC^sKŸ Œ@ÑÀ¨ánîò•ˆÕðæw”‡ˆ¨!xŒ\#R#òÀ+Ö>ý—Fò É®{™\÷÷s}¶O0¶­¯m¥Ës7¡CÚïãQ£Ø±kd”ç˜Â˜ˆ<×áÁùÒá©÷êQà·F÷¨»õçY–âà×"âƒéðØDêçZúÅÒ9@Ä«o¿Áž úèˆ4©3`«Fó˜s«sN¤ìðHZ¼÷uŠiþÆuÎÐBª ¥GOVÓÚƒ_¤S"š;/ÿ~ªÅ˜Š @tëgÃ;á+¿Ë—gx8»ÀS†‰èÞ¦üÉ2m£D|OSÒC~£ZùMǤÑÊ!¬ý†ŽK]ÿ“ïÈßœ·q4 Z'-ëîö6‘æSiCu™GÙi‚gVˆ×`¯#œí|±|?½{çý0öÖàƒQçðüX¹¢ C>@®–1 ˜w9ÿý±–ùgƒ5ÈgGÒ8u¡WÖ—Gq€Û?ÞÅ MÚdþZ€_Ý}côœôúÔI†µû1úAW) †Ãåuž)ûÈáG”ßÙÝßøLƒ,£#Ó Ÿµ>Ai’’:lÑ‘ÙGg:Òž71†7q¯‘‘…L.û´µ¹Ž½P‡¨H|ìŽq¾`,-3'A³`˜enJÈÿÃsW8k8áiçÍ#dø>r÷بÎw¥´¿rÄsZéjx«Ù¥ßfáœFÞ>Rd7Ê-îì§½• ê0ãh¡Låz6âé“é]«URÝSZêc¼`•`¯ àøÐTü\›½ (×"3ÀGГ¨[Î `›T$åyŸr3ýi5öùE6š¥‡õÒ;7ß4Èœ#J€E™ aÑŒ” ›š‹32Ÿpú6)1±·AmR•³>GøqÜáXì~æ'ι¿´.¹5ÖªúŠ?Ê.‚'žÑß¹PýÜ~øÓaîñ£ãÄÀ„ý‘&qý_jû¹ÞršòŽöÑþJÚX|@*€Ã¤“õ¢É/UqîȦ›ßúÇÔ—_MõsÒ†—ÆÓîÊt'…7ëDy0ÀPdØc2aÐeÖi“ó'dÚýõíÔ<Øb“~‡ÅóæqÚ!ÛA—=\ºDI#`cS>W×_¨‹˜ׇz®N4ê+:MØû‚X €^u.4õ-u«sdè•ÕMÔdFÖŒéÛ+–2@?2«Ò! ¢Öyœnw÷Ð!Ùê521©7ê0Ë‚GÃr :ÈàðƒÓóŽF\Or>Áºw@YxJ€Yô¥~°KÔ:m¨w0 :°+c©Ááqø‰¥'"½?szÎþ$gd½ð3³}ÅÆÀ{Õ ÍdåFRŽÖÑG>¡ÜWÜtÇ»/ù“wpjÖ¤{=ð¡ÑÛ–AR3ŠºXn–çpÄd˜ê†%œ¯”•]§ À}Ÿ£#¦YɆpÀ¯’f:²<ÈßW+ðò‹À‘6úYþ¤œn°[/ú Ì¬Ü­Œ¨ž©Îßm”þBgŽŒÀô\œoËû ™NžÙ‚ØöƒŒY9X Óm° û›]Äý/]ÔæÔÛuR*”;id”€dn+B÷—®¼·òŸ3R3cŒ»ì'|2á ¤£ƒKÁõ m•w•“ïíÂëº8'Y®¢M #àëœ)f:èT§ŒÈ|ôºÓöq¬ßSËÆ°f‹Ì·5±÷9KÍì44:…ÃöŽÃ]Î(œ¨uF2 H}°ÛuõÐCÎïSøµ™,£62:k;tžs†3–ûÂõ–óÌç®2JDUãhQ€ŽUÎÁcœu€Ø¢ÔÝdŒÐ–3K=Îòî%38œáx&ßO¹3°ˆ : ó­m"°fÔÏxûÓŒh–Kk±æÌø3ß3c›çBœûÜëÙØaMÙ'E÷µNy¶]€îä´ÐCi×6£®9g¨öµ°•±ÎpF;ï’¢ŸytNÔGØÛÜŠã ö2æÑL{®íöÖ3×!ÏHñ&¼B•éóN >‚~n?ÙÇf‘(sv—Ù‡–%(Õ<Ñ¡p\;FWWÏtb, Úez3Ïå>íZÚε›@cǦ.¬ƒ|…~¨û… €ÌãgMi4#Ìg¹g³Îþ:|X›ý€’söodˆµ h‡r|Ú jœ¡ìc³ uÏpzxÿ~zûÛß!å =LÊh’Þ«Gzx^ Ȭ4†)0Yërz,˜ýóÞß»î¯RÀCB¦÷êQà7Q@¡Y#Ž‚F÷ˆ²b/"†"@ )¡½P¯Ÿb¼¦¶R‰øè£Óµs5ª)ô½xc~¡&ðKÆ)-¨ñæz¯Èÿ1Å®n§½ˆâ­PÞfLúÑß§¶®iMï¯1LAY%?Ò9£\´#M5æšLüÑ¥Ð6M) çZ2QlTý,Ò¶ñÛhBAy#4.Ò@|¯Â2Kº,U¿µÕÕ0 fŠ%;“öTÆÑ?Cù eÚ>„B¢Á±ð|AA?u\ßiR!–yˆán¿ˆÚ&ê×û­éJ3—©t†b ÝãÖÐúä¡ö‡r¤q1Œ·ŒÂñe^Ã(®Ü߇q²‚¢£¢%ð£Î¦¸ä¢ d œ`_¨<óKeY~fk\§~ÇßÞï£ì«Êeâ ¼³- ,ÇÖÓ¤Ú§‰ ©´fÔôëY´½ÏVóãaéI› é °üÔëTÈ­Û¦'´5Á A* ü3¶z[ "J%Ê¢à£ßçY£F& A°)€+ˆª‚l¿í}‰çªŒÕÛuœ\§§µN$lË´†‚ï´PéËcƒî¹Î¡äýl‡AÿhÅØGû‡}Éèdd¹ a¶¦Ù”Óx>4&]úÆã㚘g}ŒTë<§L{ÎcG¸Çg˜Ú¬í«è‰— atÒÃù³Ðð½»»e<7ˆsnâ æÅ¹÷÷wKfø³Ï.[º:OÙ ‚ómò£¢ÉØÖüÏô:š÷yœ¬;«|äÚè;.tœ9Œð:ã¿t£ñÒ€%²noD¿@›kÓ'¹F¥KQzQûHá¼F-ÞÍ´N§< ÕA÷~­në¬3)ñ ž÷µÒDm8-ín¦9œI®‘:¾ËúPöeÄc‹û6(# 3É÷‚&Ö]ö5 „dg¼o®¨['µBÌ|>íÕ;?º‚+IÝ\‡o3h@,öÒizB†Ë„Ñ-RÓãÈ|¦>>€¼`¶ NYÄ·sÌ@˜t±Ð2g–ÈB@ûÚÃÜóžÝC8 -¥­-Ö {Ž/½êxN;#” ãײÙà††Óì¥KaSòòýmöè.ò¢œÿð‹Ô‡C4UOªà ¡îe6t3‹µp °ú=ͳ/Já1ñùRÚ±Á‡¤9ç¾üʬ :¬HÒO&uðÈÈÓ#'çô` ›4ÛØ:H+›;16SÌï OâˆCÆÈØZi~&¥ÙqÖýS^È¡—evƒ.gídÊïè9ùô ›bþY‡ìVïÕ£@= |I À¼¢Ã@zÁDø,¤Þ/ÙÔ×ùò Ò}iÐûš™è •)u* ˆ[œÛ@î·ôu¡Ü ~Q»O}¾§KK! )¬ö^/˜Rgµ­×,o°©ýÊK^°JÑ @F«[·o¥ÿï¸Qê&1^©€m ü©Y¯*‡Æ¦ªŽ!0Ç=îÁp›ïGØVî¨ÖkXc ºeÆ% ‚BŽ_-$îó3ÿùý!© ŒÜd´ýP`èÖܵè>f©•?øÀùÑ`acq1ÔÆŽÒÄ7ÜJTbDYQêàá®U@R`û…%;Cè m ©h¨ É+¢Ÿ´[Æqó9FûÞgŽAE_#‚ o6^?Ìq*–ÑU9ßñ=TöLÞžñL[ié†n¸Ÿ|í)+¨|£ÑÆw6*ÈUÆ!!‹xÉú¢a"²@cÖ^á¸Àü_«˼b¸Âú7£¿ËüL…/i‘µgDc‡V­“µŒ¹Î¨aYçš…t¹ðäŸp|¬ _1Æ!è¨BnDŒŠ Ï•G•H.-/®µ!ûaß4æF;Ð Úa¦³§ùì¹\ŽQôÍÏ" }Cs#­œ?éâ÷%ç‰>øŠ_|ió>Ò" Ö:;‘,Zp+tö9>›5E?i-ä¶lýfï½NÐ49¡)“Ÿ§=é?aÊ€¶üëº~ÝÐF W:AáËÕY—}Ƙgà¬×B£sÇŸmRç¬#†ßGßù†ŒéáÍ9÷ÑyÚö¾œ–>oš&‘}þ>†dßkè¬dJ£ßöÏÈh0rnÉhÏñ=-»œû0bì{‰„;ìÔ°ad±cÂÐã†à3Ë8#™Yýшµ­ÝnÃhÆ<ØžQU‘z>ú<',FÁ\:§KC£±„Ø×Aι*P &=QçÒDã‹ lìmÚÛÚ$ …00ØužéýÌM–iç FçŒ*Z²Ï“Ïn‘rϾzÖr% `0r^c ùœÌb·Ý£À3tp:‡¦F:¶'wœ·lãóZüÎMœåüÖI haBŽ¡ï®F+°FËÜc>_~âºï髾LîñN£øŒ¶]Á;1ÊY*ÁùÓpjdg‚à“IÊHjÊó½{smù)4gÞø¡gaêg_K‹˜{ûKÛ>ß.>}ºôÕ@¦¥F´E¬žcŸåöFôä1‚bµ†Qúf·¦:Ê[œ3kûŒb˜f/9¯¯¾þfºv}!äû'˜Ù' /XNÐËynclÒ‰ »OcœÞ«G¯˜®+×—üôàá`í~:ÙyDZíÁt°þ MÝøoø’˜;“^·–vžŽ­t|JôX  º ø048‡xÔ(iÂCé õX‰¾?c|H8Œ‹”´‚7€S˜óÑ”8牸7U˺€ `„'Œõ‰K€åžwUêÎ+‹ !„8ð7A$ý0 wRPË-hDt]þ_îâDEy"èy0uŠIM6œ"@žµûx~ ð®<:gM:ΌϦ—o’¢ µáÙ£ìå>d±d2 òj›{WW‘-´(–ji|€ý¦.À\™zÚój@B¹Rp階1B§ebl=§ã¿X—ÊÕ¦&e|‹À˜)€L^>ûbøw8ÉqÎ7šFâüyƒµ’uÔ«žÿåÑ­\¢ Ömmx®nŽ’A§ÈøC¬ÿæÔšÖ:AŸ"gvê+ÈŒËì1Òà—ë'dä÷Ad¦Ó3ÀRÎqÁ›ÎCnÃSÕHãÞ¥Á ÖÞûõ;”Ÿx'M½viGq0e ºûÚ½ØÓÐ1Ï^7ʶ…Î9û¶@9 epýL«,ˆ.pº¿†ã N"^«ƒG™ÔÍý€bPPuÇO8Rõ+_꜒E|q÷»[;´œíÍ%Oóyè{ÊzdÐÁT^É!ƒë4² ¯™¦îøð¼Ð2Dl£3†óûÀrKføA ž”Cf·ÝÙƒflÒÅHî&NêSFÓž7Ș¡ö¤N†Ó"cÝ7Ó1¸CùÏCüÏáU:ñs¯@¢À QÛCœ%œ”¹p¬nŠÔÊ•ê‹î:ƾ9#«©È‰:6}»ŽdŒ.À7"â< ÚÒ~ÖöÒ™”ûsµÑôÎ ·O—æ‘R[Ù=€`îÑ)¸?3¾2°íLR L]7»ÍÞ`Ýöˆ‚¶]ö]s)†]u^›„ÏZ¦¤6h)&ÊTÀc¥·2°%/áå=œ#®«ŽÍâ”nšp²v tžž±ŸÑÙûá»ËŸ ?Óþ!¥‰&/”)¡äȘósgÈ$%ö "ØÚX‡¿gv€ëP0ù~¿ŽC”öµ—@ôΤ2I“ð®ÈlE–€TØC5Ös2N&˘)Àçvá+yÖ¥‘ÜÚqÊð?Tå\ÜZ¹œ4úÒF½—y+àÌn_ëœ+mœ%äòNÏä2kÖ²b- I]ø¼Î:¢´lY¶]Jqi“‚ç5paAäÃÇècd" }†ëCrpô­}†éê­¥Ùþú[i†R“Ó3ÌÕ¼Ôס«à¿ôuî*èýê fbŽQ>Žyîð¼Sœ•/ÐÎŒv¬a ³]žiã|Äg(äÌGŒ+žÈõ|tc€ý(¨‚ò‚Ó*ô’vÀF)?Hg zqvI`ÕÞ«lñJ ^Ü|MÏðe€ª4÷Y^ç³lßþZCÚÄ(AL4ödi¤yžÆa@Åhé[p˜c®Œ1éYß¦æš ‡"©q5ÚÄ[C1C@ñÖ¸ÁçLX<ƒ…iùÛ2à ×p¡ÚvÊvìOÐNÂ9>ŸIŸ]ÀÜm#”`ŸáµÎ‰s”ÝÉ'Ì_FnÆMÛ:a¨ß;O®_¨Hß & ÒA>×ìlÛ­ìÀàªrœJ©E@IDATô½×97s‚ëÓyñ3•súJ;ö+¢¥8ýÉhän -[”8~Ç}*¿Ž;"{íÿ|ŽÏ 'ÞõsÆ¡lî ÙgîŸ mƒ\•‹ÕFï]sDq»·øN#QÇ4 í™Wž¯Ò.hjêR.Œ :úã³¼Öùа`ï‹;Ñ?»/ûï^½xö³}œ‹ 9kÂzÜ1FÇáÜq½«’·AgAaçÃ=j:Aé“}-Vç#æC„ôä%oòZ³#h܇ú\&ºFòÚ_ûVùaÜΉ-I Á@åmÀìZÈè+"úI¿x–{Bƒƒi ]…gsqŠ1Á9 þȽöS:û,#ç3¸™ÁúdgŒwA,Ö9÷¹Æ¤¯<)ö*}íº¡ŸÍ'Â'­£qP^os¼çsìw¾w¬ö-ÀwÚ‰…ÎãcíÚ7žk­uì“1¯6›Hâ;£ãsè)ò:køiìqO9öî9TN"ãýqM9;Ô‘•ƒç @ðQè^}õf\ëÜOŒ§§‹OàÃÔ@§Í¨ß( p¤0BÑH"ç<àR`ÃðÊ3›|'È1cfúµ‡Amš´ð7_~•¾b¤#ºcï1÷SeáÒÕß${K‡žÑF:ö^= |Õp…¹,áP:ßKõõŸ¥ÖÊgiðÚÍ46s9•ÆobiÇ™iCÏù ¹—3ÒŒNÍýÕ´v÷©Ü·A “‰XûÇÈÍã-R‚_F÷|<;ÆÙG‰ÆCð Îóî kCݤ“FG‰DuŒl;E‡Q €¼:˜Btr\™'¿b•õ7pÚ¨¦"ëËŒø ó°¾HyÏ™ýu}9}:ùµ¶— #ií‰èÕùb€ : [¦)kšwåÕ&)ÚMs=>D5NŠƒÔ$ÆéÁúÑ;ìcÓŒ«'šÂ¹…®´ZÀéê$ÖcÇê7S¢È2Yò‘sù$k¶@éâRGýðöëàG¢ýÝýtëå,ã€ûQ>âÚlR6Â…l™5#_HϺžÀ׌Î.ÕÆOɦÁóý•]w¶ÖôS¦=Áæ`}ƒ½¢Àû)3öÜiaPÿi»¿Ñµ4pFR_9¥ d»ò4ì+X-Y£ø¾-XÈ Šðu/KvíQê$ÊX‡-Àù)Êϱ[ÈÜ‹žÊ΂ýÒ!½áÑf>:$K„<¬‚ÃÂÉ  ¯²-}-yqF©¯š­Hº»?™‹áÊû:ьòMê“FM«Çù<Þ0÷¸ŠŸê׈@/¢Ó*áËáÛÁgÊ×– Ó©€CœñEZø©™©x¦²¬u¾÷­ûÙo¥ Òê ãhÁf;`ì9é„Í#xkÎóQ°ÿ§»@ô æ:ÚgŽ>ÚµŒ-ahâÕµõDµ¹Ð,iä|:vU ùžú—²¸ó%P›ËÅÙ-Ïñl5S›™F Ùô×éwš–üik™»ý'!w‡þM»Ò7››Œwfe5tb°&tª±œ`#í/³– edj§{æXã^Ž«šúkƒ 祚nãäF3|Ë‹ÿù[Óì|ʦޯqÿ;oϧ[W,¡“E²».s8 •FB>‘ëÄPÔ©^'Êôa‹ˆr¬çÓ2`:úªá ¢£¹gk Þ©\#F¬^£Ó¶ô³/eö„™x”itžFŽ[Ag8˰@ug ÞJ)²˜1Nðß}l¶uû6g²NN‘։ʣëqŽŸ£‘½ÈsÝiÙ—k´ÂÏ)|\'dÊ¥µNpá¤íD»×É ã¸u|ÈÊ’Q…9XZ¡l}›AÞ8Ãy“’Bž< ;Õø¥k8$š¦›vÖÒƒÅÍÈtQ)m}æçç£Ï»èyùq<±d>÷Y¦¹ÊEB Û>Oc°Jl–ÎZs?ËcÜËŒ¢<ƒ¿ù¾Ò>†Cþ‚¦xû0t ¦ú4jIzi`Œ­Š1FZº·m"j¶=±õ7bHOõlt¶œ£qUö4D!ÓW~£\¡7ò·J(ôÂè¢ãû‡y€^ÿ‚•ͺ|“>òì0vC8ç*HƒrvJÄ‘†ÿÖ8ig}KЕcøÇèýàzz$ ×h”´o¶ÏvÞœg>Î#h‹{Œ,¥ó1¯FË×t¨—«3Ôh#sèàs訣F¬æ,€W‡ î·~\s÷1Îx€ôbì>Ëë²Hf 0ÏÆÅØ|YG,2 pŸ¿èc½á•Ÿ £’÷›ž”(ú¨’¦7ÓOAÙ'kÅ=ä{ûç|ÉŸ]vÌuÙÅP4BÁÕ Ý=÷óÜH}î¼ð/›W×.÷ÆcžuymsMôž}h h÷ŠëÏß±¦˜»Ø—ñü¸‰å¨K kE‚ð_?kÒTŽÎq8p™+Îv3àš±/¤ô¦UÕ(eŸ4…Ãã÷÷¬7 4È»xû„>0gÒÁ¾8¾H§E—qñ CD‡ó[%ßÝ 3ô™<ÓþÐ[h™•¢pÚÎáIΉà5ƳÏsBåɳ»xO4h Ô:VÈà€+û ,ë Œó´r±† Fé¹GXï‚êŽÍW¤‡šyÖC?ŸÇø‰Ò‹ïy† e¯#å3Ö„¥l!GüZF˜#ÆåÒÕ;Îù"‹:g¼|Ñ'Ÿát•6¶(”~Á†yFô3:×kâÔ6]ÓÔ]ÌÃWœCéi]{vƒŸÃc¤ 5{‰ÏêоëÝl Áß|ŸI##^ûÆëQ¿ï'ïþ8ÍÌJ}c«Q>FE]̉T6{Š >oŠAÖåüôœ³Àˆ¬µÑð÷{ï¥9RææšJ|föç\ƒ ¿¥—뉮d¿¡A~Ð{õ(ðÛ ëM@²ïlPð)2gzåeÒ´ÎÕY”ý<o¤¾©Yö+^`Êññé‰t¾ÑN»ëÈr¬Ý‘Id+ øCÈiöH0RuˈSþ–ÇzŠüÑ%òy5ÝýàhDœ’Jù`õIü(寿Ày‰½ì^ü-®ûgûn_yË39øn콬Ù6ÄX Ïõœ¼8Ã.¦%øûÚúÜ›››ÆË”p ý,@ïqh넳»£ªÀ'†âȘmT÷ÂWÒ¥ùx Ffx”QÚ?}ÿ?`øÎ¥o¼öFº}õ% ßõôùÒ§ð€EÀœ!jW‰lXð¢´tlIÔ 6ê{mwÙ¹H]ò÷*£Ñ…~õ9yÎó"óJðó™©qxßdD› nçõ Ëæö*|6`Š<Ïz¨ŠGÖšÕïÁ¨®2µˆ`fÖS/SÃgz.Mæ= °<4>†9{ëÈdÔL#%j_ËáçqþÁ+ùfö4=áùÊI~¯±^>LÃdå±þ/õˆ]2€>fÇ1¸`:¨¹ j9 2,´I?E´~-ΡìYãÓŒyiE æÿcïM›$½®½›Y{eefí{õÞhl)Ä¡F"GŠÙ³8Fa}r8b¾ø8üq8äPŒg¬qˆÃ¨¥I#’")‘A$€nô^ÕյdV–ŸçÜJ¢R v£^ º²Þ|ß»œ{î¹g?8êá ²£È@_àcÞ2’œ³øˆ±ì1Ÿ®~x@x5³…Ôiîß_æï£483Aæ†i ƒ'$Þ\ãÉÙÁI²¬„±rùÎ,Q—‹¤ÞHŸ{ñ—S•5÷ ò‡5kÓèø†ìrÈ&Î×öáÄMÏ•àÙgŽ´Å(¼cbŒòêFyZ¦J|_…Ÿ—iòûO6qçoÿÓÚyö)_扯ƒ`8¾7õ·ŸÿY¹LòoÍ"ÏßøF:Þ½•ú'¡´ÇIM$'|&øµ…ãlmÈ[ó¦,ÀØŽ£ÆÐP*ÕÙ‹¡Jc›)··öÒçr‹c½è] ·Fš.^•Tþcì±*²¦ÖPXmÏá$ˆqµÌÞÞÜ9JC'ÒÔ€?pþY„¿ ‹…iÃx%H› ÌìîeïàÿÆU6XÞgà[”`àïÕµyÊìýMªÜ@æEîeïYëÒ´©a7ïQÊOÌÞÂagÓZ䙇ŽR8à@K†Ö@åР½:$ßxó|è1NŒDpc…Ó0gûõ26ál#ï%= gZö¹2J]‚ò‰ò˜2à!g–™éêD*/.ÝÇÑ"GˆË[ºþÊzÌ«G3Ôõƒoî•ÈŠÆSÖjï0ºÜçéX”4Žòrƒ²ñ›6”uÊçƒ0žK +4c#'Y2J:nZcGRSRNBc»†M³VÔ |m#²¹ ÍH:}€ç:¤•K8‡àôclùV¸âc'tV‡# è–™0Ý{•tÛýéîüBð•:I|+ ÎÙÂL8Ï-Ýa¹Ä0XËЕ°¿5Bk—kü<À€nZqyê>ΈA"¢Õ'ŒT:ÓÙKî-n¤µÆTÞ)¹=>„1|xãæ=Þ'J#­QÎʨ¡Sa_J;;(9²Nírõë8 ¼öÚUœdÊœ=Si#ýŽ>+D¿òò«.NwdÌ`ͦmSIA‡_ÏÞÐá0Öbg 8a¶”iø™A¢ïïcL]¯É±7ÓÅ@Ÿqú_ÙæG dc7mî› Aùýg ËCŸ: €ãÀ×òiÒeFÚwÐRu”;] &૲¸oÆÂ2p¶{–% -÷]{År-Fp‹[ÞÓÁ4ð:ƒTà†‡ÈûA™œaèž™̬Öü 0Ø!j_£²räÁ‡gêFrÆî¼·{BF!YF^¥ê£zÉÔ˜"Q…Ô*^¹‹¤kÚZ_Jfõôzt à‰¡@mTĆÒ}…Ÿ¨qÌy¢Ã–çŠÏø[ã„ÁŸ¸wáÜùô™Ï~.ݸö“4€ÎEŸ|þ…´@*÷7oÝ atŸÚf;k›(_ŒžQ¸'µ/Œ¿ ë‰÷÷÷G-7®ä4VòY¦ÛÎ${‰æ #J_ÇÆd~k #PlG•Y*–¦zäúUœzr¶¢g˜ÚÚ4°uª1UA# ¸B˜FE£/ÕR™BL~]‘^ÂÏzÛš®Ú>4 )¤ú¬&"=ÜUJæqA7|%R2ÚÔ`ˆ*$„'>Sœ7b_!x29†ó—â˜Þ2z \,ÿÛ/*T†'iÇèNã ˜NÄG„‡Æi…çgTgV¸åçé*,njɱíÞÑ ˆàÊ Û@†bC¢ýÑX¬OLŒ9ñÛå’á…€‰¸âúò$ŠamD7t“¹¶ £¶ç÷ò0u* ²Ãç„y¤',j1¯«ÆµlÏÊ|úB@5z! µ¬ƒÔI…eD;51ªò÷„OÐl yŽUø­ïgákt…´Ý̾ä||˜ç|ß¶è#ß²r@¥¨ó÷£„‹pw œ™#rÁ¶ÃǺÅÏgàA³ôËúrùèç~é—P¾ÔÒ믾 `=Ø7QP™´B Ei³ðИïoq²y7)ØôîO8ŠxßúmÂÃõϳÍãÕQ@8DšnúDeʺØÏ9uþsïø´ppí£këþs^FÃYg×Tù´ûUlP¡nÄp‡ßiáXøÇ3Tr3÷縄­ïóð¢Q>ßnÊFÁ/¤Xñh7+sXöŠik—øÖ÷¥Çç’ÇÀ»Â{^ä¾VÎQãpA‡#æðcÎáxÁ¸5`X¯6Gª»kÝìmûW1Ö_X3G×’ùçŒâ&ýÐ|Ù4<» ÌŽ|Âe;âŽkíóûì©?*º‹hÏ5ט¢SŒøbªcç*à¥}AW£c•ÿ¨Y¯üâ…4;;›nݸ‰l‡:±ë(ƒ0r¹R¼TdŸ °…½’ªâ¬`?ð¾)&Èj¡‚lb‚zzì……Å•ôô ŸIÏ~ò¹ˆ1*F<UlWÿþü:¹2®(jÝ<ý}  â›ç?w—zçoþyd?¿ç×Ó©C‡‡1^õr®ÖÖIW»þ¢$'ʳ€Hõe»ÛöP‚㌈áØˆf#ëÅ8cQú—úPˆb41:twc9ÕHe^<îNSД\·r|ìùÔ;8Htû5öçqiœaÅ.þÀæús’ÖIÌÙ…òœádwò’tÐ}ê%Ý‘ŽÆþ šå9ž·¨ÆêåUh ” š¿¶8‡cÂ>õK/Qêa(ó:ÐÛchâY“L‘<€ÀHãhž~ìWþNÚ ][Û„·…ß5NNÑ‹=•ÎN’þ›¿[ôÓÉO,¬‘f—ÒÚý»Ô—_âý5Ü©ŸN;iTÀ·ÃÈ#Xަ¦On£¹_j /r†69÷KÌ_žÇ(J£¼¥Õ¦p 5øÅó¤ÒÖ•f樼:8‚ƒg  v [뤫o§f|•÷WW1“^¼Bf ¹¦ûÝÁÁ¢Ÿ¹Oô,÷0PËS†Qåd<Ö5]´Q} Dêw‚OFž{íaˆèsxôÜLˆï0:`üó¬ò¿nÒ¶êÌ!_®J–Æ2ý?·V(NWÀÛtÔ›oÌ2¥‘BcÆ`uüÏ™£¦+K±ÈâˆQne¢ÁË´ß? ŽÕ¾d$ýõ»'ÇÃyê.iKÀ³£ a¤˜.LœOe"—pÆš½qƒðvÀ©×z̯]¿–žâœ_8ȃ0y 2¿=DXKŠtèÛÅ)TžHç +}Ì݃ިÜàø¬=ø!ÏAd.a£Ñ$ðùïs®ðŒ0û5^Æ!å6ùÿqÉ[ëúÞ QËdºL€†CÖ£‰¬¼ÚöÜÖäçò0cÞÀ›HÎÚaêŸêÅÀNúhœ$ŽÚàG)Ñ‹ãF8Kcج‘Ž[üÙZ%ƒÀ&¸ÞCÝÞý ÔjÀ“ôÖD5÷M}*-n즹î4õìDÈ2òLÂëãyeZ£Á×ým©åè 2õ¸ÞÚŒ5=²²ˆi /]¹Ìg^lk.m¬Ü þOšå;Q>HPY.ƒøl5£jSZN ÷®ñ½Ü”4U9ZÓ¼ XKg®‡Áy§™sSd<:N?¾v-J´I;…qµC²N²ì¸$YÇYž¾ Ñ¡Bˆ±Œ) ‰Ì^6o­oñ}–«”©³,c”5 °è5k}8Ÿ‹,Kò°ŒAƒ¿²U'ç–´ÛÌ[<¥÷:‡#4ó¶ß{M"¶+9†sàƒ]AGOùFÞÓ!­ƒ¯|ªtEÈêìbnz;4¼ÂÙ{nz$Œ««Dg3–F³*TëUtö¸c…bÌ,3â< ³8(éÜ.­'%:ÿYz$t1NJgàX%Í Gnèœ|½ç]8¸²Ê:ê [áÌ lr&GæîXtdµ¶ºNªÇD,›Ýa^X§wé¶øÑ)žðŠY5Ì>(¿­¯F|3K--.§©É™ô©žÇ€>A´óä3ϱvË8t8¾†vÊDt²NÖÐ?Zµ|•2­ðnðŒŽûžãʲ:zuvRƒ¹| #¡d gÏ®ãàfªõvdÇ‹ÀB„Áüs8xxegàñÕ¡=è"gdÆ9x"À æâŽ‚‹[¥¿•û6‘%\cÕÂÔu½ý¾›þG|–&ÙÆ F0MÀŒHp¾sMvч…Óð®{8ŸX#Å7(›ã®r–l¢SóÔ}¨70³‹°SVë¡ÿ.2ºÔ§²gŠç…{1xw5ýÙ§)ù»”í—œð4(`' ³"Û Îò}aªÔ©/“@Ú¡~GBÃ9Óg߆:Û˜}†UbŸàúÎÄ#³7J<Û¢ ‚%:è×{ˆ€!#9K;9jô/ž‹÷”/»}+-í4Óéì7q°êÀ?D„62´IÌf¸žV]óbºÐÅ\aO.íáÃ*ã™E}}u5MŸ=€•yP±ñNf?´Wq˜ € ?nÎÓë§xÿpOÉ\¨”;€À©(Æž¦c¿½ÿ.N[8…À)ÞFPy–ED!<³ãz„8g¤ ¥Ò©îo¢ ïÍÝK?~åGé;_ûj*üÿSžóé¿\wÈ&Lv¼6íö¦JUp[E•Æuå½Ì5l„Qæù¸“hŒéôë_þ2ÂÔb¤*!*JaCÏèm¢rnÝ‚YŸh5«Ó F\þ6 o•ˆ@¸òv5‚iÜ #. ¶†™x…Ž”Á"ÖDSˆÒ@vŒ ‘Ów#ø0HG‰@¢PÖ†TId[ 4‘"™ï# ;“?ôYð]^ï>p]œ?.vKÎŽq1Vß¾ù^“ydàK×ÁègÿÓ9&"fxZ~q±†ÒñÊ<ÿËsËN(¡à¿üŽùš~Ÿ:ÚáyVÜ·_ûqF]‹O¦·'ÜKfÎ( 3fÑYîxˆûÖ¡?*àTÂÛNÍúr4FÖ=Û7RȨi]MÌ0±MàÅsv¡WÚˆì üéüÈìšò¸°×ð-}5µ¤÷ÜÂÑ1¿¢ÇêMžQ—ßc : ×¹ì‡R-¯¸ãÂUœ‹ýÀØœ˜Î>LöQ<ÍݽþïR“x”Hˆm¢Ï8—P¬#…$†£T÷IɆ6àlZÏR/QLœE”ccc#©‚1FãNux4}áþ#¢s‡Pø¡@ã™L[~†«0uŽÏ”Ž9Źâ½xÀÉŸ^§ø`!Іâ÷¸c(Ý^@QÙ˜G™Ý–v‰2¼÷zÔíç‰n …Aüps.èÛæò}Õ¦Su„4ï“W"ͬø?~é ö,éÏ¡%=(æ›{«ic»¼ÔÝë¯c&í'ÑÉ}(Š«ÃCé`g £êH* M¥%êUÞ½y-]š&ý«º6úÜ_Òy hI¤üå^Kîúé½ç}ß­ï¥yäëÈ1"] Dö£PnÔs*ÔÞR%ÏÛXƒĺ1AMÎ µµÝ÷oì~ÏÏýMêˆß¼~•ˆ>²X‹¹;wÒÕ«×1(Ra<ð :ù–™ûp/†":ç‰x_èk íÙ;·ÓÔðH:Nˆ Òý¹ùÙ´07—οOv–³U§0œNÀ½c:Žy˜ù¡~XfÞy-•Y,WdMøÈ@Ŭä»Á#çM8cèbý4nÿô–ó, ñ÷á–´×£†qâ|f[“(?D#šrC™}Ò?ÐÃÚ °þ¤[ƨµJ½m3*TIǻϞ9®!³}(¬5ç²õw‘!1Jîcüݧü@©o9+§v (­Þ˜¾CÉâ­4G$úô§~z6|åÙcvÎìãu±Žá¬ì[ñ_g“¥¹—Òk?ø>û¨Pù"Ö,ó'âKO"bT9 ]hÇè]Tä9¹HרuñGyN9ÁKaÓk«ßi°~‚±tl6J[Y1Ê'l³—à!ÁyY` º8?¤ævºsã•ôÊ÷VI/oV2ŸqÆüú—¾”ÿä§‚Vʳ5b¼ô§c~È*Ðu ¶ÒÔö¸Q¬‘• Ü/²GtâñàO¨Á~Ô$£{:œ€iÔýŒ‚|ŠMM=8|¥YÔ”¿•劔60­º)·ýÛ}+Oî9"­Ð݆îBYþ È|~¿10i“Y,¢Þ•sŒDg<Z­K^NuCÇr xŒ¢Œgq‰²D4Rî!Ãó­1vh‚©åu( GmæV%hÀ’Qóš5í@1 SŽCr›Ò8÷‚‘Ñ:ɲfe…_ŽÂ.Œþ̯M‡ÖDg¹vÚZß9LûM¢…Q¿%]¨û¾ œ—Öv0t¯ÃÃjtn|/íÊøsNC:Ò‡cx°\>ñ̳ifŠ’GÐoet³šE >[‡-ynÓÒoP«Z]Qí–ÇP_ãVX¹íǬ7ë;Ê;dÁ`{@ŽS§Þõ]Ç”åûòçBÈ¢à.˜CÿZÜSfpý…“²R|æ+qZ|ÒéC£°ëo­õAÊXÀ3¦ ¹L–Æ»§ú禽^Dæ?aO_ÒA»4ËÃÌD?çr',DfCÓYHœ‰Ó BãDåXãXîCûz0;ºÐ°N¶)P6·÷04Ùïüœ|;AíQ_=iH·4VÈ2ìK³ØÈG™ZÝzï¶ÁQŒµµ”(…á œô¡:S»guêð<²_Ø\÷“gzÐÖTç¿‹s˜µì‡V,,m¦Ù%JšÀ3\8 nâ¨QíÇÐp§üëßÛ¡q›;¬7ûfkc-MŒU£ÕíÓ$rž:3&Ì+05pT°ùçÑÁ ™5•#zŒ:+iÌîá‘Å׈âÚHgZGù)­yÈÐ÷g·…ÁÈ»0÷Ô%+á jzD¨¤æ3RE㊠»i°T i@Sþ9{þbzþÓŸIÿá¾’†G7P¤} b oç}ž][Z &9X ë'ëykÈ.ßïÑ\>|°ÆrJ*Y…jð]Åìóï\Úˆ¨K†bô¬‚Bœ±Ê PÊ)´©S!¡€d eë­;^Ïyòô뜲QJ!= ³ÀâtUs úŸüXÍñÆd¢\¾—^éÙ#;+GÂXF?¶R´CÞ§)y3„Cùo*€‡¬ÁWáÄÀü³!:Ï#êÈÛïùYVZ Ðh?¾6Í…óžÞë-eŽ}*¼goì“§B‘éxx¿‚¹ÊSA£r@89o…æíš“¥lÌý±¨P¦ˆIµæ¨úš{ögá’íÂÑ¶Ï 7ßSÙ ¾hc¹á¨]|p\rn>¥Šd* \sqÆqªœgácòÒJ÷A“½¡²Ê>"âƒß®E<ï8é?¢Ü gŠX»æ;ßg „7D?#¤èÕÑ£dMO”*Y(EŠÔŽS-{òužT-Lcž´Og!8÷àÅ^ÀSý<©ç1õ‰˜Cá+Eà”Ï€ÖD;ci]Bõ-ÐÊÿùm^§ø ! J‰w*…ǨùÔ ¿™^ùÏÿµ«7Òø"@—ï¥íÙ—Óá2†Pöm':€ÀÖ©ì'rÖl(5¢vWP(K¯P [k¸†Q«FÄ\…t¯ë+igéÛÙxÚY ã|ãíî´¿°@:ïu”™©«ú4uÒ'T€÷ü¹ì‹ÿB J·‚$þî}ÎK¸¹)=¥ÕFéõ $ïÇЪó“u`å¡ä1=G¬l”œ g•âÓ3ç9_q28¡µ–¯2R¬35x/æ¾Á;·n]M½ž#ü¨ïì„VZç¶H:ß]øÍ»swS‘3z´:BD4©bic^f¯›è4ΑÍm d]e¢¤I ­•–U¡Sf ‘GrDwß»ÇZc8Âa® ¥¼ßP\k˜©À«ï``_ &ñ*÷Œî&òþ4vëä¶N½bSÒê,!<ÆHñ¯Ñ|‡¨/Çn$á6FOϤó7ZÑôõ;D‚‡a¾¦möœßÅ)Ðz@*Vqabr~9%½ÏY³U'?Ë;<€“‡†²mÆ@E¨8ÊÐóÈôÌ0Ò k•ë›DÿëUÆ(¥áÜu;&msWDÎÁÇÓÆiÕ×uàŒÖ©Öºõ}8–Ô1|ƒðÁ'®,¯`,!¥-ýZÞºãU4H¶µ§é2™˜W‘ç÷1†Ý[YNMf§‰³¤Õ=ªï¾”º»9žŒìX¢•gì,ëzíÚÕ´8{;Ò½÷ãhQ·ä;"}àÐÅoÏ勯«c±¼ÂȈiÌ‘™€µ[Èy{ˆ (¼=¨ýÆœtzÊ£©·zpýk𙦈&;ìG}c€´ºD bØÁ!³AdýO¾õ7©˜¬ƒ©»'“Ž2óÐ@ÉÀÙòËFN։¬Sž¥ œ´&w‡‹Æ!Ž Ôô.`ŒêÁ6Ðaov„²ããcœÝà·{êcz¹îõ¤eÝ_ñ Äg ùy•tÞc­Ýüa¦¤0ÆŠg¼#]+¶õa”ËFænRž›Š¼FT³iºaÓxÇ,AÁÂàEÓÈÊ%Êœ°ºJð„ðä%¢«Ë¤Âv‰–³$—†5yt©²µŒ­íÑ_D8/î©pÌÙ!mä}öapªÀÆq ¼Èœ¹Â DŒ˜gÀǹ*èÙ3FIú½k+ìÌþ ®™éÀuËøíÓf(™Wv¸p_ІˆäÄ]kÇGƒ¦‚wÁZë¢ÓEÈܶáÁư¤%Òé–JJñÅ ypë¼¢'ÎßÓ¦à˜'ý0âøLNÒ¿cÉý;G#¶µ= £°s|âK}Ü|ï¼¼ÏMÁÈ÷·sÑÑ!¦Â˜ŽâœóYúšíF3ñ¾Q•î ÏXTtèHá^ÕÈqŒ¢ð4Û–þÒ† “¦ÊÖ0+¥²†¾/0þHMï0x×µScçîå##,0VõŽ€K(“ÍëÙÀ}i·tSe»¸ë{¶)6‰Np¼Ò4él|‡²H|tÏ8¶3}‡“Ä%•{Tznæ÷“‘Nö=§L{\Á6{ûVúä/)}ú³¿Â6DAŒ²·åf‹¦ºïv1Ä€½Ø,îð?c{·'OïBàýAÀ=ëI¥CÚcWO}ÅßHK¯þ!õ¤1☣‘ûøˆt›ú‡0ê’‚–¨¹™Šñ c’ù’ £*[ÙëÇ(¨·¬/Iªñ¡aŒ²…ËDÝ¡X'5üÁÞPªvaL«¡H¾{?Õg~3=õÔ™ZÕóê½vÇû›ïÏz;èƒ4‚+öýÏxXZ$?ð“@¤Òdù Tí(®=KW‰/—W‚F™aiZeÔt~_‚Ú›wo¥ŒÓ—.?AÊ÷á U¤ï¾pùñTP± ßÓÑIzë ‘Ñd^ÚÄh¸¶z?#úÑä‡X"5HïÍŒqLö˜~Œ è¹Æ{ÖV1nm,Ý }iƒiÒWW–0šm†á¥%þ òç‡}UžãTêÙb6£F5Àìíˆ{ÿ~è_®Ôùhii‘~H^l¢è–>nnãPA„¾¼¦Ø¶‹!K¸y欯a¼`¼òä.ù.Ž–@RÐ@2€A¿ùóÝ ’N'#A­I®±ª<ט«çž4\£6œã…§‚ÖÑî3Ò\y{ߟ˜¦?^ %o¼?k¨sT/‘ðFÏEt-£5mð.ÑM?oÖ,zÀ"­/Ç‘øÊö’‘•(¨—‡(ƒ`]éZC=Yª[\wÓê÷ÒÏO\&ãÂ`z‚Ôü½8⮓²_Ùæö[Èë8fÅÔËò òÀ+~‹Cˆtô2ªÎˆK–tòåqŸ<ò†*q[˜{¾ËG=ØK>¸`l­Î|Šèò»éúÿ‚±Sf€ZÉ¥6˜> eMøý œ tÊœyò1¢I‘sH·¼Ïºíoϼ,pÐC㟼@ÿÈ8kþm¯‡#K{w?¡%œ4”9¼A­AÖ…é ag‹|ìÏßãF®79>ñ@zu„)OeJò Ƴ&¶” ûENOÆ#Òw,kƒ‹?-º'Í’ï‘JסWòëòÄÖW:ÈëòØò2-þÝû "{« š´É›ÔPîÓñ%§lVÞSdp¯É ËÏÙ€Ž¾–)0ⶉAý•gRg šŠñÈû›¬Èž6ux‘÷”ùÊì5ùUÓûïbP Þ9„“.s C;OØŽNĈ1±öáWÝÏî·}jCËc;OñÐù˜n< õг·Ô«8æÁË´I?ˆ"çœìÁñl›2ò½‘…Zlg'óÝ£–dÃÏïÊþÞ•“MZÙYoCkdARgSóÝ™QîûÈTáÄê7·øówÀÑȳöH)Î1~ò·´@]KÈ$ÊOü­[]G¡ÐƒÜàûÜg¿ á Ç›q±·£1J½HF:£Åñô¦'à¿km3˜5¤ žÀ,Ò¯(áD»½È<žÇ,eô f{2ß2{Âñ˜}ªþEYµ£ìYkF†9ëö'(WÎÝ8LWÖ,\$ËMJ:àJûuêÒKºeÍàÐíô¤]²¥ls~k4f=û¸·½½›ÙÂÊó[¹·usoè\{€S+2Ê!ëagΛJ6´A04ºÀü}Ö3®)àTz,¢œÝ;®—õ:`*®Ò Ÿyç‹ 0èl“n™X#Ÿ¨ûRžÚá,µm³©x6•úȃs¢8£ŒX?¢ÆzTÇ™ˆõ÷þ6Î&x7€#êŽ0 c©¶ž} œ5JÞ5n²gÛ9»KüVÒõlñÇ}Nci{}5`¨ìè)‘Yè¸À×é”APà¶ôAÝZ7z¼¢Üs$¼+ž¤í†ü ã.4Ù3B$¬áh©ÓÛ8ÎlâÆße*Ÿ e@ÖÕù0ÓY¯\*À×´õÚ};:É(eÅå ÚÆ 1•Q³p6ö–ê”}ìHk*œ:à0¦›õFž`iá>ü̼8%ß¶Ëëç¹yõ•tûÊéÏ=Dced‰M)p‹úP^ŒÝ2«~7E‡„í!žÏC¹§ƒ~ à® ¡Ì4…U¢ÿZŒ’Š"ezÚy°øãÁÇ£0ùÓ9œBà0ôq¶¡ÀU@ŒÍÆyÇõžÖoA¸5~Ïn¹GëíÝŸ›Oÿïÿ1J€…482˜^½ ßíÿÐÏø­©Ÿ~8YM0ÓÚ®¯ïu¾“¦‰T1ËwØ ¤¦å™Ë¯RnF8ŒY¼/“½‡Wë0 ÄÏþò?Hó³wÀ¢4~#¬«(j]þ6ÚÑŸ0¯ñ](q@Â0qWxEVé5­­¾H„Íß|ç;!(¨ àÁx‹{Q AñØÏòœ*.­‰¨0©§ÔôøV±…l…³MÄËVÚ…Ùß@Á3<6i°szñ3ŸFPéL.^Žè™Wô£ô“×ÞL½3zF«àÉ»ÅhIFÎXB¥Á¬ðªÂZ¡Ia4øy‡Ë“&G'ÞºÎÓ±Ç÷ZøZíj|RÐÍ Aá„2…‡5Xù9 ptÄ´CP²mI‘õ½Ñˆð¾ýá¦ðõkŸcäü£"A¿4þ¶£@wÖñ6ª±Lê>Œâ¡†¯óy¡åÅ…týú5x:x”òp­§M3Zž‘©¡m²Äì‘Ú½C÷YR_ë”æµ€áxmi™Èê1t¼Fâ°ÄùŠ|þ2êl íÓµmøSóDµ©`Þ†ŽV©-n¹!£)¥³ë‹‹%699Æ•ôýD®›5JcƒŽŒ>#Þø‹»:ÿýLJ~Î7ùck´zVöbȲM3ÜìAû€ÄZ9ÏQ©yK-@IDATI ó³i~î6g©ÞðZ¼Ûž†›¹©‹>îÎuŽ5œCFÉJedgÇö}^äCÌø¤£†§òâ¾ø®ÍÅ,òÇÖ¡–_ÕP|hm{pR™D¾Ðÿ£¦1Œ’VåSiAÈ…ðÿá8 þ‚§MŒ‹9âs‚u‰cOÛw–1v2ÎÊàhÔ5–>A‡L¡G”€ÐxÞKyßuºO¡wU ”}# †rŒ/g¾ ¾³>õb˜•§ÛذV´ûÇæbIñNÇÜGT0, ÆU ¶ÀCš<ŠaV™É¿ÛéSÚª±PÚ´¿±Íï§|Y"0~w°§ p?i¦¾Ýn nšûŽfõ:<&Æ=ùb£ƒ5šZÇz}½}øŽôËl}ž#Eœ›4(K\ÛpF…ö iªôÀ{rÏêù­)}x M&ƒ‡Pè“Ü»g˜á”Ë|9$h_þ ËWí¬qÎöòêf¸è%ø¡M™š¹DeóÈõ•ÐË`´­eÏÌü>t´©<Õ‡Ðõ˜¥d Ç(Kñ-sY6Ì=9<8¬©õn);9þ:S¸ÒÖÈ ÅœtêêÂØÎÆG>#¹­VæÐa½¾zÇ©Å7!:lu',´ÁÏwuÇkqÆJÂ)ØÉ;Vyü@G`༳| LYouðV)Û²¶E{<ã:ÊákȾ€g¼Ìæâ~²œŸ4Z§3˜‰ÀZç:ŒôèÀ†Óq;ÛNô¤¼kDº2K7s˜q†×q:2*?œŠuj`ÿä’":ƒ‘ZŸó×ýåØÛËeâB"HÁ Í0s-€­góÊ_:w)o»§u@PR7 Žm/qÞ,D[[ž³®½r&éõ·<£IíϼƒTŽÔ±v‘Ú§š¥;îÏͱîiï§ÍC[ 8or£ƒÆÉ`—3>áRÆY§Ô†S€Î‚]3L§žîݾÉ^†Ùó¸Ý œý¯]½™ž~üBÛ^úÑKߦÖÎ4 u?@<'Vbƒ»x y|Ø_Þø¹W7:«äݸüä­|åÑùuë=ïKÌdØsd €ñXtÑTüÃoµsÒ\ëß¿ÝcëæéïS|¼ àÞ“ÕhÈÔŽ9ݘû(;¥(´èõäž”9à`•öã®ÓÙžBàC…€ÇUœöçbV˜¸O[ˆÖ3ê@>ÄÆ‹N9*_¤'2ž÷çî¦ýünúî_ýYš9w†:?+1‚Ó3úC\ˆ¨iñWK|o~)<„牼© ôÐqåÊÎ#Œ0ìF誱¥ b¹6ì1¯(°`ö/Ÿ?—ž¡Ú_ëü°Á™ž»…ÕøÌ[ÁjÞâqð-ŸkQ_ ¡Á=¥¢æ†ÿŸþÿMšœšIõͯ“Êr.O†`§0¡‚P\mpN*\…1ÍýÉÿŽÉ{šjÖŽT`mC9aÝ8Ÿ1ü?ýÿ6=÷éÒ믽óWÑi:Ê'ŸþDŒEaû굫DlFß ¬-šNVRgIÎ …1¸Þ´…B3_¡îˆ¹Gd†V¹Ö¥à,ß 4غ R*Þà¥ñ v.ÈuîiÔSaà~Õ%üB,䟀üwgm' Œ@³à¨üBhÀHØGË´­PçXóØM n_cà‚Bß.ã’×PàWI£<¬ÀVç¾­}ñ8µFt¾²:S¹â ›ÙÀG6„'S¿…’•wõÏQº®»|°¯B@È®%7c]ƒ¿á»–3A¬u̾ÈuV¸Ua⋼§€éxlÛï„›NB6Vô>›Ï)XF*w•»Ü§¶‘ÛŸs ʈ^•@ztÓ7#u½ýóY¦uúðoÖ¡XERÓ£¿Œ›( 4Ø1&ñØ9û­²`²õZ#µžðe¼ñ OÈçY_R%FDÛRaŠ->#C‡Ã…xÈ«1ÎØ+ÀÕçY¡¨+½×Á@üõê!­¦Ñeûìw/áÔ¨9.þgN-\s”Â8ŒÉ!Ì;w•ÀÛ‡]*Ä|ÏýbŠèxhÌÑ@Î\mï†Ñ>Ö_¸dù½{Àg|Áñ²-5¨ó1ösòPX°Ö| ˜ó)úw.y?d~Ù\?Ó&‘¨Y<ÓéF E×_}•»Ít‰sãÅ_ùé;þéØ=ïÞðç˜vURýcc U¼ræTQ®,.­W¸ezFñɨÓ# K×P¼«G8>s‘x^úáûH§ #P4ÎtC¿4èô ާÿú_üNšžš!­,YUX:ÛQ‰(ü¬+¾—[¹Ø"Ûéu à—{•~Ы —Î@KQð²zªCœ=|<Önš òo`æ1¢kK¼C´û%f²6€ÛM4ÌFQõ“ÚæÓ¼g]i&c¼*5QØiKdö6Á¹¥¾Ô;ùFù‰PŠˆr‡ü¢_ް¥‘j,WQ-Ÿèv­ô¥î’‘°ƒcª¡èö ÏÆü®ç–´•ôÉ8”.c<ÕÈ©ƒÏ=Œ©GÜß$rüîìít¼³‘F1L÷Â+ù¬4¤“zÑžß*šå“-»¢ó™ÆZ¯Uä¨m4¶šñæcÐõ7߀&ʃC‹I kÔ©ôÓ(®.Úæ_Öº‘ œÑLrïÁ¡‡:ÒQÃ~;†àÅ…h*FYÎS ŽŽŽ…“€\Sà¶ :9—äËMoÞÞI–æ/¯¤ª‡>W¹§-]ºx>ÆÖɳò8_§Ê÷H… ï¤@¯5”á%L{ˆQ ÂÛVH²Þö’æÞˆÓf•>­j}ßÞ+4pÛ¿™bä7K8ÏVF1bÀc®–Œ’>wƒÃfyÚÁa×½áÙP#=,ªsîctg\=ÈÛdBu ÂÒŒ$ò®U rÄÂIE>WƒÍæúz8ð[†qÃmathcŒ¯üð{QþƲOýì9•ÿE`ÙM4ßÅK—b/ÎSã]cy¿È°Âo#s3ÿ“÷ ˜Á³ùžãö'_­ß'>à_î×GCHgÿhš¼x%Ö/§•{8)B:Hïîþ¨`Ì'•SD„˜>¾  û@^­†óˆ²: ÊuëØjéÇ"ïm¶*ôaíCö0ÃPã€=(sýªct3›œïûœòk;}•H}¹/œ&÷0Òmfw[¹7ŒÐòoâÆÙ6Ä/ÓuÌÏgû ƒ¹pz,ÓãnÚ…íÅyJã4e83 WÞQîb\dH:„v(SDv• 8¿À›ê(%l¥îåaà¨ñ³Á>‘›csR£Ÿ0F¿@»Êam7¶ QÌêœ,¯œ‹ÇZÌ?ÊvW£–ü¯ãTÏh û£¬†Ë=±ÐÉEd¼£ÛÓªžÀ÷ 8ÀéHªÁ]ÙZ‡Ô©aö+]Þ½·…#§>txôØÃƒ•´ºAÖ‰öi;²ÁAwÝ›f–0"ÝsbŸw=MK¯Œ¡³Ð ¼ß l÷ö83=ƒûÒò2Ù5V+·ÃHdú03‹0U†wįcñXöõ§qÆ7:@–2þ­l[‚‰Hy`pnÄämiøku ]È ±°d™’ÂðW¬¡òMÈ´È?::(ù*ðÆ£EWì3¦î>U¸ðP6 ® l…j[]£“ýüñÔû›éÛTèýµsúö#“CP&ÏZžGG¹Æ{RFöçàÉlBɄЋàPÇÈøÿö÷Ó_ýOÓùË—ÒòÊ t O§×# Î D æ4¸Ü¼y3}îsŸ§öù8ÊG½y³0ºn„„g÷‰(Ñ謄”÷à>ýâ‹éúÕŸ„j79É"mF—ƒýeѸð"F©{QâÁŽÎžb´£ ¨€nzK=n_xñ³D§¿üó?‘žqöé¶3•£Ê?8KïÄù§rL^Zc¯ƒ×…/ÓÙ ôâÔSݧèCeyè„t_ùŒç>ý*ÀKîy^CdôìÇmÄw³c•gˆ´bÀÎwU xmAuùÊitÌz÷Ô(d¾ú'ýÑ\Ì=Ö…¤J Ù€.ÜYÆP€6ˆ4èI.œ¥ò †Á¡±´Œbþ °ì& jøš"N9ÃÔ⃂\Dù" _éªß«4âƒÔøÆø‘¤·ð4Šc•Žç"rEÖè­‡v8%«ÐžP>+Tê áÚ+(Wˆ@ÐiÈ(3•2¦õë mLû7sAÈ.ŒC‰_U²F$ÏÊwÝ |šÒ0]vâÃ.mš:Ïu§¹¸gÛ!ýÐ`¾çú3NÆmJ¼‰é©tæÜy„ÿ>jkV#õªó4°³«)R•¯Ÿí*¸†Ñeì#Ó«û¹©¯Biss (ûQÌ †ƒT¤kxø«”*°ÇXoñ°® CwYÆqU̱]8¨ °¶~Çnd‹ë¢RÌÇŒÛâ´ç(5„©Jv¼ë-¬Ì(;Öy€<Îõ í @¢M•ñŽEå°ðq_Úd»¶)ì»ùd¢Þ±o&Ñ•Â7¢ á•­óšOP( ä(ˆºbýÝ$¢‘ʣՌĮâ²4Å÷ÙÜLFˆ€½„2Ðyó¼sèÁˆ)Üs«»IJo÷ô(ö ÏÆ¬èœ®bžâµósÂH×tÄ÷TtÊ÷;=ß4R$ÆÉØTDjfó9ØÔÜâyaÒ&ÚÄIB£ † aŠpÆ©²°f© ÞÕY!¢ÑùìÞò½õõ­ô̧žÃXp…Êjš£&Ý+¯¾œ†¡C“3a<‘¦¨0uŽQßüµt5˜DÚyð; Ä³â”>S;g²ó‹ôã…‘”*óÀFs6µo(QÂI¼oüÆ~i³A.¤N^MÖÇ5©€ë##Ôpè;›;i(‹þßývšž>‡ac:´Ôµ<áý-ÍØÔ»]*r³aÞóEzègZõnÏŸÞ;…Àû…€çgÆ8ÝMÝÒRßõÎû¡È!è¡TÒÓb—Ùu¬K .³_Wü™u~×ú>ùÑþ ºrþ²>=jóob4Ÿ"òœ, =êuTG×BïÁ„Èܬ5¶Â c°ñ‚+bNð!žóð: 7À‡Ã=R ‹‡%£¡óÙ¯0VÀ®€q¨q¼˜ê«•TžÀ©˜b~´àøÈz—þÈ»7 ?Dkkxìd_Ë;ÙyîÊGmgé|·†JyÖveF®sˆ4¿A&«Nî‚»5ŒV=e2Èã±=S¤ ò쮟4FtÕÇ-9ÎôÛÇÐÊNŒãÙ0†Ü-p¶ƒÁMü–¦èèÃfÂâ#a{X†o…–Â×w£›–.jÈ•×Ü#AÐdHe÷¶ø¡\vçî\з~)Õ'è„m*m¹MdÇÉ©1æ ß•éüt¨¶«³³%ÞäqˆÒ;yé:¸Ý;üB )B»,Ý£¼©s“´bij/©®Í¨af$ì·8P-BëÈ.A£¢ÎjŒ³Œq¿£ ÙÇ<$ïiP_ÁÇâ#jD'¢¿ûûËcéëââ2$äm§U&ÆÎAÎå­Md¼ƒ-"á;É’Ç\åèw‘ÍìPšœ¡j‹3^é¡rŽûQ'õŸÛuâvNÌuÕÁtÏç9ŒÎf}hbdÖ™W¹!Ë*Í8_4Îêüä¾ûfWßx#ŒúC#qFi,¶üË™Mî/Ì‹d¡#Ù@0ê¸ÍõÝ©©qdV20·ãMJÆå£««8‚…?Ö¦­Sg™Ä™A¤óP…Tødt±:XëŽ#Œôd8À)âpÜSÖÃÁÿÄ-Þ¹†ò>ÂTYÑìxÜaþD¡Qö€ûø0æ««’¶+§¦¢YR²Ó•ERã‘!~ˆÃâžÎ#œ êÀÌþË Æl6eàCéŇËÜ#kx*¬Õ U°yb=vëŠÞ⾓Óüü ÏeÙwžaŸŒÛ8LOŒ¢/¶d>0 ½cu/YÞQÙ|pÀ½ÄùÙ|‹ìcÚÀžUê4X‹ýïyÁÌëô#ècœÒ+qqhx(mm®Ãw!ó·aÛí¦Î©×;ÓÍ»‹éÂÙ©tã—‚9ý¯}9MN±À2nx{UDrÌ¥ç‹H$„·C«[ÝbøˆLš1ªl÷·D?”Llôðê‡ØˆÄ"´HésÎV6¦ˆÜ"Ï«ØtC+ðp01ÛvYñüô:…ÀÇ n´“KÁY…û!<¸Ø*ÈeÖ%þ° qXYgJ‚ë~ór÷¸/[×é^jAâô÷)þË  f6£`´úÚõÆSA#ãðö^{¯Ö݃^?ÿÉü܃úW„†FǧaF:óò÷_"CÎ_§É³gHO·–VQ¢›âðí«5›·ïœ~z¸!x̲–QÂuh¸—ЏÁ~åñ‚_ã;yE•Fâ¿‚ÿBZƒŸ‰É3éÅ_úBúÚWÿ€gŠFUÔ龉`Ó2Œºˆ"šYÏGö±º»žþÏßû½ôßÿË™F0zΜùbššžNÿëÿò?ÇÙ¦Á%Ú÷´SÉ€Xubˆ×&Žj\C0jèY]ƒä:ÏFiæÕô'_û£ÿsŸz!]ýÉOrjwaǪ¤Ðxûæm2EUàsZFù[EÆ|Òf£J€n„lˆÒ€¥qÁåD°ã© ƪÒÃ&<³ˆ5à)ÀkP’nñÙmÜÏÜ0ßÇL5Êe!QºuÒÀG6×¢€BVÁßõÔ¸G“ðÙ(hWE˜äÊ–è5.ûw>5”†“Óé׿ü()øâù, 2¾Ž±Fkx{`¬Ú±šR¯„áøœ'TVX÷ªŒ°553 Õ2^ÚÖ:ó»¥Å¥d R•–³wgÁ/"šŒŒˆÆê%L¿°ñY¿w¼:HD4“Q âsaÀ…W ¥,BšÊ(ñ><“Ø ‡þÄÔT(½:RQ¤A}wçÖh›8]æJ;¼¡2Uƒ£‚ç!SKØnÐY3@Í~AÉŸf]ÓŽŽ³‘Z­%ÌJZç«ðß©" ƒ£ÊÖkW¯Åó®…+¡À©Ò¹¯ZBI3˜&§gÒù‹CèWy£ÿ¹ç>ñ{˜ul¦»wîE:=ùÀcWþ`²T>šØ9zDƒ{ãÊSO¥ñ‰‰04ZÇNAÛ,#ËŒÇ>¾ùo³V}DÖí0xNPÓóÎuuŒâ¹QG öWœ®Y2þÑÉ¥§g8”âý)XuÀ’F¨¼è¥?ïØ];×J¬ï¦õm<ÐiÝ>Œjè"âb …µ÷LÛ„öÔtVãï~†=¤›n¦IuX$ŽÑ>Qk8F¸T2u |7BfÏþ>ÆðüO`¾4ìÛßúvš›ÃÁÃTzY hT¸ (£2…£²ºBœ×û]Z£òLy¶È÷*O@ͬPä·}æ+ÿvŸè˜–£cœ3ï³NBÓ5 Äô#?ÙÆ¥z%Zð®ŸÅ]k#JÄ=nGW€7‰"ñìÆÝqh`çOè±QwL(Ú±¿Pà0f-ŒlP]¦ã‡2ø¡k#í¤ï:kªìà:± pœÛI/^"åùo¥Ê hØwßlà\¤¡âêk¯¥¿ø÷_KÃÓüMD °ªRkwEÛøw_¥z*ÑQà„ïÚ¾ÊÜmEVé!ŸûL1L¿»ÀMx‰/F7©8=Ï<^þÁÃH"à•NûÊB–Ç;¯]¯Ü“ÃÔ¹5JC:i½ßõËéñ'? ­ÂÄÞ)WXsÇActðn­"MÿË~Ì2".k@Ê{ãï4TåÜ(Ÿº¾¶ OPÁx>Ío 7ð;äËOmad”¿6r“ÈRÏs€FÌ•¨Ñ::z¾/×Çí7th»O´ÿSO’>pjaå•3Ï ˆ<ÃZ—èöο[÷?êß®¥ëßÞMÊð‰gÒÞrgZ¿óF: ‚ù`U'7h‡Î©F:ƒ;:UøF8삃Ê2Öì–åˆó‘õñl7 µN‰í0N½}¬=–쀨^üàov§þ+ÿ.Ï#ÝúØ^L]þÈ} O|‘g$5œ›Å{ ©î¹ƒ(œgÜò7ýÃcÜG?Žœ\”¡»à‡W泑yÚôû½Õ1œK1nrV;(GÝPwÜSãûŽ/ë”÷~D9ާFËv’œn¡.8q²ß6Wö2«›¼Ÿg•c`g³¾â€2°<£?Ô?ïbœÐ"õ ÌUÙbŠATèÊ×Ã(¡,¡­·ÍRLе}²yÔá;ʆÞÖ(¯PÁè'ól[Fx«F}„%ŠÒä€ã6ð¬Iö æÈÀ×aåëºt™Îcð½<[ÀXéø¥ß:]mšÑ‚¹ëؤ¡^z#ÔÑ^™Ýþ4ªn@û7·(Ï€á_à ËNú–š0àí=±ÇØË8ò÷£SP¶w:²vᘬ³·â¾v¹ ŽS–ôÒ0ßËÚ»Oƒg†ÒQ\£0¼/ãWœ¡÷ “ÒïùyÆÈ³LC4í!¬nñ{#+ý7©sÝ 4:;,`¤g|KK+Ô¬^IÏ>W¢ ÆÈR:Â7‘ù½B™‹‘ó”ëŸ{Æl}üÉD¢¬´mm2ì{•¡ò!ØHɈcFætJk¬½iÛ=; "œ±dÝ¥äˆß™"_ñŽWuÀ[ €vÄ™#)rŸ ï ÷@Ý:ëà›—²±Òïkå=3ÀXË|}•²¸à¯åž8 …PðóÊ]ŒGAؽüÒ÷áڷ; Ó)ÎÏßKo¾q•u«N„7ã0ŠÜHA×N嘽NT}•{U”;*ÛqàÑùæ~é¥Û·n¥ßýßÿ7"“Çñø§.$ üp@ÙmgQ¨ÏþíZ'÷¦ÆXÐ.•Ix~>èF–e¹ÉòþÍJBåb•QaD`,ªÝŸ10åa°SY˜%s¨'·MM©³Hæé‡´uõ"’8ë¾PÉ_æ*ò í\Pv6°N"ö' P°èÌ$Î ¦CÜ@)Òpb=PN*k³ÒA×\\yåzéO 9C<{î5îr†„^d‘‹—¯DªFûÓé›g&0d»ÎwY'×ш¨ê8ÑÀÏ5TáÆòÇ<Ä«Ð0ûÐGeYïè$îi”qïóf8qÙ¶óéX—[U¼˜Zßyìsö°˜ÿk5224náþBúÄg~%}ù7~‹•ˆð*ë„ìYãfç†X×ä=.†É0Öxÿç½ñ Þ>…ÀÏ@`–ÿ°GèUtwt§…7á!v¡­ .ñ ÿ@sÔ©ù;—Å‘åÑJcg")Û#*wÔù¤šFÏ>“ºªìy"’8å œ}™ZýœQÿâpe˜òÏ¿ù´×Oò“£ã“Á×5Ji‚Ö]+Kizæ<ôTе44f9jdo£4‡îöö‘¶å°õ³=_»9[§§Îâ¬ó$ðÄ(°¹ÑãF‡ÞÞT½éöÖQx‘rUšodY'©SŸ~ö3ðÈîWçƒnå'ßÐOD Na[kd gÖˆ_ǰ&Žh >€—ÕQ‚¦êüe½Õ=œü¬/í9ÔŒ3CÇÄä üÄBÐÙ•ÅûiœŒ¦œ7ÒÔy˜²µ“ìQ¦JWÿtHŠù¼ƒ%³Ùì¢Lß\è)Æ'¯kÝpÓ«¯PóÝÚß4z1^—ä[çé ‡KÎÛ>÷FÌnÅÙV®ô¤Í¥5ÎòJD)…ÙÞƒs(°E˜ìÓˆ¤Qª]YA£{o% NÀ{ŒÆß}È%Ê*òhž{Dõ¹7̈Ӌ‘Cùeu…hR"ò«DIʃh(W¿æYîy´‚3¡ŸG2ïìÜ0œ¿p‰~FàŸpbdÿèH+O @/üøãžŠ|~¯«…{ïõýGußq©×÷Þ©áLúú·0žÏ"T#KAc”†ñf—YtÀÈ´æˆõ—/n’²ÝhÍ:´Æ« C¼|N;Y~Š%xrÞiB›ä \+à8áÚSyæ‹©2þ$OË! Çh"žËŸ>^ÿÊŠƒQVK^xt‚³þ”Áyá·¹r/5ˆÔ”ftÑ[(ìA+È®6 ݧ}åçæCñqjãl‰-G>;»K60¿â˜âÐξoìñ íêx\¯‘‘€}ÁÖÈ~À@,ï¥Ã¬ˆ®¡üY'ϺÜ_{¤DǾm-ÄþÕ±"x&ú2«Z;÷”Íu¸–gSöwÍur·„Æïê$óàYù2é{_YÂŒJëŒV=< o ªfiÒù»„®"dàëtjö;uåïàëá±…‰Ž9r½:²ç{:ÜC/¡ÖaW¦6óuŽf¦(¯!Ó­[bï[*M:£ƒ§¼þ&œ5Õ¿u4Ô£ÐwéžAýüí{þÞ=²m ËÞp¸ŽL–y¶Îvu<§|«|íû¦æ£ïÉ3=ÇuêÑf‡ƒAg·òóà}’S ש=6T–9öÁ—]~¤Û¥£ýte Ç¢C¢•R¤íÔ­ÞÙàÜù³!lAKœI–ÏpŸ?óô“È+Œ'd£=ä i½©ì•¯—WÓ õÖƒsÒxLÏ?:ä ×á,¡¸}š_l¤¥uŒòðò:g8W3GÝ[À ~çˆûµ þHK¬y78¥a)4÷‰ô>²X1SϽ`܇.am30fåBÛOÄ·>2‡¬#³+·ø]x k?or¦ß‹® ª¦²ÁMÂsýÛŽ²ÇîÎaD¦¶>¼{Jy}fšÒ0ðâ€ÙÚÄE “•Ûol^Ú4ºN¾pj¬…´3Ët–°âuζœ5Fy9_h4u,EЉ~A›ÆóÞ^3â´…NÉsÒ²xÒE‰ôîÛQüö«.O§2ÛÔèM ž©37K0*—‰“½Ð™môÀD{“ÙoúÑ+®íµ¬kTà|ï*"®cFí¨l¦ù%œ ÚSýN%3Ñ0e®6² ”) 6v(G|g™u&›в¶öѺRÇ;Á1·kFù~ãdƒ©&T²,¯í€` é)‚n¿ù q)}¿‹ÎTT:vnlÞ(~°¿œ —Þ<¦œ¿?‹!}=”c¤ °¾Nx7‚@áMÁ:Hp$ KæÊ4B¦A" ›@4…b…gÓ§1‘ðéeð…ŠÏ  Äƒ˜ ßÓVÀÛ“…­Íb›¦!É5êÀû¼ládÚï³¥Ó×? <ÊëóN#¶Ÿó…`ûSh y  A+À­íd4à>—† ™‚åq`è~ ¬ú$±¶Î…‘a¼îW™}™™` >Œ…ûµù(ãèÇhaYòYÐ:Z{R¡DÆ3~³—܃dúe L§× s"#àÞò á9>Ѧï´.šÊÊsçvt>SáûöóÓû¿õê‡ý[Ú ³¯N%”¹Û³éÿÝW0xÝGøèH³÷Q’ÀêÝ‘mo ês|ëÞ‡û¡µ^n/oëï>ñ.Lu¤‰‚¡Æü–:UHq† f¥Ÿü¨€â¬8ïg/M8=¤Å¡Nð|³fú²ÁôÙÏ~>ÍÝz“šškà˜é{Ò ¸o[zÕ†!žrŸ³Í3MOd“ h}©¿x)ý«ßû] __Æp_I¯!þÔ“O!Ä­#Ю†ð«@åøÝÍFY¶®<#.PÖ1P„ bŒY㛞ge; »{÷0ì2Ƴ?{³yОffÎð B4¨î‘VÁäžÁNжí+øª¤£÷‹Þ¼¤õl~Û÷žô$‹MŽ\“yå Ël Î:¯`xz¡!Ùû˜¶""ZRS„'p/ë¥Æg÷*ýEË( Rœ§{¼áHÁÑHQéÎ ¶QÀ“[û>‚ ïÊküç…×q¦¢¶j QæXäæ«l MTqFjÆg”Ò÷¾ýdä„Ð¥ÅùôÚ«¯À« Ë0öP¨Äz¤ôæõ!G±ÔFßAi_%ÑG(lud0…¿JݘëhŠvÇ#Ì4ä;7ç*l¥á*D\[Ö¿>3ŒœezCÓö`Ðe|ÖRÖ¸ÐHswïÆ{®•íˆ×â”]ˆƒ «ŽM\·}ea§Ç{ 7t.xéûß ®l•¬(P^þÁb*Ƨ§LwM vF£Óô¾ÖÈ«ñÙH¯;7o ¼íJgfPÎ×ÒÚÊ5ÄfàóVè³LZ¹>³v¤V­¢˜š¤–Qss‹D’/Cª1b¢˜j4ŒÖÞ!ßýÕ׿žnã rù±Çbí›Êß¿ò£´‚CC‰1‹fÙÂr:„ˆG* t`ˆ•G6óßFÙÃoñ^x™þ^…ô6‘_*Ó&§§#]¿)õ²ß™Å!€Èî&c2úãòcW0n§+?Iürì @r¯¾Jº§1ø‹‹$t>ÐÙãå|'ð‘>›8­bÀºsÒ*tô2¢C‡ñ^¢Ç¬gzãÍëô7†ù /-sV¶ãù:ñ¸ÎòÎax*]À´‹{{¬·ãa;¥Š"h…ë(~hx–®éšžÁGà)/jä´¬“u¿…å”Q´“h`ÛeïIû;¡åæhª‘)hsép7x £;ŒøÐm>Ê`‘º½%#`‚=gt²äGƒ¿ëôŒÚ6SAðÙT¦V×)²Áž×ج³Š4pzeɈ6›s°¦³4¬†!šÎâÜÒ©l)ÍÐÑK¹Ê3¬ƒfµŒÑ“>·y¾§·|é1¥2Œ!ÿå+˜à[{ÚˆaÿèÀÖ¦îKãaæuuS¾Åé@çœÃôË|°e1¶¬«Ð¨mªïì¨-×hi‰#âu,ÒÁ[ã:XÁ‡™´Æ¹Ð‰>M'¨NJ:²ËË+chÌ4ú\ýBðÅÏûÐîNt8¿ÊãºÂÍ#lrì'½Ž}eöô0;[*!¯»ýfY¿ƒýÇË1wåUSÞë< LíÖ²F|ð0³œr”:õdÁjÉŒ”,™Ç¨¼ ß‘SË.gÂÖððä—.]N¥f©Sífš£t‰{Ê•êsH½íycFÁ&ÖÏ|ö`ð/G”ç0›…ŽùíñQEœ/àü! סC§ùAè~ߨ9ÜwÄMi•EŒ­Å&xƒõ¬·YÑq°Wt¬Õ mD}Î’G'ઙ¤]– èål’çqm*ÈÓ®“òNt]¦Ç×À}Œ »ÃÜÜ›àízÖŒà o$w"*üìÙ ´‡\"M+€#ྰÔÁ-ècchËõSg–ÿ4Õ{S~<ö̱ü‚øcÆ=÷›x/ ¥‡G8´X%tdÌIØiì>­[¯ÎàSâ«ïèàç¹ÈŒDŽSG2qX{¯{±^gNò#GŒüsÎ Þ{«zÎnaÜÁœu”wMÏæö%œØÛ*†ò"mŒ!ËË•?oÔ éÖ¶™ÊêìQ0ãŒàܨQÞ€(œ†ªòœ‰` ÇyÂÒ/+ûišòDÝàwáh £9{þBxí¢€Ê‚ =˜":‹ƒÂ¹:J½RYöº¡çöëås,üÿÖ½wÞ‡Nÿy ޾5±Ù‡·×1ï+K÷€ûÇ}&CÓ-³Æ¾óä×ëZ#“Ì~K0÷™z¿÷r•%2Þ­H4¶é~—)°ä‚m¸÷dâ¢÷»)’<³MAešS•òô•¯ (ÿ ’R¤qÒã°XÄi‡s;ááêå˜ïƒ¾Þ^¯ÝóÃÑßûïjdÚÄ@® %üè™Ã)4ˆñJKðÖ˜d¤˜í+|!ižŒÕ¿ó¾3—)µ„¹?¶‚%ïx žùžF8k‡#ÒMßü›FøLT>Š–—_z‰”ÞßH?üáX¯>à%ÝRçmcNÖVÙ¥ÑÈJ2¨[lÚLú0j©Ò‰™9û|w·ŠJÆ@ýMgÈ(èWEë6<¾mH[³2#(]«PºàúÆ:þì½W°¦×u¦·OαÏé€n "ƒ¤‚€ ’’LjÄJÖY55º­*UœÊSeݸÊñÂ.W¹æÂU¾˜’G29EeŠ•HE$Ýs8Ý'çäçy÷ù@“  @àù€Óú¾Ö^kí•7pVYw‰ hÐhà8lˆ2,\ýoHiÏù ™%ÃAYHCìÙ3§Êž}ûp`{®(g ÒŽeþåÏ1ÔðÞ~+/w}Ä­:~ ;–ô7»ÙùOà¸ýø'>††S´k%‡>î×p<„Ãwtt4™åâB/ÓÓÈlFeß¼Ù¶•,uÚñüÑæx×½·b *åÔ™ñrÓmwÄéþ‹/–“§/FÉÕ°±ÿ¶›Ð‘J¹y;çžµ“„ÑêÒ¹‹dmQ=€ u3\^eÍ9²Jžyê©òo}ƒòˆÃ(è-8ã/s: +еJñ!4о°ÿr^pH…~×é«1F:l§¡QáÚ„xî™gÈìšLi8ó¸Ú;4à ϲÏÌ-Ÿ9}º=r$øhÖ½$˜À ÑÏšœÃ8®¾¨sÞRã=l#ê_šôŒoq_ç…å=›[Z# CK@‚};z¼ Œ:Á¥:æiß wSãÐ÷¾—²­î×ÒºÙ ÔR™5ózÔèdöÇ‚ÆäëŒ2Ü;=»uC¦)·Ü|SèKCó?üár™*/:Ìy¯ƒ•x$ì¤MñÜ=U8ÚxGS¡ Ò ”YÇ™¡‘Sœ“Ï5ƒËâºF³9æa¦L9 !yØ4ü[|½zùRÎs¼õî»S¶\0€)tÅkà$AJ`ÍaÔój…®lÃYÖŒMã¬ÄÿÇ_ø<†Ž>Ž.[wl+g1–°Á¨s @•Ì÷¬óeÖKKze=—eï‰Gbnw4Ƴhü^šf­R^PÇz³cá> <}èЧ´¤aç‰'ž—àEœ1 tÒÞ<0çvžÇàÃ=;wìÄh fÖ =ê—í×Ñ‘n¡;2rfe}Nk‚c ƒóõ‡\€"k¢Ó$YòÜïžñƒä·´ûCÚ{»~þqöõvy³C û|EÌо%4»‡÷—Ùá{ËüÙ¯e¯^"spÞ´:Ë­ú³N°“²†HÛô¹Âo~‡ÔòÚ¨tÈþ»n#çM²›Õ˱–ì#øèÙÞc—N#z´•7ß ½)³T¹çÆ£~í/? 'Øï¯míÇû©áŽÊU¯~ò2dCAÝѽªLàŽÉ%ƒÕOÀ¬î¥®…WÞÇ[>ê8÷Ï»½Ö"Ø—¼º§ðMûïÈ:xè¾±ŠÜ³e ™n8 b ‚gº¯¸G¹ÿõ![SL@IDATÜyχØ7- [Sµ)—¹fdŒÑ}CGLðXçÃè°q¶/xd›î×Ê7Ê&:ûgpz‰GòõöTŒ1àŠJ ÊÆôe@r¼{©²CÝåïÈÁÊÅÀ cf,¾·/Ç¥®Ñ8âÆç 4l̸»q€éÀ¶®‰ãŠÌÅoÂÝï|F¸Fæ£]3N_} K—%²¶÷úcÖÈW¾Ïf~ÃÇãDØXCÛT6ò~ûõªöò¼½öí6®WÞ5¾yw¿ Ñq †~2þößûÉrõì³eöÒ!8•«É8ÕÑ'¾Bg›ÓVVn†G¬âÜK’q¬§K ~çÞNÃò&üU—ÖO/¯ãp!Dg°«‚U3ò€2¼Nö¬)ßoPÖ»optÂûuqDe ¯¼ÝK[ÁCê«V¨3Ô~tLš•>Ï8åÕ: •ÅÎZÇ…L«½n *ô‰êqAÓµ‚†k.ƒ²¸ pòM+iÙ—v¸8uŸvDWYwù˜ÏzìŠü0Œ“’Rô!xŸÎÖœ¥uX"Hý‹d⦦Ã]!B¡ ŽC68y4íf»¯VÞÝ~k£Ûä'o nïå§‚ëìm±ç0üzåÌ^ÝüãÐCøQèR‘qoÓ #Í,"Ø„ŽùÎòA›Â”%u*óÂQ, –JV&m ²7µ¯`⹯F»VŠuïðÅø¥gùo ò X:5¾ô_,þý—É>Ø–óg ~ów üËìùîÃ{< q“`ÞáEúÉ4oÙɦ–ár…@««üµ¢”ˆï≑½îO:TÆ4ØióR È+{œ ‡F“®6h åÒ¨×Ü÷ár%Ò§.Añ r—³Ðu<_Cþ5Jç˶;Ê™“§¡„x®8é !•€ý7ݤîŽqÌ”Ô/½ôÅò·ùå²m×vÆ«“»F‘;V­Våg÷ØÚ"4ÊwÒ¤Î_…öõ Õ=X3Mu*‘£» ÄÄ1¢ÂƒRÀ6€2À|ÝS¡ç]3zÜKuú²or¿ôE/ Œ•öÍöô^û—·8® 3^ *4õ:¦›8&½ÿTp……ý×5Ä£Ç( AÑ»¤iSç@Ï1ˆ».õ³´îó2„O Øp>#G¨45”JO_ú⟑¹ÙS†ÑU„—Ž-מÖÐPÕ¾œ;x`@‘3TqšÁ{ÆÆBAžhÔ£cclŽ]˜…-ÈKüŒ•'9¿[Zç7’6sõ˜›™àf‡4›AÞºVN†˜i¦?P^îÂO~G§9óýôÉ“åÂÙ³À ƒ'kÜà3Ò”MIÏxÏ;æ†\cOÞáX­wóÍÒŸAÚ „y+Æ—Êeñ¼ç"ÊoäÑâPå=!žðˆ8’øÝqÊK¤OŒãþhàýž7Cc1žjÈZ^ÄÈ nÜzð`èßlŸK.–Ã/¼„1•ŒžO?„i;/«2 ³Çt`ؼ@Pà§þùo”û?ü0†¸Ó9‚7¼*4¿ñL}òý«ÉÕ•ÿ% a´ymBà퀀èÚ©üŸ/üN¼†µkø@™Ã±µ:Ï1 Ðk›>ðÐó\a|1†®ÃÓä‹Òš¼‚GÝ;¡˾³±­iøç·²t°*ïÌ‘iº0­ƒ”àAd£ûï…lcìcd fî§à’¯4.ù•—߸žúšË{7îyÍ÷ù.Ë»ÚÆµß7ÖõÚgÞ¸GzEÖ¢M3ÃÓ§Ì+{¼ÛcŒÜ'ë¹Ãÿ7Æë³=œ7>cÆ«™é¶çÿuHã³O¯—ô‘{êï~®ß­3¿‘Ú&·+“Ù—#RÖQNʽîµt ,ëçÆü•Ers:ò{û¼ÖiÝóU•5x›¾òÊ}ö÷ê+6m¿à·ÚÔF[¯¾éM¼ÿÑž~½‰[…­óvWq¬Nž¾\zùë-ÓÉôí#YVÃM8[±5æln`'AvGßQg£‘Uœœ–Ün‘‡ SÛ£])`‘„»5ôŒ8KùÆÊ Ý]TXƒçŒ'Ûg)ë«c} :¨vJ=pò°Æac áÝ¿7ê70~p–ù›½0u »¯­ðnŒå«8¥ãXu¬Ê‹ÌòÖ¹«NmEÏ$ïFc¤¼‚c’ )¤ÎEHøTqèê§œ«$SÙ4»D{+²«ú Ž«´…ß…c^¹ÚÄøÙå¦O_= ¡mˆ«eÆ·€?©â‡{ø¡~ NÊ:J§®¥cö¾ÀÌ`·døâ„•7”j RίxN9oŽ _e×%œµë‹èêûüæÛú¦ &Ò§¶$t}‰úÝØõjö®çÁkËÖ.Á¶{Eø\Ó Ú5ðÀ@WuBu)õ«y²•å-Ê–©° nË*.ÏV›»{m+º+»)A´lÒ‡?+kWËYΑoØÒí·Ý¥[G'ã7ˆ;vw`–@Ltbç/ŸY@ß.9ýÍ£ììÿìÙså6“®pì˜ÕÏ ^–GêCPïQ?Qæ@G–0ǯ^ÍzŠ|STîko§jú½2ÎXiQ]ת+ LDï¹×²Ü}ÇÁ²{ç¶Øþ–ÀׯŒfu/õŽ&‚-¬ åñgÛ·ï*g z¾H@³øè|ÔßÔVÔi´wª/µ ;-Ô§oAÅ km*Ê+â°%ÕM–>Ú™Äcù¼\Wð™V=ºàƒÊ‘drfXW`Ät£_ú]œY/ñÝ6âpg¥<÷[³JXôRø˜™àÂG™È±ˆ_`*þP* 0ö^ô|ÿÜù)•²ñLׂS;åÔÝ Åôr+5z.»çóò´Ä:3~ý.‹¿cåÇ*M´"“-‘ùî‘(M- à§Öt´rv¸:§:•<Úñ ö÷ NFÂD¼õÏuñ{íÍ]8¸•û–¼Ïy3iÖ×½ÚŠ3&YöôZa¥´p®:ÓÈÆŸ#`NÚ±ê˜:ZŽñB\ÄÑÔ<Ï8ZC+òw«‹KâP/A½GM‘un ‚—¿uò›2­€ÔNÁ”øÁ 8q;ß{^úö}Ë”Ç<Â[ÄꇇL¯' ¡ô,Ch4ª={èXùÐ}(ùíå¹'#ʽ¯lå 7¦ò/‚ €·ãj4cŽÑìç9ûåô©ÓÉNµ|Ÿ›éÂÂU¥ñ CÀ¢¥¾ÂDÍ`uAÝ0«`‘ÈfïsËŒÙhqËzT£–©ÿ*Õ+%ì>â5†1ÊBlFáy)‰rþµ¯ý]ùêWÿž²œ!tðöòàƒsÑÎŒ#ów6¯M¼ 2rLHGã62Lêf9˜È-Èm™GÏtyþ™§Ëÿßý/å7û_Pr[5V󬪌æÑ¿ÿ*7O M─ö–1ÚÑâ¦Ìç<]"åÉ'Ÿ)÷ÜqGùø'?A¤¡pHçfV‘³4θV³Ï%úL# $×à¼Ý¼6!𞄀„gyŽB§Nyö&#ûÍ‚G@»HÐÊÑcÇ#iøpÔA¤³D!Üïj”¾ $5_±;²Ù›Mi)^„hXKÉÎhö¹JŠB¡ÂÉ®½»ËGx {i”v¾Wù¯»é;ZG¯ÓCáEºV0u¯þ:¼ä/þèód— ‡gLqŽ£ÊWprzÎÓ>ÎD{§ÇøÎÍ~³å׃€A—.ŸãεÞ3:”5wo°³g€y˜2]¢ƒ¯ÛT4Ý#4˜Xª¹ Ë"›‰†>óO~¥üéçÿß”m³¬åÐð2´Fi0p/Y+¥ž_g2¤ô¦tgC?O¦è½úP9pàV4”®c,*î¥:-¯}zñâ…ÈÆ ä–ÚrŒ"?c&•³ÙÞ¢À+·G=†î¥éìÏ:yNœ_e/UÞ–¶¥[ç¥ëÍõ¡Y~·Mᦒ¦Lk´¼J˜g˜{\“ý{F–<#ÊÊH Ä ¸YäÏûUàTT4w:÷(¶Z*¼Pܲ"îá9:Ó5\8®êã0W¦VQQs¬ò8åw×FÈYëwϯÕp”ëUTÑ3lŒk²¸Ž¾UÆhTøÔBåqÞ£RŒ&£æáYD´QáæÍUø o•"•ÞÎT"PR!Mp¥Î¸!ÂD€Âòæ8CyõÒ!©ü$Ü× ¤=ÏéR‡23Y…LVØ«00žÒ „™ot&¾2 `ßRxL½„ŸÃ‹Ó|Ðç…™ºî L1áÌS«‘ðÒØ!\ÍÔ]&بîQ2¾5¾|û[ÿPžüîwÈúÝÉøÈxvþÀäêØårîÌ)Úâ‚mÄXpè^*gΞN_»wí.Α•}ê$^Ôî2ÁYsë«çPR;SF¿iÀÈù³#WNÌÓµ³å*{–Ñêòó+ô'Œ\Ov%p œfœ®‘ú›8áÙç‚㶃Àu‘liÏüt¯sÜÒN'F:ðCÝÌ6-ÇçšiÐG¸+¿¹ž~‡5pj°d¿1cñ×Ëû¥‹¡»ú €ÐÁÜoÌJ#8A-;î3Þg¿™¨g&`CÛ* ç¤1Æ9›½dÆ:oÓ¶qÊïñqŽ€‹”³d<òµj€‡6yÞöÄÁU2xÌ<42ªÀ»W.c4\§P†÷X9AºxnÙÚ5øÅK/¾Oê <Ïœ>qwðÙ`açN4fŠÛÒ‚cÕV õ1÷ÓcM ígí“y8N³‚ bo|o»4Ã3uJ’¡pJ…æPçÆ}¨¶¡cŸùÒž4æY’òeÆåïÂ^¼²}u°aƒV4‰+Ú&œ[xšÃwýxõÏój Öˆ&/¥‡|.kë½Üãy‚–à_œã^ AW(å{߇ö#Kuà¼Ù*9>pç>P^|ñ0Y8µ²„ß­±^®Á¶„­ÛF£#Y™à¾‡?^~ö“¿Äü0ÖÉC<„S8:®—Š+ùæ üãòì8a·ßÀ“›·lBà5ÿ½*6òF:‡&T²_Üêä~o Þ&JÔZáe•cø÷¾æ•ữ/_óÃuÀÁ׎¸ñ;ù¿`àõP¹¿Ôß^;¶ÆsõõÚoì-Ê‚î5ïSã½wû›ÿ׫¾yåc~müøšç^õåµ·ïç7€0Õ]õ†åÙ+eùêse¨ƒ²ø¾]DYèIÿÈîyêjîwŠmÊ2ºš‘~+{#òÝ 2”2ŽüG‡ëÄ[ä-äpxNa+ý(3®Ìáþ‹8NzáG:k¶dsÇpi¼{$rã³$uxáûya27dB²»•“•3——9¢QÛóœñ6œ]V3Sµ­½ ûÒXÖïæT¸ZÖi·4ã䦼¹™¨Ê+¡°¼ˆn‚#«f`³'(7r¯Î°uöZÍšzޏՔZÐs\?qÀ ­uúÖŽÕI†º2´N[eHã:å”›#¶f<ÈŽ’¸2;k˜Äö”h"yÒ}Eü°Ä»Î{4å{ÊØÊüSìEŽmËÈœõ½èUÊ  ãPOéÄ–°ÌØ.ž9VVNÎÑE7! ZQO½Åû§&uNâÀFÆ4ƒyýQµ\{yøÁSœßÜÞ¡ÞÚŒ9Šü xEÞ„á¬Ot$½ŒìŸê¾KS£Z¡C¼Wߟ¡Ò[§úÏ)W {eá³ç.ð{odF‚´-@bŽ s;Žø®šá¯¾¨C_¡>ô ìÆÎ%ðÛÀôÅsƒ²³íʧõ'@(e¶“-TòºDPêåK–Ôö|sæÄ:Ì܆Þ?åá2¶J»C¯ü>C@2ð?B´éU²Þ]gÏÔŽÓ_Y€õ71ÏÊ•V›~þ¹ç©dE¥/l¨¶«oXµ¦Ú -s ‚†QÞ`¶³çÞ ‡êh¥!z…AÖ&Sh;•Ÿ$ɽGÜðXíDƒïص›¹s\ pÚ>º…±¨Ké3•o¡ç²î dd[åSû‡ø»æ3ç ~èð•n„Ÿ¾K+e9~õ/³³A㬧ååXL?ÚD8J­UÞhU?Mîèö()àb4œIP”çÑzSSõ‰ Õ^ÿS&ãGÛ[YeðZSÄIõÆåT䂲¹Iý} }wŽÀ“pÉ híÒ6Zçö£½*Z8h3ÒæS·ª þ#ÖÖõUþ3"[ÛÎ*²¥º¡IeK´;ˆMcxˆLwdLá䑞kLÊõ–ø°¸h 7eݱ'¸¶ê|ê¯&|î=pÁ V©0(‡‘!ˆ÷Ñ«éGjRôK2´ƒÛŸo*½è€oéCB%¸À9˜m¯ÞÛº ó¢Î3bᜠ¼gÙyvÜyÊZªàJ?ú×ågú…²eÛŽºÀÝv tÞ† z  ]$‘d†ìÔÇ¿ñX9òò2O:Â4=ÿ\âÒˆ®óÛô)SáÜH:ᘓ©U”WøK wLj5 A£ ŒIFÐ †U‰A¤Ñ"D*Rï"âŨ¥Åù‰òWþGeÏ®½Pº…‘A¸™4æð6€c³‰M¼c^¤£ñ‰¹r‰ì Oó†âí£Ûb$”¥iÌò!3³DçAWDŽí;°¥|ûë–/þ韕)v A³çN3Þ…²cï-ŸѶwGyáð¡ò_úkJ{t—-ýœ#ET_+Llú<}.])¿÷?üOllf›S*þ⨞©5•²;–w©›yδ`óÐ(gæ†æ¹Ê5Þ1Pm6¼ wFá™A¥p²Y éìù³åôÉSÐÐ 8&8ó‡hVeY…ô&³ª¤WH¹ U\ îfaB¾qºä ]èG‡o)ÑÊ3 Š:Ð5nõ’i7¦ãAó#”yU¨UàKæz, ï,¤o•VÇŽŒUž$#ïO>ÿ¹2¸¥‡ŠDBÎ(ÔËPj¨,QÔßßÁÎs¶6¯÷¢h» Ô¾xø9dÓnªÿ€ï¯–S'O”Ý{÷¦ŒûV2CöVxV¾Ñ%îhüÍg(F’ßsç]僕¯}ùOÝzã0Aá™#“Ê`1uåB3Ê•4«“C±š¿Zþê/ÿ²lÃY¾s÷ÊnßT¾2÷E”š¡Ò¿0ÄžU{‘Q– ¯J²¢$#¹•«<†Ùkë^æsqı'Üææç^¬ ªlªÃÌ÷:…u&+èÛ‡Ê’Š€£¥•i³Ú·g*Ú‡ø*7ò û±íJ’%™ïT z -«OÉO4(Ñ÷a†äwù†ÏÚ¶Ï&"›WÏk™¯ç4û½7Û–óq͵«£2h€J`а=ÄT¦lW>Ewißù«$1ô”éóH'yÞÎÝ{Ë(ßRæW!ôwËÆަ QæéÃ~º)}–@=úQi‹‘„y:ds4Dù¤?ZùÔ¤™Öè":ç…‰<·X.„kùq¿×¨bP¯å¹m»H˜ &°Vªjêh0u=«#f>¥ ªšGq—ß SåÀõHYrÆT•Ì%ÀN%6F5ðââEÎ(ß³§<ÍYçÇŸ({ÁÍDLëtÕøhß(Æd5×K'<}‚×ÂÐ¥#íÑûPX5Jj¸ÂXÔ„1FpL*sÒmÆ€Þ^ö²&Æ(ÝN›¬¯Ñè[F(Â3⪆-a±¼j¤|-³é|=oÐû=ZvASw@ŸdŸ;c9 QŒÛKøú\”tƼ’=Jº©þ&ŒÝä¿+ðǵ·ÿ̃uK€5ø£áGZ‘Î¥1á*þˆƒî‰>£qÀ3·ÅfÇ!\u´ZJTšS›d¢ÌMÇ:ŒÅçÄ[­)už„—¸îܽâÔaV|¬^€«ô¯k…v.LÔ̘NÚ³wi¤žihðjWöú5ÆòrwÅUqIýÕµÓѬ®«LãëÇTË:¸_ý·¬KžqŽ=ØuÎM_˜;sǘiॼªfÊf‹z…ßÑÉá·²dÇb”¿F×̳í-3¯®/äPæ”ÀÞW£¿zI›Ū~#‡YG/iËÚw¿ä¯‹5Öé7Ͳ^d&ÈÁ­º±_mc^ó”÷t<Þ§3.¼ŽûmÄñ¶¡ÓhS8úâ‹€;Àɪs Så#} ä^ñÓ}¥»·»ì HeûöíåÈÑãùMØõôt•m:Ï1–M“]²ëÖÛË/}ö×à£Édlg”ÛGpÁ׌ñ¼ÙKãën´³ymBàG€øë¾/ï[[%ÙdežPQPMøhËÇb,54ØSÖûöTÞ•û—K¼B}EÃé*NðnΉnÂqn a ú²â…ˆJ?Ч4&_R~0“±¥­§lÙ=Xö` ÍžA&ÔZ ç_>ZºGi_Þºyýø p#xßèû׌lc­n|¯2ãkžxýoæÞïk‰‡ëÿy}õÏî€þÿÆ®ïóç÷ÆZüqß%¾áé¾Ãƒ‹®Â¾¬\<9Fæ™Ã¥uýréAÆ™'ë×LV“”áÜ1ë&dk>iGÑ!bùlƒá*>ÁO¬Ú£M#Î6xØ3ó6¶fîO¢ ý®±Ö¶åƙϽt˜½FYÜ@hýFʃíÈ€êÚ©•éÔ[ý¼Œè|•‰ûûëÑ ©~.nÆ>ųõXÊ'†ê1bmÍCÀlWçÆ¼„î :’U½<ƒ]g­zgüQÀ¯‰qèÑN¹²‚ƒ^½¹^ÝÓ½QáØqj§ûÐÁtV6¯Ï•èf†’ùÓ8££ËÓm„ü¬ Í,ï)ŽÓίÝc} š56@[G¤—z˲{2cµ}Ú.rl²ô8Ò&áêssMÕÁ´-z_âŸeôŸ?t›Évà©““ÊQD^…ÈÔ¡¤QI[9V™³n8tiw ÿœgžqLÓ<Áôw¸§å¼Ä}Õ8OÖ9²9íXQ*öñyMp&ü,±:Ò…Û®¡û«‡Î7d™ÊB*5 ˜ $§”æ6œ f¬Z¡ÁÌQùï< ´Ñ?–l‘ñA†ÙDo½y†ZÎŒb3š4ªk¥ üÌN6Å‚cޱÙ\-##£aL ®é¼Úhãøy3è²?÷s%H©°ÜRi97oþ³ ÷,ªSXãý‘^,ßúæã8=N’m~"´ÐOà‰Î€mÛ‰d¤¤ŽBVY¸fèBá%†íìw*#Õç·ó;Ôè~Œ†È ssoŒÀÝ»Žr¯ïDGêœ÷Ü#³ßꞦÃCáß>äFæÞ¨-ÇߨÃ-s}øùçÊïÿ?ÿ7™°SÙOžø‘rúøñrâÈ!è ¥ Q>™æW(÷Ìœìg ì*}uĨDµ!3}ù¥òüoÿkùíý»åŸüDygËÕË—ËeJúêÄUÉRñ4 5Êr¤Ñù阑ÇãtTþÔ¹£Ì)­Vç]£C- -ý¬ò¬N3åÐD cŒ¨t£ãÜH÷.ÝéÀ“•¶…‰ðk8¡¤•ô(1(YÊ·Fâ7£ä¨øé´-nªãÖËd´¥1Dç–ð¶¤*­Ve’`Z¥Õ¹êt”cœâæ ”Óà2Ép"y–òÒP§mÒcP-¡?eu•@ZWƧ§^Êp‡ÿqºÊú$J¿ ¾üCÅF%Y0è!pÆè"Ü”ÿP„œ§ð2s×(uç©bçŸÜ±¼PIU²PÉSÁó,Ê8unòKË2¿Ñæâºe ™3} ß"¨-g°°³Òö“¾8G˱;xŽ7N¹°e=ìÏ âè.Dyw¬kȨJ÷†,ž!®Òë0ÁÙuKŽÁÒ¦†.ià>û¿lÈçå•ô¸“? !u.Ž][xhFܗƨC_Œi²§àÞ2|\½FœSÖð?+›H[f¸f«Äè ì4¨hÔñ,Ý.tžßþß-» N™·-GÿØ£_‡oÏ¥4g?ýœ÷Ûyölg}» Ú³{Gô$ç`Øg~í7¨bwç?EJºfÆØ/Œˆ³X÷øâÍ\Œ›f„Pæófݼw ˆ‡ò 3@—ÀSËÍ®Î_*M‹'ANõqšq È+´»-¯lœ¹ÉÃ+8JØ¡ /uŒ‘;¶4·²52¢|ÞD—µUè^ )Ò¥|›¨T¸"ßÁ”I° ÜÚ,DÚµ,°ÙSÓ—VÊÈÇ}lßZu´Š(Òõæµ M¼9HïïŠ Úwÿ’ØÇ¨^tä¹§ÊìÕKq”¨Cxé¨rKg[æªòƒd/ý+¿…p>¯/elxkDó­!KËwlÛ>œsK NÜÈêNìšl¾:áêy»êMòì è[Í8ƒæ”C›pøµ*7ßÞ„]?¼‡}÷}¹6ìrö2:;Õ;Ku¤ûÙòßë.õjWbyn"ú¶›2 ü_'©<™¤‡%Ê1HÚÑݼ‹#‹öÛt¼·g²µÍ6WnjëA¦êŽópzüÙÁ”ñÇÔjÖ*GKÙ¦¶é¬+›¾À“E*néØWWìêèE¾^¡Œü–àÅ%â­*Õfu66 Eü_.£²íºÔ*Q¥­è¢ê¬V¬Š=‹Woê%³ºýOYÜ×uô(õ,mâÌŽ{©äL@¥²œAÜê_îƒIHãKmö“ù²ï©‰«ÜTÕÿh¥ã@ãjGÕqV9vQ;`2åôG ` ìž·ŽÀJkøé¦’«.BÀB}„ù2ŽYH×8Z‡÷ NôN`§³Ü};úP½¬[‚ÈcU'œÃþ¡¾ ý£›¾WÙ¯ P&»Âñ*dbsK`30„]QZ¢9^ý-v{ôNmêÈöe‰rôîÕu¿®úDM(k6hSXôoP°07[ZœÓ×Öƒí@º´òM|†ô©mB@œqÈóÌKX«{Xñ+vB}ut݇¶ç€¥ð_X$[9vÅ*£è8u­¬T—ªž¬O7NwõµÀ-k Eˆøcß°L9s·úîòÏä¬eÄ™2‘Î\a¬.ipüÒòdéa-Ôtº»>}”¸of.ÎÍ{tÐ2®Z!Î@D«µÕÌé^)ë],ø Õ I´¥®Ói0@‚ñÍ¿Âs“³8µ[·  "¯Q1Ð*¬‹Í;?«JCÚȬ7 ïºÅ¥;ƒóWˆP†½€ Ȱ¨«¯€ÛíTΰ_ƒ÷Å{ȺÖÎ÷â–`kûÊŸ´k@Kl60øY~CâkÅë®òj™³OºøÎšù3ÔÂÿÄ/~¦ÜuÏ=AîÂÊtÞžËeX ‹Õ‡‘lcéòÒKÉÞØ‘á«({F•VÆ'‘ša‰FO',’KL:ö¨†€É%hÕñk€¨ @u#Ð`£"ß8¿M+±{6óü|ðŸ|ê©rëwe,:-é¶ymBà½iV6àµÍœ}á)Tœ ƒ‰$ꇛž U 4'cyþ¹",h°…N·z&i=æÀ Y† ëF×J@Žå”z¡eékMVCê,Œ™_ÇŽr"gØ]¥qÓþ4*;´ÁAJwÎŒ—gŸþaÄ›C’Þ>¦ãL7¯M¼ad#Ý 7üÐëܨ#Cz±üèß}åoÊW¿ñMÎ÷ÙZ¶nõܶzé,±BI;4熯㨖L ¹p£BOH{ÉRUÈSø¯Ž™8V²gÖÒV~–ŒÜÓÌÈTÀ<{öt¹Li¥ÝdxêÐS‰£$Ü¢ò‹×™J~r òiF…³àÚû4Æ¿ö -³ßs{:…¦#/½Xþä ˆ0Sϸž¼8M€Íó%bJgT"(|É=š¯˜K¾xm›ŸÞÓPx—t”_Ä1Ý¡ÓtëÖí‚Ò ~Ç1ÔlàgãõLŸZÁE3•Ñ”Ï]{ö•ìárîä!ôŽ+Âé*aè0Ÿ$R9Ž8”€/÷D~Ëx-®öã:{úTùŸûƒò¯ÿëÿ¶ü›ßû½òÜ÷ž-ßýÎåoÿúoØ×Pü;u*êD2K›ýQ|§}•“v”'3“ué†jÓ -ÆqÅXk”¹Á¡õ½°mqOXð°-•¬DuëTs•’½ü«Ò©/¬ƒýª&Èͽþ#*´ñ½#GcûÕù ̈¶õ9åù†û¾%×ÇheyÎôªˆè¼V™ª†Û×ÐÔp2º&ôÃ3=ÌÛg›}©¤Û¯SÓ¸nY²ÚÑ9¾ÆM£œîYÝ/¾xýÀRþÊîW}2•G.Ça»Õ8à{Ï;T¹RÑ­ lÍPf]¹„‹0³Ä?˃lb¹2侎ÂÜö½¯1VÛ×ù¬NG¥k¥"©þâ:h<ðænçö)¼Õ-Ä¡fJЇ­õ ²PÏòñÒ@]Kû9õa×Ó~/q€—Ê:¥ív”öXgÇ(G.8ÆbÐHv4}‹'–C,Kgð¤)ÂV%Xýª‡óòt ~ç›ß,=Cýeßm7•é‰é2‹Ñcq„ÌtÄd¹IƷŔݷ»ìí)GO\*O™ÀÖW†‘ÛN•ù‰©Òãùz8Îç8;lâÒ•²kßΜGnÉm³…ÿ,•Oœ¬cÔ8£RlæÿÑ#G2VÏ›¾÷̳™—géÅ൘ëÿTX ? Kþ÷ÛPüÌï@QVG4€SãƒF 1f7¨ »†ÂjÇgà ]Ûu%´£«“Óç–Ø_íCã¬Æ1a¯¡§Âø‘ó³k-‰˜°Œn™ ús5ãº:m5ÖI¯Ó¶}ˆcëR8?€ª–M´Ývã`Ò8Ä“Á£•qO+YÍÂ2ÕxV\Ö>À\bˆšŠ53t’ 3±Jù_£œÏÚ´Ž †f¹;éÔqi€©>u~bŸ4ä~¯Å1ºN“dÎ+c°áv1N…ù«& ˆ“ÎÑ™ðX®è2Á—©fÁ/òeeŠu`âsò)Çd;p!Þ«KÈQ…)<ÿ”ä[u^ÐóОރL%¬f©°Uû’ôàϬ\2ýºÇ޽T~ë¿ørÛm©ðryüôi‚ÿl˜ eª]9Š‚qNpÜÎ|Þ ›»¨æ…ìb‰ÆóΖöŸÿn¹ûž& ¨C‘÷dý¨rÌÆÔ3añV®ðZæ°ÑÀ[mç­ô½ùÌû u¿3`ëYã΀KØÑ 9÷9uy_9{@5ªÂ ݃Dfø®ö6qP<7ƒÌógå=ÝðèovŽ8ñ¿Ù,3ŽV€¯ú»†]žà92òàMòÿLîi*eÛXS9p'Ç#PWÞ/OMGa ï}¬wò´ÍëÇ k<øÇÛífo@@ùÍc†Æ±A¼ð½§ãíĹªóP‡‡™Ê_0î†; ?ðPä+e„åuÞ· I@9ê…o•ýr>7rfäõe|î5¨O€ï#ãáà±¥uÚב‰à p~„³KNgÊUás?tª\…Ÿ_аO.0YµÚpÓi§ÎÓÝ?{Öç}#-r/¼ÜJC=Y·&äª%wK øWØÚˆÒQÖ†ýY'º´à<\¢ôvœÎÜ¿N5býRVKZGÿŒÃy™ i8{†ã|[]žbk'ÙCº¨t¢™½ÊlóTR~gÍ*ש®“Í@Øô}õDý=kÊãÜ#å3o´iû§6º¡pPÿuìD6T'r/³2B­®"|Ð'iSq }©·o ÊœôkÅ<÷Æe’] R7…騨xðªç°Ÿø×ÐÔ‡¬¾eŦqJ—럲L¾åß;¨ü¥¬«<­¡ÍŒzÈCŸ”r¼ÁŸ««” G¯Ô®`·ú­:ÊE²¸¯PÖ=Z]|Ï®ñÅ /ÎS¾Z;žÕ¨,¥¯PƒÃqÆ£§Œc1#ycY-×nió1ì4ËËW‡ÕÒæÊ¬4ÅÞ/\Õ´=ˆ'³èãÝ›.Í ¿8¦¹OºZÇá}›Q(g¨³èÿï¨ÕÎ$;å¬êóè|¶²÷ã±…Ü›Gú 1Iý ¤=C½>å-â#N‚D#Øß6(Á ÌäâÔ:ö«W‹êû®‡åòup/¡¿Hh ‚ò‹í«÷Ù‡x§ÌÔŽF÷Ç‘<ÆzÈŸ<ÂFÝH¿âcU/T·žœd­ç-_Gì4Á$¬­´fPI;´æâºú}K8Æ}[GËOÈýÎW?ŽØ—ªk¼VD‡»G% 3^ÇçúÕ£1„5à<‘˜?Ë\™kXØkf¾¢óŒ¾¬”]/“ŸÔ§Äõ6õØ”ˆgý èÖΘ¨˜`å"Ûi‚¾\_“;ºI¤ò؃­ V ´ï7çbOXÃ.G• Æ5GUDu?çäÞÒàÿ:¾[¨:âZ¸¶O 3×tÑj¦àM+p—vfg¥CíjT»ð?àÈcŒ¶\Á½V3Îæ@rˆÏ… í,"(Ââ-•ó§O•O|ê?-¿ð?K$P?€ cƒF PÞÎË('Šåæ!À-CÃaF·ô„$ê¾>¢v·ä€Ðgi‘&ÑòŒÓLW.Jð!Ѐc©K³V$N(( Àû%0çç9Fe$£…ɺ)• ¢<ño—ûïÿ`yàáé[Ï#]ð¤•÷›ÿlBàÝ HÚ•A·%£»£{¼^-”ïÅc#8=“ÙÍKB¾Ð‡ÓâÖýûË¡Ãß+žœÂˆ´o×zÙà@¦„Ç8F[ˇýcÒáNŽ>ðŒÚiJwhxš˜@€€®@öìØæç}n89¿‘qIo– ’ÁzÞé,›“F^³¤óŸ†+KôÓ0Ñ÷ØßNþ.Þ»9»×y~ÌYY{ví`Oªež¥ÃdBt -ó¼6zÊDjB+ŽKZõ²Ýd˜³wÅ™Äçf²;ÐOEöv Å­ Yü¦2j©¡ž?Tì¿¥to³üŽB¡Ïor[óYóîáòe…°×à5mû…ß©pLr<ÄW¾ôWåÐóOáàJdÊñœÊP‚*/‰–Ì…OÀ“ÖP®'7æü¦És›×»Á;Y³¨{‘O/ƒS8ò<'ù£

äj¹ã£÷Sf»§\æ~î¹å®ƒð^œÀ´·€qâ*åÜ)WxËM[Êþ½ý”*ì*ÇÎb¤a.ݵæÌrÿ#øHp$øp•³ÓUÊÏŸ8%¸(øAœëí(â—0º=ú"î 7+½üòÑÌÝàí·Üšy=ûÌÓ¡…8EÅAŒ_9…·Îa@¥X}Ïòwz{ɨgÎýf‡ô>ƒ2th»Æf ,#·úl*XUAÄ'­ÙÒxl/´_÷­dܰF¦XqÉ}WØKî4åZµñåÄàhPÎÁàÉ-à0bè·Étf\9‘ûÍd~޵Ò<´Î{ü!î<§ÑßûÏÁÛï ÚÀíã˜Æ`³Š¼Ü…kÀ+¤«ÊÝÏ«JG–F@ƒüA>£–Ë-é§v­`ð(ÂÑ>å]íü1°ÀP^bV½†Ÿ´# ò°FiIð¸5×d xʤeg—æ,ü”]ì94ßÑkô ßL›žuìÃO4ÄIWÍÀØ~BWÊGÀ½Ê6•~˜4ÆŸZ B:`1qž: ž¥Ô Õ™Oà‘caýu´Ç‘gÇ|÷;ÿì_üËò¥Û_|ápù·ÿ×ÿ‰ŒtÏ/—í»ve½ WOà ÎÝ:”³…ÕùóÊCŸþ•òÀCŸôA¯I#ò(»Ž]áíµñR?¼‰5LÆÀ#=ØÊ[mèMô¹yëûÐtï~åñ,ÊTìÞü)§@ß­ÅõLÑUÊ{b—G—°¤¯Täò)¬Ë_ÜòàyÉââ³Æú”^å–èpŽ 9éØ=Y£®x¬/²÷hˆm‚çõÁÏÃ#h£>yâåCìÏ—½{÷‡¿¶g} }ƒü8° pÞUdá×ç§á’zÕýòÏX6žç·7þøOôÎ×[ÿ·2°·ÚÞ5ø6ŒþuîätyùÈËÈ5xW™Ûìp$/¸Žìá5‘ÇU«ýÖ_•üœ£¨à+ÊÊÊ$ö“lýVµM&¡{&|§fP*ï€ó|—`}äÓø˜ÀN“n½ÿ#ðG6ñ¾ä‰ÓÑÆÏ7|q¬^ïJ*cPÂ&¬ÅåÖ—p˜)ë)Y*]è›InItm9VIR.óÜï¹Iô6ÎíVª¾Ï׆SQ݃G™¦YÒè—8Ðͬב·Ì±ZØËÜ«Wpi‰ àçÞmphtdäLe[÷hiIˆ©ƒ©«ž¥bÙ"U¿Ô™¤a羫-~žýy‘¿Nö\èCèRÉÎg”öf[*J®©‰qÂÎùºXÁÖÀÚPü­Î¡ê¿žîžßM€³ÇO-RpÍñkÁX¤1FÊsÑÛ›ôf$[yÀ±;_uqC:çkÆm`»ßT»C¥}x0Xm7€2¸½ÁáêÆ?›±Uø©/ÕçrLŒüƒuÒv¢ìâX„§ºÅ2ºÖ2eÌM ¶©öɘŅʰ›DÔ´N…oޝM2zWÕÕeX ÚVÿ‘–¬¼ciöfl6ãWÇc3u^V\“÷HcÚ‡/_âY&i E'™ß}ÐNm‚àÑ«M´²BÁ”¡¦gLF"À_n²ÖAI‰Úµ:Ñ­hºˆ ˜+ðkYËšªk­@{Ò«Ç¥©¶Ó¯v¤AÚÕVÂ;æ ]<çuà;ðV‚´…¨ï™õnµwî %ýÁº¡kCrŸ-¯Þ Œ\ÙÚ0[ßÀ‘ìV"’Ós#k±D x9èÃjmÂt‰@fƒ;©DïŒi¯®‘Uæ >ÚçìŸY¨—Æ÷Ì«ö/“FÖ JÎ9DïQËT\$pÇ€Mm=t‘uiíïfr––© 8ËôSú¡=0Ò¿µ|ô¡G(Ó‡£¾‡È¼éaAÞÞ«"¬ÆKõ>sãã•,|s“0#²$Œé[úÏëû”à6ÙÈ»tbÔ†vâÐÿ~ÔËÚýÇ]Ø’¹gÏ](íœ_ã6â¾âžäyÉ ^Kj ì%ö8¿Ò…¢â¢ûm)軇Ic«ìQ:ÙÚ„ÌÀ3šÏ½Óý¾ÎÚ¡/Ëê´â Ö9#>ŠÒµëMÌUºlð…oiÔñ»¯{¯í‘ ªÐ*Ô8®›öì.{årxŽ{ÆÉXj¡ puRt sÜ^ö»y½ ‰ëâ«{ʞݻ©`2¥ª–ŽFñ}ö9÷,Ò@ˆi㮆(×}^KSídx´|ðÁOplÐtÎ6ïŹ¨jtt¸œ9sžrÈŸ(ˆkôU<Åkù’v¹tþbyîÙçÀß.ΛރCëöœ;míïý›ÿ¦ìß Æ²p-Ñ^éŠa†.fðÖ{™ã,Jþ‚ôŽ“E#P6Mô. “Н •—ʘƠ8À‘“ùȼ!a(s†L\|Ó@”ÑrÜ*5Ûâg¿o8ÐsöSèSNQVeh :p£ä1&•'×Äa„¡\h4ðçC¬‘tïþÏW¹š‘#ŽSÎŽCÉÖQê%ŽÞ§l Ÿ'ꀪŽ3¿“ß*¯T•kÖ‚¨‚œçiGeUEÚÏÜ™vT¬¢IÕû5r?Ù„ò ?Ò/£”sT¤£ñóµãÈ×¼w®òu 0kË×ZA^ 6e$ºYö6,ug?ɦS[Žò*³üe‚óÈ@ÎÑÉËãýìx5˜%ãŽg«nž„AÄqèð÷ÇëÙã σS¶Ëç<œQVà]3&€b™²¤:_šÅ?v™Rˆ8’_^x9%Õʼn¯|ùKåþÝ_fЋΞ9Gþˆ†°Ž²'øÁ]³üéxÞ2Ì<—ÊÙK“TQh!¸eY®›³Ö ¿w,5DfBßP¯‹\¦)?‡cñæ܆<7Rž|ôÛåæ[Ä¡Üѹ, V&sÝÁ¿€×ø8Řç•‹„ÏÑ#/á߉¡«®‰´®±ÍñHݰƒ=De=¼ƒ=Ç J 9†Œc ÒheuC/„(Às÷V@èÁfýÚ*g€ã K·³û«Yü'\/Kk`™çœ`ûpNDã–í.Rtâª8ÎxÁ ÷:ñQ£¬ð´ßeæk6t|àÊÕ+Ì|_¿h<’}Ù>—ùÎr„â¼ü!Aâ¿79x’x)ÿ0xÁ  VÝX×c’R Ö°=qJTŠÛñKGð—ux^GhÅùH7Èàªðï%`Ȳ™1`@¯â—r†ŽjÇ-ŽQz°bËÄíFx£® mð!¸n`‡´îzÊ?²†ŒW§´¼Ðñ3 š­ Yu{A>p\‘=‰Õ,÷ç›&h(JƯ]Ìw’6gÈ€¢QÖ´fõˆ´$îLìp™€}7ßZþåoþÁQw2®¥rêÔ :R‘,àQeÂÊ3®\¹Þу®ä9ê:3Ó3åŽû(Ÿù• žo!Ë ZÁ$¼»—ð]ý¨WŒÿ´ì^¯ô𣶼ùüO#2õª'̑٣\dPš66ª6PvSYêÕr‚pªÁ'Ê^ìŠòø…tfмmf¿†v¥3YVe|€Ìi“L&x¬£Êý²5±Yß0pq'ò™‹-i3PW$°‹ 1/)@’j¼úÝæõƒ!ðz¬ÇpÙ´{írù|Ÿ~”v~سSY”Înxë ¿Ó¡fŸ2±:ÆÆ¹Ÿûý½q{‡ºxm¼wov¯÷ .qýúËŸ”MÅRe€×»jŸ?ü¾×kãÇõÛõ¯ pÇóºþ×:ª 0¥J’çcŸ;}¢|ûñGq¥âûfd’:ˆǩ—òÁ8Õ9zËK‡Wdyd¸ r§¾àOkÒŒ'N/ ?x~³ºî—ÊS8•<ŠIˆãïu˜©¬¯¼4²m[ùø/þR¹íö»"Ï5ðúÚ¬Xpå<¿OÖ+<+ëÌ? ”¼ž–ü^öWAs­¥ÌçÝôòc{G_à1§® ¿¶2À*²W+N_¤‚‘{Ã;©€ yvÂJX$Rp&²Sì!S±°i뀟ãëé¢ò•ιEʪ¯ñ}wÙèРͱpèU8ÒÚºXMJ²/\e-¥+Ö™=›>²»÷àhÖ½@ö{ÕäĤË}‡µgÏñ˜4i6ò*ë*N,QÞÚàÓÈÁÙc”}é{]ù6oÀ˜ªï'ë›èǥř²…ÊCʦêÈVݲomÕÚt²E`¥/åeé_9T]ZÜZ hÀ}Ѐãè•èЈ¿èغУÝÿ„g/z‚U¹äêî .·Õí f·íǪËò eg«–¹c:ÏÓñt6*ß‹çU¶']CÞ£ì©mEYݹ,/©,¢Ò¯â9 ÌmXÙ†U1›Ð!]CÁÄtR±z+íîùÚ“cwnêÞ§~ͣ؀Nhè:3õw7cò_pÈûÃA®ÚõKÚ§%ô­Šåe’«sЮ"l•Ôéè{+zªÕnÕ{Iø/µëjÉi“l¡\¿:¤²Äð¿Œ-ÖÊÖ;¶Qù<—îÍœæ‘ÌÕj˜3áf™þy¢=ÒLÝÍàíœMìú"ñ,– ª°ÍnÈ:Ê(9þ‹¾"óÓàí¯17e(å›aeÖÒ€'WÕéÄ-«ÈéOñ2‰bK™šÃg»N¢ ‰ŠMÐç*0ð‰)Žül®•@mÌÌ|´\f €6ÄåNÊ£k[µ$~=\;àaM­<Ð ¼,«®Ã¸…ãL цv93ÝóTìÚI8ž¹¸ÆÚJb£aýµaHëØ LRv¯Tß7À!õa–ÔóÅq]½Qác‚ ~QZ¼ÞB@ÿÐÁý—.±^,$㚣=ù´•)†±ËÌó6üTýðŒyÖ±×Hñ«GL$È|iTP¯‹îļÅWùIrnèèÀú~‰ð`ÆŒŸyɨ% ÿQÏn5:ßšù39Tp-K1 òµ Öt÷Œrþà`ßrF½¿Ý—ã‘àÐðxetŽìµ2Ð5&,¹ª \ÒªQ@Å‚·»Hn4Gœq¹Xd7)ДE×!T@Œl0‰]!i7–ÑãáD×,ÂÐŒÚñ¬å¾~ËèÉ‚aˆ>æ9'C£äôä|ÙÊfê¨,»¨A­!L ¿`ßß7¯Mü =ȬÇ9ãò»ßzœˆÂ©”ô<|虲ç[{ËG>öQèEæ†A‹û扎;vìXyúégÈZ)¿øŸ|Š Î2«žÓ±@´Ô•¼ú¾»ÇrÊVµ­ŒÆ RkJÑðÞ¥ž«©‘ê+_ù›òÙ_ù,޳)H_2Íç)%zêÔ©Ò­ÀžÆyGýŽ;á¡&z‘k¼?/gøþÝ{kÍ\ …OËMUØK¦C/N5ŠixXÃ’tsäÈ‘rŽ£TöîÛAA$t„ÜFÿ*¨)˜*¨é(ð»5œ*:± Ø’åmŒ9BLöJF:×ëÙ¿f…IGÉÌd?µì¢BÍmI+ï*‰} “L ãpÿ“äºA; V*"ò”|ìA”­ÿª|ùKTæ–<Hñ8ÃR„ƒ{·Ñ— ½[6šßØu¯}Ú|óÞ…€xœž§$2˜‘Ï^ÒFC®º~†¢šŠ 8S…°ëïx…¯¦l.8¹Š·ìà=÷‡ÿâÿZ×PÐ¥¢›=NÚv•7Ý•£°a8ÐÁtÿÿÿçò~õŸ”ßùÝÿ2ç7¯w¬³O~:ª?ÿ³?E–'j–¬à6– Iç <*_Q9TÌoœ/È|~‹ÌóÖДåAÒº%Õ@ÍÝOâ$”†’QOÒy®rã¯sVPXmI…¥q>ã3(Ó©*Þ:µt`cvÚ€¯J‰óTÁ·oH0´¬LlÕáåoUv×)fÖî+®°×¹©cͶŸTXì™ù…%Ð6?Æøå*éÂ&Ž>Ï~™¾…—ãTÁâ4”gú*—FÆ©}M1cŒðMµA‡c“¿È/·ë‘ ;ÚrbÉ*Çác‡0q²ŽÑŽýõ±B—ƹ:n•Ex*só¬²f”pïÖªóÏñG)f®±ë¶Jdµc——p¾7ä°uæ0»ìñ È3ÑY{¥pš¾Täì[pu2£Å5î- ¯xŸãä§à¯ãw]b dhf&û›}ÈÓmãÃ?xØç<ÿPZfÎYxk¤ ï£é*eùiÓ,!uq‡$óÇ“Þ ~Wo ß¡ô¬4$ͺGXmkmE=Cy{Õ£äËÊ!î3Û Â7HËcZq†hì5hÎl#šHuå¼GÛ·${Èi8†Ì‡Wyçµï2ùWèÃëûg߸寝çس¯w_~kŒ—®®oËßß½úý«Gu£ï_}Ïõï+ŒlÛ}@#— ¯ënV?Uδìï+£¹î¦7ðѽ!ú*kìûwêR÷Õ¹cp“sùAð¹~Ž‹7r%ØœjIòr÷ÏÀªþôš•%àüÁ1rÞ_Ûö½ïøã¾: ¥Žë.`ùšçt2Æñ»ÑBãÎ>;/Ûz5ì~ÐÜÏùš)ñ4pí3o6>Ö/Àçk?\÷ƱHS^yï+ïüÞ‹mšË½ÿ¼½Þ”o”5Üë•ßÔ)WÞ"x©û nÙZþѯýgq>ýÄãåêÅ“föÄÙ2²cWé§­N$f|V^¡ÓàÄ&ä|Ù†ÌiµËžÞAúóœèqžŸŽ§I™ xvpævÿàpô‡pxrã$C:‹”õæС$&GÝ]úùÏ”]ûfþöe ‘[€Ê8¬âçQÎÁﬤé~ý‰/ÔCsü¦Bò\Å}èc îÞEÿ0Fá©ì•ª[ÈT:Á—uFë&Äq-Ö lO8W[Û,7=›ìM%OåØ6|:{ç8žÓ=A^/»X#u›6ʯ/͘Žò KHë÷Üg+ )çñP`ºÎþ"¼õßèps Kì5êiÊ‘K´ç8­f™y“›ñÇÏDÙð6täžž^ƸP.b{[7Ì vQÌ0V†t_Ò†æqokØÄø—ùÒÎ*Ôð-)C†&åoú¥\_žm"ËyÇ|uèÖìe3—òbccù£#,Ñ¿´-00TeÌÈ”ìyê‡ÆùJv¯NM'j;+à¼ë" uJª+7{TÜ4£%$Žõ›,ŠZ>CðÁ™ýÚÉ4‰(ÞK«Ã8`Ûc降¡ê Êâ²k3NÔ M¬¬ Íxù®‡ç#ߣ)kOÏš}­.å9Ôžao…ˆj#`˜Á…MùÌSÞ|¹|׎Ñr…ÀlK‰ëDï¥b•z•û½í5· A`‹“¶Ô¿¸uúôé$óÉçÅ·TÜ-w&°»¯Ï*gîqàr÷2ß >Ç“Dt%Ǫ·1mØß׌“ZÇm ¦6²E¹EG»¸·¼ØÙ?nðL¸(ã4ÑV ì&! üèF73F×Ê «”êwlÚg:M‡vŒ¼$)wQ]ýX%Z¸ŸNüšÒ6Í4ò[uxù§Õ{ÚZúÐ!{áoÚ¬*Š?ÝFŸŠºª:I_Ì"·¼¾ÌµœñC¯èËÝ­¨ßŒè9ïÍÚ'S[ü_^uÆ÷czÇÆš$‹œ1YUÁŠ ½ØÖóÒæ½VXÓ«®'lAßàø*G+8vuÊ^|AÚ¨ô7kÇU¯´æqöÚ“DÒ¼¹cÇöòô¡ÁïñØ~Ôó„‹(VNÔÆb•¼ÖË£!Äa‹b&–`£o«¬#];Uð[ÆÁ?Ê:®‹\Êͬ-‹‹~ŽŽÜOrÕ!mvUkâïêø­“”jV©š,beD2¦]DØ,àÐÚ¶s/ )ÿLªC•Ä!ñøúv\ÙŒ™¼‚šÀ÷¼Ë—^:RöîÚÍâ÷…p‰BQé‰u²é9Ê`À1&dt›(‘!F¡yÉ$žJ»Ž6åïÎ’BDê™éö)0-µç<×W1œ… kãÓžLRaÇ5…¬ÅÕsåô©“åö»oð¦÷×Lx7C‰Ü¾˜m3=¿àÐÈ/r/›×&~̨ ,Î9H0Ì¡+—`¬@Ûýý+næði &üÂáÊW¿ú÷É®ÛIÚ—Y_>;F„ÕdhÊÍVÅXшº±ê”“ÚÙ4V _Ëáæ¬M²WÈQ0˜Â‘þù?øÃr`ÿ¾rçÝ÷†Æì¯Êág¾ £œûRÚùC?óPÙÂ3 ÞÕp- m^›xç! /ÐÍF:ƒQÝr.–êecW€bƒxkƒ€648‰É:€ ¬nÕqg4¢í+d+€¸t@_£Dyº?Y†Õߣˆð³ûŒ‚¸—c¦ùî_ ÊŒãÐÁ)Ѿ“- ½º_}ñ¥2ö‘K¥{Yž¿Æ¨Úç[Ú¯2?öÑ||½¼Ì*£,sN•Ÿûô§0rO—?þÜ¿-C[÷#ÏÉ5ËÄÇÂpÂ(€9W0;Ô,Ë uóŸ÷<”TžÂPOœ8™iÛÖm¡ ¿Wiú2(8fwå ´i&ƒŠ•·}€ù-eìâYôz.´‘W®ši…B!þ¡p4{â±ãœG¹h¦$ÕY/>T:Tz8ŠŒrà/|êÓ”-ÇŽ-ü…/d¤2M¡¤#36ƒÏÎqzž=WãÒã>ê<«q…Ž%A#~ù^zN(ãPöÔ`€@ÌsÕÙ£ØÔ6TNÒ¦sUI ïȨ0‡&£úÌ?ƒ}(S«Ð/#; ãöø]ƒàUµ\¶Ãgþbèà=¿ãVo¯‘I³¾u¬*hD’×x¹ŸGæ~!“¯É£lÛ¨dÞD9ZåYvhkˆÓ±YÝsÖ, ¦¢®³’ÆøÝ,Vø"W ¢´E¯é×À Ç« ¤óQ“§ÓÐaªÂæ|5ä>Ž‘î]ç%lÔ e;Jú…qÕ/ˆ`ßü¦Bæ¡‘gÃ^‚éÇi9cÛL;Y#ä¤9ˆ®‹Ê°ã±×Q…ð>öã|ì_|QŒÁÛä>‹™œ}å½Ã¦Mæàš8ÞÖ¶®rüØ "߇؃ºË©cÇ©üCÐ2¸´ˆž¥â¼€¢›@µGñC‘™Ó„/££kz Úïí3ÄìgÆ…ó?qi £.Ù‹²?;3Mäøð §6†µöΤµŠþ$'“XêHæX±)И™ ÜËbJ+B´°Nfd³"•I¥=#ìmØ’æüŸvTЗlk…õsñ¼ƒyº÷‹³Y_q„q¥ÄxÖÙ5æ^xF=Ï 4èÂòx¤.R‘ “ßgpøÎ`œÐؤ!ÂséXÀXS­-c?*¬…EŽi`ŒžÛºœ—.©dW/öÜóîð—JŸVº–Õ&¿"$#tš ׄoÄÍ@ü$S"Ñ9ŠŸq ÷&Œ°N‹¾\ƒeæ°Æ…Qè^¼e~]ÌE<<'c‡ÇUZƒÆ¥ aÃCMÌÛ+Îtú4¨×ÖÍìÐð¡,åºu _iœC~3“Cþâ¸*_·2Ag²?ú¨†ðëÿ«òÐÏþ,¥¡÷åhŽ/|þså?|îÊÝ÷~°üÇïò¿çNþẾ?y«pó{yí e†åµû"óñ»=y¯méÔðúþ¾2(žõWGÉçè½ù·Ê➬Wæâ½uŒ¾Úí6<ã8loc¦y¯ü×ÝZ“ÃÄk4è-v\îÜø?÷6i¸ƽLÇ”—mG&¦ ÏBb£§É9e¿¡²!wè|Cš¬ökÛH[üÃeÖ­²°°—Vý/â7ïs;L¼†¶ùY’v¿ßø¥q{^í»Â@|¨}ù0‚ڞdSz´-:ªÿù–ñø{Æ­¼e€ ²ß;åUû——(×*«4ñ^§v'Üïï:÷*kMŽtÉ™×»Ù{o»ýNlý3eÛŽˆ`+d¥_ÉXÆi~õò…Òׅê.fóZ~×c¡–‚Sn®[wÞTF¶ï·MKµŒuŽN!-:%Ü]”–VMH`™Y?u¬…úc{«p¶¬ò2*¡k¼ëCȤ'v…£æö¼é]t\O³Ô…éŽXCV¢±šf"K'¼©=¿ÂLÛ+ùÔdeë{è0 ‚ ¡„èŽö õVÖ§Q‚Ú5øI^®¦¹nxïÜ䕬E Nè%ûº†Ç/b¯-:ÔБç§ó‘É;zIBœ&ž ´s~±òá³Â©«×sÁqêEæ6 ÊRÑU&†¦YCÏ_] ˆ¹]=×ê&®‘gp{®zg»·{Æò7q+4,¦3¶•£šÄ3_¬KÖ0™îÞw…#œ œ\Á–¶®ø{‘t±Š$þd±òû9œ„îMÓTôRÎÔÙ®Œ)Œ¢Oïj L/ìΚBÝŒ§¤$?”6ôgÉc”˜Ûø®Ú=¨…nâÉzå¾Õ5l€ÀHØaö*úÄè ¾.K0É‚ÎB¾PŸ˜™!“—q9v3°ÇÉÊ¡y¬üaž2üêvÚ7|Æ9üZøXX¡L_š™Ýö£žä«ºŽŽ÷Ëccµg]'eO›Ì±÷,{‹3«‰¦#8ë½8GðDMC”‚fô£•uxõÏlGWÞïœ;úLþéÄFÓËÚ¨ÃJ#u>–,,͘Ç!é[xÔS- Zë¦ò2y½Ïz´dNf˜xLB zN'Áê:ˆóÊÌ`—íb<:žÕ1\ÛœÙ-R¸4l±²|×tJ™ÍTšZ¦Œºø§SÝ>WuXÓ®¾ÊNñu–qª÷Ìb“P×òèh÷mâ½zßÇßîØ:H[àðÖïØÓd€³‰UÌxæjôÓ&ü˜ÚHzÚI„æ3ap+Ü? .ˆ­-ØÖp‚Ë;Y'Âá tÈèà€åî4ˆ;ú,©¬à±sî9Mëì‘ÊÔªyfƒWØ9ÚÈl÷´Ý®1?Ú)ÓßÚ{ܘD0=ºë8Œ\èyâWÆ.—‚ªþ–à~×x‘ïºxnp@ÇvÝc¬`ð„ú“µôxn2é oâ½61«É53/ŒaLŠ}^‘$xÃÒôèœÄcÖÔLxƒ–ØŒt¢Û¦4'M¹ŸFgÅ–ÕL߱τV£ Záòn ¯ÀY, îü·Ÿ,œ3vç½§a äIe”o÷åð`°¬ú¹s§ÊÉcÇËýwÞ RaàÀ¸Õ ¢WA†I¸ñ‚Œ‹,¾LZ «(Ø–Š)bÎ*€)DñgÌ"®Hºà BÓŽ 7ýyDc€‚J«Ž £„fi·)ŠgîÉtd”[¶Ž”ÃLþøÃlâ” a!ÄÇS}Ò®¥1Cã‰ÌÇ÷#sÎú¿ÝÀÜlo¯ Á00 ¦;÷Þ\†·îd³˜/»nº¹ìÙ <è+‡‘SW ®‘yÞ~û‚Çcd­_²ü'´t•RÊî\Ò¤Ñ_26š0 þdRÐÊ¢Ÿ8q¢?~œ÷û‚“:âPd*¯F)·nˆðþÃf)‰j^2bu ƒJ†™ŸýÅO—/ýÑçP,SŽ\úÑInö¸Ù…ëÊ‹(49ÉçèkšÌÜnN=öxÙë­å^²• (SÎ{äã/·Þv°<öè×ÊIæ ÝzU£µr}5¼XÆ8r- kdUhØ{›0X3Œ¦Ö<Šgå/ E8¹o£³mÐm-C¨ìiä°Žµ¦nÐø2{´¼@¸%R™¶|¯etx P«Ê©{|Œ Èʱ¤9M£ÞÍø(j„׉¬Â] LðF×D]cU “m¤š?Ð$ßÓ7†×кʨsâÖŸÏüªQ´gïVÎ?Ó±8B…,•Bù†aµ]T[𙬓 *ïu:ÊÏjÂZvm…¥—}¯£¸ ×&JF,Ê¢QÔë™7k/ïDGpâÿâÕç“û‚r¹4æpÅ!è乫 ýʹ82Ëĉwh|öòIx¶¸Å«úˆ¿V<©ÏE‡q …#¿&pE[8ÆK?ÒãСëó+|—b¨Ññ:îx~Ãy׊b/~mׇqT\)c“8&¦“!½ g‰=gžr…*ñ:#À0Þc@¢Íî^³M˜;ʨgÔÆ`€þf™=LHu ­ûÍùó'ÊxûÊ< 9É_!ú‚O|Ìwö­þÌ­Ysƒ!4¶¹^a@¾àr {t°üÖà;C–ÉàÀåÁ·¥:“íhÐİ2ã¾¥ýÖþiË—‰¶^ öéÄ3Úk8ýTäõyôl:Œ¼°<xÓˆC ,œ‡Fòॼ™¶}§!Bx‰ŸÂÓ>!—Ü¢=Á â¸8dFæÌX5’i¸3»Þg-½X+ìÔ2ÎÒ¬÷ŠÇÞ êæ‚»:åkÅ &œWø÷v#SuÑæÕÉ™2IÖM:½*ä]òDçUç‚a‰ï•…úì}葟+÷Þ{_J¶÷ÁÃÿáño”?ýüÿOÿ™¬Õàp¹|y,|tϾ›ÀEJÂOÀÃÜvìØZvíÚ#ßEJ þêoþN¹ÿæŸ6×'0s%²,M…._¼ —K¸³æâïæõþ‡€¸$Ë ¤¿‚}Ü+’5ºÊ’ÝJ¨?›­äsÜæۻ¤¶°†}O¾£ímÝcfz©lݱ»<üó¿L:Çyà1³j§öôÄxæ×ît’Þð. Ã4M‘Å)¿›†ïc¼•Þ¬œÕ‹|ëŽ}ÐÜmðõ+tOdÜV€èÃÁ~Ó-wAóËìG㙋™£fò]¦”üɵr‰Ù‚] üW?ZÇA k³á©Ðø"¥|ÍÈkníNF;«J·•FèO¾+€ÉÅA!ïñ;÷Où¦,SɃ‘…̼¯{ßeÌU~p‰¼"ïƒÆøä{æ«_F¨¬çù«u•#{‹c°/÷)ù×5ªuhtê\"²†ÂÔ½+>ãwTuœq¾†™¸rðÆüâ?ò9å åû2$ÁÕw¨Î'…™\|vï7"[ð¾aRÇA‘2ÿzŸë¾­Óç%ÉLóöîŒÊçŽÆJó80ÛÑSû8¿Ô1øŸãd÷OŸkàG«0€Ÿ:&÷ª §:?²Â±ƒñ(Ëe<ÀÎ{rJ?{KdR¾cÂyïXÒ—ëÅs¶câ†ÙcÊóÊwÂÙï©\Ìš ÚÝXKç”@@:±×x¢ŽË>bm‹Gà€s\æèðü¦Liå+±ä¢Õ5öa>r—]»@aöжøUqÎ@³È*΋ßA• ú±"•ðRŒ—BÃÜ×,X qLX¯â|ÈÚâLÐIàÜÜ’xÉo¹§þ%³—϶%îƒf:ÈÊ1@e[ì Èͱ-ÀKÙj‰ÁêLõeÏ §÷dT^8~¸œxá‰Ü³ãÀe÷þ»Ë:v3´…@N’q*=óÍ¿+WN|‡ã¶—í7߃z$‰4®3 Lz'û¹2?vZ.eêÌéÒܳ=Ç=vcY¾öoâÏN×™+g¸ÿ,óÆÇœ1v G0wX]T8’ŽÛšg&Æcæ˜00¢#`” –‘}û â(É gN”#Ï|½ô¡ƒì¿çÁ²}×­àAˆÎu>/ž>YŽï1œ›—Jïèî²÷ΔÁ­{á³à€8Eê–g^>T^~ö›8  ¾Þ½¿Ü|ç‡Kû–Ýq$Úœë…_ Ø"¯ëú /?‰KÄ•0ôîÖžà†‡ÁPíœ}î™Ü:¿uf·Ào-™¿49¸tâ(7ÀA5ùÄ€Y‡í”X^ XÂ2Ð9³‡´™Â=CÁy‘Y={‘{t̃$àNTx:Ý•+-ôM NuyƒÕ´LˆÔù»B€©tÁ×ù^:pð±%~¿z9ûâ,Õ‘ZáeýT‡²ú²{Î$ûÓ*v§õõA|P´Mo/.Z»ò"|¥èÆØ»éDî\KB“©‹ªƒä±U‚ÃÔŸ*ß2”û€#1(×YÅVŸsÿÒy è—?Ä”­»)Ïêw¨†Þ«ÍÞ=º•LbC»t±Œ FÎô|ìøÕí,Oo¯Wd~ÖB×.à™ëfÔF^ Ÿœ;^…Ýœ:»±L A’ÓÀI¾m>ÛÕL@‹ª¬ÉÐ Ö'Á °÷:?õ.m!V(ñ¸ >ëæžî&(-7Cò^ËòG¾óNèËjnUSZ¡-èP¹A?ñ*ÕÌH6èÀ …⢟½x`´qfcg9sö¥rï=”Ý7í籺AÒ].âí¸"ˆ1hžˆƒ©Ù©ÿŸ½÷ü±ô:;Oå[9‡®Ð]ÝÕ‰d7£(Ž$rF”4’0Ùž‘eØ0öÃbûOì‡Å~^,üeðƒ]»¶žI°r¦ºÙl†ÎU]9Þ[U·ªî­œö÷{^– •GÉ&G”ÜWjVÕ½ï}ßsžóä¯gNõ|Sk#ûg–fÙûTqOg›¬þM0ãƯ!ÕÕ"+L‹30§ƒÍL™mÛ°~V®#àˆ£*°a€ l„'LØày(a`•­F짯ÒK;23úŠÌiê ‚·—ÀUš èHÔèÉö‹Þ·ýUoOïÈç´> 8~gñèÿe@@eT!¯2¼Žqk•Ù³TrÍÍN§³ç.¤M‚ËÐÍR~:c¬‚Ê!mnlW+³1 ËŸÒ­$âr%ʤt‚ cÂÊ·Œ.Z$¢8†ÒÁûügåS;s?žáßÌìí>;ÓŸþùWbüfËÎÓV¦+}ñOÿ!tÛÂL [ª;>Öó_Æa?Úåe‚‚"”'¤¦×ßzß0Jáéq½_‰õÿ¿Tô=áý*j;›kéÆ#¨X46ôes¢[ÒŠŠ·Š|e¡ ªüX¸…âÑÝÕÆHƒÁ!³óój±È*d÷ tnO3Î*+»mã‚qˆª òRE–5 âx[N•q¢ë 6óz¯†¤2d–ʯýNcíÊiŸeE\Èyá¢%ó·Ñ/ß‹¶ªð•Eƒú½ŒHÙÇ`vþ¯_:@¨AaÍE›-|LuFoŒŸùµ×øèÂ$NÐB¼·ó‚8³¹¾øsîÜmpû1f= ø¹­OèèdC¿©:%½jˆì‚ç&‰~üùRafšyÐ_Çk ]ÍàíI ŒUðPƒ Bc}ÒQæœV¿ìéëJ×^ýišM]¹×5MðÿÖí[裴ö‚uà™T&{ˆ€ ¬D}Pºˆ4Œ %ûò3 Tת,õ¹a@B+: ÃpPÎryÅûA@ag­‰4ÂS=3ªT¡!ïªì7ø£‘ïç¶N•[يˬkáÆϪ#ËÙ¿5˜³5úwhG….U$¨ ÏIY*ž4¾è¾ø8ÖæþN>wŸ¾²µ[^QMà/|ϱ/ííéìÈ9fοþPâ™ðD¤|îš¼;7ˆ 6[:ËojÐ÷7a=ìcô~Œ2çdñ•x†º¿e[‰¬Ïôù—ïeŽ7“u`mÜO°F%tìCšï»o*ìt k;¬7€áw ?åÒøkæúlÏ>  ëóoŸ•GîŸWÈx©ëàë;ÞÇêYy¬Y#Ñ—ç®SX§«Æe+ÉÑÙ÷é–ÅÞ­ØÙ ¹ìç¢íî9¹À Ø€dž“ÅÞvŒ£“³QŸ£å+û©Ç©‘Ãq&>–Ñ#mïèÞ7I®ÜÂhø¬Ã¤ ªxgM8·Š¥: ­§e‡‚ãd¨ª¤ò·»MqÄÓ³à†ú£ç ¬n„ãSÇ‚8Ž/ö,Ý6G{y³È3˜‰cžY$Œq{dü ËXÿ ¹÷ð,V{MÄ1ùÄ«t˜è”É®—"ý5ÈËó^,nÅ8®÷Å·£ [÷ÍýxOy¬Œ7è|„á︃éâu¬•µEÇ4 {¾£]zâ '÷VÆŸãÚ"Õ2O\}2ƇMNŽ3û"Æ‘Ùyp1kÿóKŽ ¨›sïl¿¬AG[ÒŽ §^¸{@flUn¨üô:t{¿%ól«GÂ_ 3âäÕV¹ ÅuU©ž(_ ÝEý6(á¨IçþÚ «ÿž|QG­RÉV±[[8À QYZE bÛ3×£¯*¾å²`ǧl¬ þ2RX40¨¥.¥@l›ûxVVžÉìŠ|µZý*£Ó/£Ÿ±DÅû9OØ µÉC&†j¯îÀÿ¶HþÚdüØ]ýjÐlk­L6©@¾d€Ö HÊÞMàÒÛ7ŽHÎÚ±Þ?”¯F–u/S»J¥p£-r;zƒßÖ S¨WYÝiW+‡íçº[àýøpåwV*²pàTAץݴB·'4C®é ”ÏЙî뀵›µºœGfl}àv-ßÅ ®´B¶*–¹f“ÀN‚›v)P/«á?­|Vn8Ü@r=Ï—Îöפ\©'[$ì"uvÖäsŒi/°atvº³qM%IÈúѬðÖ\Ǭº´ËKŽ ü>F;Z©û»æ*ö»OR3Ü/tWíí8Ûì»Îôbe¾^®9™ò7ck'¥´«ÂFúf,†QŽ6d¬‡Énl©[jÏÜ ]>1 Î @cú![B~E…7r\™k Y={ƒøÚ4c]͵‹LÜc¢ÃgîyÏrˤlígå¤û [ ²‘'8BÖÑ›&ñ5S tRIÞ@üPÜ0‰¯²œãûv̳*ž]‰Ô‚ÁMø'¤û²¸Ã[q¾Öö7!Å8_åkÑQø€³3ÑÛü®BîÕŒ>Òsé „{y¯¶¾‰f-èÚT‘L²i ú¾>ØÎv;&Ðu” ­#ÿôûh·F§_ÎÓM(_L`7ðlŒg{‹ÄkˆõÅHûèc-èž·øT‹?UM¾ò”0©·r?u&²}*µ¥[mÐÆ&üºÙ`Û} ›µ!#ñœWÿð,Ô7]Oø£Áç:èM|0ùÞâ,ZÓAc$Ù0Êg‹DL÷ézµû¥=õ†C’Ž+ñMè£rL@‰-Ú‘ôò§Ò·¾ùÒÜ4ú+ •>ë¦.§Ÿlƒb—êêmº+×{øŒõ-°F“v ±ÃC‹ÄIz£ Ãä§J 8»S˜¸é^ô•¹v‰k›C·P¡©±$»LçÚQ®Vp~ø%°!e,€Ÿ:ÜK8VVÈ€8¤ÕÆO>ª!oÖ°ÜØýâé“!{a|âašžM}(B"H#‚DfP„Èâ’V8Ï7.¿ˆ´ “ÞÚ"»…ëýg–†ŒQå6>Àrö‰Œi³Œ²J–«ÏøÁØb4DÖ2[˜–•xÎbR.¢èÖðÑ: ÇìK²Ó úuõŸ3G–Ì|Ÿ¬ÛÊ”QVGÜ…ùÒ–†wŸ|áE ÚyÄÁ|ÐP|t¿Gøõ!€~c-Sq:ŽA™ÏôÄh:uú,4^ñü ˜ YQ:CçÒ8áGð•+/tÉ |ɤQéàÄ™¥²à+2ùÝ+ý]¾ãLÂ2†÷ðð„aEOW®^Mÿü¿ùÈæœeRA}óÍ7҅ǯPÓö¡ð Xà£ÿ<‚À/@&#Áo”U]ôáxÿZñ_‡²-’ü=²ÓT’®ˆ™ÿè×sFžJ¢øëÖ]d/sIQÊ•aY«a„¹JRÌœÑØCPÁ’~ÌÊÔ™µÍs#[”‡yo×gò˜÷)ãHV~ªi`¨$e3X32RǯJ§ ° ³óbß»ùnºòøã¡4«Ôä–“W;X°0€ð38üŠû¸v_:;4°Ìlà#”(•/×o[½M2½¿ò[n’ÏØËo²¼G×~„! Šˆ =Ò•¸Ð@BE!ÁmÐ5pÊk~/5¾E¶_|ÿWmWuÌC=®Â!ü™Ï%|=ݼy3Úñv’E]*u8ÁàG&ª;JwPrÒ”òÛ÷î¤oóéÔà`ê£õ |ßΦê!!äíwÞ‰ÊöŽÎzÞ£­ ÷±K‘8î¾5r"( Íhxù¾ÎTq-ú¦?‚ÿÒ€;¿çç³ÒwTP{-ÓQ¤qdÐS#Å l¿Á?,¼ÔI…µ÷ ÁÞ‚y#ûßû°æyÑò9_e‹2hUÞ[§á$ì `ê>êÔÂá˜çûr=]üç>qVñäw\ëg=²Ž¬}%|t5Yµu—Iºë ç…û6¦óWÝÃo{2΀cöÏFâùVO¸>9¤³¸|.»ÄèîÀš‡|÷-|ø\£K&–÷öyŽN†#»¥‹™–¬µ•îDÊ0çƒ+C‘Ž(Ï6â   þ§íªlˆ ~Ö¢Ì4y̵‘UÏùï¼ü¸ªL :à3âd–¬¦!®~ËÙyœ¡¸àó6•k­d™÷&cdtf²ÄØ{ú _'ÛËžðAßýÑý>*ðœåWa„ èì†ÞnåëÊüxZ¼ý]‚=“©°OڅϦË/|–,;ZAÐWqÀøõ¯§ãÂ;Ð72a|$]ýô?K­³Ù¾Ê«2ºÉª-7¦^'ð½Œÿ45ô<‘Z{†2y@íð!‘Ã=Z§µÔLå–Áçý-øWÁÉÍUœµTɇG›ó£M~”÷@q•Ìà$¸á˜6‹X6Š´wÞ/§Áv[õ{t,ÉS4CucûHì_m¾ò‰Ô;8”ʳ¯¦Ž&ô^ŠcVn¥™{0ë>ti0x—ö´VŠ®Ü'˜½›|h¥Ážz h Þ™¨¶H€³¾Yqú|Tum—ÒôÝki«¸˜rÃiðÒ³©’„~Íò¾Cçë+s<ï £K©k†Ÿx!ZÎÁ×”7H ž3iüWÓúâC*-;ÒÈ3ŸÁ¯ržÏ87þÉLN˜¿#ç z¢ãyòåT3r5Ñx%‚l[?s÷õ”Ÿ¼Ì%@a@IDAT*Ò©óϦ3,UQì°‹ìß50C ~öþ[$%Üd…–'ÒÙ§_¢˜–£º#úIKÓc洛±79ô§Î‘4òÜË©Š$‡]x  Éê|ïÝH+£¯bQí¦å±þtúê§S3rîÄåR‘`îěߠ­ò"šÖtæ©/¤îÓ—ÓŠ¾Re$ç½½–O“ïü(í¯>¤ÕlMêyì¥4pö©èêlë»ïÝx%-ÝYPÛ{¼yÇkÊçMÒ˜¸s=­O¾™êŽI6è}<õ§ ª“7á«ò¹£Ã47önZû1µrš­ïICWþ0õœ¾„ÿ ¤A¹³ò¿0{?ͽóTw°œ6¨XÛ¼ø‡©ÿâs>GAñƒ5-ÌŒ¦±7¿“jwçS‰sÈO=–.|ìáãµ Ì ÿ ·Œ^ÿNÚ]züßNKMCiøÙ/¦žÁ‹ì½8mÓ²úá-Ö}ÿ{aHð¾ŸK¥'¿.=ù)Ž„Ä1³›ãLÞý&t¥TêÛÅÅórts0ð/æ&ÓèëMìñï‹#éüÇÿQêºrΤ2»@Ü}í[iwñ5dPcšœ#ñá±Ï¤Ó—ž‹ÊKeû…ñÛœï½oÔÚK‹3$L¬½”žxþsmñIƒ“&¦î§±Wÿ}ª9 p¦º>­,> Aå‹©»ïŒŠAÈÜe‚ç÷_ù«Ô°ùfjF?œ[º8ÔÍÈ×Òì­TšK+ù®”_FF‚çù‡ï¦ªòÁª(7 X¢ì¾ò­´¼8 8Úe ζ–¤–ÚCŠtðí— Ð5õ¤z|íeàyÌÌiƒ?s ê,à?3m‰ÇsÌ{¦õö¦#„(¾VçŽÐ1 (nÓIó€$à½ÒRZ&ySùm7ƒ%h©µù(­Q 7W(§Æî!¾[NScwC'°Øno?Ow„õÔQ¿Ÿ&¦ àú\ªG¶ï@¬ $ŽÁ”wÑ·o“¸bÁ‘±jÒÎÚm‚òðžf®óÜñclä碂þx½œ)ï¥I’}°7½_3‰J#‘$yj0U7uú-t”ßÞK=R=XýËYàU¡õIÙÆ}þS^ão‚©à¾ïÚn}ÇÂC¾O8Fßua¡;FU*¸¦ IV³¨:7!ÉgÕ7·§ÒÊ"ÏÔΠj;CüTVˆƒáG® S¿×“”kp+*ÎÁ#;bYźCp¾š@¾6ž¶½ 8Ò¯Ú¤ ð5¼oo ;­¸JðœÀA>ÛJ‡~ íÕ±žÁÓÃà_1¾>4žqp@E5ALýÒê ¾¯|Êq¿jôd× hVÆØ‚Û`¨AÀ˜™Îµ+ 6î²§#~jo¹?Ç%ÃãùÑ¥ ž` ιä&+ÔÄ®B‰4 ês£;²b¯|Ç2Vøïð»¶¡ö…Aé}äk~Cß¡0´2œÄøµçeñÀMà"/É´íÌ>ã쪠._Á:%;ÜâSß0™):8Oí)Ç– ÖÑn<‚ÿ¯¬•ë6vn­$ššÔÝÖÆ,o’â1ÑùIÀà¾kìiš/,-Q4GâÀ5ñ}iiÜ©Iƒƒ©rØìÓ½€5–ÅpìÁRÆ`²u÷£]åù›„ÔB¢Ðqš›²]}^d‹vƒøvôj'ÁK›Q¸[(!~›HpÄ÷Ý—ÁÖΪ®€»:¾4¹ƒ|-SØHµ¿É‚ÀÙNaÂzFÁä¶¶ 7Ðk²]tÖäÞú-¨Ê ;Ë33Y`¿ÌÜqÎQ_°4§M a ºòñï/m±jhÌ„?ã4&Ì”wïvK«³%:ÿjÝßíû†7mn '|?à¶Qœ=ô‰Èüm§€ü;{èRv… ›”ϵçì -ÛuB¿—ö¼zv±¯,ÙÉdF÷ZMb#´gkYG:‡I]d%€“ìQÁF´ó ƒÀ7žã>”«0‘ðI4p¯‹—Ð7H$¿wçvøõ?ùŒŠà Î'ÎËXÔú:›R sƧìJ‚Ì}Hdcl™¸Ñ‚Ïây‚øZ  {"ÖcçAyÁ.{Ðn[5£y‡±±*«E³1rt?•ÕuÃÎå5··3ïëÊ)›âa'ÙÀêyqv± •%ç“­w†dŽüpöƒÀÛ+ÅÈrèd…"Ì ¡È&šÅ# 4öCÉ £[gÙ0–MÍûKfìeÈS€™Š”*i:J ꨨ¾¼F ±<35“ΟõlvAÖŽ®²ÿ'ÒùË—`Æ0ÜC‡Q5DõöÛ¯¥ÿÿ’¹ ©%ך^þÔˬ_ïνAà·q<œÉd÷maXª$W iˆö¸mdó¬`$Èø6ÈS`-,,cužF¸B*‘©J§*::¿|á%÷)I_q}üW¡ mqý1LRÇ NÔqèèì¹³Á o½÷nzñåϦ—¿ø%– c£Qõ¡ÓN>¤Ž2×OýýûOðÆß¿mýÎíHg¬ô’t¬”BU‡ÛBðDVøSy¡£Æ |P%ƒ,GÜHlÊåC(xnÖ¦‡0¿¯Cg*-­¡Ôã¿Qú1ÚuÈXÙ¶Kƛ٢*ÒdÙrºŸØÅ! Z²èTÞ•ã+dÿ«èèðû¥­0×q¬2ѬA8”ÖšI$”p”ëIÚ$Ž¢Úb¥^¹/=‡!õkž ûñYY% T ãåOàõ·½äò ºÁ³çS?•1?ùÉ×Q˜ÉòÆðÎf ;÷*s¨(´…Ø WHªù%÷üÛžóè½6NhN~¯*~:klƒ†YÎfËù ÿü"&Y­)|“æNhô—í8¾J†^¾òªzÒýñÈp¦Bf«'pÒ…œb :Ä;è "†~ Èj/¨JéÌé³éÆõkº®¦?ùÒ?Ô†®ÛÒ³Ï=Gg¥;é_ÿ忉Š'é#Zrò½,`—e½fhsè_CPç€F“ïû²…FE%ÙÑáØt>˜Eļ'~‰qB¬ÑêßöbÞȬ´(a ù’_”æx\Ü'Z”ó{[ŒP 3œ•Ùî1 ä³..ÏHÎÀ¨ÙâŸ4iH¸gAv®Á1©ž ¾APx€s® Í…çy~ÕÚüôPÕÍå¹[´…ó¿/^86ib|,`è,4+Ì?¢f È’‹ÜßþÜŽïiiô11æ„Û;“gù>ógçÁwOpG=ÅçËÿt"˯Ml0˜ì¹97Üû…aŸ¬Äø”QÁxý>Ac×&Tš÷· ˜r“ørÅõpïHàeA¡•+ûHÒ9!M<|óÀ³,mh@Ã\ ˜ó]e‡vP {¶zm“*í—Á®¡tùÉÇÓ'_z‰ŽbCŒíéŽì}÷Í2c1[ÍïPU± ½93R˜ëàrv[Ž jí.c€Ìö¹:vt>ûžŽ$qI< èsf•T=42âJØüºŽŠ<ÏS'LIÇ)ÎçùÍbcåçæIäœJcã8n/=ŽN:Œ#` {t™{g8Á£¡ö ü}¹_ÏHØ*WÅ{A{d;¿‹Ë&“(܇ǹH·âl†/žCÌa”–|ŸÏ½§ÉÂÇÛ»G+Îŧ¸æý÷„½<êg8Ãç¡«ÆýÑ Ð H%cœÀWb&§êTʾçwYlÀާÅÃNxKT䱈Î'_““é6-z»; &NAÛø"z3¼hnò~:Z{ˆÃo"¨•{éÞ›?ˆQÕ86ÅŸ5*Ô'n¿–v7–pÐS!\_•VŽ-,.ÈNlýnÎÑ,î•R¡x˜zÑéæÌÏM¦:Ê9o8 ôCƒ»kt)#[HˆÂ‘Y×RK—É%òJé$È4V´ xX­UÖc—Phõq£2šïܺþ½T"à\…Ì0¹–z:©È&€7þî÷¨ˆ¤©ß{¤~kñU£ØU°µ“ùÞ«ßú¿ñËÑÁŽ`\(Õ¬¦êº¢¯§†®³iþÎ+)-ü(5ÑIoùÖ5‚ÔòŸƒŸZ4@ð¸˜Ow^ÿFªÞƒÖ \>¼Ÿ¦Ùãù}>Uà<ÞEîlÓø[?HUë·ÓP›<ãvšy‡'dgŽê2+'wéœ9}÷zªZy#]èHóssw ¤âä¯'€æùàäž¿-G¿ÕkŽ™}w•À‰oTâ+;TÚ8ËÊõ;éÊ…&:ô¤¹Å›iáagêy2m"çäõk ãicì©»G7ð^Zx=MÝnJg®¾2iÿ &­ågÓ­ŸPq·Õ•›i~áùÏâ7‰pP:w÷§©všy´TÈî¼ïûôiµ›£ŠŸs´âtöö«©u÷N:{®?-.Òü;ßA„0š¬«?ô¸|*Œ½•Žæ˜NubÛî³§)ü¤Wÿý~Ž­ºB ¶4õjzêr<³ ¹w;M¾M"Áã/R-­?Ø„‘±´Èýk–Z±[ο‹Ý mÐÔ¶Ž³6!uk£˜–zr¥túÂYdÑ2÷ùÁ:­v †Lðy3wßH ‡G麣Nµ±x+½×šÏ_…ÖÀaô …Ñ·SáÁOqÂKK$Í/SM]E%+I!VÚjk¯ÌާÕ{?LCÝÚÓÿHZœ½õc*i ÄÔ˜ÛTŒ¿ýƒtº“jê3ì ¼½ž*:S;I*êÛÅ^{*UxIÇèsÅñké=‚]§Î~ïRd6sç èn.•±£Öé"p˜'XTCñ9ü Сx——lÜOíýÄ5 á•ôðæÓôWZº‹Þ¹O÷Èkth–)„Aưy "¡#ï§{ß Þ\Ñ­dì;Õ›ÖðÞG_"‰’$Yç$o ;lÁ“ è8ï—›4³î j çØKk33xÛúÉ8\g ²ÁÖÅà™Á·(@ Q¨Žó9ØZN+ $ñ‰½<ó`¯ÛŒ®ø!»ö+RW[=­ßè4A‡&tc#Í´”^*Бs\¦kàν~_AP×€­Œíýµ‘ˆZà¼×KèÏè¥Íè¾5$ÝÌc»ÚÊÛ€Œ#“ìê°Eeèíw˜[LàÍõ¶Õ¤Áõ•âX:*/¦ ’š/êÛÐNû05nÿ«^f¶êÞ×µƒ´)€³ &+kj¿[„£oOJÊ|tOûС•»þÁRNÍX€•æ%Þol^dÖÈBm}¡ËÆYÂoIMØ“Õüê Úz&Y÷ôÙ›ês8KÆÂîái«¿µíòtº-A3Žsu4ŒÕÝvVWzûNEå±~˜ò¾sY_/§ ÍúLN8Ý?~MíÏð‡™ÍO§ èÍîQÚêúáIÊ×·²²–®]»AŸN¸Ð®‰Ûvl& Þ@2…°ÛŒ½VË¿‘…vˆÐ.Ô®Þ„gì¢U¾¼æ¡ JžîHM}<úkð‰WÝÂKP ¨?xü­L099FS‚ â¡~ƒÃ¾üݦmòÕ©²„gàŒŽ¾D¶s–~ô%¾obyøŠkµ°¸±õmwiF<ÖÄMø;ë<~ì¾ïC’äÉ î(ã=ÃZŠ*-®„¯±_ïã3‘ƒÂk×R}^ú޵áöXgõ9П¬ÇÕ'L`ðúàaó.sTÔYä ¶¬¯eíÑY ]Q~Ò ¯_ÀG ¼ºñÓ…_û÷>.OÕöíè¬Kuø\Ž¡+(9ð\Ü5Y© ºÖ¦q&À´üòŒµë÷=/dŒ¾váÉÁ(œÖ—`ò§°w,„^íXÿ&讓 7óp­D{ü‰~2s6È)¦^@ǽ• ~`/€ t­(0нˆr>ñp<÷Ùagç󺻺² ß1KBÂi8\ŒTœè¾ÜŒo‹t®u,ud èôˆ¶P—þ%®˜Õ“Qe¥D0>nalFË¿/!w @òf†ØÆÚ¹n¶qß&+ÎÊÜÁ¡!îƒáÁ<4¯VÉ ¹wó=©Y‚ÌVÑáæK§Í£×#ü6! @®V©€¶Ë8L‡XýÙ?ùJ8‚oN LmgXŽ/[zÉÐÌ|³2VÅCú Ñ I/*ÖªU¾*N2s¯#è[¾¡Ëàƒ×óaЫ¿Êä NÌÂûŽÎ˜ùÑw¿“*>ó™tþìj2Be)«ê^áGÁ´|Ø£×#|HP€úò§Á(• X2æä3ÏgfH`«Lƒƒ©„r¥ãºŽ`›=_:¶U@U¬¬’Rh?¸w—6`;Ș¶P8 Æ” Iå…q>7£Û ¢¨ð e xTF7‰b‡0òÛj(³ã åø®-bÍJ4¸XFÁ^'ãV"m£Î!-¢ŽY‡Ê¯2^g‘Îó›×ßN.\Ž,Ã*Ÿ¶GûuÈ͵JïÒ¾{Éáü , õËÿ#Œ£e<Æä¸îN*w—g©d?ÊùïÓN ¸VÓgVE¸H·vʺÞç+¿üö>ù]€g îˆCY‹0Û‹5¡\g!qqÏÖo\@¸)YáüFÔ¯ƒ¬Â„/ ‘–l­n6ôààpúÔKŸJßøÚßðF †ÎîZ‰‘pÆ}»@ÚR|ÂdåŸ8îÚn¾þFê …ï‹aü™µ«Œ{êégÒ§>ýqèý~T@û[þšõk@RCJYªÐvY5è¹È.к÷Òø©F Ö”¯xµÕ”êñE:Ë|w2çœÙI]ÖxX#üHç‘ß•ƒi¤*[q@Ê'ÔÉå8Ø#¸Œ~ì}}Éÿ ÐGRE—ë²µ*Ž5Ò梇û\sWn³í³"€çšáKpQÇï¬ßó–ØW/÷\£±è^4Zùï’IAf€Ã$KL ' ÏvÍÂö°R‡gðwfü³nª¾_K¦³û÷v†Y ]CÓ`¡¼KžâÉër;¿¯|Ï$Ù‚s/Ç3ÐÈ=¦ªNYø€U*OmÄ è¾”lfÍ' Â_>oÔ5dÎ o@ Y\wù¶…£òC§žÙýVhoétáV(¸w“§*Ùh%IO\z:]ýøSiäÂ9:o‘í㤠G‘ß;i%h ÑÊÂ"ƒIÖЊs8ù¢s ÷=ÄÒÐPÛ ¿&p€ dµ[ åâzV­Eõ ÷,M©÷ ös bq²™Ø{ú4çmËÎítö±‹!³– if|‚îHKiôÖhØ…xí³ófŸÒGÈï͹h"›¹o›:aïÙšÓsö¼„½™ù'c»µø™ôª,Ìݳøó‡÷÷Ô…§§<—ÇÂwìã3 Þ묒>2ªá§8ë=ã Ò„¸ãÙ{Oí¹P?óåwÄ×ÎJB?ðWBþ³6gNOÏÐé:N÷3ýÝ!ÓÕÏí %ΉWÀ#þæ8Ô”Ã<]Áx«+²ª"t÷À{.pü°]«{È6!Á©‡ãog\K€qá$ŒÜŸ”’½-lY´Åó¼Rst>?`à­‚S¾ÞèÁW˰Â0ƒQü™ÁÞûz¯XnP™Ë!È¡ÅÀR5˜>Öq@{á¶ôԳϦÏ~ösÁ¿ÝËüÜ •º…tÈ$«ÕTä-..‡C±Œ¿‚›ÐÅ¡#òŽjÛ!xôþÅŸ.]y*`Sÿ©„ˆ{Y„»ù_ì[þÊG¯ßgpÀ[å"Uâ?Iã´Y/áƒj€Ôšh—^ªÃERîz¡„޼•n|ë_¤A¶£?ãlàs«ÈìN¥@lA·Ú¸ÿƒtïíoG‚“ôå8Çmè}me»æ(•ëR_o;õÉ´§ g¦-p›èγ€º°¸’Ö -$öÐ6y%åÜ è+Ë›¬úõË8Èc|D „•T$°«\k@–9wöˆÅµkSÈV²ÐÓ@ogª?*Q]wgŒyËü»Ÿ¡×[ÎÃgæîüûb0Úë6©´Ü\‹–£úA:ÚRéÁ›i¡À,`tåÜ1Õ«Tg¼Ù^(¦º¼¬2 óû_ý¿RC{7v Ý0—Å Ö­äÐc‚ië³éÚ7ÿuª¢â4عý¹t´E'@ø÷!ò5WWH×¾ûï˜AL Þà <gu1µol ÊÊÝE|æ _'nD@Wþ—ƵזÒú€ç{ˆòùóóS¥Ó½JË©bkžöì+ð»=Zª·âºA°}&‚²pYZõÄ:*ò~9­ReÞßÛOÝJ?ýê_3æØ"ÞÙ[Å°ŽºT›Cϰ‚x'-~ûÿ¤¢¸-Þ­Ý[!˜ Ÿ-Øê~ ŸîQZ}ë[iöÁ ä/˜uåîjt-x8¾É sŒâÜþv»÷nTVÀow SOÛÝMî¹Å¾LÌ,•ß¡}úD¤ "±¦ÖªÐiq¼´úùAkÛÍÝtóëÁ¹Ñn—ç©;´6¡' ½{ox’HÛ•_ÿ«4öÆ@ßáþ5•TÂP[FΔ×¼6q=­ŽßŒ —º@3ú^sÃqš]$à®äÞ6Òµ¿¯¬FOpî7sf3ÛÕÀ`ß6£MÐÉVóßQ‡7£§ÚvÙûì÷ ¿ñõ4FUzµ¸Äý›ñ×ww5§»h?ޤ~xp°‰ œl\cðnøüpš[ÞJc÷ÇÑ™ ܱ›¦¾ù¯’eƒ­m-ÈÇ ˜ý >W¡£m¿žî|ÿßò1:ÕÑNÀ¾XÝgôœúá- ·¶¿Op‡Q-$8ôw¤³CM©ñÒS¡ëX‰~ÿ¸†o¡¼Å³àU¤pù‰ÇŪÐ÷­>Ÿ\LIú1È£NVÈçSóÙSéñËÈii™€ÁÅyð`e•™¾Ð¤Úƒ>÷ÓgúèúÕÃuÎ0†žÐSîÜç™Àˆ÷ RštæÜè…À¶š„|a-½yc4‚!õû&ÆÒ1³ƒ/Ž a ÿðòìï-¤ÉéEh¾•uÐA bmw Åsƒ¤j ç]3k»wŸ¤ëuZ&HÚ  Ò™ð]>“FÎ ‚÷$±žâÚfš˜$¹€€Øz üàl·8ƒSíéüæªWÓÕ©öc)BO‘¨ý¶„zèbÀŠn±v#2ÁT{䌵©[™„b—€’Ì«i{¶œ4àbâ·º¦³’ã, dÙ^ÿXïïm'Ðnp¼&×xáÖÕ/­øDc·‘c m€}Yf§"iE™u`Â"±õs¥›“½¨D€¯‚$«NîëH4}Ò¶¯æàß/mnª݉Š)`ß­ÜÕgVÞ°-4ö¬Ÿ¡»ª[ µûP=]»Â§u?-ø®d»îS˜>8¤£"~5«ÍÑlyæ¿k¯ûøÑVì³ ‚˜Û‹À3-X³«º»cc€§vg¨À¬ÙÎuËÓ±]ÃQ'Ò Ä[`$Œ˜/j'“¦}Kè‹Dý¤rõ¥=:ÀéCi'ÙL‚ñRírÏUÿª¸×Äh“2ç%¾VÃS °W!WÜ»Év؆Þ]Ÿú‘íÀ b÷÷Ÿ"€î¨2ƒþŒòàúH‡övØÓÏ÷où‘8*”®Ž \ ç?Q5o‹;švâ§Ú…OïÑ)"üS$mTakëO•ÿh³Ë_äùJñ&ô0÷ºKG *ô¨è$¤Ë¸æˆ)3ÛV€;ÁÅuÏ—/ÄÙµ£+òÿØß–熯 ¸¸>ñuŸÅ:Ž]*ô=é‹)Ã7ü)€õydç'ÿ bœýû‘³è¿þµ¯a צ?ûòWÒ믾¸d‹w}\UÈ–”ÍðÇÀcÄñÆ(ÔË8DçÍÈĉ~¥(v¾®UÙ,ŒL´ÙAF˜¾>—W(4®îîÇöòá30\Cöçz‘*²‰dx ”ä¹É‡K´)"£”¸)öA½¼£Aé htt !ãÆ2»&W…bЙ^#ò›…ãêaVÉš)䢲*„Ì€—áfÕàl’…JðÙŒ%  â2î}DÞpn@h°aÞHüŽî€¢L¶Fvkpæp €EF*Œ±„1ÍÎÎÓâ†6(%%²Kß»{;Ý»ýNZšœ!s×A8¡>U ŽÎ‚p ~P|tŸGøM!}iš8c«B¤_þÊ?K§Hy啟f ü7»Éù[ Æ ^0“0Ê`– ­¢Òa§r"s×™%“‰dÞó%“9†©…Ó’keRÒ¸ŠÁ1Ìë¥ím˜¶s×mϺJ ùk×®GϹè}cç†TŽÏÿƒ£Æ~ÿþ“Aî÷o_¿Ë;R! KR‘56ÌÂãùÒ‡]%”w*‡áLç=Æ’ã÷v†“Šž8¦#9#%”cª ¬bmCœ¢uŠªIbV ÖÁjr\ í ƒ_öŠÏ¸&hœõ†ã›Ÿñú;¾ë½UÀ5(äTK¾˜&GÿyºKëÁ>æÈéÔS_Ðaà±D5K¥úÖ-ÛeŽ_¶®Gïÿî@@ìÒÁ!îŠZ4ø N/1§C ¤5lPïçiQ¹– ‡ß|¿FjŒÛdcŒ¾òÌ éÁƒÑôöõ7p8u @_/cô•"‰cÛÀ%t¥Ý =G{tŒ˜¡‘s©›Š¯÷Þ}7Ö¬®j`ÖVagÏžK“È;2z ì¹Zö©12“c`±)h]‡7F ï)#Õwubwð7]“x¶º¤úª™¾ÞKÃÈlà0¾¤ Ö¥~oÇ%aéOtà e¾o7×b¼-´ :»/•ÂR#Ï=†çžîÏ‹ky†I­YÕëUš šV$`4óL?s Ü»G¹ êÐ>WV"?ó9&ú¸O»`ø’§fçï³q„£·‡3†µ˜~€“j½"Ú éˆÁ Òq Þð pc°ž}þÖp9 ,ÛÆéäŸ>“' ò€— '¡]üž±o× q®A>~ï5ÌAÄw}v8ˆYíÝt.h$CÞsº'y±ÏöÚŒ¯ g!Àm0Ô¬\5`+\–p2LMÍE›Aƒ„-àâ»ÀÄ#•äõŽÝ9¢‚ç©gŸNgFΥǮ>ÎüHÀ»Œ~óìèàÜTDÖÿ¸‡A~˜¨±^,ðœ5ÞË û°¹Ð mÃ{Õè$çy¦3 ëpÌœH³8«JÖgÒ€NÁMœÔž¯çêsÍúVî´S Ð WḰ%¿ø•C^Éûu\?>8{ú*íC é©Ï¦Åéy*uî¦×ÞxQ%ZΉcMÒ×ïÐFX¸Z lÇg[š r‚ Þßu|Y‹4#M„QìÊ¡1q×¹nV[HPVàydÔ«¦ r[…¥³N¼1A žçyïG‡Iâ2Lº6‘T¼“º šã“–~Ò«˜a Ù=„íËOVšŽ_¹ô#ZŠ¿ýƒGðåm8E PÉÄM vЩê¡«G„=ëÇé¡ãÍ—xëˆùžxäÞ\Ÿëv:U¿üÙ’2ªG¼Vaã¨2qÎ5Ÿ­CÝÎ7Ò6·V&¢HGЛÖnJ˜ðUîŸÉíø›‡Ic:XL4áÂx~P'ßqÿ_ÜP\¯” '´%ÞÊÿ½¯‰õÒ–xhu¼ÌsUÇ*®fç- ÎÍ.¦/ÿÓ/§§UçìFµnßz/Ý¿{7u8³RõÉÏ@£  „S7 U&öë€+@KŸû³šžýØÈö8x­àw Cå™lóÃ~Å#øOà™?y üáÑë÷ `UðŒüMÕSi¨±ðä-œÃ««T…¯Á£ ƒ#gNÿ:>÷¨Êí ø¹Npiž¢¹·rµ‚@ô@&m€£²ª5•pôÞ¹GëbGˆãÂÊ:¶L…5gÃY*.×8¸?Î[Ï,ͧŸ~ ¾BUø”Ä5>EÛhZãB^cöÓÒòF:;<Î w#Ç PU¦Éù#ç£ ‘­“SóØY}鉧? e£3¿ïÎFÐu“Ê%«›Tºå O^}:]yöãÈ9‚–ÈÓòæa½û^zxÿ6÷¤âa;§ ý¤VòK7ÿŸðSÔâhnïì†áè%9A>37·™º:H`[†ÌDÆÑÛÚAµ4= s ±=TŠo¥â”òûˆk: µÃ{˜I>¹‰×=Ý$OM¤òÂkq&ÌñÎ(*3Sµ8oˆ>>ØJ-GœÛìÛ´ÿÅ‘NÅz#s9ËœõÛ7–‘·Û!§šë Æ2Kz »±‰$‡Šx–ò/t4\S•NõÐâ½ð&Á_/aí§Ò´ÕèU5¬¿°˜O§ú˜“^A7Bº‘*+[¹Ïæßé¹ÐMÆÇ7(xèL½¹20¸ËÖ¼·tëœe^í|Èe‚UcýÝ­ióAârHj:ÛÒÄÄmúéоÝXW™†‡û'³´½^ƒÏ60óœçm%æ„Ó|ÕöÍTv¦¶&öÇHåcóé.t1Zuß›©¤'¨ÞŒsØ73¨'YwMj¥ÊÝj¿þØ0m~M8ܡҵºØL·n¥Ó8®ÿïüùlšÀO+ed2÷Áè8-Ú±ƒv³‘0}tŸ¨¯CvCïêÙŽRžÎÍäYÿ0•Ä‘Ç$ȧòùµ° Nõ å©ç:‚çþÞB×"ef®®ßz{Va{Ù5¦‚`I!-,Q¡J…è.ºŒ|Ð`õØÃ¹tþÒé‚®x›^Š`ý!6€£š[»b@{`{p¬OR9ÏÚÊ詽½T—rÞ¹â /ï?\ ëSgèŠk«¬w%‚n5Œ6r|kuUc$xV7i#©ÿˆ©­Œ‹ÜÓq êm-òš4ö`‚ \‹Xóñ*¶ë_†·W”CT·ÔšžÄnâ·îÁfž=̾›X/Ò;cšËt„_~{µÏC}†Mð+ þYyA…ή/Ù›:f ² :°½»4ëyó¹-³Ehuörê›ÙÄÐnÛ&,pv¤…o‰ä´E&ðÉgÅC“4øþ½”.]8ËÚÑ£¸½¨‹Ùý!ìb2Ú|&ë,HŠã3m4rŸ³‘:¹¹®ªùLÊ pÿóÌ6ªëIŠWZ_'ÉžgWV2Æš$¿t„ÿš³7ð¬JŸ«3Àwè6£ÁŠŠ‰ 7Ïó2pU–8ÝØl‰D7|ÇÈTgÇmV|È™xfúÅ¢š…çq9ó´WµÃ™OÇwôS7¢LEƒëëæl¤cá§Mh§’6ɇµiÇï0m1}Òã!>ðJôNqU»VÙ¦Q]ô ñãc|6ÐXŸË€2g‚mã‰âÏr{6×B¶å µÍÃ:ÄÁò=JêÍÞRPI|öÊ—xþs_ÞBÃW²\¢…ÃÌÔtì€8ÈŽ€°täÛÚ]åÍ6u¶e·µ‰Svf È†4ŒA`„ÏhµÊ$DjƒÎ|P¡Ú§-Ñ÷Sa7;ŠÛqˆñXʬMd³ ˆŒ/Ë\ø@3]tVt¡$×*ÓÜÂlêëF)ʥ险ô½o}?m®,§ZžÇ‘q}:uÔäq¾™=*‘†ñï3yÞ£×#ü}@@LS¡ÛeäòUÚδ3c$(…cNšZY. øÂ@`z^ë{⯿Ã$—` £`&þ«¬èà”éèD×a´Îߢy8ÌýÚJ2zƒæøþ: ûI°dfn>Ýz÷ôÒ'^ˆVžfiJ³:Ñ}ùüG¯Gø0!F#´MöW›™²˜/CÊÿ*}d-V¡0}ç«ß? ó—ÒÇ?ñ"uZ°#ƒñá|ñ¾ÀÎ2]Á‘³KöÝskQÔux›´b°Üİ2ó‹QÈjqìD›YäœÏs Yusì4\¹¿´!})ŸŽ(ž´N"³r†$/_ó5ú}_×,s•ÔD"©³ÔÃÒàAѼýî­ôÜ3Ïb 2“ ù¨rã–Õë^^™ö¼áZÃñΗ54þ®{¨è±Ç2ÏôaTžGIîD™,F1`$ë2N=Ûy_¸0Â=Õb½~ =)?L ™žžBéÅ)ŠA!pÓ€©´X¡Žèçw-þ'¾@ÌJž.í¡t7·õ¤ËWŸÁ±1‰LDçd=8ÕA 'ÒƒíÚvȨVïTÕ9©±ðpl4dÕÈùóixø\,ÈŠU«¦‹ëd}ãTO5wÛy`oÎ¸ÖøƒBÂùí}¢%#ïEæð²‘¡ßªªƒ¸‚•A…a¤ eéѱ-Ê2!ÁVd­0àò|šWövâil 'í¯ÐO#€'½"à¥)«ë%بœæùžA; _^c`=è›uûy­µ&&§è(³€ÓÉYÔU8" üõÃ#3}ÝŠQùœ<‚/±fueõŠLwpÍh~æ½5Ð}/x_ÑȲí\ #Ô6s&Dûéà°Å*Pà<™­¯³ ž-°Í>¶5›z| 9¬_˜ k+ƒÃb>3Î{!«¶wßÚ>:1hi‹Ó£‰€‚³ZuÄ [÷â5CƒØ.-áä3è: g">>3K˜Àxå^ž“Ý|>Ç3ܧl¹²²—µQUˆ=¶ Îlá ð•UXãddoòÓÙ±Éô‹˜žþøsž ¡·ÝC¨†¥2YàÀŶÞyfXz~Ú<«èx8OMHòþÛðÊ»©(ƒ64Ö9W[Þ¹&mgí!á£~gûUÙΔ7!Ò3ÕÑá>ùíÚ:ÃɱŠÒÌîŽ\ƒeœ:Û>»³­«›Š®2ßÅQÚÃ\V®;BÞµÙ›g¿”žýÃO¦ÏþéçÒë?üIzë{¯¤ãä•ÐiçgÌy¶Q!¤SF\ÆÊ!×)þÖ@Ëç„^ÊOõÒ8iö©­8“„héMYÅFƒ&„±7ó§°êd_vkņ“¦<8ïëý¤!Õ¾¯ì“ùÓçdè®C‰êªF—QÉ'ÀÔd€ ]Ö/ôh»ƒŠ•CïðàÜLu`ÉÚYE)gB¥fØ­Þœg°èÖäüò%ÏPÝÝóÓž‡Ÿ$ðù«$L˜,oU‘÷𬹊õ²~‚Ž®°eó ]ºÏÚ´=ª«E]·‡àìW`™Œg’‘ÿÎwT2X@組±:NT–ó»‰;â–¯ch^~,L]StÐà§{ôÄk°1»ë;Žjò¼‰Á®Óªhu ‚—a“˜ðÁz…Ñ/«NÎŒ„Þ473“þ¿øßÓ_ÿÕÿ›ÎŸ ÿƒø´ƒÐVÖ‡¬YÞÙD%ŽNOõ®5hè…ÏýIúÌÿœ® Mù¡‰è~Á"Yc¬˜½áŽßYô‡ö º{ÿ—køÐ÷èÆ¿ˆ×Ñš˜D“SÃ#©éè1þÞÀ÷DçŽéBÈ¢m[òð-ξ¹}‡DXª¨q6®­oäæúœË¹M¥Í,Žö‚Yú¥¶ ëUÐ_Ž ºtpMM©·{þ0;·’& wÁs¥ûè½HêÁ§K—ðwQ… ½?àšÂAvä„í/Õ‘æ¸W-÷N*g­J_¤‚}b*•€ˆ\x]NþuæXÏ¥aªP-ð±£×ÄÄ"¾ÎQE?Ü„©ªž™¥Í<Ïï%P*_X`/«%åÇÚM{ûÙ¿AÉ÷Þ¹ŸNŸ? Û S k]¥´_bG[C8ÚX§UlÍuûØ é™þ]‚@k/ðxë­{%¡iøÁõ9ÇóTSÆã€ïvz÷½ðL*tá-_5YÀVÓÇÈQõ·Q™áô†ÇËg—òE€çÒ…Ç.Ÿ,ëÞ}ªñ‰¸ltÚ¤’ŠG¦.äá…Ç. ‹švÈH›ñz:[{‡¡ºµUû©©½&=öÔÓ$ð ¸nšŠÝéYF  +M.¨cŽ„9{î>J‚%¼1 •}"*Ñ7¶ËòÎ-*lŸ `­S»øï¦[¯ÜD¼À£!Tœžã;ÏJ0S9ã\d[//€o£óÍù'®wä룃Àâ2R™ˆœ;ÂÎgÏÚq07²Ê@é83§uÈ;;»ùXUCÒÁÈôq`µX¢#p‚ËtÒQ6RÝyê ‰$›fâÈ8ç®v nî쥶nºµ †åYÜxó9‚iènð{×mpåÊãgBözŸÉÉ<Ï1ðYG4ø9?Þ¢t²Gv NOO-ëèG­ò[/ T$÷÷µ‡¿Ìó<œ‡þ¬dG¢{8·»2·žFh}o@arf<3!α*üdÏ3‹«T5§^‚ÊÊÏÎ6O»ÚUƒiÁ$´t„\:Õ¸»Ê³ïЦ\™WO…|-mûí‚7A€z„.”‰OsK´ÂwÛ›LÆ#HÂüðyp³³¯‹ ’#SèüO³ó$-ʘ닞µI@KAS¢ë…Õá3tçboè¡KÅ Ù(Çàh7·2kúñnï°»E'Iö±e»^ÎÍ wO IÌ«T‰O#o©0ÕF÷ EGË!¿[gW ‰…uéÁm2(z3 ¸·»þCǘ|)ôüòö!–6`¼Ÿ^{å~ð‹î®¶HR0·lÕ³õát0SgyëÆ$üå˜n‚]à¯E=tj¢€ý¤£Ó w ã*ÖÒ½[3ܧ…ôØRè¤ëT+ëGAÅA7%°Ëþ6˜Í¿¯»è# µï‰9øßÓC iô“ÅÙ"ã7.¤s]gÂ×ÉÁ _:dó¡¼þîûrô¼¿ÃHOFÚÆDˆ`´—‹Ä°-ª¨‚6.c Æ­ÑE‘èÿ¶‡×[ák W=Q/ªÆùiç1»R™\RÃ9ïÙÞÔL"‡Õ­VX×Û(˜ôb»ö –È1G/Û%I˜$x«‰›êÛÒUm=ºXè¢êˆ„ ‘)a¯Å:`ãÑOWI¦r ✻*oÑ7#Q›7ÙëEëÖ“taÇÑêà›T\»vmP«›[” òË÷í@eŠ:¯ÉW' ÞÚËvf1±×à(ç,h²“ŸíŽQÉLëÚ Š5áÕyøMÀg{è£þJIà/¿]#Ñ&š93å¥÷5!Üä¤N‚ÄîóÞ³9ÖVãÇúX¿~På—]0=gãovÆrݹX5¿N˜h]p°'Ñ%,Š[šŸ'Ž‘¤]Šyô_½+ß‘î´-—òx&ÎÊÀè)d”òyzšq âÕœHöêÌÑ!¢*68mífíz#•WÁ ¾¿K×ýúXòð¤"|Aœ¼paü èˆçèÿˆ™ãœ‡kàxÃêFŸ1(ß|v8=|8ž¾óÝ¥aùììÙª?:…6ä·Ý¾>*íXcòlùš¾ mDm¬º½žƒì¿”›ÂÖîUâXT™KñÀÄ$ÇÛœŒƒ³BÛ(õÈf“›*ñ Yñn’¹ òÚ:Uª€›>†ð)ð~øm .ï)L•ÕàŽIÈ"cºúkï¡_ãHÿ?í¾`ÇÃå…®7MÅ8¼»«‚ÆE»@°™9î×Â.€I»sè=ÌÄ}³&ÿ;NH:vF$Ј²œLÌûT‹3¬Q_Õìܘçû”y ¼À®^êfÒÎkHXi̓Q­ûæ›$&õ¦Ë—.ÆÙe¶(:‘ð7ØÕHYWBwاc©ö\¿ÙІò\’V¶ .ÀG©_+ûô]qyè$è7‡ËE1™MÜ• ‡&µ¯‚àÝJ“w)®R=  3£†7[Íöªn:6ÄÕÿy¯WËf‹[q÷4ÎJ·Ét>hLk «tç ‹Ì1ZpNL‘ObQQe÷„ œ/jÕH´ÜAdbf¹ ®£€0LÜß½÷ÒHÙ$ ó*Ø»ÂU…E#à ŸüVªöúûÉòìhÊ›³l^@¡Þå³riìÐB*æaZ«TT,¤–î®´ÍsdB"›0î^ ð÷…¡³6T¦­H}ìÊS0šFŒÓÉÈîRSØ í|åpº£À˜A'C–‰Èdl½§©ôuÈga4…cL¾š) :ƒ¤~¶9ÖYR‹ÌSg£4uÌ}ü¬‡ôjÌÕÁ{ïÁƒôÄå Ñn¥ˆ@,ÈŸÝëÑ/ ð÷åŠ/~ªd#QQT2Aïû´¤ã…JyÚÕNV)×ÙV½gª·0ûQùb€¢\.1Ëw2Ýzï6ÑCfy²i­ŽÂNk•0 ¼ð •ù,û™Å3¬‚%ãB‡®2ÁN]ƒ|(|­”wÎÚ3ð¨aið\§µæ&–Yeà]‡€¡JY¤¶Ñ±ÑôÌÓO‡ìuíÊÏLrŸìü~ºQhZ#F¹fòMóý’ßËþûË;8€»ã¿TÖ  ï`D¿ùÆ«$›Mâħõ2ß[«€ªàY=ÓÑIµëyýõ×ù,»{<âÑ~ç! }‰›yŒ*åM3-µÔÿ¢–z¿¤ÒPP~xÍÉë}<ùó7ú©¡jÈÃJoõÏ«Ï>ŸÞ»“~ôÃï‘åÜ }d†ŒA-qN½S£X}1tR , ¨kÐ=ýÌ3è²ÎåS g •·ÿõûßa¨vÅ,Ñ ²‹ÇÇâx&ެ3I`'¢N³ný§’o;Ëz #@:mW—É\ŒhLýÕ5¨£‡€-qmoÀL‡™÷‹6ÎÀÌ`¯ÁÝ„bPDWé-8HUz×´ñ¾a¼Y»‚ÛÀ¯k2™Êmñ•µmVWÖy¡­eiÓñ‰/™=®þn0Uý! êeüÓì‰Ã@¾¡!ì™Ä÷º,!Ç€›ÁNÚ!?¾§ò°} D+Ïå ¶º30/ü„†ûßçïUÒ'F·Aw‹µ&ø¢:Žp =ÉÇ2ÜÒ¥õ¶†6÷Ð~h„çj¤t6Àï™4Tï$Ù§.²ó3XkNÂÐ}¸‡Ð›x¾Ï‹ÌéZaB–µ»ñËÍ™m•žvâºÙÕÎÛìà9«T–õ÷ ¦Ïý’žéZÜö†]²,lf–0¢™} ­äçf¨Ä³E"óç²¾|w…çØ+##œó\•ÛÚJœ‹8åzkÙ¿•VÙïá°ÂGŒOÐ|ýÝŒõu"˜ÉoûÂfðû±Þ¯¥ò~ì3k$9“ÂVp½Tœ‘$•ÃÕщƒxIƒm´ËéÑÔÞœÎ\I{ééGßø^ºñÆÓ©ÓCtvpö$®·šN‡‚¿+÷„w¥4¼ÕwÅ_aÜü²äÎ,™íþ÷1§º6h¾@ ]T<Ö•öû©®”©4`œ×#£u=¸g\üÜíø¤@IDAT58åziR6Ñ@¼99Ó:Ú÷:çÏ®Vý/ÌÌál—Ò¸ÞCÆü‹„U׌ÎJ`äLQ+GtD|¯Â9âLx+UÕLàÌ$5y’Ë’WøÏ6ˆüz[B<FîÛ—p¶ÒGºÕÑàsJð :Ƭá`ãc†ë3=!øû•®\ƒºá%\¥O¿„ÕÎG¼ÇµhA“žk-¤Zž ø©ðl6Àô-Gnx?&PÈWb}â¥÷öo_/^ ûBgØ­wß&qð]Ú7Eµ‹'bEˆIÍ\—„œ©hr“´uöÉgÓç¿ôªoúiϼC5%pv î¹N–ë.â?ï?Ó??èW<ãnz²Ç_xûÑŸ¿£ðŒ#™W™A`±º©'Íី;4ÐŒ“|jkH?è´™ ‘U¾Ó7 Öá µ=éV]­túÖWDí@ÒØ¾*‚ ê}èÜ­íÕQÕŠX‹ªËÁAä ÁÁ»÷¦!ÁÂÑÜô±^¡Ìp UÎØÛwhe̵VŽwÓ1¤ ^WäoyÑ h¥+mwîM#?°—¨"jk£:ž •mᘩ¯«•àWŽN>Ì'¦Z·“¤¡ÞSÝÈ”-]‘¹¬?‡Óøü‡ùµ×ï†N28Ѓ®•‚€UèÖ`´úL%cû –l¤ë¯¾M{Xä–â˜Íµ¶5Eð_ÿ_IXÚUV*zímàÚBõªüi‰Àü±öG½|€ïá07qûÎCtô.tÆU‚«ÛTþu³ve£ïmSÑjà½äí»2³=7©Ö—ÿËDí"“x@0|~¾YÅšqüol€ñ…þGÇî©L£÷'ÑóÁÛ•Ç{úìœR§ÜcYÇðÏ[LÆæ¯ð—ì n„g)‹¬ž²;ËJ–ûøSê|hSioeú:‘Q‹}„Ç»ž­*†3cƒEü>êÜ(è-è‚È ;ŒY™j¦[:hg`Ë“a³Á[¨lž§{žJr×$ Ü%ßž˜¨,h'a‹õ¼õæí¸Æ*1õ¤]`™h›^>áŒ_u×÷Þ¾z†#‹& ”Ö©„ÙÐaKéÖÍ{Ü€ekÙâ€uízt„Ù©EôïYôCüËøb‘¡;ØÑM8vu¼Ù›×ï#Gí¼ïgïÂ}… ²ŸÚ äŒÝMo¡[©/v ?´ :ÃU9` ·¥µ)Hd˜¸7ú^+ß©e?eÖUÅÁ™0Ø ÒQâÕ¿øVÜ¥W;ù(ËÔi ,™øúOßAg"PƒoW+x èƒ"QÙÛI—¥<Õß÷©&÷;øÛ@×*ò×ý«©»î•÷ b¿Ã^ÿ@’ûë—[¥¥¼'ÕlØßxõ64BÂ#»7Û7wµxÔE<£ ƒG¶°®8–TÓù¦¸JÒÈ×WÓ5–golD¢b;|¹vð‘ÊÒ#:"­€Ë{>¤Uå5¶P3DzDÕ Ï<@G,¨Y'qfgÝcÑü³@Gº$u m5$?€;è{“–òΔß(Ñ:y]Û žÙÚ ¯ïÿR ;ª‚ÙÌ|ÇJÅ£2•Ãèµ&yÖÒ%@˜oV¥uÎé ’vÔV7B×%öE?àªj‚átW8Àn\(¨A¯ªä~5ÀÃNO«þ&Ù¹†*ÎjtÔྸFbÓúJS7\=¼š.¨o£g",®í¢Û^O½ýƒéâ¥Ë$”dkÑ‚ ßÖ ÞA@ºŠdela¼Ckh寡-Û æ9#¸ŽÎê¡Æ?,‘oà¶Ã’rEÙeu:Œ‹.ŽOÈtÔmæž«;›ä˛Ѓ>gõV8ô­±Å²úå!°Þƒð´«éÒa8Ñ“ò*]N¬ôSï1¨¯/ŽBƒ$om£Sòl‹Pä…ê…ú¸wááµµ}|Ç€˜÷•fa^Ëç…»„ÿ¬,ø“„ÚÓ$½4?~ÄÕ•2²™$ú!ììv‚ÿ­ÈU»³é›ä8bïSSsÈÙ%*½éêÑÛž¼x&µñôºpnêÓŽe0àkB¼:Q9`f2’ö×.ø¥?t `BÎ2ònaêwÆŠ¼;1xs~¨+ öt_4¹O\Íç±÷¸ß ÛØÉêô‡/rÏD²ójº¹p3l>ƒéƒüë X ÌÙ®’€07¿úS窌0Y8ºÅQµ,ΘÌ&O×ÒnËq]u¶³r;Y¯ì—Ìõ%9Þ$]‹›Õ‘ïmdÄ*>ä:Æ<(·°2«H7`S žš'¼„©I"âØñ>òÃøu¬”4> ž>‚üÚ^U$¼8E\v,´b;øcÞ®fø!lµoG;Íy&v:„ß+SLêÖV:âzý2¯á±ê(»(]ú|OßÉ1º~´ÖëbÅeíjmÍè¦J—‰Jä… ÌY" ÝëHRÛã^m+÷ WÖÌy· ‹)·íübœH²_!9½=Vÿ“£%L"ÙcŸGð/;ƒ¸ß“¸v¹f†þq“ÔÂßÁHGÏIÃÚ¶ŽW¨Lì¶#«ú yàcÌ…˜š™K+òu‡3F£5ô&³¼§&F£ Pœ97«²òë¾ÂözOôäÅŸáð‹hÀ(m€ ZùI–cË«¹Í‚ÙB™ÌdÇÂìh01€«@Ž OTÚi#¸Á_:ª`2:0øeÕ´:]çŒò@©¬é¬‘PdÚ¢Õ}¾<ð*2quüyZ:ï ·8ØÁ!f)‘Õ³@‹ ÛuVÁ£JÒô>ÖÍâ>Ä´™þæ«ÿå¢;}î‹’jº˜Âì…ŒÄûèõÑ…€XûëcûGw?¿2çÁCCièÀA©³ovv†À­È Iq3œÉ€x®2bÛIiCúQØøOºÙGi  dÈÐŽZ&4¦ÓÚë ŠI³ •ÒPŽ`üþ®3\'÷שàF†F8]$yÓ‚è2_Ÿù›ðžŸßóïÒï¿8÷»ÿ“µJ÷ž…É:t‹Ï'ÆL8¹Áë-”ƒ%ZÒÚQew—ö]´ØS~MMÍP €‘ˆ\5®C¶ˆpŸ™™ÉBÈÁÁ!œí}[AwC¼Y}¬³@N¦|ówÛúÚ"|ƒjé©Été±ÇÐ+³ê†Ó§O§?ÿ'_¡û$üƒ£¡È^ïÞ¾•~ðÝï°?õJîϾt6HsÊÐvœ­dwkÔUcpáp2ÉÔÊ`7«óÚ…«£;CPš¹,ýç\F°ß¥E•e¯†ŽÉs¹¯‘çþC¯æ§ë`9{ææt9ƘZ‹óáN\I[ÝÅWT²°&u~ÏÉ÷ݯz öÓû Fˆ8^|6ÿÔ—„™ÀÁ§׃2´ïÛMsSÓ·ÀCÖmÏÙ£|0ŒT~çî±'ïg"H8i€SÀ}øò|ž¬Ã}DÕ€kæ«îaøüóϧ?÷rºüä­8v——øŒÊì‘•%ZâR¡_€~¤§ “Š r8¼”'­ÄjQ8‘+™|c„|nÏf™Èñ|~ºŸ:œ¿¨’#ÞÏLwÜ¡²/«ªW©&pTçsÎe‹ŒpâËÚíQÁoƒÝ+ó Ñ:°ÇI¾n#ŸyšC§),À¡ h ­o¯~òcéìåKé™WŸOßûê7 3ZÞ·â¬í:Å›,± ºäùÚ‡Á7ÐAMFSÆØò1œïÃÀßíNæt ·2ÖSF¦»õ×Fœ[V‡¸ÿ2rZøœÒñg"¯Hèà§Î ÿ'x âŒ*:³]ß ¿áoÓ­ÝQ_æùaÊ38#^àe ŽmT«º­­¬äÙ(ñÒŽ üœxžã®'ø!ëõžÒ+ŠÏtéøîD÷°UÕr: \TãéííùÚYwÏ]{†Þ®T¢/ØÚ»{’Å"&÷±åà[ÙYÐ}M}Æ5Àït8…›@AKfAG|.7ˆ[á˜síð3ƒ3ÞÓug8›%á¶ähÃ%n rЬ‡yÀ8Ì´eöµYL.à|õÈ'ô;xv«¶ˆ>… Êçâ<îÞ¹¾ÿÃFÒ‡ìÆ'¾hwT@[µàº|#æžãXÓ±RÓÑ™¾ð5xŽó³òg¯gÂÿ\¯ÿæñ»ˆ/“D5î\ã×ï¨Þ‚Ï»3ë[º¢’ÑýÖ“°¸Ë}Ê.áx|ž#é«®©6ÖT‰c¿º{1mqnÈr’Å­.ü¯-T4ï¬á_Æ.¯¬k‰€ÀITêÝÇb¨û#1àÝÊTäÛ™àÙ¨NµÃsä°ÒG5óÀkÀõ:‚ŸV«î FÒd½ÕæïÑáó¬ÏVÒD[×¶ñ íbÓÔæÞÔï°¯XK Q·¡³FžB€CƒtñÀæ“eÚ„«þ^ÂõWÜZI›Ò*oÏ¢=tWÁô½mgfgÚŽÁëTAçxn5‰:U•Ð/¿VäE_F·b½šCu4õ¾ZŠ?ªàï»êÌ\¨b0÷Ï:ùý sFUð0&ùxÊÈ6‰À`TŸC×┉ÂqÖ®“5;â×äª,Èk²Žð¦tc8FÇSè½…x޶Ÿ:¦6€:¹U´õœ«O¤)kÕNÃ÷°ø[û3:0Á×Õ/w¡cT5ìϤս=«šÁp´‚DmÏQqdälì·¼!Ô¯H5.v¿÷å8ô>^H¥¡Uø·×B¶à >DèvŸöÎß6yƸ«v¬Ý–ò«KøîVÒJ[gZyç.>‰cÆsŽ@þYh{’À¿>D‹xr$ëH3Ž nVž»2’N}þ“‘lVG’q} ?F‡XGPèëXZ#1vÅÄ ôÖV‹Þmç ª¬6Fv§ë€BÞÈ‹îô|õã¡ËïÁ[g©j¿ùö}|8+‰>`iàô)d ãÖ°ïæ ´Oâ«P®Y=­õ])´•SSÑ}nf–®ýŒÊ¤Cîì̱@1Ô2k@gâ,ü®¶„2@>¬}mÙâ… úˆ¬\7ðmG´ ÏÝ1«$&t¾Ü? ¦8·F䈉ûõuøÄÀQ“‘HPÐokÒ^th€¨Mj‘êÔ À_ «ÄGý#ž§òÊöë&ÓYi.<·Á;ÇqX´T‰Ÿ©›þ’«Ð#]„þŠý\΄ºÚ´‰JÚ2`‚³Y2‘ÏSÿ BÜ„,+ÓƒÞX[‰.vRp_rát |®P…>µß… üf¼²ª‘øª<ÉΪʻ԰Ÿz’wçáwMh´ØÌïûü†Vº@‹;è–+t.-‘Ü¡)oßêÿ{odÙyæ}}{»Ý·÷}ŸîžéY03ØIƒ«D+’(ÉŠ$+’Â$v*®”“TÊ?S)ÿH~¤\¥J%.»ìTª\%ʶ¬04E2$A‘” v̾uOïûÞ÷öžçyÏ´)‘ Í Íf¦ûÞs¾ó-ï¾£^®-Aœ> ¢zíö‹ƒÊ=*˜ŽYî^VÚ$ªØ"2¼È›0×&"ÜÍ–±~¿†—Ü-Ê? Òµ“ü(—LSâ&&(üû³ÿJ´]@¼'ßûÑ·ø¹=Ê-Š$–/Y"2ÞÈÈ ÊÔØoÑÍ€0FÁR…^‚ª1ÒÃ4sGÅ7ç¼Ç÷Áå…¾6Î=#ÌÔ@ids &ÈqyE‰îs£eÔ"¾Â¯cAúù9¢<Ûˆ 5JÓR5+Ì0‚Y'"!Ó}Úv¬é´±A‰­‹¯¤‡zWêá$(“a;qø¬{ûò£lî½{ßôà¸ßq—KP‰î±cƒ0»ùt‹²«•ÕuT]X ‚'PÈÄE°8“³oZS÷%Ø:  Æ¯‰È{&áÓ ÁÇü£ñ½ äQŸâ›Bmdñ.5q÷€¨<縉r4ŽñýÔ‰aˆ®L ‚.>ó®L ~ÇM,èsoë“æ44Èã…;•?ùŽFYy•}]–áUf”ZÖÕ¬®F„êJ¢Q¿ú•¯ÆsÈðÔ}ŒÓ-™!¦Þ÷脈}þÛ/`Ø¥T4p.Bi°ÞÆ@/*…“Šì/*P±Ÿ A]â bPà„rš|ÄRJfﮃGâ!!ìTïlŽ :pwuiX·¬±ô@#º|ÕlD³FnݸA£ë©éÁ‡3ž+n³  ~ðÁïw¦YtÜÖÝÏsüìzÞðâs#|ÍR•.¤¶Ì¿õD> ‘©ÇÓÁË-ñ(}p'F¿ïoøž7ýÃï·À7ýÅʞ–”Öpep‘¾â€” Í:26ÉþÛÀ˜¥¢¤3îH®ÔH†póG½„/å;P/^fCœàÁèçö¥Ïý.à†-û–YÂS9P¶œ'ò;_ë(¢Ï|á À1ÙH”¥>sßÙ1ÅE³µûûú"ÒÜà.JMªDY>xjj*"+0¤ðéýâ·Ñç–>­†6˜±®gŸ°á0O `€ïµdV&½ªðHCÀs¾Ó©%½*GñÑ@ r¯-ÀqÔÏØ7³w5츟Q‚ÙÌ —ò¯B­fµž…‘ï*2*o:¶AòÌùì‚ë<¡ÜmÙp#ŠËy§ôSÃ@ÍØ›(3’ì8Ò­]Œ®Í}¬(ÎTC—¿©Ìh„’¶x¾FéKÿ”<3ßèš5~#®ÁÐÒR÷Ñï“sÓYe»PÎö0:OUÅñrùž ^>ÍG*d¹â3ž Ä÷s¯£U|ï ‡³ß8ÉsÀÂsw _ø^]ƒûb0ÆF‰3Ë@'´û¥CYƒï(Ýó¡$=õá÷cØ¢çÜ6CÜ7‡?=1–J8u ç€!Ï~3°:OÌ”38ôÜx¿}ý<‡ˆÀæ3Ù8ô0”n~–×)ÿEÏk¾²º@ì1óUþs ³*5ˆp¯§iV½.–Šã]ö&dѱærŒ*à´=SK¬àYœœÁ`Œ—ó›)»{€cTýÛA)áBz×Ó§‘³'Ó?ó¹ôgÿþOÒÀð r+L–ýÕë{ÝOåÏÌ€©#ý/àïîçÌÉ3r *Ílq쑼^ƒZm³Å7{̯‚×(x…§Êú4fz›ðxÁýaœ€&È[…EçákdË.`ϽßÌé J|šÇñ{NòY+*xÏ!³ úÔ]G½ˆJÎ!Þ,«ój8®<¦.L"X i¥39ܹH×ÄEi‹p©.ju0‚ï¶sšž!cMudùm¶Fs£Û —2Ì9ô|×,0n´•aݾÛ?;¬!Ç3Ò ³ˆbÃÁ'é9;ÂZ V@ÆV€ ßã«|¯0eù|cš¥ã­”SÜm‡Ò(‹7äp¶ðãº>éC¶~霎¥÷?ýD:68LVj;iH}ñ…Ò³_ýrŽ=!³ìŸ2Öá™Å|XªÕü#ÞÛWø×?ùߤ¡‘³QV³šàü¬™¿î^îžÔ©Ä:Ž>3ÿ£ñløÏŸ=û{×;cÄEq\Ú091ž¾öå/é´Î4¤œÍåôæ­ÄN¥£Bš¦|u>s–ãðÊ“ÆÕ™&m“¶Èó7à™kkÐ œ€Ð-ù£ÙJVS—%xü=|µ±UÐ87c { ŸûŒ¼BZ¸_Ö!¥üêñØB›¶Ú滸ÀüÍ^³ä8[³5½¯G NnÐ5ךñhÃJV|Ž7ÖC¤Õ|4’wÚ[´œwÈ›í_­ã0¬àÚ °“E$|Az›ªp¾H—S™ S0cðûlÂ"Kd¹N³@u¡Ã ðcþY§:+Ê*ZxЬ* ®‹³Ú¥Õd+CjFuzˆy”fÏhûŒ“:í·(͹Í2¶U7+è·leLå“Y€yyt¶B ÷Ó8C-/›Ï“È8wçK9‚8q q¶¤¥¦Ê‘ORR¸¬Œ¶_ÐBiÛ:üovÂ@j~GFX 8`v‚½ÁQX%qâ¼w·xÿã@cËéw¿‘X›QÆ2 '9ÓŠÀÃÂŒfd¤ºqäî§kÜSÆ"ó5;ÒúE2}—ôò&ÊãžZg|¨eµíqVRžÅbYZ˜ÜÆÀ“§Àì{ÙÀ$"7:8÷€E2s 8('Àa=œà¾Š\Nƒ8shûÒ‚½M…Ij<·¼Œs€ÃœJ ¾K3ÍÚ”+Ä íºS¶@;D‡Ä ÁÍð9ø"08·Ið&xPÑV–VáÙ{E΋½¯•‡r,¶X,q¶â:ý¼`™³³BN¾ècNÚˆ—K¼x/–µ’ÉóÉLf{Áî«R°jšÁi8¦¸}±”i¶aeVqš#¦‡;¸I GÎÛ>SÆ.| *ÏÈÿÖ‘ƒËqz570ƒ< ^VÖW§ÎfÏ3“ï´§m‚+uüÎ8«:¶ÉänéHù¿<,2±÷—7€#ÊÒ|×Ð Oe#S—5¯ ó„œÂ};|^Ô™ÂYzþÊtdô²ÃRjn£½Ms!uwxn¶QA&ÇŽ¨ÃMY]>ªl%íñL Lg#pU¹€1uˆûŸ6eQncl2ì ¿È^d÷Z9 Ë„öânÆä¡ìG~†ÿrŒÿ 'òdåZå*÷4Zá)ƒª 1^È(wß'œÉÁ!Æt¾"ó+çΤ2¹^z˜Á7þ„+-tÞ®ÑËiù·£*?) cV8ÒºGîOgΞ‹Š>òV<÷&üå†ü—úÈN {ûå¹èT/4¶ÒŽi!öËLë͵`zÞ{–¡óq:×Ôe oüwŠEÞðn Ðá.A¤|·I6z ö¢¸‡J)îpãöí¡‡Ü¢þº o y=b[œsc¹©HUU¯jZ)«ú/sRævŸ}ÿŠÉ.¼« û‚™Â|6f¦Ê;Ø 2³"ƒ~(iL-NÎ}ø¸&­ ¾ | £V€òóåÞôõvGæ³ÕêVò-)UaÐ°í©­"`_ö1í­Ðföô‰‘ath³»Õ÷•pkYÖj±5¹  `¿D«ž6*lÕt!\™¦MÖµyˆCÚ¤-@W¸çaá³î….UAð? 4x ÿOEÛk%XGЕýU8¤«Qï´dÙÎ…;é®úi9”5À„z®<ĹX`Ÿ3©–<“Ë ‚°4=¡.AÏä VëÙÙYe¼}‚€=;¤éÚ@5þ “‘|¼Ï^X,9.üˆžCvÉ#² m¶Î³2^9°m`Ìâü s©¾–À è×.v+iTTgr”¼7“¯€‚*ŽHº&PMžs:Ü[â>ì]ØS´-X‰ÇÊ/J´–$«© ¿¬c°7‹ÆÒ –ÙA"""&Aù‘.ÃfdÙB2 'Í!»‰œo0ÊÞ´ÐCœ^!2…ï ç5;;›8aÒ8i/•ê]¢›¤›Á ùWå׬´0€@ 5`Z&NÆæx™ñ∀UB:†Ñ"8¡†³~Jf j$mÀ€Ã^¦VJ=Ù jCÊ8ÈTÀ9âÚ$Tœ »¬E££åõ¶w6é3šª8èê|=@h߇›W^N]ݼ×Þ ô‰ÂlDΛÊ$¤ƒ»wó;uBàÚÅK-  ƒo¹`fyæ*p\ €€žürìS¢ò©‘-„F¾ƒ†„+sk‚0KŒ¤2W‰›¸ŪÀÍ`¶Fª,i´SÑÎ¥$î–A¤Å÷"Y§™#$s ÈP¤Ms0eËùj¸ ¢éCñà;õ”î­ëçmBÑC*œÀ˃¯‰$Òl³™Ì>\0qìÂÐ1°úˆøè‰Ò%Z†[ ±4=1EÛfÊ æÒÐñáà/“S“ádhÆFˆß`Ž!@‹á$guv)€UaDØÃÙ 5s×rfw‘B|VØW0°L“Q°ŽQ…á«ÅwkXSh×PÑ0?KÆïÂ÷\œÎ£ ´ç¾ñl8FæU`Ö?”ÃËýÅI!ž< :¢ì`©iÅ÷»X‘Ç|/½ürô‘Öp•¯Qî°½Ìüö€h\œ„ê¡–:Ø%Ûãûûý^wïóŸÃ^,fF¦åÇ^yõ•Ô“ÃV9¯B±—0¦/¼èÀõwe9?Ó±n)´¸O¹îÀ\ÜôWþÊÀˆgÀYÛ.Ù9¢Ø|ì‰ÀçÏÿé§Â°d¿> œc›“<­1üFqÐQhÏ/Û4œA®¥|6”wÆ't´+c®Rñi•*­«šIÝ?xŒªwb,ï)Gv5rY¾êŠðO{·)ÛšÍêº4Æ™ê= tiV®6àSYZã«k‘V¨¨ä VÙ˜èø;Ÿ+ÃZf—’g:Cu&"¹#Ÿ¢° 'W0¦X÷Xó“6õ8XåÇâ¶}ÿâ;”,?;’ù#`†'Ždl^²G ›Fw'æ™;_b:É•Mœ£ç²ÊÌȨã]‡¿÷E¯=ÎÖñ¤¶BRáWžq/P¶7“é1îfìGàÿ:Ǽ4NÝ${ˆÝä‡ù¸'ÌCxÐÈfÙ®püC½ `4œeÆùì}¼“=ôÒ8/ϰ$ZÈHÐ6é­ÊŸsq †å_‚¢€?Ð9QܼÛ;,AŸG©uli¼Aãé½ï~"ýÆün<5ÌÙU¾I–ØLºsó:™À cD첕eûÃHÉ~šá,ލG•We²šÊ?š½Í:¸%æ+o^Û]_  aé\AõáT/œ?|gÐgÖnoIé”㊷‘9$) `”ûäs³ßËÉx¨àÜ4,Æ;ÙWç-eÁ] ×g—‹TÇÂè´°J5Þ-~»&ù‚ëõ—½Ç¹DiÆÿâïý}‹‰¬‘¡¡áç™/þé¥çŸ çFoébdE—A§ØÛˆÔad43dÃÞcO4?ûÆ& ÉÈXÐ>¶!æÏíqFg”ÁLLægô—ç›ÃÏþÝ?£%¾ã^#œx:í’ø¿gÿh ?‰|±Eéêñ+”sæqÑÀvá]¹À€’½½¬ ±ßkÐÕ@¸!ýf0ñEÜ÷i•z‡tIZ¢á\)ß' nÕ$4[ʉŠó~'_S¯×I¦…xÜg,³p ĉߕ…”ãƒnò>cˆ1œ›d;ãÓápâ{âpÑÿxtHîÁ\@8ߣØê]0“Ø"ež¨)¿eGø Íö% ØðtFh_0K=*”#m :…mNûœ{N©S›1{”É,=pÌ{Ü ÷Õ}qÒé|œ÷I¥ÛN>£‘ÚB2úäüB.`qÖõ:†„¶ Kù9oÞË8:˜Mì½_pzÙÙ$Ÿ÷½ò{ççÜÍns/¥£ò~y»‰{ù<à4®‡ÎÆ8Òvów=|k·â‰sqe‚kùҥŧ®ÝÃâ µù 6N÷ÁukÚ~X1kÖÐ.¼esñlägž¡6[ÀÂB#¼çnz8 xCmOˆÁ›–a?:ã#9Òßu¸.\Ýge1?—¯oF>‹óbFî\Þ<{Ça÷wàÜiº»È3òÍWŽaÜ/ù¾gášÜk×r…7q‹üIX?BÚ`ߘóÿØæb6¯¿;gó>¾9›mrÉYâkÈÿq¯2fÜj Œk2 ÝìTù©€¡äçÎÇùÆÞ±ïî _gófÎâ†NzÇŽ9ð™¸êÏ‚v;×fåmåVâ³ œôÚÚu†dg¼zŸlyö#‚m| ÿ»wñnp;΃3û?󟜵ÂpÎsÒ,wÂ5{fÚÜg¾Žïö"Ö꙯τà³!󬙽„tÀ3 9ýBZ¥ü®Ž÷»÷5îçýàŒ2FȘŒà~xöU&ö$2ÚCwa®AÙ*u,ÁKYÉœ0 ¼˜Lr²¼Û`UÄÄ€³2µ™:Ï·öî8«<Õ@¸Àm“•¯Ô<ÄÔP@‡w9ÁNâŽN®¨½ªÆWâš-:)÷…NBYíŶH”¹w[òa¾„ëšl?kõ>áÄ` ÝÊÉž‘gí»ø’}Çé…ýªL»2PXŒ“ѸAe»*üL;ص§ÐµµI»·V6†¤{´å˜ü±Å©ÎÏ:‚A„«xhh¤2…xéØ!C3qçè¹åIJ3ØØ€Zª@J/ԩ̦Ö!i©tmíÄCì™ tÃD8ö¤‘:d×)w½Fp”:…rhž€0õ«M2–Ýß]Æn*© ìíjgäÝ®fÔᬓ¿­­6ÍR1¤D²g+¾«–ÖÆtãÆ-‚SªÂÞ§o…¶YóèXBÜžCu7‘•ÐÝ-‚µ¥µVr³éðY³t~NTÔa9K[‘Ùé)œß $uDV»YófOÛîÅàƒÈfh/RD·ê'©àÁaZR5]\J_ñ¶Hª8Žõ!Ô CÀe7™Ï{¼®žy7âGó¼õÅYʽ+t¸ÇÄgêšë¬Ÿ %öq› ÚfnΨD[”Vªµޝ2NUêÀyæþ3éüƒçÓSO]H·nÞIŸÿü_¦¿|ý2eî[IèÍ|’%²ë+H ƒ–€›Ê;“wî¤U䉦æ²û;Øs‚Jp|x¤ýfƒ=”f({ V[Ü$Ó}&*séoYÁ¦iû7Xaз‡fê X_I3×ho¶ÛEËè>àÄ#îÐær¾Ïp¥™èˆÀ…Ár'íáð.äj9#<¤yÐi¨­I¸1`^ù®¹¶ü%ƒÝjC$Oœ:ï‡o‚Û{”=g“ùZEÕÛè2Ó=p'»v€‚1ÀsîŠìQ–aOþýÉVÐÎc€‹ÙóøÅƒ?ç‘%ù¦-WÊ”åØ0e)ñ¬²Âê¬&Q€Eì×X Á9YyÕ¾ííðœZøÏ ú`ÐyðJ¡Ü³‹Œ°†ªHKƒQ¢‚#û(Í©$x³*‚42î÷}ö†(ã¼ݤ‹UMÊTA9K+žØ*‚_°E”;û8¥9,‰C5ˆ³Çä ”aÑ'3°ƒ›Œ“Ÿ~˜Kfo™¤MAI¡_áAzÈ ”fq.@H„CØ Rå(‰DYÒòÓdçll!AXÖ!&@rŒ³#R¶Â2 ­8¼1\†ðÂD6‹Îq¯ŽêM²–,ǹƒ¢#Œ ±@eÔY åtD0ˆˆFÕˆ˜Œ£¡³['×q€9êÉ$”0ubæÌx§Š„¢’ˆ‰¢,ßOãññëéOÿh>=ùÔ‡ÓЩS!øÓg/Ø…ŒPý0|ïž{;ð#î@0Až7ÄûæÖ¶H£R‚%ž*èÙ1  ~fiœ¸¥P®²±!ª€Ð*àJ¤ö·!| Û9”JZ5ñ³Eé¶¥]ù%¦â—Dt_a ÅÅÏT„ü¿²²„‘Š2A0f1Áq€æçèÍ3¶÷cf öó{×½øÙí€x£‚§À$Öƒà|0ëÉŸ'Æé×Mé½è ¾ÌÌ-Qfq•¨lŒXâ ‚½•i<íÕ:AiÝ%–S2HD~'¯1ÃO…Üþšþ®‚{ä ešû0pJãó›{D[FbžÓH @é=–ÐÙÂY.¿²$ BYæŠY GJÅ@3íOÒ„Fœ”òëCø¤†„1*S\¿yükJ«û8JV©Oäí3ý, Є]y§J#÷fß0¯ïyЏTÄ¥9ìñõKWvV)Ûké?œAÌK§¿Žù¹‘³F€Ú7êÖ¨í&á»gãg÷®·Û¨ Üº}›ÝÑP ,—Z`VeÄHYtH=ø0ðÁ+„åS¡ ÌQUèÇ… pîG,Ÿ°P2 ƒÐ…ÇK£×^ÇÑò­ÔJVykF£–zàs=dÅuàVãªjè™ûÎ@ º€WJi"“ÚÇÊv 7Ƚε…©i§…L² po+8~’œñô ßÇTš-³©CÎ2ñ™RåÄýÑ #¿fKBÁpFïL¥rÖºŠL QÂPžñÒ°.MSaÖ–ÉÕf…`Üç™ ¼³§¯›`¢F‚ö¶ÓÍÑñT$k)doÞá›È¸`N¶Š8Öß# MãÓôj\ £›e©…AÛel³ÇÒèîúòþ%ô÷ÌL½!@œß4 ó ½‡Otž(óÇË èµÏ}ogÌ[ÿ"å\Õ7Ô‰òèfó7–ÒßýßOøÅ¥ö~zöa/ί§ë¯¿žfÇÇÓúâ<´`þ9‚µ`8HO 5(Цƒ‰C¥÷î…°°<º`xÒ{ ‹Î}e3#Ý:8s>Ëa0Ñ(賀̸(¡Œ½mm¿S—S÷+]ï¥QËuºš?kLðL Љ}B7Ó¨¸Ì\rÐw2¥-øð0{¿í`”¸È:ëÈ9væ 0³ƒ½‡`äžôhÍ“•ÿò_>›&fnñ~ᬣ–çj0f#Yk›:ÁY‹Ae®9œNÂg'm“ù¦¢ì½„qÀKXœ/ ¸¦AÔ²îÊÆÊ1&2m€°¤AC^ëÚ LyAœ?B.ÿCg´¤ϸ¾@ša©ÐC¬)5f‰ð¬ |· £¶r±ä_¡B㤆__̪X4Â÷ ÇÌÊ¡­áý±¾cK¹8K‚‚Vš™œ£á¾šì±Êµ„i†ìCN‘13C£N°{e‹3‰|oŒÇ<Ê8Oaf‹}VÔí=”õXŸesßõØã‘ðâ ¯pö­1‡fÎ6ÛK=ü`Èa#à'Öæ¹>àÜ’vÌSæp“½ñýá¼aÁ1çp°^öf“ “vÊÒècK¿ó»¿z†mmmé¹o=›¾üÅ/Þ˜±šUåàœ˜¿ãGiE>—^Ôa5@ëÜ#§â·È˜ 2Š{ÏÚ_—{-Mòyþ~ /Oþ­Á[¸øŸ‹Wg¼äHëþÁSò´Þèòé ‹¥z±¼jÊ”jÔo%óiq‚Ä 2€tÐ…“A”—‚ŠÇ¦ŽÔ¬Ý‹¼ÇÿY†ØKB›œp¬|¢¡ß­åçpÆÝè,8%¤EUŒeæ˜rxÈæ¥óÁ’¹eèþŽ#¾+#È¿‚f1’%à³l_ÄmçfPGÀ¤U‘Áêx<']Ò[]&€_ÒfÞ!Õ3cX^s‰ÞÙ:U´3ð>Þ%r=®A^Ó€ñÕ€<ïÑxmYÐ[ »â>ƒýæQÆP&1¸Y§+{wwá¬Ô&Ðvâ>ßëDqÙÄ™;²6îUä?÷ÝïÌð úÅ;Qëø›—ÆÞ"»qÿ¾ûÅX¾G¾­£X}È•y[š™KŒuÊ‹ã]ÈT:¼×ý“Õ9ýpQ~.ïБ´8’oò–8·Hvp\Ö¨S,LÌ-Œé·}Šy09y$†Nî3` ;aO^ |š9³\ ·±'~.ï Ûð¨³$œFÐneEèM¿û¼óæ~a…a6Zlù£^í’Û=ðrîÚ`•5ø*¾×™¬Œ²¬Î8æ ÝWXr^¾é,Íäpú38C#?y^ü‹Ë=EyÎN÷Õs1;ך¥çèœe{¹?›ØÑÜO°¶y.ØG•±T>ø™ç#ÐŽÁ½…Í=QN¯Ô9|>ÚÖ¸!¾ Ê,±'<¨Í1|§ç#.düÍýŽâ;ågâÒ÷:-0<æ¤c…qʫȶ¹OAË´\ÜÄa¤Ž¾Bçëô„·ÒŽzç“‘7 ÛÊ>Ÿ½ÄõG¼Ë Qevß­±8Ôw’¾þõÒg>óú\W¥ó÷ãýVlˆÀŒe[ÜJù9yÉ,ÕÞÔ ´ctÓ‚LÐ L,F&¾ÙÏêô+Ìá‘GÒ'ûCØæÓ¿úÔçÒ"{7ý× }ò±E ³BK7{8xl ‚Ö …W¯OF»Ï&Ú}Ôñž ì]%`NÀ¾óÒŽ çNhÕQÔ¡ŽÍA\ªs³Qàäà˜É¿9ÖXUÛÈžb‡Ù”÷ãÈ–O°¶TiÛ2Ô¼’ñxù‹-Dl©`•ƒÐsíA¸èÀæè&Ï…L>ñNeT[‹†l||ÆÁíeª%p¯{Z®iGÐÎ#ÙǾ],a¡ê—¶“B½-Ö ×ÌÉäëCÚ9p´7a-´8™™[IÓV¯"ho¸¼®P@W]¦õ p¿šÙÄ!øt”¹ä>âtmU`2âõÕ’l¥*ýIT³Ùãó²22{_±²4„¿’,-2Gn#'OR²¼…eƒè ™©D.9^É_opÉüëÜ$KÒ® 1@ì!Dø›:®ãCc~V²È¬…zÄb€b{ôYÁ0Ôß7ŒÑ«?u¶·a˜[‚‰+0JDö™38Êb³l ÒÓPh “ÒF6ÉÜv þ–ØÐ™. ë´p¾ J  D¶øÙ² 7™§åˆboe_4àõŠÈNH}ÍöÁ¡LI(ɳJÏ~®¥w”™ñ ¨Å"%!ˆ„ÙX#:‡1ê\÷%.‡~¢+F ¹Ž˜¯k;ºŽ>;úýÞ¿³v@Xc pŽ2„QáÜÞ#˜â"nÕ!(4$bö) K™r PWยð¬S»×ÙU LK£Ù ¸Š ¤‘`  ˆYdÄBg%œ Q¨<„W¥ÜÌ4£UÖp.Úsz›Re:œ«ÊÉOŒ oƒã[B2ð6XåÛcŠž…ø‘èRv~˜»™<@º>35IÏî×á—•*«Eë:Á¬Ü`YAºÌ©¯gt[:TáI†íû3Íäi{4L…S >&n‹íy;ÑŽ¿½§aß$£8¤â.T/m¬¦0ôóY ×ñ½¢,/ø¬À8Æ÷òBi‚ þk¯¼–à¿Ý8,4l؉0gnü+×ÑoîU&ÀêÌÏî¿rÛþèÚRzÓˆ ©ÑɳûŒªÕø#ö+´ø^ëH½þ*‹îýõ¶Û#¾$NP21~'=ñÄ“±…`ãâšïUIi”I•i”¡üNeA§»<Î??ú•üTbÅI 7¢¨ÛÛ:Óû>ô‹TºC¥eŒ u©§»#xZ ¹¹ãñ&Á¢ydE•´¹W_M/ЖAEqíÑ•ôÄSï£O`æèÊ£$Ê žùÜgÿ}d¢Álæ†Q¹î ÖÑÈZrãåÚ,“m&å6ûa9Pdc•iÃȆA@'™x¤MYÇ›ÆÂØ#¾g$þh4U±WæEÎ¥|e d J´øï^kwû2ÃZ6žôËyÔB¿‚žqƒ‘Ä«ðêQ¢ª]—4QšÅÐñ¬?(;(SóWdå»oÊùÒi¤U0J%~Æ€ª‚亽4J¹>«Só0u¤iJÛ©´Fe(æ¾±VÂA¦[C†ìYÖ¾ýË\³ëÓHªb­1°§»jÍœ‘+eÊÛ&º4Ë+ å¾Ó¢k¬ÁÀßÖ¶¦0*¬’e3OÕ€ˆÀ渗ð¿NX•Aéÿ©w´ñžœâÜ‚^E{#LZÂVçÀ´¼%7 iÕ >°÷¨åÓ·Èru/<_¿ð뿚>ôËMý=œÿv½~=MøxçÊ•´„îQ¥aGÜænò.ðöh¬`ÁÓwMÊnBFW™Õ¬#OúŒYw4ž«¸ê$q-–Ü?”:g,§¶EyØ Ë RnTA 9;Úöy³×X9‘ê:¨÷í‹â¬ÌHìyóqìH@IDATwCwâ,ä£UŒ¥1<œ¡ý>«€©jdÁjz¬©«•ÐíäiÎyYõ0òà•í¬#¾ˆG~®D§•eó"ÛŸq3Ç8Å}Kf\¹|eZg7ðiFÌ ŽhKél QwËàI£Uw__´ÑšÈsê­™ñaL@ UVqžÌù³ä459.ñ®صڻO˜Þ¢Çe™*âõ½,yžaxVX7ð¯ΨŸd™úY`0í1ð^ã£ÆïJ(n¸T¢£@sø,‚˜X³ckg¨D~X!ä©÷¿?ýÊ'>Af\{T ±¢Õ7þâëéýŸÿ§flÂP%uCðWÆ[Ú>Ô(¦îÒ?r_ú…_ýÍÔÔÞÇýÐ5Ρ†m¨4“’GÌóÌâñøû­ù+›ÍÝu¼5SøÿV逗tà;W†yaKûßÇ]q‹ç þžå£ÙYf²ŽÁµ^Â|¡Ð M­I¯<÷Bл*`=t |àO´Ø€V1 8£óÙêÁÔù9Óóå{òy§8íT¥a~j)´Ûq]ø¥¡Ô¾¯¿â¿¼ ^-D[·»sÎä4葆U¾T.§"x,óN öˆû+ÀŸ*h‡‡â`-å:£o3öeå݃Ò9ÍdÒp³–t~ÙÃ3 `r¿ .@~‘O™kòï·U–4_Üô¿žž^š N«†ß@' ›Ò-™LÐ^²¸Žä÷ÛL?iœsµ´Pz$õs<‹µ!3±w¢¿N=džv¼“CˆlRÔùKµ¼Ç}ó~lAÜ«l ýƒvnc× ,]w›3.“w¹gà7ÐdmŸÌeßÉGª)¡Hv–s G0cû>å»R`<Ï1ƒÃ÷yÏÉÌpƒÉ´ç¸./õ1†ÑºÉù“ ÁýÊ|Ž»ÀÀ™c›„3¾ÏÆ–ƒrr€ðë¹ë| xB6=r2Ù«ÕÏ èŽ ä\õX¶%~wýUðüì~Ï ÙÉy0_ï‰ÒÂ0Ré¾mç"ŠeçÁ,ùEÞrìM8²yPž$Ì!œ8eEÀßü×ÏÜø85Î>ü‰³á+ÖÈzùÁÅžUuf9hy³ò*ïÿ„Mçj[š£ÌD×$ )cE`p¢#,ôö,x¿S€'ø¡30ö<`%Ãeß©þÏ«bÍî¥g$®‰?lEì“UòŒâ^î‹J¿çó WÌC%¬9ÿŒ§>_Ö ¡ì¼u¨ˆ[Ú'¬¬¥ÍCyEY£±¹!;s=¨”.^¹žN‹êD7¯ßI3ó«ÑæÎóîMÝ]m”€ž"py79=‚í…6r“q[Ð'³ÛO ÷€G¹ty««»‡f ØÉ4>³²:O__;mû‘a¶ÒØøT:}æ¸ÊDzýÕëi¹S[¢rD?™°Ð€é©9ZP¤“<³†tõÚDœ´4Rýcp°šR|8•Ê(¡ÜH&oqs gá" tf'¤YMj7ÇsnÞO=-´—ɧ[×F .Àá‹N¥³»1`m«éÎÔ|êH»7.¿v59>}â7þŸÀž|pvÄf{ÏáüŒ/ÏÅË÷뫱ò>6Ý‚¥Ïpqs• XpEZB64µžƒY"YÞÇÁ»‡®RFiíB=Ù¡T’3P{[”ŒMø2€ÁJZü^@Ï¡9‘iJK„zfÏtÒ ¯%óüt0‡#^2ǃN$dV¼ŸçÊñI—˜·46èßÁÅÀË ²–°1ÌE „º±ⱺ·¾íÑò4ñ@Y×1¥CUø¬š º´Ì¼ø(=Ô6áß!>¦vçêAÒ“*3œõ‚/VwŽqás¶ÿ0øHÜ<„vË¿ó¬[¼uò:+Œ­áÌ´b‚™G" k ä¹º®º|iÜ[÷ÌòåQÖ~n äZ´!Úl”8›‘‡M«è¡ò!éÊ mšzûÓÙS§ña­¦úÁ!xiS£÷ïÄ‘)ýÚŸ‹›”´‡>[ÝOßÞГèî[È7‡ÓSÚ¬.ooïáAœ¾ ôèf/:?”î;;Ìþ•Ór"ÝÇÕkivsTvn º¹-)¤ZB47TA V±´ŽÞ<›FÁ§uæc`º6IiN }m6É×^NÍ=Èß﮾$ µ}ÔŸ'ƒº•$Á¡¡átj¨÷Ó>Ý{‡úäÌ*NÖZ‡Ð6ׯkWçÓ•ÜdìmÄéß•þà?ÿµôÁ>–žyæ›é?ý¥tþÜ©”'ù£Êg¥öØëe2ì}²„AÁó8Ò·Hæíêê& ;&IõnȤ#Ú ê ìÝÚ¬’NžH/]¼ÍÙ‚üÑÑÞ6+®Î-§c§‡Ò@g+~ÇÅDë /Êa&è.³öö®äŒ Û €io{‹=¯åøqœ£›ØòÀê%ì;Y+ l ¶áª_KÈp¥=åITˆÏ‘YöÐ3á9’ñƒW×1¿\™I’8àQPM\ª—÷²ê‡âxàóÏ@–‘ÿcÛÀž}Îwwå«(7/¶ßÚºµñ(‹ðÁ4Ø¿„¹}¼ŸqøSþ@QM°‹ò§}ε \½vƒd•sð¡ØuøYuzç‘ùêêñ##Ê“ ȃ¶˜ üÞ`.ÕTuO2™Ä@iù86wõqä 艾ëù<2U+*+‘%±³ïÒJ§Œh…¨â €aäçзQZ«è¯c©²âŠ)§hIyh “ñ–Øþÿø_ íJ;|é™/Goè*¯"ÛÆR™³†C! O-ukv‡6?9––†ŒèåAf›M^B(Öá×@ôÊ&†Œ"£ˆz`“½²Rù¨¡/{¯›‘DPe~¡ÈÚ@!Hbš ”Ä`o,8áFBgO…U²!TêkªÖÓå]úLad±—˜³}?@þ5_B%±KŒþ¥ÌyÁàeŒ«'Ï¥z×È<‚ˆ36Ù©ÿØ—€ pçåZ#’K‰/\«óöl¼ï'}—#Ü»Þ>;àù{qü!äzþ ,–B6KÉ’-AYGªÉ>ç0¯ Ù â  Dr×”C Tjí£†Ø66*2 ˜ÔBÌŒÌ,¡dX>Ãh8ñLç»9FïïSaDÃj¦Ð55>>M@ÍŸeY…Ì>ƒe!¬ÿ„øòö9½{3ýîÈ ùÍ¥b’É0hÀ44˜H;®@5†_zíõô¹Oÿ?©€àR…À+ß´å@d("\Ef”]aZÁØr¦«4¶ï}Š’®º¸$Œ÷(wFVÁûTˆáÁÐZB¡å^³Ší`K …ÔˆWùS:Dñ?Ý›:æ)~˜A+þ€91–¼<mùÂ’J@‰ÒB VOf†ýËêùlÀ•2 )kDÛ^¿q!AyW#¸å¡uýõ½—¾ø>硱F\E zì†O~Ð%j¤çOOo0u´C1ÃSƒºQå5D¹jô9D r×¢½|.Þ¿Ýûëí¸WÀŽ‘¡–@7hjtt4=ôð…¨Š`Ô>_þ¹¾À}á;dîÄKdÐÏû³øñ1• ÁwþØUÂ/2Ó.89p|$=ýñO¤?ùWÿ<äá&Goss‹!ç)?‹KDŸ9sνµ46z c0=+ ¾|è»ÒYŒ$òZé‡lýðOÿ?LüðÇX7ȵfyl wºž ¬É mñëè33DÅic ŠƒmÐ ¦¦±ñ'ËÓ˜˜»Ž2Ñ-‰¦>JSòo#2¹÷*{»i*,±y쯎-K¹»ÕîwŽ,¿33¥gg¡€ñ„Kv3ÓC©s’r(wR [Í8ªÌ¨Þ‚ÞhÒð -1ûHºt€±7¤r¾ó‚®± žÓˆ6¿°gœ9†5ˆi`ôÌ0$2–Æu÷Iã¤r¾k×¹è:¤mÒi ÷Â2G(v|¯ÜìÜ3:Æß®•9iävmMd¡VOŒý¶Å“=Ì”ª3'ÿaš$CÞy Cê.Ýdô«`k,ò¼,Q÷¸dUÔ4„ò _»±Àçà^4ˆ«ÓþöèO[é}O8}ü×)5u¶]=ƒñãÅkiìòexÆS‚'ÖMæ¦2¯’•¾Âð{Ho;zmÓOÔ£j@),P®€ ×B¤¿sƒ¨²÷(ÏÀ°ý' 5ÅÙRx6j2Ó¾@P%[A˜Ú5¹_ð-aÃLv×Îpq{üãü8 Ö+,4Žï}±NFöÆÃ•"{ L,áÈå> rìsg©}ûÙŠWòI<ìpš¾q3mg}'N…qsyð¾áô[ÿÕ¤Ïþñ¿¥‡ëpÆ‹%ôB÷×s‘>8W÷YiÆ ‘üÐŒ`׌'¿†4š™…’SFNwíçËwÊÌTGëĸ¡Þºò3™"Â/´BýO¶}Þ³Öá^Ù™9 D i‹L ÷Åu1$0”ÁÍ“þ³’±|Á˜êæuµ„6Äòt³F4ú©SªS»&˜£W!*ùÃ#wl±¯­70ÈÝ&ø¢ÊsçYkd¼1®†-³-Öö)ýÈ^Ô!×tõvÑC°±Ôg BÀ(h 8û陨ƒ× ƒa•Q„éÈbdÝKõ44éŒ(@×jÀPGužZT‘I” 1ò¡×;]7ÊBN@>óç±G)£dÛðJ'Нtÿœò[´Éš¬~aÕƒÿá?ûýôÞ'ŸLÃÇÇ;Úøü ¸óÕg¾F¹¶ƒ‰‰)ÖÍ~k¼žu™Q«®%Œ¦îü!¥þþößùÔ;È8‰Èc Ž<×=—ÿÀs˜ç[})‡¹S!wJÛÞê ý y¿0tƒW¾Wv'„œþ˜iôÇT¦ç ÏÂ)7c€—¿˜‰–e«G‹€M³Ðs``85þÒo³5´°øw8¡ŽáܪLãc£©Ÿ 8ò«Ë''–À=ÊnòÎ4|²ò 6ªÕt#íÖÆ&gÂb†xsýÚh_}j œ«Á‘½=@xíÊM2Ú†ƒöÜ!›ð›tIÇÓ™‘nø[>½ôÊež«I]=méêÍ1èÐ*e>[öqåÊÕÈ\“çJ3 DŠ öÕ½.r-—S!†ÏL1‹M:§œfµIGVcÑQ#­m„Á*[Ø¥‰ž«Ù¬ЩüÕɸ?³Æ[Ñ©và/S³A÷¤…:«Úš <_oA—Ä6"žÁ¶"?Ö¡­|S‡Ìä=‹ÜcðAòÑ4cnÂSƒ.òFÛÛô¬d ,ˆAðŒ©izƒòŒòôAùtp 7ä08ÑaoËÀ&÷(›0fÄAd”ÎEþ¼yûÐàW>¤ãÄuë\é> ĻČtù–¡ãÜÓGàÖ4×ìÁÖ–¦tíÆíhÏUAà­ÂÚZp4F¹8Ð××jW®]G~4TGZEzèܲIb®“騱þ0Ê¿vé:8ïc}ÚšNŸb¼&‚ÜÆHôhG¦¨J¯¾~ç ­‡H´R×8ßñtêø¸2T™ºB ›ãš©<ÐÖZŸÎ=—ꥃÃCi'Ôµë·xNeàÂsyÏ£÷_¾xéZ:yj$)žÿöËÀ-°†\½Åùœ;3œŽ±ç7¯^ÇéCÕTœ!¯¾r‘Ï‹ðDà9Üvßéi–ju[8†‡úÓ›£ìÁªÌIš¡ãè==ÄΦË8¨xà\Ø£_|á5×ÀAæ­>õøã¤^äÑ+/ Ôá¨xþùƒT£3¹±¥­.]xø|Z§ªÖÜüïH·)k|ãör@CȽ»8KΟÅQϼəu kök¯]#˜m<ªMÇp`7QuªúôÀdÁ'i›‰reô–¯Ä)ÑQ‰CXÖ!'|™Å‰4­$ø:bæyÿ Žp*Jd·ä¹0w>7È×òíס‡ äi•íÓ¾R ¢-ú¡.k_AÆâ™Ú¦²}Ñgª”»q¢ _–#yp¤œ{ÄQÝœ‡•µ9€ýáØôçZp[ü”+8£Féõ] ûûâa8|sÝÀÌrûñ6‘Å©N ˜¦äu «õeŽ€`ƒ=†F†Òßúð/¤ÞÞþ(Ëœ9‡ßZ®­|§¼«ŒWB§ÈU*ßP{^…ƶÀ{SÊöîMô>~Êãäk{Tíï#cœO´ÝÔÔSî8°y裡f-3äQ•–§–þâ/q ƒƒb"ÈzúT¬ìqH›\Ü[ÜÞ‚ëUµ áƒ9Ôë‡-º’’èfÉë)g<Ÿ·Š‡83ýŠ °\ß³§.À,…ã åÈùò\Ó:ó¥aYe³ ’ÕŒ —6ÙË\g³:¶ÂÀ×#Y ¾g€–ðf·}¸# î•óõYE°“Ð-=PÜëLo°DàÖϰ—ËÔIåEÒ8ù6›Åç”—ç‡MøÙR‰àÑ×n-¦m¦Ç†ScwwÚ$ Õ,ñEdðãçTª$¢§¼É©êÇÛ$½ÉÄ£=ôÍsŠ2ÛÀì2UØ,Ùî9uÀœ£2Iº};²B8º ÜÖi}áá‘ð#ÜšœK/¾t%ÆÑƨžc{»¶¶•×5* SøÐ¦§ÇÓ¾ôíHÚ)Ãñ;0ЗúúT#ïª{ŠÃ[Á¨Â Ö±oy{`O(ÃîPÃ^ɧ+ö¼Ìœ[¡Ä J¤Æú†´Åù)JŸ †²¬Iæà…ÞÊ_ñãü•}!• ZÊò%—f&‚á5SrcCÌ¡½@šC _{E"‹v1œ‚¨ÕÔî¯ö÷aºëÛ™RC¯ƒo¿ð<‘4M#"†â"eý–)G?<Êävwõ2÷»å;ÞYW.‡á† OMoäD~˜í¬WÛBß@42 4–¬ÖÁàAKø4¬X²Nº3烛:ÔÓž¶×a ìY%@¹É=дx'²]”e,0wKÁïpPó5öX'å!ÆÊ0´ˆòü¾{úÛüy,:5TFß9ÎLcŽe14niœŽè*î¹E •í{×;{TìCø†9÷\0¦%™ðGì²¥‚†:{)8Hˆv€ñjð“Ú ´©‡©3Ê:Äd~a=Í ŒÑevÒ‰ÁBš%úSŪ±î 5"´èlƒ€íóý:„i-ìÑÁ‡È& r2qDÆ­ lļ†qü"TØ-+_Žiq~'^÷°ñ;§*M Îü²ŸàEGûôÙÑw?÷cüî›Äù„ŒO>Œuòömˆyà‘rЇÅJIK;áYUµòOa;‹¤Í²ÈÌnTØU‰³Í&Ï"„áÊRÀâ€k £0Ñ‹fUæàa[ü±Og< £JÁÞlíZ*G(蜱¤J¡Ê¦å}ýYÍì’µu‚×à;*':ìE% æä‰ ÖaÙ€€«¡ÀuýêU a'âYé‚N‡ï¹ØiGÐøg(œ“{çø=|çƒpÌqï&KÿÐq”oÊÓ¿HyDaÔÈà0ø³?û„fáÿlìï¼åÞO?;pÄ› ÐZ%»[9óQÑF*é ‡œ8é㟣_î.Jr,®qñósù˜¿üM…m™ÔˆèJŒd÷?x!ߺ‘¾ù—_C~ª …1‚*1: £ õ´BÈïêîÉŒ(’þÌWQ[pžßCŽ_pLÞº…¢øjzúƒÏ}ps…ϲZó Ë‘‘áú 6sý–°÷w^z‰Bù×ýÝùÊú)k¬1¢ÝÞ¢öÕTnÏAE.ƒ6uhBÓ0ëÔŽ~çü®b.~«°«ÀHcÜS­n·2²Ê…NP• ß $Љæ>hHQÑòÞ¸Ÿ·)c¼ÚNÕˆžŽVè †´íÌø¥KL£œë÷~ Ž+ÍÐ(u4OׯÂÒÐØí1ÆÇ'b]ÒZåç‚ì'Y†ŒrÀyeÁ¬éK£l+†±v ³Þb~Ì-Êž;Wþ‹HqÖé^¹MÎÛ,^YÒiK½êèŸ%òšªÁÞqtÞ9ç,ã^¾ PqË‚9 ¾bDÎÈ=Ï!. v/3C“{eà†ÖâÞwQrZãå¯üÖo¥|ôéT†â=å0^½ðõ¯¦é›7‘Åฺ5î½JÄõA±g|c¡VJ÷õwÕ§þn –8|¥¡qnÌÍRéÎC¾QB!ž]æ_ʘo³å•ÀŠe²QÚ  ¡úÀï.Ùxå@«qh˜Ydæt ”åmÖ7á3 K9J|97‘¡ÑX^æŸmîQ?‘Gh@ª©²Ú=ËwãÇèUU(ݾ°A»‘ Cy‚ -±«R/ÌkD;„O­cX^Åp%ÝhëéO¬³¥“lô?žÿZezåÅg9#OVƒ©†ÙÌ™|Øgœ,ËŒ9rìaÁ¸ÝÀž˜¡Ù+Üã9EK`B¸”Õ`8¨„ :¯ý.²8ÁM¥[L ±æbfÞ*ö:Üá±± žE†€¿†±”1”+„aàIÃa”%>ʰ†—°Nå`3v„ÏRÇŠ•š à>Ä`è„C*à ¹ Ìeì7Æ™.ªM\ÁØ.ŽLL°ŽÕu—1ªùG¼ñl;ûº0˜  —Üop²Ìe4½Ì¸‘Æ01>Ïœ(î…c*Ó áŒÙ##E<-ãÌ…U÷oœå^í-è»Ä{á!ŒvèìîC†Žî{ „ibÏuˆ-â¸òa7è´ `ö¨ü»)ÒIᥛÀSï{²Ë+±ã·Ò¿ü§˜®\º‚á†Ö4d|D¶>çýN¡5ÒÀœ[V aËÑŸ÷Óoÿþï¦ã'φ³Ãîd‘í£ÑZX®¼&Ïq3ßÚ+;Oæ]¹wýìvàèô噵 Ç¡ÏS~·ŽÜ-¼KƒÅ÷0 ƒ/ò ƒ7vп¥gÊÜV›Qÿ¶|¬ãÉ¢b ˆa™qåßìY¿ëÝivâvZ_˜³lH ƒFµóT«bg, T·ÂV‰”ž c1»Mþ!Í´Z‘4L{@m Ùnðämô-3¶È^sŽ–´=œ"3œ<><Àú þZÀ;u J“û±ãµ¶µ‡cX¬‰¬Ù™« Øü¶ awAìg/F÷(ÇÌuįâœX¤…–åk•Ç4„붪Ê*þ.œk8>§”Û¬ £Î¸îë Cõko<1Ìyì¤ñÉyŽ9ƒýØÅØÛÚÖ…“ŒÖk7qܶ’@T—V·®A;¥»–YßA–jJ#dôVâèËûú»¢íÐÆä"ûƒŠ}ÊSšôØÐ1ÖB 2vÈ‘‘ahz <Öl/ÏBûïÉÓ§¡ÁØ;qçž¹³ØuªÉ\Ô–höäàð0ÖT$„—ž¦Œð95‡á]y}søê RKº«¬»™y foC+MŠ[Ú;ÒÐñ!`™@>xÄŽäeíEô}Õöª=¨µ?u2汈½ÊŸËq”-!+Ô‘Í*ŒÀ‰áù]ÁS–‘ú(ukVÛ,çVż!öà•{I°ûÒ6°ïŸ5âL­Ì“Ëœ4¼·ã<êìí&ÁCgr sohZH Šeg¥éMM5©…òÇÚÇÕA;{zâÞ…Z¨ƒ¾o^žbµ«JÕó>é@{w8MÄKN[¥YX{A]àBKjã³ÃrÇ ÀI{U ©’# Ñ.×ÈœZ)õݳCð£z>gWFÀ©ú‚|§9ÞêE: ›ÚÉÔf¿„9ËZ[eGy¡ž6)UÊRœy;6óš&3]Íz5s/ >–Ö,¢ŒÓÔÚ–pTêì¿÷°Ó+ë¸6°@ÜVŸ)à«æ½V=X&ðEÙ]YÝ 9ƒí7ÝNÀ_-xÇ´Áÿ6ÐÌ^¶JLÕàÿ4P-ZH¨* èT®ª‡ïWàÀ64ÀMëdÊ$ááh§Äp Áöòl©G5÷œëà ¬aÞmU¿jœªud9êìSŸËW™5Ìçȧ5œù6gQ*PR ÁZ¨rx@æ!sÂPxR¯”±q2e¥„³lÎfœXÊyê¬ Ôkÿá•ÕȘdVë:Ä1ä:!§©‚ Ööö6 íìÙƒ=E/æ ðœø|¯¬õóp)#KŸÍ¸/4XÀž©´¦\¡îWD'^Ë[é­äI™ÌžÞcWÑúO 4!+‚¸Ám šŠ$`Çï`Ï!7‚o,m¥ñêDdFì"ûEbø¤s±³º‹,º¾D/Î8hp²<ð²œxØÖø^ýx•ªÈò㬬ý Žë\C PW­oVŽt>ùj–³­O2¾¬WÑ-`o€uS+rpÜ Ïz•ÓÕ“½)Ý¿8sÙbKþ¥Ã4ãñ´:d} Oü¾Ë>:ö>º—6¿ðUp{µÈeåÏÇ{ x÷ò{¯1Ø:6ö&ø÷@M´*{ñ›ÏBÚ¨8F`ÎÿŽVÊÃÓWf¯bÏ ]‚ÖæCÔ‹-Ã^^k½ ù)Øþxý«Ç6ØÐˆÞϦ&¬¾ï|êíï ›}&½ôËàBšKÐóÊ×Ô§3§qüð,“•~)½òÚeZØ”וÎ?“>ôÔ“iÁ?Û –™Õ½4½‚Ÿ^³Iùö]ð¯ :üð}­©ÃŒu¶”­¿V$à Çz%øÝÕIŹ“¤ÇßÿT€&ÌÏ/§Ë—n¦ñgÿ74Œ’òçÏQéâLzÏ#Ç‘ƒ¨Dµ”Ƨ²ê«k;éÙo½Ž£¼Ù¨/ý£ôÉô­g_Oÿøûg>u¥Ó'GRo‘JqÏ=÷NÜ‘HB°j·í¥#éYì=K-µéó_z5Ö7áñûÍ.´À÷Òí_ÄÿX„Ÿôуœ2p|(ÚLÛC¾ QÏ=!ŒÂo…3ñ W¶Ak6.Ð;r>JÆÕ¥tØsì_ü1ðL‰Ë2ÿ*ˆK+•êFÃHž˜97¿¤Á¶˜úº[ˆ”=ží‡Ìa,ë@Ñ0 µ eôúÑtûÖTj´£\µTÒ@FÍ:e?¡KQ pì=Jj†Wq ªJ{+÷OÓ·%¢Sa^ÿ„²„ Jö»2 ú[Ô±œ#œÔé—p!‡ãò;ÄÒvaá;|uü'ü×ìH{§93Z«©¨È{ü^¯Ñû Š™Sˆ„C’‰7 \Èû4P[†èɆŒˆôEq”;gÕKàaðK™ZE¥BEBYÃÃ>8[‹â¨@c—1öy¹å¤åÙYÙ0IF?ÛïÆ £Qá¯Ë m˜‡ÊµÊ§Ù•ðV•„”%×1 î"@Íb?tPF“ëhË ¾{ç³sñlÄ㈘súîû¾÷wçu¸[á¡‘L“ÞtùêK,ņÏÑȜ XÅÑÈÆð~è†WÙÏ÷þ~[î€8¬â}íæí(#ØLFewwo¿*ò/ÿˆ÷?èd¹~ð=?èyBÃ^>d¿O±y” ³v 7ÊŽÞý(À7ÒüÔ$‚y]d·¬«¡Aƒt;Ù©—/]BÑ(…͘_ýõß ŒÙ9ð㸣¢*ÿ·ÿý³U1þó¯<ƒìÝÃû÷ÉDèMÓSSá4á_ƒ”‡•ý×Ì&Ë£ 1ñQc %™Ub4¶c€Öx¹SƒCšß™hBÃÆÜÂx$‡B{4–9·0ä°ç<›Èðbk5ôW#;ª)Où£¢«Î0Ãû“‹ŸAØ»³§F¢<|†B ìØˆ!/ëñ\Ž2—eâêÔP%d!¬Bxö*vöTA ÍpêÀ¨g+ £èU ähüÀF# © d&[Æ@z¾_¥|[åŒuåÊ-I¨‘Œ3Çç~ # =:……%•&³|¥ÉÆ}䑇BŽ·ÊÈ,k•´iì08×=3 ß ¬ËkCOzv=Ò÷J‚ymð€ÈØB_ð̲à*©Ú±Ìjpíð_ÿýôÀ£ãn‹Èö©›×ÓØÕKô;' ã5[ÿuY¶oi™€b2^œ|=ä~°·2ë¡O.FL{5BC7Èä94p±×ŠGBׯݘ ûý 4>ƒâ¿’•%ìéjÃŒF0÷av~8"ú£ ¿»®- |ßL¶œtÞà‚E2÷á1–bo!cëÔ 28×ut4u!ǰ"‘°UGFvK#¼¬ c2?·÷ô¥ÁãôŒ$ zy²u“+ilžV%Ș%ôB÷FE8Ƙ…Fž‰«×RËF Ø·0 u£ž”Œ,Îk~z!]¾þrÐÜgá5ZAC"[[‡…g ŒéàèÀà2=Cÿ9öƒiÈÒù^1wqÊòã8 ÚÉL630Ix×Q@„F]qEƒ—p%®ú^þW…gèÀHx¾4Ï9b¼ê&ˆ%H›Áã1f;jT®FÔ°a–s±nîƒë”.æ_1(Œ‹~Æ/>c&µ|R£´™vÕUd.B³Z[0ªMaªa^bÀWuÞ™ Ìà ‡zŒó|8è»v)Ç©¬.®J‹åÙU|¾ìil”–a™Œªy¬(Ÿ˜Å)®‰G•%¼ß nß¿Gö‡²SŒÁ¾yE:¾¿5 ç `<Ñ> þj„uŸ•™²»ÅC³'ø^C€È7¿ñõô‹¿ô‰ ™I¬ ³¸°¾ò¥/2Oªä´LлrU8-˜OF0Žc0jFÖ©ÅN±Ç ÿÍßL{" 35Œ¿)ll"]ÒÁ<°}s QoýuÄ'å«GûóÖÏê?ƒÐ]%ò\ÊåÊÓÀ¦2ºe± sC#Y÷5”¾eÏì=61ò}­Ptíêõ Õœ<8sÌþ3™a‰€Ì½ëüK0“Õn¢’ ÑÓÓ=³5P‘3¾31As:¥Ý˜np’:o«Žì†óôæ­Ûa 6€R»g‘’ð½}ý1o³îÈÆºu{4‚°Vyߥ‹T¶a aÆV~ʘ]LNL±_è¶8¢o\»Î<Φeެq÷Ö²¨ T"X_eŸs§ÉžóÙÖV[nÀaû Ìfæõ22ðêrg„l@BQKkGðÇ"í·7¯^f¿¨¦Í¿ø UŠ{𓎘—zôÎ’;ôð•¿n­­¦Ë¯_”“­œHköKc·²Z‘óÝÿgiûƒ|ê=Õ´ ‡Öò”U^.¾v1ìWÂãvêåg›šÚñ¹‡ñ¦Æn3Ú²ÁïFoÜ@6ëšLê»gGfWš¥š€|W™ert4ædYƒÎ•…¶·)oÎ>mò[ Xeqv!ö ±¹ ]CF_c¬›—/#Ÿe6+OnRB¶¥ÍÊF”¥å¼ÉÒ£òÑ.Œræ8=>A&§2?ròd8bqPo@±:7É¢lT¥µŠeβšá¿òÍÀc,“w&)}Nð óœâçÝå¸6x:˜Æ6@œè+ëV0À¡ÀG–WF #‚Ñ P1³¾ƒ@í%µäÁo{¨Llp®r;UdqzA†"iMû¡úĸ85µ}B·áËæP‹ŒŸÊgØ“RZ&;Ú –Bðåƒ4ŽCܪÏ'ðMÐsÞ`ÐùI«¾ ýÀìpi0Ží&uøWÃ[Ô ´¸ê¹kMMÌÑO½!]xäÞ¾ùí+TVYJ5È •Þ5àË;1Øheý»Ïü¿éù¿øŸ §óÝŸ>ú‘“øáÇÒŽÿO}êÏÒ7Ÿ{{ÎÉ8"[K[}×¶kÒä°Éðâå•…tñ2<:½…hö*dõ³®Þ~*÷äÒíñäìÄàÉãNFe’ã$1‹ËøW„ÿô“…™ùÔD†ÔqéS ]T;,¸P ÐÛb p»ñ¡Ï/á—dN–k‡²¯Ö±;`k渑;8Gø4P“*ÅI>,P/2iGQ>€cô+°…éÃD‘= ÊÆ•i ¢‘[QU§½ïwŽÚá)G¡ÝRµ¸æëÊò¬Š‡Ÿ[uÇ }¾Ø•tê;Ÿ"–ûØ*µíhˆ %œõ|ÏyÄ~Œ¿ÌðprzîÙ?O¯¾øb:qâYS§VŒºfNÍãô?ýл¹Ç˜7<ÎãÍßûÈ‘ï^Ü»Þúð=9 Gá"@*Oè"呦x$” ™¼Îî2¢ ž\wN,igVÀ(BgDʱV g>g”¿Â©Â¾ßÂû4Üè°ܦ ÇHêùÌRNQ¥¶æmI_yYkö‘AUTKDÉ–q¯J 8®ñÀq l¡ ²·V)a‚ð=2ÿøÙ9Õ!øÊO¯\¾Ù üþýé:¦$ßÇ>!\ƒb¥sþ‡¹Ô=ÓhZvíãgÉ3H ξ6d¹,;ìäÈpé;IðöB:;3ž®\›MGyž}\åüö-ñ‰þV‰LPdý³7RK?å>QÄ 8F@ç'~ÿïRÎR¾/?ŸaC§ ½ýWw•ãjôÌÈŠ÷×c°!‹À±)‚Ƽ_¾“Ç€ÑÓ;Hvíd–q^½*K”&¿Ë]·ç©ãBVÕcÝH#yx­ïí­íŽªö)_#[rÇÄ>r¯ý3¥9òøräæÐ¹…#Æ(àLê'îš@åÞx7ß¹…·®á¥ÄÝ]Î*Wf©SK““±.å\Œäï““M=Ù×f×[ŽX˜wÒÒûÃÐÞCe°Žgé@lű×é½Ú ¶AÏ߬š#‡¶p£A¢²±÷¿ûB8¡módG*£07õkIRµÀ [pWýør<ïÒ'ƒúz°OXáb†²}⬟ûŒç£|#–pÌLOÝbZÀ%Nƒ¿ýË¿–Þÿ·žã’ òL¿ Úúà 8¥Q‡A d·„AÅöº¡ÁXBù£¿ò‰ôÄ>‚c½k©¼°±!ÍÒjSüÌæ2 wåçárR³øççeR?ó3ƒ4Acï‘ǪNq ³|'/²š?k$—Gøy“ð¹<]û‘¿Cx2<‹ƒõ1pˆ?½Ç —'ÓÆÒBà øiVš†øE²òÄM8?ÁNyŒ¶B*ƒVR¾=_> ß±×Hu9ᦱ€ã¬‰LJd™b»¬d4K¿1d3Óoä»à%NN˜œ”¡Yîr '«Á‚•8ÉëÀïZú‘nA?\¶ø%?‡Lgò<ZŽ·Îþ àø6)¶§°|l瘆Véf§~#ýR•k60Þê$бPÂa¸k‹þË£‚±üÓˆã/‚ñwä= T“¶¢7TÀ«Š8¥5•Øj(õ™k°=r™ü…^¬X9g á³gµ¬CªXQŒ¶Bøâ¨¤GàrOÈ,%«ô‘ÑJBRútû8ÿí#ªóF𦤠nø›8»J‚„ëàA{Q6Zç!:#Ï4“%— :ËþAÿ«òP¼œªxòPK{ B'j#¨«º†bëŒÏx0€§3÷”àò+ãlas-íÉx/ A¹üNZdP³£m§9YFÐNqå™=2Í8@ (" ÷ë(Û¡-ßš ̳§ek=z8ç´„c°‡¢2íìiÜôVo¶jMGhk4!C¹Í6sdq 7òƒ 쬭Ø:ÛC5éÃÀÄUª‹T¥©a/@Öܪ8 ‚Ñâ2¥ê;š‘ï€åµ,Q ¨Ó­§[Á\38­•ŧeœKf®,¯‡®¼Èsv:\­Ü¤<¾º ¼—Q5^n€d8ÑÝÞ™™Ï[È6\ÀR½Jk€AÇ_¹¦ºùŽÀíY±åk¨D‡ÓJ[»Ž‘f*uàÞü?ø|y•À–=$¹ˆ<<¡ªÇ8>'œ-¬²×ÈáÒù¾í-&¶è f\²êVVÉ‚G®ó|eFösíÄabîê"ó­å\‘óç–iYŠ\¢]c{@-²[3«f'†ƒÂÇS×ÁÏ™“û™; H’ý0ÃÚþ³äÈí´ºAè2ˆ:t hµJCy…årG³Z­²ÇÜÁm+ÏÑ p³‹À¥ö+!¬ R?-µ² k°güʪ²9ëçŽM†uÀW ¯8æîNEZÇ1o5{K׋ ÌWƒ^±½òGð¤¼NI¾SæÚDÕ¨ «ƒý®Ë|ÁAÊþ «¶l8À–ã¼3GúkÚÜR!P™P{Œ Úƒªp&d`аéHÚçyȇ¬W:YÆÚÞÉ,ý2ž‘Ö{n?—üË@Êñ›W"™Ñóò꜓º`8ºîún ÖÓ±eb¢ç£Yz) –ðkòy2YÑ1•±-+7 ƒVNT?ö\ ( ]‚³ŒÄ‘З2™æzÞÑÙ˹X!æœ7©î¢ó\x0ðÁgu å¬ Ä}ò5ap:¬?Êwï”e DÙ'|¦]Õùla33XB¹4‚6(U­3×v‡:IµåY™õ Öj­)°±BŒA6Îv@Ú…0ÙºA0p€{¡¿É3š`d<ä+H_ ×Ö¹ ï´=dà ¨cµiÀ:ã®d®£õ›´Ã*V£“7R©Ì„·2ôá§33SØÇØïÔZF îôeÁÞã:žm选ÅÀó›Ã–z‰n@‹Gäé¿:—mšfhIõ¾ÇI?ÕT¦Ókôö®­kNƒ´•YX%!€ ÷Ÿía-KéÓŸýJºô«ô¯>›þÁ?ødªkiOËôàßÎS†³ÚYMï¡=´wny#u5«8òôÒ*ge žAç­ÈËЗÕM| ¬ÁJÏŒXìŽk5møf©èW<$¦’V4§¶Ž¤‘ùtîáw 7•¾øù/§/~îËéã¿ü±ôà¹3éøPGzö¥QÆn&£:¥ç^¤uI€ï}σéþÃßKŸûÂ_¤ò¿ÿ_éÂ…û 8´µËÕ¼&ð—ÔGe éçèØhØA:›k ú'y½`óÞÞžÐeë^º:—úZóéƒOž'Ø`!Ý› HëúÒtØjsà“ω?sÓKø[Ú ÷8Ë‘+¾ñêåÔI"BtÇ ¿Ú/ª 6Òc2À. 0pm"’Ë‘§€iu^+2„㾴ÙуŽj«ƒØ±CƒäÙÊj ê¶Ú] a§.>–T@IDAT*d–Ž‹d1à:lÜ+l:GƒË9‡Ã}ø(máøÜÈøàkãæ ÷~‚Ì¡Œ”%OŽ)÷Yú]|4Ð,O(—G®Xˆ9Ú^d>¾~W,£@‹ Ý‚öcÃnĹ¯­BÞop·A*+:ÛÙ#¬«Ÿ—…ò~Í­~"“BOøo“óU†‹æå½àvKW˜…Fy$¢Q÷Ùl#×é“Ö `KPì«Fpy—åç®>èü]cA|y÷/¿—¸Ú{ÉlVù¥uÊ<ï]Cè6RBa/ˆ DA^§wD" ™sD³º)¤l¸ ÉJ,ƒ…=ÊGò«q#}%¸:“}^¹Jwô™ã 4Rx f•q*}] ·KÓp¶¶½ÊF²~ -w%|ˆ§sAb§ˆíFl`¯ñ_eߌ …b lµF ~[±9œFYµR¾æôƒ¤j”t™“F€ïì)þ—û½N78J._¹HÙüñt‰2÷‹³ãVMÑÛêɧÿ29ØoäBÙÇŸæu´ÈËß>ûi¾ç§=VÏ?íQßœñÜÏ8;†÷ÐåéJ$½N.½ôÊ·É Ÿ!ò«üP‘Ð9Ad÷ |tS𥂬1ub2µ´†€H4TSgÛƒç‚x*.4à<ÿÿÙ{ï OÏû°ïÙÞ{ow»{W€€`ÕhJfÄXŽ’Èã±?2JñL2™8™Ìd&™(“H‘F¶5r†±l5Z… D! àp ¸º·½÷Þ7ŸÏ÷ÝyAòÌ@4^ò°»¿ßû>ïS¾½.BdìÑT€qi‘(7£rë‰TGÐÞ@Á²òB}-ÆZØV²H¬1‰23AY¼]Ë8–¡Ó÷³Az  íúª` âñeBV§Óõë¯Â„PÜët]ÙO‚Ý©7÷úagúæÎî x;ð†nh²e>§§'q~ÀK¤[å ¯*‹–CÔx£B]ƒ`'ó­fä ?©=TÙ•¡ÎÁç,Q“E‹î3Ÿ lb”0d>ö rž-(gò:• ÂÓã-Ó.­WÈ›J£”µ3*Ý~I! ?Šê @sÙ%y^>©²©ÃK`—¦º‡Bn„¯Qè>¯3\¥E¥X¾g}2Y§‘8d6†tn,Ћ³³«# Ðϰ;ÎUƒ–²–r¢?oÄ¥l$(ëX6s*O§Œ[?7ªý‘w¾›ŠMÃé«_øåm!Ôø³žièíí!û¢1”™Œ$/½ð™b]© C·ëÑA£±@#ó.%½Öæ[ÉZ>tèp]¹ú*eÁŽQ¹åFúì_ÿ;Æ¿'ƵÅ›4†7’½éc[Tš”£-Ën™Ajî˜ÆæhÓ , þlñì;þ4V\‡V¨¯ïã½ ¢¤2ˆÊ߀ƒâš2¶¥œ5NÅ#¬W¥Ìßurzÿ¤;öÞ¡Kêy¤_v<35"•”eÙÅÂ1ÞmQ åËŒéÞî Ï‹Ûf@‰®ö8èéë]OÖS=ta›j¯"Ë\Œ3´RÂÁžЩöÐÛ­°ay?'aÐÄ©d`¬úBœ#ãj\atÞ‰„u8WOXø>JLè`ÞÓ5!÷ïå¹§ÐèÓ6ÁÞkϬda°¡ãšy/|Š+Ê1¢1„LàÌæ tröˆñÃØÂžª»Û_ò=lÏi‘«Þ k[PÕ¦·¸˜ñp±hjj:hB•ã°–|œÚµ–ÉžÕ*}ÐhËpA7 Bàk€·LE••Ux²{©¼Ã:7àyÊXòï h ¦O²‹!Œ\f„VãX.( +žÉ,`/#c\ÅèJ°ÏîV "_¾W. ãªïa]¶åP®ÉÃ@_nÛœ[8Út;Ǻ`/Ø4]è^ó5xÎ6yTÛä|8ÜTMÖ~^¡öNøû\Î9Tãᦠ¸–aÄÚ»èŒ,ŒUƒíÅ>°U:fp÷#TPõˆLmõCÙÚ/kªÉ^T?ôæ´ÊÜÖ6÷)ãH+«"Ü3Ø }Р?eÌ|²Í<–þ*óTV €T¥ÞÉ, pè {­£»×ù‰ãäçöTíX=Ѓ&ñ"™•bUvdšÁƒ¡½:O=£Z‚ò!Õ?(]^Ê>ZÁ³(‡¸Ç…e[8¢áoœ«ãäçó9m@•+ªÈʶz!BQêmÂÁ^H§íilV¸mKÇû™•¡´[ÕSi¡­•2®ð3Ü¥ÿåÃjÖÑÖH)ŸBæ(ŽŠôÅRŠ´i+×qV¥ðµŽêzà*Ó­óÀÝmû[¥FªDë#J…w†[¹Üa|·ÆD§ZªEˆGÚÄ[˜µç¤ŽPœê/#˜ÀT² +8;özYÜÔY§#DÚ’W¨­‚5³gmûA90(Hó(En+F3 †´£…EÊr¼1p@§±=Áã&c e:,˜°r‡™ œ »¥í• „‰Fö2d|t;u ïPM®”*ºGÜC–2†ï6<î³Î}LP°v•â¬8]„C$tXŸ2”4P笼 œ–."z±™¬˜‘v„žÁoùm¸—¶‚TÖ’gâÛèØþŽ%‚>¨ŠŽiËp-ö9×Ùt;†‘{Y»tÖ¾¾:Eƒt\¶JeVϹ@ ó²*H)=Ê3ÎGü.–nrfêUÊý®Û½´Íeïñ»hcp Þ÷³/òž|S/Ï[“~÷÷߀æQþŸ=ôSy›ÎZ2Ç3vßÅw?ãù» ‡+ÏPF‹öÀ£ÏîÒ{Úõep‘Ñql—RÛ€buŸsL+$Y<ü6ܧÜÚÞ~=GA_ 2˜Ô Yœ›g>%æ!,èX¤êÄÊì ï· 0Gc0AùqžÚë «ÊØüR62h gªÑLV¤?εš¦¯JØpŸÌX žÍ÷˜Û!²Òˆ86&¨‹ñ6ÙˉEø/ºÎr€cuv´D»U‚d&°Õ«_쯾«]û  œ¢zÃ:èGŸ¸‡{uúúó±Ù.ÒNã |F5¥NT^•—¾ðå/¥Ë/O?úHzüýÃT îu¶5§k#«ééþ ±àÂÔ7½šN¶—E¥ŽÕMæüOÝßòó!A&е é#®_îÜÍÉ4<¿•š âZ§´eb+n6 ßÛ @÷iM}T…¹6 }&hŽdo§‡ŸJÿìÑ{ÒK/]HO>ùÕôÂsßJO|àÝé½L}ƒKé… £¹×Ó{{.ýéŸ9=þØÝéã¿ü~œìéÿþO§âzÎþÔ‚N¥SÖ€dùq}=åò¡[›$&›tXƒ ¶L[µ7~²×Ú7zz:8ch´¼ÁÙWo GPgSEðr²H>TÀ'½r¥y€þïoAhÒ{œ®:ü3•ð4qª?æxV+’ÎT(¬[®ËSÈ_©H£Ô·,‡n;Š ü6«ð¦£=8­Ü œ(~>Y:Ÿµ’MÁ³à{¿ ¾iƒÖ.®sÜ„/my?‰V¦°‚Ag+M‘ÿBåÙ9”ÛÀ /F#P1ª„èWµr Áâó·ZÇIûÇY%ù œ· x¯¶´D%ðNìpVI0˜_Aê›/”r+«Èdض•k¶·y>$®Eiz`yqqZbõEúº[‰˜·BDØ*€õwù ¤F=×ìB Ô Òv2%#ÚŠ)9ÐÖNŸ"ʯñD‹*œ¹Kf¤à,Q‘p„騬ÒÌ]ñ;ÿQ ¦‰Fà FŸs…a_Á§æãð “Ñgn–Š·“-#BN`”X# §|™9¤Ê>K)"ëÑ?ʹCd¶Øä0ÄñD®ü`"çág0S&Äo0'?âch޽ˆh ,–îpÍ! -ŸD&Á05êç)ðn•ˆjLKF»ðÓ¨[{2¹Æ†ˆâç>:ˆœj´dÏ+¬º·î…ëþQ.™y(aüT1ºyóZÚ^YD`lb/ rÓÓÑs®fc¯<3|¯WîÙ彯÷LìóÐqhôuî³×»ÿíÏnokÛ ÃLƒ6%ØÈ–)¡$ÒüOcÜ/*á92^å>ßrío»ópŸ@tÆe/ÁåÛ¹Üu¦*²Nœ:™ûà¯Ó#úkXwš QðSJÿª\)´Ÿ{{†û¹¿nçMoßó–Ùà]`’‡EéqŒ÷œ¾³CDï`( c;̆ôŸ4á§Më…¤J¦…cMa*:X,U©¡>ÚP×ÞûÁ¤þÁ4>Ü]ªŒÌAú2#Ô¯3ÏÖö¬ »òë9ª™=ÿÀCã ê‰êL2ô’Z"€Sâ‹Ù\ʲK8ëj냿ZÂ˹È{ULŒ V„Õî|Å,ígº©þ‹'‘í;MƒŸ";Ã2ùŽRÔ(£k(6Êyñ«±ƒsØ[&Œ¯¢#Ûc’®„£Ô±Ý$þåäeÿ&Zé§>#oEYA&ýó¼¥1f«ˆkxWQ '8{탖vVnȲ£œ'qÊJ—r|A_]%§Ø©«GŒSÖµïæ@ÌAuli´Wƒ«ª‘¿uÖé8-c]9  ¥«·Ó{>ô~²›é7 Ÿè>zƒ(h΋H7.^JSÃ#dhA½g&®Ùg-S/¿¸ëpEºïžÃi‚RegŸïO/ž½‚StÝÖ!챂§±§áôž‘í—|N£„Ž ?É 2Ù’¯€aì#Š´F2 A, §J|ÿ±W[Ð|øˆáNf`Ekˆ>ñœ¹UPâlÜ ¥“aPSK HöA}åÒËWÓ7àùm¦¼ï=÷¥{Ov¥§;qb_K/Hׯá}àÉp¦sW¼Ðˆ»25•òÙç‘ëWR FíÖî£Qʼnz‚¾&G‡ éóÇz•‹/Q®ÖÀ lMÀ½ç¬XØfœ¯}²_¹ø kc_ö÷È’¹fš¸lßPñ¼£[æP®X÷æ’0òqßÍ4ÑJ'¿´Mçl88ا]”J«1hüoåñË< /qÌ€¼ËW®ùGì³°ççž•ú®ðèï¶@P&ðâŠgî½®#ªÏÀëÅÁÀ+Ærÿç1 E¥¶À)²ópÄy ²p®ßÆÆçüŒÿˆûLÚÄzwåug.ý‰’¦ün&È60bÖ¹P&}q^Øwˆ¤#BÇ8;ϽÆùŸ´0ðƒë:ô*ªpÐ&^ÙÐo'ÇÒßýµ_O½‡¥C‡sÀ;Æ}ƒ÷¥Yê)“1΀ÇOñsq–‘ G4rïµk×SC¼¶ŽJtzÉC*oh§äã'қʨê!}ÒœÂļ]¼¼AZæ?‰uóãíëíøžÆä+âFࣿó/w ë¹Ï…YË4ÁR•QJü‘öE2¼è ©«k¤jI¥*g%Q¾ÝÛl$»°q³¡Sð B«ó‰ÆÎ'ø½Çn™Áò‡Þs|\z´KF¬|Ö à(·ËX–ãÏ¥ƒ M¤cwM]dcäÅq`¿‘m˜ådî2ímVÐI8r5~Æ|Xoeã‹5lBrYé¢ônï"ó G•û–Y†Ù\cÙ òcßÃ2b¾Úó ,ÍÝâ;ƒ“•/l'b’@9†dûRSâû"IaDV÷2Ï Dx£åêužq.f™r8hSVÎØ¬EzšA–8‹Ôx·2–gªc[ŠŽ$Ïs:¸Fö³ý`Í”•ŽUU[5¦¿¤híÛ\„®h·NEéªY¯ÒR×±GõË%l Â‰”gÇ2¨Õ:~=CÎÙ¯¤:ÒÍf´Ì¬{W#ÝsvÞžß<‹:ÄÕ­¬¬&ÏsŸ¦(IÏŸ™§¢üV¾cõíD:¦êÉÞ6ë~ž*++è²|/SØÔ6$-/Ç©%W™ç&†ú¥-ì¥{ÁƇìf ò¨`ƒ#\9Ò`LáX8xÇr̳˜5é„ Þ‡|dg^DV¿ë#h'‚ÕmÄaÒ —wÊK –ÔyçžGiýìJ‘MŽ §ª£„ñúÉbÊ™àÍö1áFyFü±T¸r¡ëRÇw~–QgÈ´Å}ò^‡=fë˜+ À=ÚÃÀ7 ”;äË&™Èÿµ!­/¶Dð];¬ß»t¬¢ ¬^b–êòc曞¹ŒïBve¯ttè€6a›ùzŽrh[¶„M™—%ÂGÍÄÅQÎûËÞ±¼…‚ý0AúckV×ju(ñC9„å#un‰=“6d´N:-B}Ï&‚ƒwÁ÷·z…»¿;rDIF[sr¦ôJ{‰{¨  ,.ž('YÚÙàSqËÕé¹,à\€"ÖìÖEEÎGYH§NNû²koq=î~Äu*w(×;v¬KÑ XΪfA+y¿rmè)ž¿óxµCyjé<ÈylÁîÝK¸z³¯lÙDû.ÎÛäDéuÎç#èxêb¶,ÚØC3Í aYkôOZ®³*ìµÀ ‡:Û´YaÄóÜ&Õ×sÞšUnÖ¾åÿ¥¡âƒ -Nûwò|œ4D˜f¸ á{¯ø|Ø™) B® 8 Gâ2>¨5|P¬a×—r†œ‹còåZŸ×VŠ­1¾çVÞ&¬ŽdH×d0‡ë>íçn ‹~-hv¶)m ü[¡Á{W‚Æ)e¸ŒjKgËBW–Ùrhòì&‚Fà²{O*øÅr¯6÷¥ÙI2Å¡½ÛKØ ¨žD•±ü]ö—5KV]$p¾|˜ ¢Í¢Ú4¿1šªZzR+m’k˜¯úŽ6Šûïº;ÍR…É#Ü` :ÃÛ FÐ΀­_])YäVµP6W,f>A£8‡ ôË|ðà—>öÏä¥Ï}élš_ÜHMØê'çöRw{ ÙêÒÀ@_úô§?—z;¤_ùÔ'iÓš†éi>7°—ŽÑ†ãxokzOAuzi`ÞLpí#ÇVRSÉJ$Ë}ƒ,í´M¢ôq Gf-ía;êJSߊ¶r—)¾CôFt/øFvÂë/Ñî¶rÏ-Φ÷>p<-Ÿ×g²~ØRØ|ö¬¿ÅAúœç¨STß™>ö«¿’&ÆÒgþì3øÏªÓ‡>üÞôáwNg Fß¿îÕ¦—ÏõÁGWÓ}§ïL¿õ_ÿfú½ßût´ 0 Є£Q*Ÿ{ôpo錠»Ï /Ìqa€léEJÂ}ip‚v<ÀNmMÁx,Ø«&ˆç8=Ö«±óÊ»Vp´/Õð*)Î]î£=Æ ÕP‚×LâH>ÐA°åSàV|Ù`ù°í]§(‚µðÛ"èÀû ôÆ-—O”H*CîRN±U¶hÒÏ2l!ÆÁCå±öQ¯Ämq<ð=Òì3Ñ#›œÉ„]™¤ú(Q®‚®€oµ>BY¸º´6i«!¬â?R&+(R6àŒ Ò‚.긗fÔðd;fä”°mA„VðXAe/v#ΖyI÷e_Ò “Дu·Ysæ3R†ÞBßÙŸ"ÖXUE›#eðH›ÏJÇB–Q–‹³ÇwleàB³¼U¬ÃPÆÂ `Þ –@ZBψL'%í2héŽe#t~I8ËX@5‰Ÿ‚DÅh4K}DÔ=a„ÆÚÎL)³øVWÉœ€ ¥DØ((0« d¥Ãá 1[Þµðù€}©«ˆ3ÿñ>…k EnŒ |¦ dÏJXáÀƒPÌ f’U !ÆÑ[¸c!#tnÓ_B¨aIìºì]g/ ™¯sßÅ ä¦  D²šé¾@„ ³G°$z”2óU”ÊäE"û¼Ðí#PÛÄÞ!dy*?âå¼<'÷ÜÞHׯ½Jy ²š ‚ނ׽˜;£ñuß÷1çày»3D&©¸5àįâlèLqû1ú}ßüÓû‚£ üé½áöFVíVáQÙ1+\AÆ@ûú Óžék/áÒ}÷Þ÷¿‰è³»x˜^CÁ°¡©„q¶ù7J¦eˆø*ÿ³¥¥-u¶XÆÝÞÉ(F›Sà§8K™ˆè.Q­–ˆ³ä1JkDÔÕ5µ€¬àB5-! Ò `• ¯ð/• 3^S#ýB‰3[]árnaüeYfÀ J«‚FU­¥ÃŸzïHÇï¼+²Ì.3’ª¯@w;×[åúŽtòäÝ陯}²¾u'âÛà€½ÆÖ ç5âBÐFŸ/E°w¨Æ kEü\C™0{Þö*‘Azê\q. Òù©%œÌvë€÷­Y&‹ÏÔC‚÷¼Þe@žó7s+'Ä}Üï>›QÖLAó;‡Ò@eµ‹V"²ïè±ô…Ïüa:v_ :{=PÉÐɤcC\ßgÙ—?½ÿæVðÝoxýO¿ûžÿ0þºÙø5‹¢¾%kí£1ÅÖ ö¡TÞlB†Ð¨¥Cê§eÎì’ØÞ€U .ðÈuxZ-¥¸r°¼‡ÌÙŽá㣿üñôÿ×o37Úò46a|\B–„‡ÂgÓ\à’s¶§ŽqeZeÙŒHeò´ sQQ],Í,•o×;::‘Ô~¡ñ3“½ <ÅÊ®a½™Xc”eÙ2\ÍŒÚ`VŒ©ò«ƒ±DÙ–õèh·Ò’2±Ø¨¡ÁU¾FQ~磠¿Ð4ôé•i$Šùð Vâ¢ïò|¶(¿ê}–Sô9Ex£òãžx^ ã°>é¸%øjÍb.îmÈþ¨ô¥1G>&\(d+äp o•û‘ðF›o„ œ]²„™cKÃ\Šž—Ùgþ®Ü¯Ö9»N•$ ª{È/:{urfå°Ý3W2ÅPÚ$Ý+& ÌyE%*æ„‹ç­&`9o3uÏKÏ\÷B¢¸´V£Ë JÂ8v }ì—>†¾E6Žõ6JÀ× (¯"Ç Ò¯r!MÐr£iFë=u#Ý× .:Q™î¹÷xšÛ(KO>ý-"è_J­]80p@ËXgÃùñO7À†=Îhx‘ϰˌÌz|€Ÿ[: X‹2ŸN‘*œR4|¹F/y’{º£!#ÞÃÎ"ëéØu¿3£0ÈXî›Ï©wù¸2m#)™b‹²ßõ©)ÕÇóò§_I/_ìJ¿ðóO¤ûîx(§6¾Fè4mJ8÷L‘Eöc]ì:eÜàÏCC©é@>ÑÉ´†Ãøà‘£©¥µ- ]¿FðÊÁôÔç¾H€ÁH:ÜÛ~QþULQ>vάI£ë7›]X2Ð…cŽã2bÍÜÆüÑaÁ[õK:€5º¿^D¿Rô°¥Eø<Ÿ©Kº§ê­¾×€÷É},Áp(N¿g¿ Ò)$\ª3ªÆû¾ÝßÅ[÷Ôsä ÜŠxŽ“™@£ïŽÎŒÀ'³¶À?ưRšg£á0‚¸ù\<£t}~•w,ŸhƘðì•Uàaœ€%æþ³K¼š€iÎS< Ã5zwΙ¯NhIE÷I8ˆu…<ÃZÙûÜ:65æi@@g¢ÁÜM·µ‡kwcßyWT¨`N'î<™º{…ŒÒ„ÍV],³+ª¥]Æìt™²µÐ?³› ŠœGO%[Xdèp6š‘VMpTeuczÏÏýBºãèQð<Þ9{Y»÷Ø<‘I ÂÐùƒSX‘Öe˜ã.½5.¦¶§BÚwñÚ€í7îoý7 7¹ëÖßýìÖ¿½ëÖ¿ƒoìó]ï’ïþÂ%.oƒ³EÈôz  éO[¾å_êð:[5ý„‘ü²J•'´ ”2º¼¤˜¬ehåó¹rˆ<²m³°·Àùµ¸§No;-õõV–ìl÷ :k$󭌤‘udy³Ê„Ø™µÒ¢=pIÝD–Š„:YË)m]Î…ÌÄ8Ò#y“:RI º óÖa€Aû–£—GûŒÇÚ( þò튻èO5ðå$“C” ”Éøšò3é•ò:Sü„ï̺’*X…ÄmÛUB:¶6Wi¿g·±NßYì”:#C“UWa_D©v[yYùÈ“ßKט$ò²vEJ ïPžáå1gMæ<_u-ýâ –ttØ­³Ê*⨥}ÍÄ2KMذ* ‹ý2»ÓòåŽg™Z×ãï~¿QKogœ4¶"²Ý’mvlOPÉ>Êt²y&޽Ÿ^˜#[ß…kePe,NŒNxhÈ Øu‘Bc…?ûG ð©áƒ’ܶαì²2°‰^¼&û žì>hw4ÐTYÃ~ðÅßö*ŽlXçŒ|µAÖ2K‰Š%VªÂÁP½ßEùÔ³F†ÄÙÅK¢Ú YÒîˆû`†¼<^Y;çlßñÖζئñ)úÃ"Ëå “8U8 öFÙžÁ êú,$`GÙ#û§C¨áüì•k€ H¨|¥ãVç´okt•Uê±èlP¿/álÅ'ßs3SØ)&™6ù2Ÿ»Våùo>ô<ägï2ž­Ü£ƒÐ`S+å˜a¾@›9“®<yoÈ.Ì5èNÈriå•L®s~öcÖ¦ °–ÊØù˜{‚ŽÃºÅ±ÐWØSé‡óZ âí$voó^ƒt 2€%’)Ñ_°¨û^æ±E0޽Ëëj‚¦lr“`jæ³VÜžs¤ØoÇw¾V¡AÄ1´›êŒÑG±D% ù{È®J¬F+\Zñ°€¾»Î»¡ŒRp×@RFa¯=+BlX§6œÈ¬þ”xP½2(Ï3w~jV¨ôN (D>­Å!d–s‰ï½…Gpó›|y¶™t9HGWfwFþdöê›êZÒH3Æ÷XG.XGúºhè_¬š³.—ŽËžc¶‡Âú™{é>’Tž+'†l ÞKß$mî­4ÀjiÂp{{»šy–,XÎQ|õœ=@RÎ Ú¹º8Œ!‡‚w›ÐÀeè§´”ãã^éD¾-“Уï·8Ç9d¼Ð{¨Vfà‘ü!èðΘ”&NïŠ÷ê¬Ó‘N É”VxÐn.¿¶å ¸ö=Ö'>Ù>Å5E%è‡Acþ>9Þ_FEˆ2Ö±ŽŽ²±B` óª€_ZÆäÉÆ¦"…¼Ö–y8•±ÙâÐyëêI8hm‰ÊVÒŽräÞ=`´Ž…!ó.!o »r„þ¼¦ó! ÌÅiˆ*™UVX—Ë(ƒ~1é"{®XYÿ#8M`Înúë/žcʰáàÔ]{¨þ•Ÿžz ]ë[—±~,Ýu×Ñ44»™ÎM ÓÆR‡èàòvªÁïP-.Ã÷˜OV¼-Z”ÿû§hIA[˜2Ó—i§Ñ@{™Ù!Z5#w7U`ƒ¥µ³tby.5‘er|r)U°ÿmì{;D%šû©´»ˆO᯿q%UÕ5sø Û¦ÜÛCÛ0*½<}~2ÚMµÐÓ–¦#é7þñ?IW^>›~ÿwþeúÄ')=þNô ”WŸY%x}?½É羑ÞûŽû)Cÿëé÷÷‚?ç“ðÛ‹Žiâá"ôFOôËÐ~Zn 8ïï ^‘öÊk8Õ–“éO»µ™%@˜¿°U‹#½­š Ýp¿gÐ-ÃÌùÌâ_kÃï° ¬èÒ×"¬mİL5qut}(“ o&;îìP¾ºí_dÇS!ExWWÕ#Ü40Oúµøfµ>˺ë 50H­&£=ìHà~Ø…>9•²@ØG ÓŽ+±²ŽrA¾UW¡­^¡#|æÄ]í+þÓqo[ž½|«ži—@ŽVwiÓXDE”Úºv²Ahþ6i}¼ÚÕlƒr ìû¤Câ…eéå;ëd–¯‚KÒh”j>CNN¤Áó3’ÓYÚ.­ð ½OY³€Ê ì›ü^_ØgA®B=!&ö¤ØÀ`­³\gFD«2šÊ®4X r!ó0Ú&2„d CQAEEYA»«ë årÚP:Ù@V6 _ëe ô˜0C=/¢ùA{€O§°½“¼ßÃÊ'ºÎ,:$È,rÇ7@ Ê¢MóÓ"BÆÛ ;«d€4Lh(ðÝ‹s8÷1bIÈ‹èY¬Ãƒ1H œr0DøÕ’µkÔ¤„˨Ÿ-*û¢”ÒÓFAÛ÷×Ò— àTоv}8=rï k6§?u›l:hsLðÕà¸Õë_?äëx(2”Ø/{"°Ï"±ÙzñZ9Ÿj Âtfôâ1ÿø1/Šp1IÉIûWêèììŒ@mÀ˜!"ümº~üÝùI¬VdÕ‰¼ŒãçÌTB˜aŸ-ç"#¾Uéö­­g£ÐuI}WØ}óÆ•´H´¥0P²¦ ;áVHøÕcDR@µÂë:ý §l%z2g‰Þ eMgéM"e‘&Df’/ä]Dsšme?X{e·ž¹8Ê“Âj¸\Û&°Â!ŠùT1Ÿy„éщ9„Ë,–GDVIÉBö|j;XÄ»‰"„áû^Ëo¨H”ÚÌ—ÝrÝú÷k÷æ–ÛþÖÿúFÂhìðköù'½·ŽçÙI³dÒÛ0nÈ 4¥˜¾2#ÐçüÔ} 3  ßKð\—´èǺD. Íè=:A(øoî\\³¿{_¦˜Ëë,ŸCi²<­‘õ–Ô9xünŒÝÁç§aÈy»ö?ŽËgß„ëõñîõ?}¦÷¦¿òßk'¸yltçœQ㙓¹… &*–6ÏdDúôŸÊd/?Jb’$¤ùSGçÌôDj¥4˜†NaÚ²ž*øÐî:q<½û‰¥¿ùÌŸ§zÊš½82<‘úûÓã?c¦{î8šÞÿ¯PDô×\~&ìG °n@h®ß²·†¡t/s*k`SVÀN•p°,S%n¬#w»‡b…ÆðÈÏ*5…t æ±$õÙN¶ÈPž8yâ¸Eöò¹ ŒF+ ÖxæÑ‡‰t® ºªâ¯Ãé¹çž#Š\doî:yyd• Î w^ê餲eë/_½Žî@Ÿ8èõ'…üÞw³zFÄ7F#¥‡‰Êß €3@êPo<]šO6ôóùo½.¼|>λ›Þâ]:Ò·^:‹\OÏIÖÙÕÕÁšQä1R:F¾ëäoÌ €f¶GiIä]éØ$U¡Œ¬¾óø¡”Ÿ¿pº† Å9>tï©ÔÝÓUq4àûþ .¥ _A'(OMìU+%¯^»~Ÿk7²ù*kWO©C/8pà½àÆ)ã>{ÚÝmf ®W¯m/Eq?r¨‹ Òx˜NÓmäñjÎºŽ²¨èH‡{(±Y•ƈ€W^ÎÃp;xå•´85Bh°…§p°¶Ì1pѬˆ;§Né¾[ž>ÿÕKéåߺõròb¼CGúÈ?ù‘Æžø iœÍ¾“o øY°@¦ ]Ž¡P >tD„òüШžãW™!0ã=‘pÎF¾*CÁÝãÜ}> ÌÁ ÑPî5*úùü È\|ª M¦ÿåýƒôßþW¿™¿çÁô1ÊCüÕ.§ëãên”#®d.è¥c·9kòed‚<Öwèä©hÑÁÔyôhš«LŸy˜²¶•ÀéàC:)PÅÂhÌôø >ˆ¼k\`|ØÀ «±_®“ËßÃAß¡Ló¾&(4’htNÔ‰ÄmËwð“ÝA`/ø'ï³0¯ c’²†ç 1Ö¾»ð5TÁÏ·á‰[ðtå úÁÔ”3Tôw™³Æ exû³aoä^ÝÚª4ØÁÓ™o8kØWÇUV1»Jƒdxkâ3ßk6€N$—jeË:8Ác¢Ã.ƒ‹‹èëa„ǨW‡±GÙfkßá Î®á[¹Ã–1î—ò†:{2”ãaÿuײïvO­Ú晸FñXY(s¸é°sï-è=î^"hüjzðÁ‡¡x?: ëªæ^:ó1îZöðÈᣩûÐQÎÏ,PYȬ×Y±_úlÄÇ>ùÑôÐÃ¥²P• ñ.ÀÕIÃ,FEZ¼…a—¤¿z‹].ynû`û†Ìï­·oȲßð—¨+…sÜonn‡$Yk!œP­ §‘™ý´Z@"P-ô+ÇUU-B?p {È4n«+äS¶1Ç\—À;±MýFÝ`[m ×nÀLQ3Ûœt(SñaŒr©]í è •™ùcÅŽ­°˜˜¿ í'èJ¸tN×µ”‘©®Yº´ b§#ËKa6=mÒŸ ÏmR구Ù‡¬23˜÷°yHSt„U  ü²8‡ÑÞë· 0#yVrfYiTÕ&ç«uŒD–"ó¿µ.òÞrl-Òtͺf!›…$_Žùs¯óuŒ2è«ú¨vÕ ô'ùƒ%{K1ì7µv’•Ie>h­%d-]®S¡Íѵì¥Z„ V~L¤ÉÄɈ]E§¿´xnÓÞÎÐ!öÍàÓò˜foZ@ZZ©¡›ŸÚvÔ—°×òHw«w²Yü ?b?·q¾ú‘öÉ925Ýo+”Ò‡¼¥=èªçïnáüÒ1¤³Õ±%ÅêìÂþ Øÿ²J©Î¶0>$Í÷_z¤¶¤U`ÓŸî™›$e8ÿItâY+ ‹–œW']‡ÿÁF8c«¬0´WãzÈE®Ù9––5#Ø«–ØßÆF«Ìè¼Å^È{9Oì¾[;8aXKül2’@°³qîˆÓã##ðdf…¹Þè™äÃ(E8¯2¹Êö%Ø áe¯.\C6Í‹ªuM©Ë‹Ù¸HÈ2«ÝYÛ.mç>€÷sƒ›T-]ÄÖµ /¯o ªßs9“Fæ°më$0€SùR=i›r¹«À¸üu• ËÅ[ t®ÝMn—Áq1°“Ù¾÷ûp3‰8}öN'M?DÒ–¬ÌbÕ¤:ZÉh`îö¬"¹!ÓßYŽ ß#¼)g;fes}TÁ:ù¥;!#²éðÀ«Ðŵ»«0Ób\pP§ªý}ùW<‹ê8dÚê«pÁÊýâPì¤ô‡û­Î„œªyùê@³9Y‹÷å“i ý=~'Þi›–6/±>epË:ãÌÂw­¶Q­¡ µ¼£¤„RñàžÕU‚^QIDgЩz¬º—¾3>E/ÏJo—Ö¨Ì8Ç|àJÀ))­Kyèš{”Ý^&8`½„}Ây>G £eÖ&ÌCÓ‘“'S1xw}h0h^a!Jî5ÿf°›Ã[´:_KÖ×4ºŠ¯oµ?¨ó°‹LàïeÈÇ¿YÞÚÊ6nœ—Ÿùâ…ÔŠN g‰óÓ»;Ä!®¦?ü£¿ÁÉÛ’~ë¿ü úÉ—¦®. w•¦»ÚiL9þáet!îi§¦U8#ƒtüŠÎ àø¥¾ñÔP¼›YómÈkËSWS%¥ê×Ó8­e«pRèc„/8€‚¾ßÏ9¬VŒ?¯»¥’Áåô̹d’ñ/,CSô ‡×úRÿHAº<(« CzRŠßáÁÇJõ$~ö¯žLÝ=×ÒûßÿÞTƒãüÜÕidŸڻͦÏñùô‘'Τô_üFú?~û_Q)uH=p¯žœ ˜”ÆKií¹c´òúÀçS“È~áÒYíøfS `6q¤Žà¥‘ÁÁ´ÁXúU;[›SÑÔ\š¤ú)Cè ¦ï|/°a€\Îôj2÷M¾à còC{‚+ÛQ5Ü$M½®€ å#³®··HÆÄ±¯Mfo ¹\Er.áùà›¸¥Ž¥œDì?-ÔÍðqÚòƒ½/ÂÑ-MÖ&eÅ„ŽâýÛŒ//ØÁ‰®­C›ÁÕ´sHúô×êGl3¦B»‹´² :ÀÙ#†sä"í /[Tª«Š€€™éEÖ‹ÈsV‡“Fԑů\°ÿœÄfb¶y¦ûRΊÌÓ„²-઴”–<à©2ƒ¼*è’sö¤M½•»VÚflýU›´+µ 2¯2Ñ.t°p‘h-{»(tƒ*Ê¥ Ôs¨‡ÈëM¶Q>ÇøQ[&ƒf§ô<ÎfË®j¿1K6XêÖžæÍô™1â^&&±1Úéžb04 ‰èF‡ À‹ô•°Ý$ÍË—^N—/¾Ä3(×,:¢ñ¸W†½†@!㪪DÀ¦W†Ñ³ 7@¯‚²Ð¥a+"Øôm€# ŸF!XÐ5XáMA¸ÖŒ¡>‹âˆhDzqi‹&ì 0'rzmn˜mdÖ;€£ÃØ5×ÀPëâû†p".Sƒþ!–Íi¥Ïá÷>žZTô™§ÌÂñœË­WFà¿ÃRýŒæ§Qö]&þd.‰Š’Dq )DóöõÃwÀ}UÀ2ºfÅÇXc¦°;==°¸Î,£‹%N¯ÝÙj$žüÌ¿¡ÿù×ÈBY•…a¡žã2ŒEf+±é¤ÜŽV—`ÈЃm„r‰4ÑÖÁìV°‚@ñã„ MP¡öŠ1²“ƒ¸†ù<”s 38½ÄQ`akN%Ül&±blXƨ® '#ÄÝ’"*‘·ŸÕÜèÍô'ÿêw†yÇéGßÍûÁ‹×¹2‡»Â\¼„»¼ÆoÿçßkÜMÏó;èõ·ÿ;·ü¿9d(:R;Ö®®öiŒR¹“0úÂÌJ6^#…‚·†ÕFcrtóõ¦æ½‚FñG)=óågSN{aA™/2jØ'—¤Ñ’nê'A«Ù)•uy‡<«BøØ]¯üF}É{WÑËÔ‘re5£‚Ÿ«“È蘒buÄ"flåSõ@?òBaÍsRÿʧMð&ÓA<‹¿ ŠÒX'/ Çhxmv#`­ˆ ƒö?ývúŸÿ‡’;uGz×CT?úò`š^Íï]ÞÇð0 /‡æqÞö웤?}U:|ôŽ´³„ŽK†Ù©3g¨bR±cm =pÿ}¡½rñbèœî=}£SéÜË1vèxˆ¬Êzʨ_å¹YtÙšô}ê T»xþ¸ºIˆ¶ôø£°ÚÀà ç.R¡æ™( ÜÔŒ3‰*ƒƒÃáXqo›[X?Õ—pø›%¸Þjpquk¤•ê0RM"÷è`î¿ÿÞÐ ®0'÷«¨¢(ýâÞåêãü¡Còœ;Çü8?«ëÈ#´›Q^2à¥ãÔè0-€]@'ÝCÐŽe+`¸¡mTÓé äN oùô@ïÁtòCw¢SÖ9ÝÀ€qJ8?õù/¥¾Š15hcX׿­°Â$bVö>öÌ wé”4Wú ^ªcy}G%™ÁeT$гásm°êd:UuüÊ_«Èø3`ÈàåM2Ê” uîuu¶ÁŸ9[x§ÂŠB.DºãÜSÛŠ4Oú¾ˆÃpdx˜=’~¦4‡sÁ ­F‚<»ÜÞßw^À}:ý²`m´ŽYMÃ,s˜DïVWTcÚQ…MÞ¥,Ĭá8@™O<¥˜3t¬uÜÖá+ÒW³H³ÿXZ8IJ8m?ÎÍ žÂ–ÉYÛÛóð¤Q‚?uâd GÍ d¸[n˜5¨@XÔQ``º¬|þ™¸SZA{˜ÍW»ç2‹Ƹ›³íØ W>QN›£ñÙüÑÖ^¯ÕìzêÌ–¹Í>åÚ……;ç+.‰SH4?8éÁ庱Á!ôix4Ÿ™ÁÝDªmuHët7 LëÜ5øAùÈ`Ï­´žÊà¥~î»x™—2œÁ™Œ¬QÁœšÈÂTn°ÚgAÆžåó1˜PyÁ@ u™zŒk¥HðnKùQ|Á#â|‡¢“=Œ:1„ƒáš¨éa™è¼ÝgËÊ&›À—²¦óÑ.¨ÄÓ@õ ÞÚI\[ȉ¤‚ ‹Ñ¦ëå¥:M¤/„À:7ù’ÏiE€ß œÌ̽=*Å`G׎}Úf‹ß}l‡yí0o~”Ë­¢``¬t¸ì»î_Ž0ùûö.åúÁ53* ԨūZUSlÓNjIùÛ1t숷L‰½2 Gœ–&…î„.Q€<+ÅÏ¥‰ñ;s“v„ƒˆOÂϚý†ò“åú¡û_ò_Gá¶·ÈÅÚ8ùÊÔÒ4m:f N%K`Þü<’WÀCÝ‘È!NÚ¥åkèÌ.&äYöMºè6H£AqØñ ÀÑ¡'Œ!l’mAL2¤Åûlµ-»Ð±Žô¢¬x#ãf°"Ý6pvg_ çQº[ Ðòa[’8Ál»;02–žþyøNxè‘•¥eÊÝ ò0ýÔ±î˜ÊµÈ¬¶Êè9NzlàòÚh(ÇIžŸ®SMŒ0ÖT./á]»ðñ&úV[•bGiEþ‰ª½4Žíî´uŽ,à-*Þä#ËTPpŠ^å3—ÇS#ã¶·d‡>¾:Ñ]—Ff°mBëëØçyüuDJwVšþìßþuú“?ýwéç>ò>¾oL_{ ‡¼ÉDS+éÉÏ}%}àƒïI¿ù>™~ûÿxKÖ¾B¼Ð¦)>Kc[›ëÒÉ#øk–8³.Ún§CÐ[¾+©¤Ý eêåOâƒ>á`cy>CÛkµ%à —®Ì-,!—øaF8ðξßg¾VV)Ž2ü$AЦEP?‘2…0­Î©-D>)Íb†4·‰Ü·‹³[{†cå+3Ê;WÑCÑ¡„ݰ @©Äݰÿ‚È¡ï@÷¥êJÚ¤%iÊS+<ØÚÎÖ@Ïœß{+ÍéUæcJ|fà8âmðqU^¡ŒZ]M`/&©š`‚ôçn%Ç d!ƒAäÇÚtBVå÷îZÑ££¶xMm#¶ñ°§E;o*Šè/ð¹Zâ«È¬&‘ʧÄe«Šió—ŸYÁ{†=ßÄ2åþµªˆ—…F´y r*pÈtŒP4šq“^¯[{çS%¥RV1ê蔞˜Ig¿ùåœ5ÎDôªŸ²Ï:‹ê 7*8RŸVˆiʼn RªQC¥Õ2‘KñFû(‡×Š­ã²œ¸Œ T4*2EÕ~ôöõ¦ï€ç'!âÜÆÅíߦí³dBkˆ?ªQf”Ï„_8Yuw•ï×ÇÙÛxÝmߢÑpÃÔè8¥ÅÊ*hÓCåøYC¥ämkz bÓ‚¤1½ó]ïK¯¾r9¥Vœ²C8ìYÜ}ð ÙÖôªCö3Ã>‡ã•(ª*Ò®Iúéçaœƒ¾Z^þy ÿOÿ»ÿ1JÏ"_¼t‘ÅKà° ð[‘HyÕýRñP>Ëãwñ}3 èP~—·k¤%“€gt˜‹·.¾4'îGñ9ÐÝÁ<àBIAñ*ç9edMöZ>þ"Ë·— ŽV¦16>MatB9Óp`i×aF–üw àÔÔTÌA£ôÁƒ8Ù-Ü£“­… \«gÌumF÷ "ÅUþ\›KžÀ˜õÌ©uNvÖ¨§ß5÷÷ ¥sU+Ê9Ãk7úØkŒÚÀ’ƃíô®oDЧitFvß8äLŒN½>ÿäfôpPé<ÁØÂ^ÈøÜóöEÖaN,™í}aœ38Ù ¯ƒèIú VZgž•8a{¦¤´Œ}Ô8tãZ”œ&`c1çg 8tN¼wƒgVPB9í ã÷«C9¬O/\Ð0t!µ“©ï^«è SÒXÜxEdŒ¡G©í=|Ë÷fUà<àD-ÇW¦q”ÿiÜá9ù„™Ûò"ƒ§3@…±9l³%ìh@‡U„-omäò+F¨o1ŽF]³ÌóuŸ/âùàuì·Æµ)‡g&`—wz^î«4ªÛ¿2¡¸r°+  +ì —ÞYëÒBÖ÷VºœŽ´Yݼ˜àŽêjKR€Ècâ~§l* ÔÖ«žçç›ü.ïñÌ¥+ÅÒG g1kvÑ¡÷p¿¸°mu?ÜcF•0ö&aüÂæ5 g×ÁgtP—C;3³;fÁ_ÒevÏ`j¤? |a?µ½-®ï† ¾‰kff‰*_3ÐÛ:ð':à,ýÓ1íßQ•Ã#`®Òé Ž@é}è ¼dý¬¹É‹tb¶f°£ÝÜçÃ^¿s/kÔ±¨~hÜŸ°Ÿ³zßÖ ²šÃïÛBÇ—&²Sìÿ¥"ÂvDyoq9ŽrÚÏÃ?fÐ Z‘ÿU—ã<]I_½–F·‹Òžt´M*$ÁBöÛ@žoœ=‹#»"59 íÝÀŽA¶38¼Þà&ã½ÌS½L>j¢Š!NäG8§õôù/}ƒ ”&‚ƒ©pÂÙ½ïñ^mÆÒçÿê+éSŸüUªÝU§ë#TŒÃa©MïtOyê›ÙK7p¢ÝOMåÓ$å4¦£8ÇûÀãé tšràžµˆ@Gv-AÁ;´7)¡¥tOWišY)Hóèklgâ)ôäÒíªÔ‰ó{zû*ôrdj-կ̧#ÝTí"X`s%=‚®|y8Õ÷¶ßLšï„ôtÖƒß3m¤‰¹D•öTþž>O®ÒAÛ˜>ù©_NÏ|ýJÒ?—Þ÷¾GÒƒ§ÚÓ³çÇá­åitj9=õÅgÓO¼;ýçÿà×Òïþ?”Á6ü* âL9W«Xè_­„.V¼²6ü¾ÝÖÊa…Â3tº¼âƒÇJjµmŒŒà@džìg°šÁ›0kV¹6…H v‚Úv„5aBlgvdÿÖÑmuý‚¶:º….x)»ÀU…½*Á$}83£ÛÑ–†:&ƒåoÑ’Œ1”m¤“…ŒµGEñ]õ}`½Œ€8yk(ÖÍï`»f“±0H/´£Hë˜:𦎻N@¥‰Ò&dOŒ’쯉ϡ½H•ä%*-Œ²J D&3šÄ ¬dÀ¿¨­Ë±NåAõ:З‘‰v˜4°äÉBt]‚Ò±1-‘ÑÏÆôrH«ig±šUahKÈÝì2¯p)áŸÂw^XNôE1)ùÞŠ@¾<†y0 DÅÞ5—.¾ÌäÍ¢ Æøy²#êëqàTõò«¥)²U0Bðªi~¿vùRƤٌ»î¼+œ ý7û0°M5,c‘qð,d7èY"ÍméȉÓiúé'‰<*NºQà[ÓÀkô–í'“à$%ýˆjÇyahnëLÁ°ž}îy"ýÚqâÃ¥Y8æì;»Å&µ(—A…ÉC'j-"›Èi¨ñ‹U€ðö–’@nE…Äñöhh%Â’ð *ì(Ÿ‚ŒkÖw=ц ¿[ÿÎ&*Úùa`(,H‡NÜǸÛ(óCqŸQ¼ †2k }-ÇŸ:-•a?³Üë)Gçç <þßK€Ý‰¤Â¥Ÿ‰À…dÇÛ‹hÂÓÁ÷a@s\ûÇï¹ÿ 2'2aÐÉí‰÷³wßu1Ž#åÞ¯ÂPB©¦ G5ôݨÁÈlôo.ÒÌg¿gŒýo9fÇØqV>³?âÜ[¿û{îŸýöïûcÜòxügì}¯YOîÙlUûïØ8÷ŒƒçmßÙï_;^îÛܳ±žÛ¼Ïg½ß½UÔˆg)ŒŸùËtù×Òó¿ýŸ06Êù8û}¸vñ¹…ð«DV»÷ØÉôþ<}ö©ÔÙvؤÇåX[ªC9ÏKmÊÊêN„Aa@8šç^˵5­I”ÊÂJ(¾ÀŒ±¥7SŸ‹È'æmù÷K#k;¸cÐÎìâZ(H|§Ð,a÷{… ÿYbɈæúHkŒUVñµ|üÌþ‰}$5¡ ùœÊAnÝ3/›YN¿WÈvß°´ÝǺº ·~Ø™}ÿóä%Ò׃ƒ˜ÀOè?·ãOhÄ}û-!8K»—ÌÈß=¦µ8^{¯÷¶Ü=ßooã™ý…«àÌÍî@7'B¹°¥/-ô6ö4¢zk%ej@– _亮|æû¥½¾_:zëåX·^~¯ñÂò_ KD(ˆ¥1™n„šJ«‘ÌÈUžtJ«”›mººjvÖ8ÙD±!:g£Ûö¶ËWÌúÖ e†ã2Ÿ[YEU>Rí…øL¸5â µÀï줂® G\“7i\™Ÿ¥| <Ãòg~fe•xôé0tmR.is³ ƒòAœñË‘9yG™]]]Ð…z¢.ïN##ÃQ5¦Ì:[—ŒŽ!¨Ï†ó]ãL¬•ùxþ–73Ѐ-e2'¡N÷œaLAÊ^‹f‚iü©Á@q€w•€“FGáÙÌUƒŽÕKV(ßþà™3±§–õ¬‚w#›¬®h8§,ÂÖˆ·¯7aÄÐú÷žç=ÝŸ÷äøðëMÑg5©ˆOL¡!s6·¶§1Jž·så#òÇÕi¢Ó5—ñ£œòiÈë½ô‡|æšäƒg(§šõ3‰¬{ß½§Y°O ìî€UŸ/•€/§ÓÇ?ñwÒ—>÷ÙLF$pÆV?:‹9²TÞ@?g‡†(·Ú’J(9ê%½ÞÎËÍBÏÉ…–~øÌ£ô»šŽÒ¬fdìé?Òþóß çzÎPå¦áÑq.莙;÷IùO#´ ¶ò¤7…®âÁBî”oЮå>DI63†;¬ÛÅÍpfh=YÅÈ•[TŸÒáeÝø;>`¤²2C8™‘5žgAkl)tÂÈã“¡9Fi G[ ÙöQ ùEG¥†(eOƒ‚tHþå«OFd½YbÊ–^Wœ«2¥s’ædNkdu2ÏtE´Ù’´Þ' ŒÒ­Øk–=??™ï w$Œ^J¤±5л]é¥o¾}“•j0ŒzÎÓ”9 hYÖ Æ_àxE×,—ÛüÝOF„ ÇŽHõ­5ɲ|Z5†¸jŒu¬ÉÌú9ÊÝš) A{¤¿/M£¿D–rÊáÐað†ZÃL×;¼Õ‘†§6Ò§én€‚ÑÛâ›JÚÓ³g4vG>Ì ìpÊžáGa´m€ÈêAA6Î~²:t+€K•É"t a "z…C¸gnþ$¯n46ë(E¤ =C ª¡*¡ê Â#1Wƒ¡Tž= ÆëäÎÎ8s`\£âzßPzòóϦ¦_~Œö ‡ÒÔì•ôr7yTN ªœª3Ûžß–q¯±ÊÔxš„¦´ì¡ƒ-ÀÊRëI©ÖÕ‘®%þÂ3/¥¹¾™4½5 ©K¿õOÿûôÀaü¬@^0;G…‚Çöô­#ð©ë`wàÃö‡ÿïÿ5òÕ¼Ö`<ž4ÖA~þ¿’Ú¡IyØ5¤4A4¸Ï]¿Y~ó2[ÎþÎh`ƒ¼Ï{Þ:WÌXÊéûÂÕÛ×ÏÖëž« "Åw ú€Fa\–N«qþ¯üR#.ß¼â—&Í(ÇÈ3J mÐЂü–°›iÈW¾ˆ–Ð i¶tI½W^€& xéðÔ>¥-Âì&D_yêsèÝ‘…[J&ÒÎiñ]^`™UßïüC§„v«ð¥72{ßeK,mÄ1˜KÉט³öÝ]ø‡$"Äÿ¹Ôët„.;vúTWíáôå9HOTºÈ«¢l·2Ï[&ºj´c2| “ÔCæPYÞ@@•Uþt.nóÆb>·rŸFY×U Ï5 AÓ®Î8¼3$ïa;­UûâääLº94J5’–ÔFðžôZ¾n¶²[êƒsFéÈÕfçX½½=<}#/Ë@&iG/üMþŒŠg­ fÀZdisÎåÌA`?w¢!뜧%‚©þÇY$ï5ÉHÞ¦0Ÿ˜9‚ʬšSRZOP­HÇ‘Ù{(7¬Á}ª.·-(Æ{ίÙ~»¶Ži¶,øS%pdÅVÀ–`.dZöS9 Ã2u“ŒX«’*I[š]Þ+¿U?wNs3ÀWiÖkÞìríîé4ó¹té2:7½Ôщ²¬9ì,@¹ŽaEç¥}ÑG±1 ¥»ï>I¦¾¥fiÃÏjœ5 ÅfÙïêP&Z‡Ý¯‰Ê“ó  ÊýäÅâ¨ÁŸ¥8ätÖ3ÅivÁÒñzeN’¶F\Л<ª¬"»é¨ßfŸ"Ø€sßýŸ™¬9X¥B˜7ðOgHTÀVØÎýTŵKËÔQ`Íö Y’D9“w §¬'-ÐqnõqÅ>Ô$@ÄØb$pè“YçJ­V3ؾ”mû¨´³CÀ‚Ÿ¯aÑØÒD‹%¾7iÁ{جЕœ«óˆìaæ ðû]÷ƒ}* È YØ;7HÙm\2x h"ãD€ ;¢\”ÇASÙh(»äBнõµo‰+&Ãéá'±b×&m )NUÈUÚr„ß°3w€ÊÌ¿p¯þ°Féh.ýLº±¦ß„1t`™"í׎N+`.dCö—Ad+~n`¼Ú&¡Š-¥ |l(oK·¢T¾ðÄ>Ñ˾Øâj÷5 KÝHÇaà ¥ögf硵d§¢ãWÀ“œ0ïOu"q:Ý\¾ú¢½ÉµS âß– Ê'LÄ^Ò&mÆë­*À|·ÐC…ÇÅeé5: sT¯ß⌵ײ>ð¹Ã¾ÚÞ׊ÚºÜC€Ò¸‚NÙöCûŸüa|V‹dó®­-Ò>¢.¦=Ä Ä•y”J?Ü•n>s1=OðP4øÁ;O¦yhµ]l ¶@{  U·´RÇœ.áãæn‚G0V¨. Ì°“Þýø)p® }áéóØA)%ÏÞêœ}癣Цg¿v6ýêôñ´²[œ¾y û Ïžê¦]}eÖgÒü:{Më±=Z"Oâ,Ý@k¨,Jw´–¦^‚†nÒ#}n‘*Ê+‹à üš±oÜH Có©ç <°²™ìxh>ˬـŽ5pž~gÐìüçqæ­(ÏL`§h¨ØKókÒ@…tJ{W¸×ÓR‘Ú±Ïn€§×‡ghë!ä :Þ]•´±® ®‘‘>Ÿî8u2èÀg?ûÅôÑ~€µ¦¯ŸI5”~¿tuŠ,ö‹éÌÃ÷¦_øÅ§óÇAò,ÙñÀ¼•E»ºZ‚†÷õ Â×¥ŸØI¨2ÓÙÑ Ýä¼g›¤,aPø’>Úv+H |jø»::s•Lh7 L)èÝ4Òʵð%užUäíÛ¬gýJȪ^kÀ”W™ø¢—k“ÎíBƒ´=‡&3”*ƒeþÄ"l%wÐë˜p¼N{xo©ß±F‰¡Ùó[+ˆœÊ@ò*«ÂD5>Û7#¨Q¼ÅN~1%ò ­ÒÒh…*~PÕۊ˳³“PKmU&[Š—ÈYE“UN¨®34Èóú>¥Ê¡ä5awßÔöLÛ`ÊG«TR10Ç{¤ýÊuÚ¯¯÷õóRÿ÷ Nàsqc$ûa°¿| õo£sŠg»ŸaLI…ÖŠ÷Ð"j "k4LD¢¶Áèƒl„ðl„„u³›AYJ­¾’Î_z…—ïÅÝïUÎq{ÓìîM7n^á݈0•á9[Ó“£±}(æGï8žî¼ûTØ* _‹P¯]¾˜NÂ8<ó"ÑšqDŒD_˜)Œ"MD×÷öôÆÜÝ8ç¤1òfœÍ”û£ Î #cÇp¸éQhÙ† HÜ\OΉ¥DRçˆhxÚaÓe¾¥S z J@%²ªQ$Zy‡}o$nfËhЉ¢C@g߇Ä2¶eì™Ã^G¼°Œ±ÒhÚZ"O5¤h 0’i‘jZ™¿7„m!Œ(!îµïdSýæÜ`´2.öÕÞËK\yõÊìs© 7€–½ùî‚FAhùq«Úq(ÝaÖÂvöX £eÉËûs—Èë^Ô4ÅXÝÄÚ·{-&dïó~ŸÏ½×ŸþéŠ`fŒ«ð÷Å'¼Ïÿs_ö]&hð{=︎{â\Þ›‹þË핟çÞíïÙ{ãÃâü,"{÷ç”»7”¼lÙÞWîÙx¯à5Wîû[÷I¥NÊðß¾rïÈýÌ=ç ¶p©Òhê#Gï"[È,O<0iß*ï‰LpçìCûSQ±•˜F*Ži᧸˜MÓDÅŽN§^¢N=F…zñÄH)ËZxž*ÌkB*°*‹îË2ø t†¨c[Âêš|±ÄNX2¡ÉÜéðu%ïíh#˜gT†8Ĉ"b 53K-ä»"ó›[Z e“Àk" u0ªlèÐÈÁg¼Œÿ8W,Ãç?û—”àJ?üX:}ÿñœŽ÷ØýÎíuîgî r?scÞúÓýò\snœ[¿Ïý§ñíùÌç¾ÿa?÷ñûßæysSê¿ý¾8xñë?ëÀ·|}ë>¼öÏAaq†6M£É£ßÎ¥„¼ÖÖè\æäÊíákǸõïÀ_æç™9…¸F×\hñN Áî½Nû5"‡Fmïè伙áäŸÀQ…aÔožÈ 8Ò£î üSqš™°dPÇÐÀM„ıôØ;ß›>üKŸ$B¼š¼•.ž;OdœÑü8X—‚A– ÒÊxâKô^‚Ž*è9^o½paê—^¹¸#Ïp^¡Œ8þ)HçË‹ Ù ªqÒ[ZG¸×P¥Ž(Þ»*ºÒë2ú½ ݼ‰òB¹RyB’¼I…VRŽ9˜ û¿—>šzzz0ðÔ„³_žäÈ‘à;ݽ½1¶s—¯ZN[§›óO½ÌDÈÖl°Ý,‹Y£J)â¥tA¡ß³^c.Ÿùן†ÑG¾±!=úø;qž€VØët=ؤaWÎQmài–Œ1¾(Q¹2xO%e [œÇÓ‰‰ ð3z8 Õ죫ü¸òîó—2â“ñ3k%VAÈ9¾=wçþÚËu)Èz®dŸókƒ£ñ|!òžý¿U¸cÝÜìÏ äMtD W⯰󽣿öm·ÿ·Sµ `)ý³v0ªTRAG^eà‡2õʉÌGSä…òâÇßù®ÔwýzºñꌌÝ`˜™„ÏZÙÁŠ,:o4Tš½^ˆ\8ƒ‘ÒÊ^ÒD÷M£›œöÁÒá½µ9‡<¶Hïñg؃|"¶)Ùö²ú’sS :À÷ÁW+²`…˜ý}v¿ä³žCqÓŒ«D€Ñéä±#øÌ’/O‡1ÖV“‘Ë Q,ˆ–—"ËóŽ‘1ýŸþg? ŒDîøÄx¼C~«ümÜ<ÆP·Fçq3†¿eh£™¡‚‹AVö÷g7ï9HÖtÿ™—‘½!C¢Ð–@ï£7z#û½@FÉ4{u]AÙÙ u esdKÍXQ^äáÌb­¶—‚JCœ›÷èèÂ)^‰Bþa*ÙCÌwNZÚm´¼üì¾Ó÷ÐΆêTdÚ¨7  …r¥‘=ú%B‹ÉÜ1À–ÍÀp)ç^ÔÙk ƒ­ÌzR¿2æ‚Ïå+k'fdžÓÔøHZ.Ì26+j“ÀÛ(ËÊ\4þ˜±ak -óºŽ# ÅþžcMŒQ”¾y¾/`H/+#ÐPù[q ^mÉEyÂÔÒ~ÙmwÆsÑ`îÃÉËíî‹F(>FÆD/b9ðŒìðÓû‚ßp_9ç&ÿo•ç4^jÑ!³Œ‘v…¥ås²>ï½à‰Œ¡óÞŽ­Þc0мÉ*YÊÖÞg,¼8%ÜJC”­&\¨×ö’UðŸ}:ímOxôp;‡qXDF´ Š|7 ìB™æYyá 2K%pVpì ^ÑXjß¿#EÚ»»ÓéGϤݗFpÐÏ  ¸2—¦(Knöam#YKÌIX%ŽwqQòw>=>Ì~`àå¶ŸΤ}ß»¿¥ùJ"4Çs:àÝëÐ'Ø  Çg 2ÙÅŠìÞT ‡ç1dbö ý0ä?@ö^¯¦'ÇÒ‰S§ ðâ  Èß`µQþö€ë¤/r Y"Ã#ãY5öÙ—ú¿uŒù«8Í•ÕsúÛ:€.aõïÛöÜK™ïâò,™;À7…ÀŠ¢I@9Ÿö^úØÝÒEiütE'¼µ![sò~±pîþ4@XÍ£ú^¦kTßp„+é™4‹?otÔÕ4"7O‡Æ0•Uu0x!à†œ“4UãÉäô$gm_ÂÍô"™ç–”ÖØ7µgùžF|öîïþÚ§Ò‘ÃG¨8‚lƒ±G£Št“iqeÿeË‚¶°ÜTÄ»ÌêG9& aìzãÎù­q)÷eú‰?9ñ7hjîÖôª·ÆF¿‰³Ð9º›š¤u¿GÈ$ìzÞ¢»4~c˜ïY`ÛÆï »à˜8£cH»[:‚òV ´T²>udÉEÛ^ð\Èfœ–›²±‘©Y·Ï™ÈäýÒ1­_ÚÅÅyi5ÆRøïtùxŒá¸Ž÷ºÉ`¤õuä=þgj{×áT ÜVýÿì½×“åçyç÷vÎ9ç4˜Á`³(ŠÔJW®²µÞ —kËŽ¾ñ•o¶öÂÞ¿`˵®¥µ^yWZ’+¢, ÀÌ`rèîéNçp:·?Ÿç7M€b0È5IËèéÓ'ü~oxâ÷ /‹iLÇ`¯ ac@ßú¿VœÚyK]!xž·E$%t91a€Å]QÂ÷•}*””^ô_•ú܇ø7ãÄ–òóÐxMè#eRu@ûM¥= ìÛXÇG0a¹µ­1}ç{v GÀ—]ZÙMOœïD§ÍQ¡ý~úêW¿LýòteÜd'ô:ûúlAšc΋ۥTÀ­¢3IG ²„˜vÀ}*Æ+ÀEÛê(˜ƒÝ«ªXÏ"Î:o«'‰ =µZF¢ô*…´Ä¾¸ïßʬ=ö®¯¹4޼›"fµÿÛ9­†}ª(Þ¸eçŒo4¦™%0XŽû**e¾Ü£«ŠÄpèóÆ0ç¯ïœÕË‚ƒl¤€ ¹=:ÖZËx‹ÒUæ¢,ÙMOŸëIO>“^{ííôùφ}ßJW‡ñ/šÒ¥kcàé÷ÌÃØsé έt9W‘ß7©d®¬mJ¯_ »ã…S$Ö7·R¥O2´ºÃ˜Dfò€>t%þÇ:vUGžA[ÈryÌqJG;·e|5‹ ê iä´UôÅy˹Ö.÷”®¤{m@íƒÀÕ°MvÓcìhj²‰† -!§ï­áhÑ®ôVÎ{v£Ó7* cyIR&¹Ùî™±M>_U×BÔùKrÛv€f£ö‰±[þ‹È»vV(f~Ú‡ú‘µ¬‘÷ZÌMÁè`¹:ÑØ)ÆpV9ó^¹-Âoà_=p?)7äyFÁ\wÁÔ–x˜ÿö°cLÚªÆõ¸?ÊÄÛ¶éš”çâH;ØU•O! öÄÜìô€D÷I ‚n¶([·‹’ãkÇCMÅ“÷G©jSnÅÅ—4D™W´QhüBÊ10Á(Wˆ^¥›Eü 8sG³öª0Pæh ¹Ìy,]a•,öL£îÊ·þœÖìý1Ñî®.„miZ˜ãœ„jÚR߯f*‹`Ýg$MŽ œ4\SdÔ™nkŲ¨ø+ä¸Îõøýa„i “ó W3=˜` ™ÿd|4§&‚ ѹÜbºyãFzçâûPv¦»m+$:•€„¶kO6ÖO@C€voÇ*[&aôb¤®Å@¨Á°.$ a¨¯âÌŒt +Ïz×`·DKëyÀ“g„¯®äɆÚK×?ºDP°3@/¿ô·SÆ€À¬«oJ.<†Ò'‡D†Ñ ?¨Ê*uüW,ØvC0Næò< ûR Ž¢æŽ®¸˜‰J`²CXJQ~À8hâ¨8zH˜fœPá‚9æh-ÈØ²¿3ÅÁX³H4Y  ³¦ÊÙ‹8ƒÁØBF/´P^d5±{ˆÒ‚øÌÔ5ZÂTVÊ€‚}¾çC%d«0–˜Ÿ-€¥E×XFÐàpßÜ[* z‰Û¬N¯b÷¨ :l®#;f1¿Ftà5,⹟“¹tªTh‚L~FÚO4ül‘¥pÐ`ш= Ü \ø\aàšš©+c{¯ ³¸/k¯òæu×ÁÏ…‘Çà ˜+ðœƒãÑØrÌV‡vœ¤ÅñfêS Úf©ÐZ¥èg¤eJ'\€£%œ0}·ËöV%Е‹TU]Mî$5¦F^[ £| Áª¹Ê5×Q2Å8Cf»²úQ­º’›ùcÄ[}dV¡­¹äEÛ® Ý cÏTYSEKIoæ¹.óó ´}áü™2Çx_ú3ae¥á˜΄#HZ­nò™yÎMßáõRÏOç3̃»-¸ÿÒmÐAìÙî©ôæ ri<½ôÍ?–“®£Z~^º3 Òý#ö-¹¿ <Ææç|O¥êkҌϽvö·t¢qïß?ŽþÎÆå뾟}7{ÏúûsôÕ£ûH»¾æ{G‚ÿ]×çqI>ôñÇâ~Ž·½ÞÏzx#^ó3þcý)_ð½`²Nk8†/½øïáÇMZŸäÒÓŸû2]B‚N½ëóÉGŒã“/ðÜ,y 3нgŒå¿}݇Az«N7 …"”S9ºÁ6/ò˜-Ðë9ãÄL8bxôè{ñåOüã-BiÂ~ÆLu•Yéü-ݸf‘´Áó£±+!ç©ïpŽßP²jtÀqôÁüÄí”›GOðy¾ ¯iô˜‰íõ÷öfȄΑed=Vct6à`/áä–`<«K ŽW޽Áe‚ÁôÊ,=vWùâQ'š"žá÷¾y‡ñšáÇÑ Œ¥ˆ Î :•Xu×ÛÛc°º¢cJƒ*7?²ÌÀ\œUŒ¼0 Í–­r÷2ƒÊTåœ÷[€çÊ. º«—/c|xÞYJýýƒ¼^AvÿŸu¥¬lØH÷éFóð¹³¼fj’Õ›f)Êk½È«N*ò>¸øë½—Þyó͇½}ý¡;&&&RÿÀ`B£Ãw´ÒØó†žµk; J’fÂéæõ§žþlðïƒ!Ä8~[þqNÒ¬:PúÉx˵þÙ|ýëš»<¡^tLkØ"òfv‡nù)“eŽÿ§Õ—•ý›´aî ßE.PA¢eÝøöë­jiˆ žúWY‘9Ú®…òáÿû•p\¶ù‹sÑÈvUߨY©ûÉ@Ún5Núڱǘb~½¨ô!©ñ¹žÇ ˜…¼w"»{àfôÞ½°G º NwÌúh ¨#0äµä5eF8çTv™i+fí<u¤giß½}‡àõ}ÀÝ®pê·¡ "ìqxXYáõÜm‡àMþFˆ ÷=+™l j@X›£àïÉ'Ö†‚vÎÐÑ©Ò6ÑþÓ¦ÚÁÐþ=þ\ ›îõ×_£Bõ¶kqÂa÷¼¸·Þy7k:u§ÏœÂ1ŸM·nÞF®¥gŸz’D¶Ç‘ƒœ-IRª•fVL)sœ‡ÿ)+Ü[ÛÞŸ={&äüÄÔ Àöt$‘*ë9ƒy|b*~ìŒå^øyÅ ¬”a ‹@»ÃÃÃì c†`i#ó>ö .lÁ¹ÌUn髸câÆµë7ðEbÑC}ý½ÜB‰ÃÎý¯ I ‰NGto²îú&vç*jOÅ×l™öÎŽ&É&žhWî£Áb-᎘÷û«î]åsê;ºŠ¦ÇÏTQ9ß‘^¿¼”^íb$#iéü¬1Àmklm§5+€zyG¿A#H–ykWÚöÜìj÷yšÙ&€)O+ç£â™» ð ëëw*Ù7íìM|ùc Ýç±_Ê{¿«$ Ñ•&Ýß}m;èÄ5trÌ 9dyÖò±1&¾•…8Àòu¼½Z‰s`»Ów©BïïkK=´î>w|)½{Ýjìæî\òŽ×µÃqÞÂÛxž Òº£§›„‚E€’ƒ{úÒúÅÖ­u-õédÍ™ôÐù‡ãŒðY‚͹Ü<ë陵9aàitšg„æf‘MðDw_'IÎ$¤²ç§ÏœŽ¶ÿ.® uh™»¶åÐñ¡ôÕ?üƒèP¶J"óõ7#x!í×Bk÷šL#ýº6êàMh®¬ ¹âú£ë›šš°ýëá¥yþvíö¡»ê4ÔÜ’ªž|,zèLè:ÿò¼6Œ~±ÄÕþÇÿù?L—?¼1<ƒB¿úC~‚ì«AséPÞq´õÑ BHÃn8áÎ6?ò(Ú±òq¿'Ýêßutw¤?ù'’f&'£bßê‘Eæ¬/㕤ãóyÐ+ßÝÐPVq¡°ôiX–’ßþÏ8¤'y{rl"}î‹_L_ÿÆ7‚·²SPP>‰¯@ƒÚðÒä›?üaÐë÷Ê+/§q¾ïz¶œn;H”W7¤?üão¤s¬a1rÛ3U½7¿²û;îÇ}dá–òîµN°¦’Šƒ’Ò3Ûú÷çÁXä}INÇÀÁ§}¸%®§÷'£ìoÿͶ̫JN?>éÿ+>òÿÿó)WÀý…Ö9ÜÇW‡Ï”’â7Êbõ˜­=µ›cÀoÒPçZ™í}´ ¬¢Ç)-´3A ù’€‰þ²òȳ´³ÊAhß(gR ¶À¯¼R Ö ÓÁx©ãüRxO¬CÞÎt¼t•b Šr7Ž1ûÑ!ÞËqB¢qeƒ6Á +%²-d­r°ÝŒ–ã»èæ2ÁWÖ½FŸ]Ç=ø¼ÉJÒ¨8Œ2YZ>á½²ûLÅÖ³}ª2Äà¿t¾™Bµ­$QF™ìk{R2‘õàpÌ1Û ã* ðÌ’¼°²ÆZ€j#Œ69Ñ$«h;ÊTlÛº п¼;vl=I…9zI b;±<±ýU@Õ·Õ‹êò^„œ“WešlæÚ™ô´AQ"—6µ; ÀdñwxϪç<Éxí$iRQW܆]R¾òý]tƒmu ¨XAêQMn‹Ál6ÂÞ.Gˆ-sîy kuÒ>vG6Rùeò¼­Ô)=JóØ•P=²Œ$ƒhÊQ=dÝé=öM¤â,u°O£•ÑvþéTRI`ezš[$©êÂ.’¡6Áœ›  ô ôÍÄËìGY•]p^ügåZ|V‘ÚÝLŸ°„y­¥‘®œ¸mìqàØñŒbb'Ð÷ m-LG[é<àx g`G‹jì®þ”ÇÎc³a¦íE²)8—6í˜u_/t²EYʸ—É{TaW5T ±wØC±ÔCxª„Öïµìß&Õ &†TimÿxÀ_…ìûûYÛˆÝËþˆ¥·uuÖ;Ê‘*vl‘ë8ëWÝÙ#IÜmãH:Hh#Ys™„Iê i|ÈyÅ«¬‡I6V=˜èh_‚7î7ÏÃ]ò%ô½­öž ¥c¦Ä íVàyÅÒÜÝá™'NÆ}&Ƨ¢Ûìü’þ •ø?¶ž6éÃŽÚ•2 8¹`e‘Š÷ík³qHÑc|'@IDAT.-§Ë4Iß\KzÚ£ÑD`èâ„‘ÀÊþ˜¼e×} åt-ûþ ëg7#«‘½¾\ŸÛá1;ß];ûÙdYZaRè]ªÄÁã\`&ìX5»…ìfô5Ë"q²ý²=’nM<á«ðŠ>»þBüo©G›îåT}›`]Aç>I±Ëø4Æ{´‰ƒÖáãt• î–o™CFí¦cƒèæZ*»)®ÄÇlÁ»´³•´¹ÊëK 9‡ ÖÁ.ÈR÷ÃsÂMÀr¥ù‡Ïô¦nàƒ¬¤îî®4Màû‹ÏƒïËÓÿþ¯¾›þáþ.¾Nwºx›XÝó4È»‚,_&yÏ.x–Aé—mb«Äðî> %ùªæ4¾Yœkòé…‡LÜ*N£séî­´IâZg/¦ó¥Ì”¦‰6ÛøÔ…ûiíÞlª&ùm¿´Ž 6Ým ’':ä ~Þ¸1A r—øÛ~jgŒgÛM^¶ÛËFºqo.­`{@‡ÕeÄȸ_=2¥]YŒžœ^9Lão§BÚäKŒ•¬Ã ±ÀKw–Ò…CRåÒ»ï}˜â1ì ÅØ«ÆZÚ/ÝMŸ{¾*}á³Æño{TjÓ;?lŒI’±Ï>t2éÝC'ÄO?dÓVˆ“6¢ å× è ƒ¯{He|*¢x ;m{‹¸‰ãŽ™ûø`Ź]FL¬4ÑL:òH ±£èæÃ=ìDbE·#Ú«èÿU°®%øb•n›y:#\ÐL|4¿jL—dº]¨gØ×<…&Q¯Ñ©tblœ¹ƒ7 [ÄÔ}G¾ øñÉcʰˆO²N&£•aç¬a÷«¬—gï9ÁõÖff)Bö»G‰ÇvÂ/+¶ƒ«¥ßZl»²^2$¯ßº›f¹€±³sÓ<ÏÀ‚‚Ú¨Ò)ßg±ø‚m;Œvµ›®‘Ó@Ð:ªãpvË‘«àr¡Ír0Àg»…ÖÑÅÚ,Ì9ǹÒÖ ÊV7Û.fÆ`iÄñõ<É·ß~“ÊÛSa”ÙÐÌŹÑt‰'¨±Ša:©Ó>5Êb¦tçÖ•tá±§ÓWï÷ Î¥Åà³í¼ßÛ!‹ÎÀƒž£°È\Œ´Çûüߘ£(_Ž>ÄZmhm ØH5_ñÁº]6T²ÂTг…_‡±b…^œU )få,¼ L§cí0Žq³KW> ø²–=ÿDúݯ|`ŸµÔpUù+èꦶ§ÐÑà¨!ëP@O&–)l»aEÂàÐóH0pC6*`KDRJö®l²@à#ÆÆÜ]×@lfn>ÖD°H¢›aœ2-›m9 ˜V¦äa ·< CÆt¯<ƒÈ ©Z’¶vYª2÷¨ÐØ'à¶ÁØæ1üÍ\tl0v2ªÀ»Á'ƒk ûu XÇeå–Ì X#èæWã{:bÙ¸U¦*%«.¥™½Œn6QÇPcMú”ðU´y=÷1Úð°òfCšUf2‚FÒ´å 9&÷ß ™pL¹Ÿ¿e(ßWq¹aj"À¢Ý ­f|(ôÌ¢3ø¡Ñ$3ƘaPA0 =.c;€BFpE+NƼX[ÍF$kkÆœIÍ]ý©ðÌÌæ<û&m¸‡ñ£…Ï5PL ¸wûvÌé䩇BèÙö«V¹|£ã#².Vo¬ÑòçV"åw=£åCpÝJšÛÚôv6@k&ݦŠ£µ£ @Ú^ÀA’êP8­´ˆò\õºFÏTÌÀÛœDé•wÚ Ãµ~Ù€Ê-Ñšt¸Âl-Û‚(Èî ºrêȾVøm’µ­¯‘´H’‰„*8 1áú ˜Pãî™!¥p|æ¾–ú†N@{8JùK;¿è躧@* ±,›‹±Â·^Ó÷¹M¼o&·íL¥dtöu0¤3ùÍqY™Ntáõ²d}¨ º´Å¦¯¹_^WzUnûY•@R¼¡åýpà>Ê+ô½¯ã—5š•/ŽßkJ ^ŸEàý,0ŒÍ=äc?çã¾_´‚B½¯€„ü)â½ ª˜<¥0SW_ Í0¬šM ¡¬ª;KLpâÆ^qÅz¨Ùù[ý#“}u}€S#û­cíçÿ΃±¹Ÿ®… QuЩJRZ«©Lq|8‡33 (B+”†•kk–ñ'òq8R¬‡2ÆdÿvÜŽ/ÖÙ ¯² Ì—$'2û¢`û‡ï¿Ç˜˜3¯CWùoE+óçž~6M <âvVzïm~4à5¤¥!A\²›z ƒï ´”[½{/Úútp¤Áå+w#3½‰À•›§è¬@&­rMèY0itJÃĘëî²Ê“ŠÐrX+³G Œ‡aý[ ûú¥?„n *uu’‰G…/Y¶*¶•¯àÌÜü\a>‚½ÝÝÈ|Z®«s¡ñ¡Þyí/³^TA?A ü\Ñ#vCñ{¼óv¹s3@œ3?Bæìã2Àõ8ûMÛ}6Æõ·"u>7CŸL°òœ=Åæç&³3˜qN¥«4„<>`{£½#Ðc•Ì*—ëÀýV=œ’†¥Á3åB ò>øÜ7ƒ"(h+oÀè¼GÔ RÊ%ù_£]Z‘oâÁGE• KØKs1–É‚>À¦ˆl·5dª¶ˆÎÁvhRžýÕ<²À¶@¶† (šËfi›¨uT!2ÞqE–=¢äüùÇàùµôæk?ˆÄ“äÀÈý*|¦ÒW~ïkÈ.lºàÕ:ôº/å”ÿÈmuUèžûðížñkeÑÿù§ßd4gs‚ŒÚe=ÂŽÀžu͕ߙ¥£ÍúÊ›ÚfÚsG-¨  )ÿ¥%åxì‘G"1O`¢’öOdciß… ç=(Y溓ÅÏ~7¢›Ï.Ÿ¥ÅÚ²´*dbÑêƒ$C€jå ²ÌVü&C p”ÓqlCZPr¿@Û„ŽóÚ˜‚]~Ï9šd#h¦ljä{èÌñôö›ôL~íiõž`Bè0æ¯î y“6I*×Àkñ™“§O"[úœ ¶ Õl±´ðdOÌæGÅùVÖÒñDâv¥I±Ï?ÿ,:‚@kÁ%C_XY^„þµAœã=´×KÇ­|â;Øe5ØÉf?kcXYîûL.îm çØ™0 È øÌ@#YÉ„EiÊmbéÌ€ô“'ËÒÉ“]iŠÜãWߺþ„ Ÿïi¨ôÌÚV_Œ5™WzuoÕùÚ•¶Ø•c­pèP‡;±Ð_\K]è"ÁŒßΡðf'ಬm¡Oë}äg¸zÒŸob{JVh—0[iÑýaSbL¡û±aL¶*]ÛV€%lÎç(ÅÞ4áÚjùÑ pw $ÿà­ëé¿üO_HÏ=¹ €q=}tŸj,øÕµ2©Á3ûöÕ™øŸ‡5‹çFÇÒ{-€9~óvTr˜Ôf›[ýÏxÓÆdTXª‘\b»}ÛÂiO{„„ç–Gå´<ʸÝë×o¦ÏÆ>öööDU™àš ŸœÝ©æô €ùul˜%€çe@¦ŠH’Vçè¤#8Žð3 i[{Úª™'O°ûø¹S¼¶™>óùÏq´Äcèv:1ah+EàŒqYåç›Tà™å<þh:~ò8 ÅZºôÁ‡é]ô´gTv÷ôDÇ™[õ=CÕÄÆÇžx¾(I¼ÿ~샯þº5œ8 DÛT|,ùÔŠ1÷I°Ü{ô·Õï±3gÒñÓg¢Mú?Hño¾•ª›*°[%ÙëÙYª¼Ükls3ÿõÓ²j!hTº€G¤¯É?Ì  _A:Dª¤S'O¥Ó\_Y,_›TìãH_.b«e²mƒêûÞHÆ®EONp:Ô{¸n»Èˆ?üã?NÏ=÷ tÈšhû yû#ÛBºýÑçì™rÂßxT«ÀŒ1ìê}ð7ÿÄõt.®‡¼ï|~ª½ý³†ê>Ç: —ã»üívs­‚±|ùËó³.õ«|¡…®¼ ²áf¼ô›Ö/4å£ñ~¼”G¯üìËH—h>l0 Äà%«c`°ÐÕ‚§&À% סh[žñè-+BÛ#YucŒla6d°gJ/j.cˬbw¯Pí»Å5åw’¸PèLƒ‘vê°£La)Ý,UêLmvq }ø*lžy°Å9°%»°øÐ‡­F¶·ÒéRH{Ëä.yXØ òEZÕÚ52O¥·"òÐÖ¤t®#¼¿ò:ï»Ñ>÷óÖ.®~1óózÚnêmmŒ86‚Ï©GôEÖ8ª†û{Ü‹-mO »Aûj 4uꦬ 'ú[ÃöZ#xêÙ§›€ÀM‡P-|6Ÿú{;èDÓ–nß!!Ýíy§êrî*C‡ÙÕäH“œ·sø¢®£‡êj¦ïôØ7+@Gi2;Æiç£Fl1ƒY¥¾-«³®cÚP©Ÿ`¹6áèè4ûB váUàl³‹›#ž>ÞÅ «ào“{ï õâkÍáÛ®Fw¿&dâÍ[Ã\×à‰v€wÏðq ý»-üQ­•Ñ»gi[=8¤{/- ¨$À®ªïh5µçmWa#‰_èO œ›´´W ÀOU|uM~_K*E§P†NÌ*¥i1ŽÞèâìw£ «$GmÞóŸE.¥Ò öA•í•TMš i@ÆŽAú¸‰äâÒ,¹ª­³3lFiCüiö¡ë¡^üQ>;:‚ÝVAò#¼b‡uh 1m[ݘnÏ=̳îÒS‰¥¥ø°Ó|¶ ýF¦ ·E¥žÊýNÝ¡Wƒ×Sìó>¶lx8EÈq|áîÞÝ—ÁUÒ-P5‰Ä ä1¤e±ÊhÁcîµ l…œ•NáÙ‰sC$1¬Ø9“J™ëøGqó¤¨ûÏàŒÉÊEqÓ5`LÔ·ýuow'´ºAU9E5&0€«mH)oh¬+§è̪Ïh‡¤z’B´«ÛÚHœw÷ïꢳAÑ;7黎ñihµ\#ÃIµ5 4®,³?¬wkªÍRŽM¦Ü9$¨£æÜ Á©— Û]xG|6Ãð ¤g6PøÈ4kpåi~ñÃõI¦°¢TÚRœ´åŠð ìcòµ}Ñëv30 ´ÈóHBäºÚ¶ìûjâ©Øôj‘Æn)û qíì0¡½oÅoxºAÅZ0”Z’/Z:z –3™ý,¿ˆ[ƒo’€j;áx ?·—k  d!Øš®³ôùË>2ý§}MÂrǵÎöâÁ}Ù ?øÞòNŒœËƒÅÔfA:lÿ&øû,~·ƒ†Å`ùa0мSÞ2ÇK7FC¶‹çÛ=DeòL}=…ŽÄ4´‹í<ÁBðìPº¾a«^¿y‹**•áõƒv}E$QÜÛð%˜µÇðª"é:UviëH3%¥$$-ÓIŠ×‹IÝ šµ}%Ž=Ðßœ¬6¶HÔ`¤Ç0 ƒBac›X¦ Ðe©ÂÉŽ…pœêñ8ªä‰¯x”±~¸2Àvî€MÂÓʈy[ˆ¢®üjå·øœv™k¦\³ŽÎ)¬á#>K‘>L$z «ÔïÊ ;Þ¥[zöz&æKŸÊ'mZ“OH~š#©‹˜—>¨þÄ}¢‹{¬Se=8Ä g…?z²`­é;/¾œžyì1*¯Ï…ž}ö!âQkûi~ ü’d7õß:2J[A¶]}~KŸ÷à‡"‚ÔÍ=HKõÚts,YuúZµHÃ3k©À÷y¿Í«Ñ]WœîæK83únüúÎVÇg,ØLÍt i¯-L÷¦à×b:l4ö'DY*dži§ø¡pñQĶÍilýËýL¦VP†? }”¢7؃MÖ£Zš“´@BìQÿsfe?}ösϤÿðí¿ŸâùÇÓëo] Z´øðí÷n¤ž~8=ýÌ…ôîß }"_Úµ`êíÚZ[ósd:È.¢[ËÐò^Èe¶²oZpÝŒ-UÑar~@g&O3fª¹ö“ÈßBõ»¼®î@@ó¾64L¢—X>/ÀSÚÿNkÞò;hX¾Ÿ³€ ˆG숿íLS\ ¯Á“v—Övà»ÃšTmo“oôsMœ_åÈoñ?»¬”rn})‰ê|FLÔÊmí}¢ÀT È(9d®è¨}:¨nm¿ »Ç,”ñýJÎâ.¡¥|1ºh†=³È²ƒ„wYĺõëÅ· ]hËhS£2X_?Ú#ÎÅX—²Aù.¯õôtSÜÒ .&­ìqL±´`§WÇ\Lò‹]ëvöЧ& ”‚ù˜ÀX¬cæ¥Æ!â.6¥§ƒ3@ÈŒjåÂ~ùÖ(ÁZ…°™*Û´x©xGã…àŒ³,`Ʀæ&ÀZ Ô²ÂBÁaPAHÅŠ¢^²ö$9Š/r:ªžsbkË‚b‚ulà4­ßë4ÛyO×3<À0ñ³µÚ»»ÒÀñ½àóiçxßPOÕ. ­µ¶·<·k;utuýÞ¸7MBL_[3Ð[7ykoVm)Y‡*a³~‹XC Ex´£¿½½ Çb*œ«Ö¶ê0†"y¯ ãBÃVeoõŽ­U§qT[º{¡ëeÎ*CIÒòÖñ›e¤ƒ]‹²7X/»Ö":VŹOäž'ô ­ ˜âùaVx™¨âL£` nƒÞŽ_ Ó f«~³Œ}÷~cI º¶¶öÚ:^GdD²g;3>ÈAGÚ¢Dªx¯¯á”dLõÛ½®òD-î0V¶Y{ ( ‘{¯#¤b’¥f #Mä-øÜù*_Tˆ!*M>ÇÚª°ls®Ó%–#ÙuM2QùÐØ eë5ø¿2¹Áw×Àä› ³×òÁýûc´؇·‡N>œ.<þ$²#k‰¤%ˆ`ɼ\<äCåQF¸O»!xÝ䘙¿`]ç£Ç‘3Á’…b7Q¨•Ûgyeùºg½“¤-“8¤sïã5üíãG×`<Ùß™ÌÏœ4)cUuwe¶çŽÕ8 ¬Sê8V«v®]½HZ,]J<ü 7Ù9ŽÎè –Ò‹Ø¼fðÓýŽãEü ã6Ø,È=3Mb/A¼‰‰é­ r¹!ò…ú—5À.ôµYl(e²½ ›U™i…ZØûèÜRì±B‚ L‹1àÌóÙ¦3†°±olË>ÐßCûÁ%d^Öaj›Ëû™`.Ú!$ÀäÓ×Ó¶$âDÅ :Ÿõ<„GìÎäõMŠD:ÖÖ15]å‘ëTDò'ã² ™NýC}Åéñ Ø{‡-éÿ~ãRº~í.Ý_Ú‚FHŠ©€,Ñþ•v”ûžç5]oõ½ôásn6G¬/´ã‘öH|à-^Æf`î’4ª^¯Eš`­ .¬ŽÊp0¥WéE4‘Á‡{ë}¤mde„dm{Â-èÝ}òFþªàý^_â×Åkz<‚É9GÚc‘ت¼ÁÿûþKï’Ñ?˜¾øÔ@úüsŽçvº7C[;@£ü6~Û˜KÏ®¥g»—p?éS`­ ÛpϯŒÏ¨Éú•°æVÌ›=/ÈcéÐ}<ýFñæÞ9´³Lâ² ¼ÝÒÒÈç* 3èˆï›t…||èìÙ4JÀ@Àm…®Ò2$[;¨€…2h.m›Ôiµ•öÁèȈ›2¥€ÉÙGÏÓå‹ }äK€¥Ð—UN‘tâ|`@h\³]®Uƒ¼hÁVb¹sçvš hïºxTZø‹ÐZ´We¯Ý}Ï{äù'èÜðÌO{‚À9ôuȦ¸FÚÌÞÃ=Ó_ÚeßVlõ~öܹôÁ»¤IÚìW7g²Ü?«”‹ïñ{ƒqgvlv¼€]Æ +îå:»Fòp;óu}´M;±_ô‹ÂÿCÖȯê¯i°G»eon›ânúþßþ ë7 áçq¾-Éh•5 $<–Îã{»o^K [»T–ÿÙo¹RbýñÇ‘½]Jð+h‹µ.,ÿÉOþø÷~ÁF±ÆJYõ§{ìkŸfŒñ9çÏç£Û‹áZ('\˜£õñj°Ë/ôø´cø….ʇ½®ÿš€îž8F'kòºÕÒÜß÷‡Áí|éT:þùW’)¢Ó·SI·È=’¼‚*“¾•çú”ª&4¯L6AE{€Vžà8 øË™ú)QÍ o«K JªÓ¼†¾Zè0yÅ#a LÍÒ*ÛVÖò¶rvÙX}cb Çú1!øø‚ÈÑ9ÚÐú£Ž²J±½¥ qj€ÅÀãC÷a(owóèkpˆ[³Ç¸|V°[YÖuƒbyNÒ7²)ðìljÚOÂ1ä ÷ð·ºÁçsóiG ®”çàO T^#À×ÁúÜ$sQMYDáO nÏ%é5Œ6˜VOága[¹¼Œ?&\¨½ÇOÙŒL9mâ’{²˜I¬Î-á›qàøá*ÉÕ%$nu„Wgx·°‚ÃöÈd­òÎJY±Ïm¶šÒö¸Î§ Õ aÙ½'ûS í±’ÀTubkˆ¨!ˆeGŽ*Ư¬}¥íá™ÛvPžûˆbÖC¹ß6ëyȹ5’—‘Ùž+.rÀ‘2&C4èTÔ°JtœdOµÄøæI¢Ú€VêñÅl,ð ]ÇxëwöºÃÃÑà#Ø©e?s{›Ž:7©‚½YýJý¤¹yº¸îÜ‹Áf¥ ë ¤3uˆç¸6á7çÁDç ¾º°ê—<{KúY$Á;W‹†jÀ‰Õù…Øñá*HZË$ì‹'åàÇ`AƒrÔñúC;$\˜`æ*¨Ì¯'Ð>ÇØ<§wO Ûéà÷ì­XÍ2þ­í«*éšÚÖx› îa)•‹Òè:˜º8¼Õži»ã‹««¶@Þ#TêË©Úd шyWØ‚V¾‘·LðŒZ ÁÆ'&ƒ¿I«X¥¿qß]äOØ—êÆ }©g \iG›0ipçY+§LzÑ·°Bpumü{™û‹K#%ÙøóÓ6PWaµ2* Ysþ÷"hcDÂr‹ä´5l«îuPáaD‘’ºœuÜE^U`ÿÛ"[~ÚGÎÌMÑÍÿÄ@,jâ(&‹<Ä•õÄB¶À5ò›võƒß™ƒ´oâp–˜¡‡„ñ5æø¼â^ýR…‹o[ïyð÷Fh©ŽkÅ<$8~âÒ|‹‘ýü‡Ÿ‰¸ úEê &Rí|ôˆ.»Ìj+áo^MûÈ +ç†Ç¦B?ØLúÑ>Ö/°û¹“ƒ¬!¸:”ñ3ëúf¼.Ï‚ `Wº‡|ÐýdÂи,:I<þ€µ–ñH•VRpƒ¼q=mõ¼µ±q"»óptAgW7-ÂéiŽ׈$ÖG¬í¶A2È}çÈË\“`6r¥œNÌÛ$ÒÌÍÓQ LαÈÃÊ õË<¸emMV·ÀºT‘`*oŠT ›Å2=zã°‚nt$ ÙYnß½¥«/ÖLœ\ž¬Aæhïõ÷ÒÙ¢µ:½öÎux }IRž*‰½éý+7XÃj’nO§©9޹ç¬CUC[£rXP,ª¯ažð³6‰ñœ<‰FÚÜ[È‚“=-Äl(l½=mKu|L’ñzê‹Ò…^Ú‡£s‡gÁÓ:²¨?b} }‹ m«ÑnßI“ðÙ>2¬«…¤:w,Ô¥ŽÓ¨£•{S ýÈP9­Í0‘#QޤŸ³²ïŒ­à¯Û„ 0>Fiø8G£TÙ¤]LÂëf¢[-2ºŠî •Ä›k‹Óg_x"ýå÷ÞH¿ó…f :Ò›LpŽê[XK#cÓ$ü÷Q\0J¡ìû‚<†LŒo’ÐQ¾+.ã'N„â±_fRò!þRš` ÷ÁAv]ˆþs¯ÕE„òÛ®…b·&ù©ÏŒí"wÊ@ë³{´Œ4Âc~&¡—™È!Uóºt:?ÆkXÔ,í›´G/èŠ.ÝÈs»1pXz§c€~ ÇÀ”£ã, û ] _F,^ÌI˜Óg³cÔŒMÿ# Ø#ç™‰Éø•ÐË>¼“ÇW¦x„H) :§Î=ßì¥Û—ßKËæ#v³C²DnÅ.ÊIdt¼QHwpd’˜“ø ˇÍþ’Þ‘Ý==èJt0<¢n2NûÑà¸ßÉb|+ˆ5b£ÛÐb^9­½ì|L—‡üŽ-ó+vàÿC|m³ÇÌòj㌷6„¡à”Õ‘f★gÆa3ްÄ?z÷*Š‹vl\D‚Ð0×°³•…çÿ-à$/bÌä,es Ö´$ó\‹±q‚lh£…bs œ7À»H€ÌÖÎÝ}dŒÐ‚F°¡°ãÚÍ[õ[ ™—LÖ–‚i„0³ÃÁ!­†¸§àÁ„ 1ÒGæùtn-ý»¿øs‚Ád2`+°Ì¼Û¡äêÏÂѰ=$ûÃñÚ_#IÁéæhŒï°:è84‰…Ìc…[!ë –83°×ÄO¶wßèÓð·’Ö`˜Áw7ûבȗ70 ?ø=MP¡½³›Ê’“d‡NÒjî%Ói0A@[¯A§JÚ#(|¦Æh£SVQM^°·I»¼6Ö òFºué]Ú @»iea&u6Õ#³ çܾ”^¯¨I}ƒÇ"xQ†CR[A¯1`[ú¿yùo©„&À)ØÂú¬À¸b0ç ©H̼Ӱ9ÖÛIkì3á4}÷{›nͦþÖúp\ ¢ocœNNcœ Ø— ¶¬#ˆÈà#³IÂ5p,Ñ{Ía>×G¢v~|mƒ8‡YËæY½DsÉŸf¬¬a€›ÉVÎû\×*/¬ ÿ‘ˆÐ+ÅëPy-sFK†5teÆ!Z+îážÌ 8൧Ž|5sÍBª"çã¼ ÎÙBïë2 j‰`ó.´°›âZT©EgP§¯·¡à‡ÒÓ©»yù=¸Ù–$ÐÊÙ±­“¹¢ò‡Aø Í‘]stÑž¦à Ë e>ݾv38Tp­3§Òíáéô¿þ³ÿ9yø¡ô!¥ÿÛ¿ø€ó×" T ÚùˆMŽÜãžd( t„3£[~úÔZMžIï½ñbT«j¤Ê›<€À=JXË-2¯~òT$Îí’u6khW[êVÂÈ€qÖ‰¤俉Qêù±Ñä…Ýjƒ'•‘:Ds#ì·`T>6–×ì.R‚áÚ„ÎÏ3Ÿ†æSЋõ[ôP&„³ ý ™Xæ™}V›¬h›ly9‚ÔΛùÿºVvФÆ{†ìm‚¤¶Á³uu/Á Z„ÉkŽ™a2¾#à]Ûê#å)"4œ¶zè³6ÏöÚûè£Qû྾æúðO¶ïGzðþ§ýå¦GOT ¤5Îs{õµ›é‡¯¾Ç±X­1f×MýP¬õ¹ArÇ¢C,™FV{ì‰Áì¬[MFòú$¬óeÝ}~À}M˜0Hê:º†‚—ÚîÅaTÙÂÛ1ØÒv{ɳ¾{t]RGêàGU?×R‡eAPö €Kõ½»Ã¼ÿ²_áüK_|F‹.T¬Að³´Ãåì´ÇòØxg% íiÒ³ û4@hÖ\Zp\\%ÖÑjSi«;Zºl¡Ú¾»§—³go¥UèÇÆ&ÃöÜ—®¯\ºk" é÷YÅmDÿÀ„÷îð=w X»£`¥|ÀdDdØGmO«6kS_OWúà£÷ÒPOk!0É ù Þäóv»bêñº|œ½Í½Ys r(sLV|úÙ"áÞ;ø0k?‘È͸†Ò±ö¼Ï¯ö´¾´ëêð-Tz_“³ž~þÙtá‰'°gà}ÆŒDеä±nüÁX¤D).{=÷7b2ü6ùÇ¿}ðÁç“¿Šc:Jj‰[fïñþ'‡{47Ç,½„ncþÚÈ>÷HH“ï0_}ŽX'tW\Ä5ûtOû¹Owµ?•]—qð*bÌêUd©ãôÅ¿Ç×^^[&©~ãÛõV湪ðÛÑÜ~| ¾šíš@ÅE$¤B”››6¹VÌÀð(˜¡7HTÛ@ÅyIúœµ‰ü0vi•*4äËgi¸° oHùGòž›'!ÖßøRú›‰ Vì󼊱zG_U{Áy¸>ô…íVýd¾ ¼ÏŒI& þÞ@êÿܸ}/݉€X;A z‚¸ò¹mUÕ—”ÓCv‹Ñ–ÑNFÎ)Ÿ±Í ä{We´-¶½•6C>)‡™ˆòOðWY¼Öà ÖÀiôoj`¾øÝ·ï €¦xƒ9•W"¸¦KóWI¢Bm‘0^Èšg]Õ±ú¡Õ—½==Ø´ó&X¡<)cÔke$TUКU= ?%@¿?7OàÔ,º€×´Z¸¥¥-æ¤þ5ùLð×3<µ%ÄëBP  ½„éŽ|Nÿ¾j0šÄíõ’¨³@ŽgüR1BÒ2üÐ%žÕ»»Ÿ$°1¦'&Xw|4|WÁdåi*°€õf«±ë'òãÌšÑåV©Ê¢ · \ŽU^؉  ŒPÿY¼Á@– ü&6Z8la8ôjÇ•‘‘IüÙ†(0Òß3À¦þ]*¤%úæ.¶óÙ$0V~@B5Ø›Çú1yºÏMƒÇ€ŸvB“ØLŒI}ÐÔJ²_-¶ºUš¼®OjGH}~¬ÆÀëë“F0ªº…‚l‹p=;Å5 ^â^aTИé¤u*X ж·7C¨N£c3q­°÷ øb]5Á!tù!º×óÝW7 4 Ñ´ñ÷ R‘âÏínç~.ß¼ õÒñ¼Ö rl)Ì,Ç_Ž*[èS»¡–õÎï/PEË:3öÙÅ ö¢’ teQ c\ŸÐ¦*_ÆZI2Ãôx°KGÔ.ÑÈ쟫×n? áÃÅ<éÀ‰°Îz1Þ6PŠ膹‹ŒØÆž0´ÁŸÉ‰ÕQÙÚÆºh£rÜ&ÝeóëÄ8”`óåzï (…~b\>¸ìå¿G>š¯~º‡€íüîþÝ‘4EW¸“È+T¨‡Ÿy!¾ösÙÚDÑ…ÉP娷í ÑúrbeÊ{íGt½lV̉uV6".‘ÉØ~|?ŽÛá;Ý$*U„óûag…ÍáU¸Ž¼ –§ï*=mút‚yÙÁĽӶÓ>×Ìo@£uèÓîÓyÁ*ÔHº"¶DnINëtMFAÈ™ýtF¥Àk퀠|ÚâìîÙyp1d—A·òŠjäO¦»Üç£?àÞøÜvîâÓêD‹?´y¥qíË‘áÉ2Û%³½-—ú{:Á<9 ž´ÒýÏŒ`ªkÆ#–…`‚ÿ€umƱÊAqK ¡êê輄Ÿ"6é^ïáÃé/zœ„~ò(Gº™ôÔLlmŽ.Re¬ƒÝ.y+Šçäq»’šToÑÅKpvŠjÁ+jÁ'Œõ‰¹•âçÿÂcµ=óø1x¬w4ŽëœÈm§ß}™µ·F—ª»é¾ñÕ´‹,ßF~¢+i«íyÝ·¨Ï-Ûx²‹•vƒ¾M <¤íXU¼“äÜm:ÆÌåèø¹çñÈÈib.RÒȼÉ2ëé\_]:ÖFO`}¿®õýÍUé.mØúÀ^6Õâ‡éx#…Y$Ý™'Nüª’Îuº÷Зeìëä4GM[ÌD||v5'Hþä œ–èfÝñ*ü½}:nïÓ9y ¡ºÂ±‚î;-”S8e9c¶‹Ìøô2ØcSú xÄMb (¨º6¼‘ÑM…ÌñkÕ†N;vb Ú’ïÓ![lucŸ±×Ò!f?°öü(±Põˆ ãÊCeôÌÔbø®=ƒÐ%Ç‚ËjËZ쩟§½§.ö+Ÿ”€k˜Ø]JÜi·ˆNDÐZÈVFoœ2æO)ãì ÉÚ°.‘ÊsÆœÖÛ‹–Þµ›²„l1mll†:hOš0u`õ>?ÚEÅèêbt^~¹˜J ÕívÞÚ¶]<˜¥ºŸxÇǸ÷l1-º¡tŽNB\Ç#ÖÙÿÛׯÒ)p€A’ÇùY"Žƒ=…ì=ØŸŠâI¿1†£Ÿoñ˜ö•¼¨LÄu=|ÚÐÔ^ßBÜ$ñiå¼¢wŠÖÔt.l_“áôÿ¹žôoÞûIeë‚\F_›ÏïˆOr«Ò|˜‰â*ë ¯0°bÎôZ‡Q¤4ôVVfRu9BŸÌšeˆx‹ªn³¾êêËi£B–‹ëÙ?žAcÀ,³\•qšÉij=”Ï5¼w·må„’(õ¬ò=²fø¾‚so·2MMÍFÛÁÆý»£d¬•€¶@–¿€‹-\vh1eÕ_]-í¶¼ŸUv&hr K¥©á^C@¡0ªŠLFú›a W¢ò~¶&ñ·ÄcŨÏnµN§U$ÁІËi+»‚B¯(z®Ï2óÈ4 N\CƒqÅaF‰‹^‹€iÆ!QèoáÅðµ´Hvi!Ä{×lWÔ”ª2œä|œ‘éØŸ73ÆrÐ¤É Â™ 4óÓq©oê eÖRÚ=} -$x×U†’Uís¬[3Y@¥ÝeiøöÅôÎ/Q¹¼™>÷•¯§Çžyõ@ aÄM謢%Q%Ùª¶«ŒÊw%¼ëb¦‡Dïü¬0ìá< ’'˜Kk#íIÝ m×±ÇÙZ¬'üGvã…2À3 & li`Ù>ï‘ãý\åc¡qÛiáÔH&i8ò\«§e„’ó¡2²ýx欒¼@¥³Ù%h6 ø,ãF ソi(ò! g›§PD †Æ9ŠÍƒ4ì…¶Òéë¤R>œ1èÑ}M˜€jeBÚ$qD¢@Ò°Ó8Èö‹×fΜ;Ï]}_03f¿ã–|7× Ùêbª-R}âQ„´ÈuÏ{o½žþ›ÿþHO=èƒ8qât¾–Ξ åkv·ópª¤]»ÆE´sãÎ7?|àùZ´¡‰Ö7_3q„¿PrhcÃ÷pX4PD{ÍЬO«C0²é=Âqþ¼ÝÌú/qà *`V1Fö8«DÁ½X-¢³^I•´‹­ƒä^-aÐ[Ådg[ÄíÑŠMCzk…öWð˜É™ífZŠÙXÐlò6S°ÑÀnŽàúýûã" mŒ†IÎGU9˜‘¤ð—Ž ªÐýÖ¿û¿Ò?û_þyú¾¼hëêþOÿ)ÆI¼AÛJ¬w«f¢Ê—=,”Æ5|­œµHç}%dF¦@¬~7ÛºŽÖjVô\°E, Î÷¹÷ “}0»U°`ƒoÀêd“ðí‘á€ÀÐóÆO¯ ØÎÌ-Ã@(2ÙÊ [¥A_Uyß6ÖUZF!cMÒ&òN ÇÌ ze†Š™g“‹ëмFŒmVP\×3î+‘WÒ¬aÈkìaÊ]£X>RV«ì4•Òa=Æ–‰L®õÝщô­¿z9­òý2îc„çi}ãþ‡0ûo ÜIX‚¯6X[²µÃS:÷«+(~Æþ?ýÿrÌD}®“ÆÈžG¦"†¤ÎÚåK ÈäƒvÜ[Ë[wA[ð½U¢=}Yö¼õoÂ0ÃÀ†yȧ:î‹rQž´óŠó- ƒN#¡ºZ°† ?ò£rÁöb®£g… wõØîõ0ݽsŠë%ø—³Ý¬6àfi @í3F«÷Ýu„m“רÜ7™ª96Hò„ü¯h7Xí.L$°•œ°ä$ݹG=·¨0ˆýÁûS1YpÛ»ëÅé­78cwç9Î×;NrÍûéâßÐÈ+ïÝÔÖ•«óü­s89>Ê嬈Ì: l Ó£šž½V§_ºø.ɽÁ‹¶¤Ú`®y²5 ]#÷ÈL{eßýÑaDŒ%äŸÝI¶“Æœcvãœß|£ªYÖÎn"òÁ,Õ´ç]‡ …-8ÅmiLCeŸ^™š[Ä:>4ôÂÙá^û™ýÂý·äí÷\ð ±¹%Íßæë‹Y”U#Y0?hÒÛºGŽ=»œºÜ{ñôé}ÚÐ]/|þ÷Òm’r´qŸÄ¦ÔÃy¬i'Ð+›û8weTDÓž,ðl+OÌÿ,ĵä/yü­7ߦEå-’Qé Åžk/G’<È2…¬ŽŒ[æZ9N:­ŠÛè_—ot¼‡úÓã_ˆ*Y+ü®UÙÚ°Ê;&L„@¯ÈßÚÁ®’Ò%D5]wרy:Ž/ÿÎÓã^H^ºœ^}ãMl l<ÞW~LÏþgtW"{Øc%Â,€tþMBS>š æ=LlàRnÚµD~W¹ßÇ !;[ÒÖïozäÒwÿ꯱q锄üv!2zÐÂdlÜ%] Ð84ØûQ˜!(í;þçZë,Ú®TûÚ}UÖhë v ¶iŸ`·øÝi@Û¸4¶’G¿ õŒNLüN ãõº® Jè3®çýWt bíF"(éÜ¥#A@N’ÛÞK9ã›¶–jÙNÎv«…ÏÚzÒò~czñåÒkóJjíî Z‹½`mÜ «ëJ¸ –91/hÈý¶BBù³f¥}Ǭ¾Ò^“Ã&÷"üƺ煼WˆPBÐBi'0w)öƒß˜bð¶õ&å…­Žþ{Àá|ýd½\K³¿£3÷CdZÄØÆ¬™~‘ã5Y¨€Ï™Äê˜Ky]Û@¿ì辎QÛ¶ÿgv~5ýë?{1ýÉ?ú©¿ë|úßåŒÏË×9’l;–J1‚B¶#•Ö˜¨Ý_Ô­WÚL<|G³É½+ðK± oc.Eð»4«?"Àbb sµR¾°ù€1HŸÁ[´çÄ/s®ª.méÉ5 ]·ÃŶâ™§‚n¦¸5ŸÉ‚ÈÊù ïåA—î›ûã˜0ÙÑ×M¢\_$ÕîÒÎѽŒä¶ 9î-€Áx¥?uí6ô à«Ý$¦ýòÌi’shÛ>•ƒ7ñK#ô`IÎD˜$H0¤ý+ˆ‰7ä6üw“z¤¯Kt?dŸ@†A–š=E¦!;ô[NŸ}ˆ³¯a#Ø>Ý®mÌ…½pNwÛÁ>!÷fò…6¸òAÀÏÄP»ÇÝćy¯»› øTºzõ Iç§é–ód+òµòËõž#AI_ÁNQ¹ùǯ§gžz49{>0‰'ž~6=záQÎ=¬ ^TN2ýÐOlAȆ¼r…׸,©V9í²¶&Ñ0lº(ð慎ý>ÿÿ̇×ñ3¿ÊGðán’í«ãÃ`wúÍ}õÁãwÈ çÊëÑÍ ¦µòNµ£·9`¡»‡jÝŽç¿îd#•ï<*C»ÖÙ„Í“úuè½]HЗ³¹ù41:MÀ€À-ºÕóÿÛC>ÔF)„†CÞ£ëõ™X¾Ê{ÈRßW`U„?ƒXJ+Øè£TJåØ{ܔ؈•P¶nU™LçY×âcvšh$AR^0‘Ê ºÚ*’õ+`^1êj便Ïõ½•_â!Ò…|í^èã ð²S%wŒ9Š%‚Ñ|VFëÌÐ!£ßÛÕN²a/>1IᇛòMÊ”¹MÐ4‹÷Ä“sÌÙD\ú‚Î-ºÄAÊG\‚Äø¿Ð³ë¬j0v‘ NÛ~Z5/6PU^ÀÊQÅ´†üµg¸[ò®’„²Š< šxÖö:xŒí­ÛÚÛ×´ æóžºº~CÑã‘¡Îc]ÔeÜw“ßùU@t°·R®éuÂ6rÿ°›ÕUJÆ}²% H æÂ§ã—_£@IDAT&‘¨772†Œ\ÂFlŠX,(æCût:LNL\'`Íž/‘´|H@©³ <ù.R1·‚|Õ_ƒÃ^QWçñçªj°ïѵ®÷:&ÛðZ5­ÿfëo»•-"÷+(¾bŒ&ùVA‚÷&Dpž5´‹´cÃ"ô¦oST’uyšÄŽ +;²É5´q­üNø¬ê@ψ-ä¬ùJl#u©Arq`»;q¡ø ¡ ö€@.ô˜é|hð'iPú¯[4±»–`§ì [Ù1À)¾±¢„¦ª ŸÂBªR™‹ç+»wâ\KtY\\Í…ífE²4bB³Á]í“ön97G̘$¡bÄ_À·Ðg9Š€ÄZWLÉ‘ vA·Å$ŸÛÞ_q%îC›œèS¸QLaÓXnà¸8>â„5­uÑi1d ©Q1ÉuMf¬ËÁ =Ç\L¿´‚+ƒñˆ‹x½ü(NWȽí8° Þwíìðõ£KtÒXÍœHNY€V ^‹˜jGt5¡õµ¢Ö„[™ƒ³Ãþ³3TÄOà0ðì|˜ªÛy*¦y˜ô¡‚?WNɇ»ð A’¹É؞ʓ‚«µÏøœ;·5>ãú£Ú;üƒÌ3À›o­ý¾kz›û˜hQ@ñk-^£öP86v°sR$ÇqʼnòÌôêzí%»­ié+sõµFFì¬Ð*ºƒb-h«… ¦xŽØ‰ @Jß=*:#@7ŽÁu¼¯6¥tûË<”ƒŽs–î#Ãtä]]L ]­©¿»Ëiñ®wæÉ/ñpÅ"8æÍaÏ+R]3]¯XÿÌoà7þ4%¾vǃû¸ævœµÐJÚ×v£Þ°ýLZjt€Úù?öˆë˜|…/?n#WõÓÄç(ÿÝS÷j[4,ÖŠ ñÏLÐ4ùD:£ô<’ÄžšhÞÕÔ–¦''øAIŒ)§Ý÷â ¸#ûÜq¼™ë›°æZá 0Þí°?C„´hŶbrÌ×úç‹ßÇ.š¦JÚN óu:K"ÚøÄ<ø…È4ýK±A‹S\'É_›JL[ׇEU¶õޤ]徺“±Ø%Äc Ä\óäû&vHê£$…®@sv±-(dÌ¡›IÈZà¸äO[ß4G1x€]zKH¶)£`rƒXRžõšÿ^¢âÜ=È!“ò$›÷¨ ±Aߥ£•ääÃõ;T¨SéŒê#‘¡:9Ùš>¢pâÙ瞦‚»ߟÎÑ$]uóùYÚ»¯C(*ãh–*ä:ÊXÈ>Ç3ƒä¤edF)óîi®ˆ€øý9y»€Šr‚ÔT—Û%eiÓŽÃà­ET§#$ðAÇsQâËé‡v×s|ã2c{]™ÎvV¤¡ŽJZŸƒ ¬¦ÁÆCè¿Ä'»7ƒŒÃ‰¦ü1A«vQÆê1ïä¿rƒNÈu>9hÅ;éXg…Rst¶Iƒ= éÅާݱ¼j ¡Œ•,|ÖW—#!¾Ô¿4ÖjÌC\P|´H,Ÿ*s¸ŸjDÙ¬o¥Í±9Iš÷”Õ>¤Aí ;×F2øÊwYžŒŸ¹—X®ÇMhCYDáÐ87D6ÒEŒÀ÷.êÚ)ÑŃkX$a¢M¦—àéCŽÏû( –T:gå±]êѯËk…†-"³µCwgl©úŽ^½§à1ºYÐ…f‘âèCä·2T,%Ž9æ²á33í*.ËûΉqbKwuwâCrü,üP8µ@ò¨±Yκ òØÕBö4ü#Æí0å·èJÆ\¶Øƒ%b)…vij‹ëºJ_yif",9Ws(­bÀïèÀf€©SÙ‹ßÖ!|p®«†/³!noA ĬÑ5ˆB¶¬ÈŠ‚RÚÐä0àr@ÄJ‚ó 9ë\¼„ ´^ÕÔS÷ïb€ËŒû]”U;Æ‚ŠjmÓ,82÷TŒ… „FBAYƒ½Ñ¦!c++ ,”r¿2”ŠK{KC´ª©Æ°#ë$ΩÀiqîV_E“\6€Ê!)÷UÁ™¡Sǘ½—AFÁnä}F¶Æ¶-¸žD´Sær^Û¶ |Æ$r£×y^\à™~õ!ÀÚVšå`{¿R6Û1Á¤¦æö4D5±(ƒ¾ÝýÒ#Á8 8fcÃ0~Y:óÈ“´JšD©Þc‹Å´n&ãSº±µ cšìÓüJºNËÜÁ'0‹Ó›o¾“úºzc¬²4f(+H4N$JÁ4ŸûÛ ŽU#¼wòUf%kÞ`²-@$duηNDiàJ¥b&€ƒJWaÏ[Ðkв1@™EpG=Ëœ1{Ëlìs*Ñ 0Œ@¥‚#Pª¹Žëy¤à5# ›ë©ÐãžÜC‡[F £›ýÖà¶=ÉQ½Å5"ƒ‹1°ÄOñŒs¤ëÖÛ‰:_éݱ˜Eœµ¿W9ÏLQ*œ4À|ͬk•‡sr|ŽÕ÷å )QÃDúq®ÿø¿ø'é+_ý*ÁÄ‹éÊû—éÖ0ˆ³c¥_m8&Òï.câ+¼.0.ð© !s¶wˆöÔ£ìSæyñø^>mÄ0‘‡ Œ"Ä‹9clCø.­˜¹F;‚Gc¾Ô#ΆÁØ2⬆3K]§H9„[}cÛo2J±v%×S£ÑÌM+u Ù !¼g&îQ¹æÙ«Å(Ïý`•#HlBŠD °j‹¹mº"DЙ¥61Ç ÍKó½ôä³Ï##j˜ôÉžk0º_ÑæœµlëDH£·®F6íiªeÛÉ~4[XÞÕ`TØJƒVNÛ.ˆBðc+XƒGÆžíçÝ/ƒfd`¿ü¢,uÏò‚K\LzÕÑó¡&IË&{dNÀa8hz²UJ_wŸËÞ‹ÏòÜLxé;_æ¦#èÅ¥ H(îcÕf+ Š ,è°â¬ðZ,ù9ù(ªûB³È”¬.t*ýÊ7ÞWCçWB™%FpNåÍ<¬r_‰¥Õ¶¦âÔÎõŽ{ƒ€»œ=“:©p²ÛÈ¿ü—ÿGdJV¡Øu~L,Ð)ŒÞ£ÚÝç 8öL×5woߎ*ý†‰w·-3³Ñ…d왼á9xŽÕ E ÆîÝä ¶±ƒ*Iº©"pg¢@ÖâÉ]¥-Îø8AÛMª7[ù›yãœM‘Jîe–Ÿß °‡µ‡c‚>x]`(x ^*F 8{Æž:¢†vCžt€gpæ‡?|“츽tçÆ‡\g›à¦íöȬàZ<°šÂc¾©¢¯oj%%G2ÉÙº‚DÊ7;H×íjd|gtiP ;—GÅÀ¤WOH“\Û}»yýZt°±5`~ƒ³~H̲•ŽÀ”|§A¸—£´ëDÇ“ñÑ‘ Ñj‚ð§bÅEŸW(ñqÖ¸ŠõðüwäÉ?ÁüúmzH±ò™UžSk÷Ò Ó>‰ sÿÊXoeƒtf²Oð=D¬ìù»yYšþè°3ö¿{ðD*DÞß»q=MŒ§._Öëè“#ÒV…-ƒÌ„~ËMЄ¿ñx#áÅ‹´lðÒs¹¤Ó¬}‘´0v¬<‚DâḑXaòëâ†ló‰ ¢PG„SÀo Ÿ»&Y&1÷æõœ—4`Ðãíä.rÐõÀ•÷<þBÞ·’â zsNÊzùÂ1èŠz9œBdNM •*µ$X©‡"øÉÇ&ü0s;»`XÃÃ%àí ô©çh ÊÕ!+§­†‡Ž $;Ž8ä42³•MêqOµ™L=ä·vUfá`¡'´W”-¶ÌÂ^%‘Ƥ2Áˆ*²}u˜L+âþ¶ç‚©µkËÐQ‡´Ð‡OM.R¿ L넪ß\&tM:6Êë·o£ Ñ™žÓvêô‰8{Ù­ß#ÁÕ fõ†¼¯Se¥‡:Õ@dØÌÈv÷H=åú:¿-æ+ˆïßÚVžíé¹ÝÈù%’¨ìÒ$eèJ'¥p>o——¡ãÇXw*R¹—»ouÛtiâ…³¶ì'k¡÷£LÊ›h†.áþuT¹–£Onß¼™n^¹BDV!&(ï-³4çz;æ 6¾¿špô 5“HÝ ¾†D¶<ã Q;“jqÈ*²Vwí-T•Ã/ØEµTùŸ®Žo¦—Þ¼žî]½Nð¼'tŠ´§ b…‡U^‚†VJo°VÚ6®™´ÁÐbŸõiCä)¹£4–‹lo0>pNƒζW7¡ààÐÎ:b¹–_•µlÿë5ò2ZnÒïŠy"#µ´)¬n÷ޱ>Èu×dšpí=ï/Ûo@QÖ³E¾Î ƒ¶t$¡ºæì«γÜ*IȺ3šKÿê›ßNŸûÒgÒ©¡žôÐãõ~É*ŸH£ã؉TF,,nÓÉÌÄ-ª9»•;/j÷Ä —~— ’F}Cz²ÍßÎÅà‹GliWÛ‘K;û@@=i¥GßàñàwÏy,x6ƒÝy¸éh;¯ˆoUÎÑb'R7~ÈÉn—Ò«¯¼Æ^×…}¨Þô€‡›ÇÃ3Z{öéh)ìÙ½­€ î•ïÚÉg ÿXÛ——b¬ÊÃ|T“2YÈ^°WV©*N×_ÿZœG~çöHdÃÀ½užú?_þÚ—c_ê¤ “ |[\³h=ãSíkÛÚ Tn:™˜)=jÈÒeÈKdüЩ“éýãÿ$ŽSº?rŸ ¸›á·8/#ÉN™­]U‡'µ/ ÂYõ/•áSzôÕêþoÿ ÕÇT&¢Úîû¯|?n& }© ÷Ø“]‹9_w•ñ¶÷Jçž-¢;J7xB[ȸ‡Ï=Lbþ)4è¤í2"!mK£Á#؇ð.ÖpÐl´}uÍùP|.XFÛž :›2üàˆÿ¹T<2š°/ñ¢ÿÈ)?â~üÉÿÄã'?ýñG~Þ{L,æ¡ÿ蘶C–«w"Š‹H'ÎåGÃá¹c00ηâ{Ò!;”fIR~ïí‹t;Á?Ƶ’¨œùJÓ?eØò×ðLÍ@©I[žeŠ-‚‘WLê7=ÆŸµ .¿{$¸¾CÕœçaZ”aa„þìøñþü”‹¸·êKM¬LV †²Ï¾W ýî³ä7³68c«M; úˆ.8ÊhGþÀ×€Ý O = ÌØJBœf›€a1²§ Mý¸þ¶ŽLµc–‰ò•¥1I¾Ad1iÄ~òû+Q¦(<©# \§m‚^˜§`ElE¬¤­Uÿ1Kè¶ÃÛÞÁRØ`+èúWvš´}þ É^Ú¼&k©‹¸§¾mŽŠÒEº$zd™GÚ)õ­`5èjÂA :0Íî¶FÖ˜J~ô]ì58›Éö9o{yz”e_b^­à â„Ó€÷k+$ ‚6QØ¢Î_ÁÇq&‰¬”×jÏußLpŸö=ND|‹-E¿+¢ïÕåÊ•f0a‰R ùm—; Èi,°€î›å:«ØˆÕYò¿8v¦ø„g¿óqÆ öM/O„­ë1tuɨo–¨j4¸©l6éÁNql©\_UøsfßK_Û Z±f[XKq6šµ_ þ°‹‰mdíì&3Ö•"°ÍYÆÁq…ÎbQ‡ØË¹xìT/ø,GR•½Ê±>ò™N›ðÁ*‹ÀñÅ7–éÔI Plª[æïy*• úyLi û ®Ë-[}n‚Š× ÙËÏ*{«j+ëæ– ØÔÌgYýÆØÕw¶ãU‡Ê‹Ùì<%¯i"éc»bêR>Â=RTu¢=u¼d!tl=ú¾žÎ­ë VYÇ"æ*¶'O‹qæ Ž˜üÒD²5ùÐüÌÞJÚ#â•â7{èLÙ ò >ÓΗÜSFÄçyÆÜÄØB69WÆþ”À*iÆb“Yç`•1b”âïútb]Ê ¾ˆ]Bõ!¼U>®]¯6' ܹep&°ÖÂuc±š]]XdQ÷³PD_D»%üæ‚Ùó0PdPVùâÚ*w°Ù˜Hö>Ó‡30SËĵ‘o߸“ÆÂo_N=XÉÓž^ÁÏ!cª$)Åäh‹íÊ>×I»‚å¹›­„«ñó®uئÌm‰ ã©ó$•›`~ú;'ä2ŸöšGwtç|x;WéãX}®=¼ƒŒWT±÷Ú1ø[ž5÷³ò 4ÊpyYy]L籃^ðWì޾#•xOý“[fI´QÚAØ„ùClOâÚFŽêÇÚ¯¥íOýJ‚üÑý ½f…´³B»öº”#ÒZ –DrrŽqm°fvÞ5©×n(õÈ÷@_×kÙY\/;ê1à (Øã~SèÐïüõËØ¡ÓÈÐÝôÌóŸa÷ÀGЃiÌë½?Š9*û\/i͵2(§~–޵Mô¤Oçm{j×\Áwæèø¢èªqµõï™8ô…ͬNƒV-\J‡¬ôeÑŒ];E± úl£ÈÜ—ˆÝôÝF~å)œqíË‘¥&õØÍ·³»;øèÑÎäÍ2{ˆ.£ûcn!Ÿ¾ôÂ`$íкg ?]¾G ˆdÛƒŠ Ù‰Nù¥MO…n$X(WT‰îBöZ~ºƒã¡ÀôoL!Ÿá%q,ÏM¯!s©?¸§yµñör‘î)ûÊðqOvÐ]ŒÂO»¬ÖÕqŽ8t¹RY¼Åëø÷`†öƒÿ¢/ÀkÒ|æw¤;±rH”@£ØŠ?o Óõ}õ‘ÑS&—‹µ˜øèñ!%ZÏTTÕ!‡Áv²â* úlón‚™EË ýCÇæP‚Œ·šÕÝ&êy£5l‹¬»¦q6e‰a[Hµ¶è‘!ÄöЃÕUCðUuºu‡‚Ê]º,1>åL=v’»¶§˜©òC?PœÊõ2Aj‡–ëÚ'úµÊ»/ç¹y}¬#{Ë{ü»ÇúknÏê÷e±µUÞèXDç‡ð “-½?ÊÐEò¼Œam½h6×R,v 8‘¶NRI'2]$™öI8͵ÞØ.¼m´ÐÌìôì îFKYÖZaY ¶(IÝ´Ä)cByh‰,§&Z9o"Ô60-ñ? §'}“jUŒ ·5^÷ÜTÏ$7 ¸Bc­B@¹€qf5Æf9,:ö, bà:N ö<Þ~˜ßÏš½¯ ´u‹NDœuqf½@;܈Щ_“²‚(·Èà©`Ýú»[Ȉ˜‹ Ÿ ®¯ô³-Ãé70‡5áÂhÑÖZⵈ¯g-œ>ÒÝ»wÒ믾Œà9Œ–uÛ8/ž™ìcmmÇ,c÷ãì¾¶Ž24X_ÖïCÀ½Üì<€G+A‘6Œ»yª$o‡T #µ¥YaL 9íÊGWÒÅ‹ßLÿÕýßRñUK%ù"(YžŒë˜ jä±>‚1Qia©ø ž¯-Ò€5²²·›*©züÅ $QGG†ö9¬…t枸Ÿ¾.-hà+èRtôT†fñFuÌF:­x²Àrpv~OÛ ‹F“Ó¡Ô¯ïËh>4Æ Ó#!èüB2œ^Asi;œ4öË ¿cñ¡££°1 3<¸«\˱`èAÅ\C±cpdv~ EA¡øûê˜TÖ^ÿhîGÆÄcÏþÖa||÷k¿ÿûq¯wßx™xöfºüaŽ ¥®Ø'éÔvFëu.býäGhÕ¹ªD=¢œÊfǦS!/[9kÕ B%À½ ômF´dlÑ$;V[ƒéXjPV Ä0¡ šÁd¢c|¬s>Ù?î³k¬Ñí>ØÉ@cÖ _Á0Û%nøið;Æ„eI;ÞCÅ®<±C€ìŽB)A²_¹¯.äW3ó<Ûó·Ûº:éìqî:L*7•@­$à<þÔÓÈ¡ÚtýÒ›¬åüÑ™þàë_OßþÅw³¬/ùÎæ25ÔýmG éHC>@} îe@2 ŒÕ}ªäûÎ!îázKSVܘ­íC¯Òñ0ËÞëÅwàtÂ6”î”E\4èB‚qÿÝiL3€.h0RØW #ÀͺÚþU:•ÞÄàÞòÙ.ÊñD%}ïàÏ3zËÎS©¨4ÃÝvç&‹d€¤ô"Ÿ˜HYðœÀ‚<ˆ1Î)ûÉEŒQ§ìÏþõ71&?Ëç97‹}ríu£:‚u:J0)%;üíwÞIýƒýìeG€É79žÞ@öm07!@åg$×µÄk /âž(«4Ê Íîîžêžj kX–0tIQŠØO ªÆš¹n-׫$C{–J%[¸˜ ¤ó%ý ˜(å¹/Òp Áô@-3u(D¿þÊ+|ŸÏH 蟣ÖÀY`K¥lö;†‹|ŽAÙùЋ{²…%¦±êúi-a”à”É%ÏѲÂ[@Ìõ¶"€$öܤ—ZçÔãøWsÝ’¬ä xK9°Ÿ0Äž­büšL%}ÛšW‡áC§‡O_ÌEW­Hìâºá^G‡ÝD77 ží<{îÅË8=;Õì]ÎÃA´«L9™™f¶ 97ÏrÌ&oéôw´Tã¨Ô¦9‚^ ë´:Ä8ìÀ‘òœÞ©ñQöa’€Û©Ô€Í°ÇºI³êÛvÉÒ´2C>0¸_í}óò§¡+õÛñp&Ò·mý­T†ƒo2É,<¢Ã=2:r@cÓ¤ÃNŽXäCåŽ2Á¤û "~¹g…Ÿ|¸lG/-áÑߟüÜÑóÿ‡»ûêÑ3ýû~sfX†œaoË%w÷ß‹þ’%¹)°l y9“åä%ä8@NRÃ@,96bɱ,ɲÿ½m%—ËÞ†½Éápòý\ÏRr`‘[~v‡3óÌýÜ÷uý®_¯³µ…“ááB´I‰¦ôª¼xT‡‚7რéMíäƒeì‹O/–äõ&þ£•%ÜÞœbÍa¦Ã‰âå‚›Ó±T×K†â@‚û*£ñ=Æ ç 'z´¯/·Ùõ‹»g(A¹pÀº‡lò^¸Mó+0j;·$ƒöSôŸ‘<ìz<[ûü뎯†ŠÉäíßÐ]hTõu?{S e¿:Ápâ’§ùx =Ô4ã\+Ôá8˜1ÂÞ´ŸW%•±9¹Úm¦ÅZÁ¦B´ß`5ºö¤7¿Ý%Ë\gKíÞY`¼ÏoifI’YLf=/¹À:eK_¿#iáÕtþ܃éÇ¿¼Vìô÷WÓî‡3@9LÓù£Iµ’´W­M h$¥h¨c‚wpµ_ÿà§µç¥÷^—kè®Ý7’u]k¦Ç Æú$Lvëá!Wd‚ƒ܇3ôN§—ñ´Á`÷è1ßíõúÓÀ~¿ãUZ©»Èù²Eéë ¡ªVïÎÞ~%õ¡Gmã{³ë«Ô¯óÕÝæÚýÏÿãßÞûÚÙéßþ`zÿôéÀéœb‡Ñ˜n@9Zrä½4&¢ º[wrׯÑܳ§%fŽ®`ñ.g¬ ýµðqÎÍ$àX7xΪ•» ÿm†^Ö/‡ê vúý¯ä$¯U\òŽÝ¤R”øììႽøÎÉÁ^4ÚæM¼ ð8x$çÁ_úßÉË¿ûünIéÙñHmñ«Yò'/ë˜ÐHMÜ ì_gO:Ë8UžÑ˜ÏÁõµm›âÃ|ý+ÓÙ:ëpçæõºûä¬âôâ@4îà«_ûJ÷ˆwžÃÁ˹PÔY=óuU&tUú¤ û^bß7eck¯r! N|åLÉŠGÓE§><çã³µ[o—…ô-míÙ0ho$D¼ˆ8Ò! úãO­­ÿ»÷wƒÔÆôíï|{دTaTòxnêyoºvßÑ%¨×ÆñäþéÔ™w¦_û­t•öetÄVJI¼\eÌjzØ–Ãh{U÷ç-]–³¶¿Úϼ+<½ æWÎéþ½ª™„†Æ‡úÜLæÒ·{§Ï„µƒÿõ£7ƾü-0yàŒNû‘¼oùwöïvÉø=»×슷Ïs?û,¹ðÖî%«è–kíqÈ:ë…Ìî3ûœ›£ÇÞ¶”èAµÐZIù—§?ùç?‰®§¿ñµp@u©€e2ÐC¿¼ßì®ÿnÿµ{ôHk÷²^òÿm5 ª0TìLöõÿËëùóþƒÇ¡{ ×ÛÏÝtò¦açk:]Ç>`îöoÚHÀ¥Šáàü–1V|&IÁ06}hkÉ7lI½wJŒ¿TeœÎ-»ÒØød5_u®Ê04²ÚV§ Y~vðbôˆ6>4U%ý:‡µàÜ£lþITèßz[FÀ=Ù:ëh³¥Sf]§côLv,;o†Äó$¥!¨@öÓcŽÖzS"ÁJ-»o­Ü#îtïZŠW ;=ZÇñÔQÔR¶*™ÈO>CÏ£š´Û#´Ñ5hGrŽoCÒ ç±kF‚V{K Žu¦ÃlJç8¶”mÒwüp>]us:†uóã,/Udl/:]UPcO­Üµ<&7_¥3˜ÎgzÓ|ölÆwN%ûS:$ºY³Ñw3[´Šê>‡«[6ÆmfÛmäÙGøkéU' èDDÏ“pΞèÁƒ-ÙŽ¢#º\ÀS8ûo]¿X˜Þ?ÓH¢‚ñ×._œž…S;ÙÒéKªÛIÍÝÉ8¼k™~âıðh–¼?TKØ6‘>ÁOBnj™n<ÚÀÿèc=ÿe~XE:ìó­U‡üàèZ¯ö³Nçl†¸Ži(ûEUÀ—.\¨+ÀÑӛ탯ã›ßúVè´ú oË|ɟʺ¹äÖ¶û§cc|ÝOvûöè¥Ú!3ᄎèîÀþ=s QèT¾˜ÖÝ\^ªˆéàè ‰8ÌÐàSN·æÈ/ä0KžËŸÖ _\-Mû¦Ë/æ÷½7}£µ¢í/>ÿ<Ý}Ìß>”îËç"ùøhTúÿZ]®våëØ±s6òMGÇWä÷œ$£ülÁcÈ¿dÛ³ü寨,Ÿh¦nxfœœÑ\ü­39/o¬C¾Þ|0ôn~çŠ?vud\H>t#×Й{ôKiØ%d>á³tZ2JÐïÚfœ×¡îÏò<6>ÃóÚÒù*ŽÛwd×Ðù·„WøŽD œ7nå·HÏ‘¨³Òî§ûîZR›áÍFu/ã÷æ+RU|½€âÑcG¢×ºEÄ‹%°“`4ľpËìw~˜-‹Óƒóèv‘Í€ŽÊÅí•¿3}ïÒ­àÓ»uõêôþ_- ~¾à ‘f4‡¡L÷|xn„ì£Y2Û &îöç{Í®§ë‰‰¼jžôh©œ""Hô2žÏæ}+'ÿ|÷üW¯ û:'BÑdL­›Ž¥;žC~àm:¢Û§ÁF‡K¾M8¡V÷Bk€`-Ñà¡`qçÈfx•OiÉ…S~¿yëÎð›¾*îó¬˜Ç¶îÁnÛRK°oã dÁÙž¹©$ÜáwÍ~Ô!X¡§¡Çn7bãQq¤:­†‹»¥ T¤Sí«b7‡jÛn\1>c„‚Éyfr,yçY µjöÙÌŒ!ûZþà%óKW¯eSǧúÔ¾`A¡Í>ù°çd×Å›èˆ^ä&8âKšøŠ„Ð™ƒò¿¶?~¶çUáê Œ°Ø³¾ó³˜„ŸÃ[wõ;òÕ’«ögCèX3ü½Ñ‘€ ®w+ŠqFè™B˜ág/p»7XÏ• I†Ý¹«ˆ©Î¾Xßd¾ø\ÉV{¦_~š§õ?ª{ÛöP-ðÏ»Üx'?^]2^×–±=dK¤‹Ú[sü=]˜<1'[à”NýzËútú@ i%1|þtnzÿXUÿáèÐWÃ/Ý-tŒÞbw8C9,pÉØ(aI9¬)Bˆ³ZŽèѾ¥_¯2psÎ÷áäk…Ú‹øSbæsºïÝ[v[ÄÀ˜q_s«9{)ÛRÌU™ üì/ð¼-ÅcÜWÀì½÷N—1+#¶ÊÊnÌàNÎFlàôÉÃU[¼˜Î_¸23BúqŠ<Á´ƒ„Ð/<£|#!‚i>| òSÿõ¤<¥Ô Ê· ‘ìDNFÊÏÃ{e/õœ…Özå=.KƵ`s–̦Þ2Á÷ìÝ{ šÃÙÌÁÀ?ŠÆZ0TeƒA.–]¹#ä ŒK“ ·s`ì[:ìÈÁð‹éùŸþ‡é7ãoNGOž ÑFKº ÷åf"©z x?ùøÓ2]ïçö¶üÎïüïTöOKëoL/œ• ÷ïÖ ¿™|fnÔi½VëIƒÕÚ¼<+k(*ÉYòW›Çwq8¥(Tˆ„³Ši[vg æÂ‘öÉß=}ºõ¯Mw®å´M¹Ù‡)9Ú®on÷S„8ÇGGŸó3JR† Œlǽ1rpÁÉ`#@FË%¸’ƒ='õá/JA5A Ž!‚^YïJöì½–Üçwe(ÇIß×#â!ÀxGñ”8¢©¶.˜Ay„f=…‡ ÄÅÖ›CáG7Ïe%vÝÀµ~bÈ ƒ88bD]ÖƒD`‰ r bSí‹Ún»-…¹ö[û²_•EÿøÿúÓÓû+9{›=½Ü8)¡V©=KµŠ94ª\9y¶Öfĺec[šPý+Hþ*áBi¡À0€µo¾zùq­Qû|‚Üdb DR]cG×½ÐË)5öj+Sûª¾Wgý¢9Q1ûøÈ“”J•î ûÌ\Ù1 ‹ç1ÿ{µS”…kýxÉJã dÛS:µ @Óž”a‰7²²WcÚOë†aÞ}X³®•zºþƒ!œ¿û+¿V¥óÁÁ“¬Ózñ†Ç´j1•7ÿàwþ^ñŒ”çÖôÿáß›ö8>—‚ûhPf© ƒ3t®ð .©¤õrC( ‡eç-0¡z°çì|;§·/BÉÙQ˜^å˜k_o ‚e0g8!Ãoxfð’3´Ctáù~Fà FÃ)Öçúpkð™>oí*j$ÓPÈñmð"ìdd¿\‡¦Z¯= Ã<þ@˜1Z´'±>ë´~ðð?%Ž1dÖ‹>Ànë|¸ä]H¶Öjó§#;ÞÙìªeਇô¿ Z‚°=¬¤(ôÑÇe÷o›þÉ?úGµê;8½{ætÏRÁ]v<88ƒÖ…ï¼*]…¹Tßùµ¿Žî>üåJÂ’e™ƒ7&!°4-Û3X=«ò™âÂ)[„'øVe‘A€g:|íw$Sô|N§ÿ1—Ux½ ñÓð®{š…ÉŽã»>˜5 ¶ÃÎí‘!2ÄÁrd½·f²s¼Œ¾d”j³=çÉ\í«/^]i?e)÷É%x0 ãÎ2À5éÒ´þ¼='×`äë*TC,”øÂ¸Zb ¶v šj­ç«ŸŽsÝUÕ3g4 ,8Wøø:Çy/ïÝ1þ®…ŸûG°V;‹nåèØS«Å‡oÖ•D@ðHgÞÌ×¥åæË•±¸Ü£æä©"ç ×¾êƒ¯ ßkÁ›Œ|I+”‰]-c¼œÙjô¡e?e{(ý­sÄ—×üEÿ6ÛkÁˆö‰'thé?Ï›Mû“i¥LSŽñ9ÛtÏÀ#÷쫵eðØ·/=®Ïª“‚^‘O¸‚„ÆýCÞ XõëÁ§ÆÔ^³·f?÷ï‘ãOýÓÇ}p†áùý{7ª:ÿÉtåÜgéQÉÓŒ¶›7šÑ}üÄtçÖÍ3WJô˜Fjö.Õµ¨V¼cMá-ÚFº_Êù¶)g=Ú3ö@w’U;@ëÓQ{ð´öà§7k"€xõ¬Ro yÓâýÇhÀsà g‘@$½L$d9ŸÇí3ð8\ÀüMÁLü͆)ãv*XÐïzHô ª\ë>†…9Àélƒoá!ª€Ì ô|¼„sKbâã诠ˢs"1 ŽªˆUÐ]úŒ#œaA7É@JÏ¥9¯ÁÇû>t›/ÏEн¢_G¤"A·˜éíãK]I{1Ýu´Ts÷ad§]€ ÿ€ÁëÛ‡¶låT Yt•ä¥.Œ. 6UË•,ÇÉ>ì‚Ά¬üø“O¦‡ÿ2ã³=plŒ=k_ÃéÝs†Ð½]ÃxÜVµ’î֯∘ìV—ìÉé|% ‡€¿÷'øDÝ‚áBö§ß÷ðSËn-ŒDzö£3Gp»ï¶*â9“é†üS­­o%_þRNÚ#Tê«€¦‘Kø6ýtomÕÏžMïÊøÞ·GËŽÞWœî FåG÷!_Ù2:q-¤Ì¾ÉVºæ£ísUÊO_mkü gÞ£ªª|êþ—âíÚV_ͰßÞžÍÁK±›Ñ^°zÁÆ ¦F¿Ø øš¨«¦ðíš;ºwëìêóö p¬¯ÖeóÑUM^Fò<]ïw0… 3~–8á á"ž.‰^NNªÀîC/ÎÑdÍB¿9éþ!¦jrgïáÎl<·ßЦ·†‰3Ñgbk]UgäÖ¹Ö™0tþ>‡ k޽…¹=é¦ïÿ‹ŸM|ãÌô¯¿ÓÙì(q«@L(ÇŽ4 u!§x¸Ã ‡*-پϳuìãBtÙT’·Ž œ’‰ág éúdW6¤6æ¡>©‹ÛZŽKxõ÷w;ÿ]É®*K‚eórÂA´ÉÞAºÙhלÝÇ~D‚?œ3Æå˜ßƆÅ3þäÿd´ØLôAN:4ú•ôg?`ª£]Îù‚Ør–Ïìûdo¸ãïÃiÛ¹©Ž Ÿr`{©:¼úч}¨ š½=$ÄJôckÅ‹â7:á˜5ëz Ð/ WèÑZãml°G9ñ‰—œžÝž¨ÖÙÑ3à'p Up/«Ù»¤›\zk8 F2zŸx[Ïÿýÿlúäüù| µe /Ù¶âÚwNÏN©B§õìÈ®x3¿Ø5ý-9µžÓÍšW»ßbt~ƒ&ãh•ÞC0ÌçèêýŸ3¨5[×tx¸"Ÿ¶¥Wr€KÀèÙúzŒFåúT=¸çæpZÁ÷m±}4xt¿Ö0îáy0ò7/ðšýØ]ñC÷¢Aø±÷ÜÛ=f+I% ŸÉD´Þ#ÇßÁ}\Þ'Æ"I+îàMdßÖÞLŸ\ø|úÙ¾?ÝÏ‘ýÝßøõéØ;ï¦×…wÁïtŸÙº¬éßÍËÞ½<Ç™ ŸG2–µ&£Uó>¨"7&S•Ÿ„Øl¦lVÔáû3ºÕ/øO‚ü"~ª³Ëö|mdûŠÏýë/gÜí‹.³¯™éwk%»A§Èqmö³àÅëdÛ­;·§«× $UÕ fÏó¹ÐiÖãMl’û´¦”H_ºt5öûlm’Žj5š]¡úAZ•ñ—®\ÍQýhúú7¿1ø]hk~ˆÕÖ}·Ñx„õýì'?Ÿt‚9\Às5â;h [gG:^óè%’Dub€’h(]¶Ö¸ä¹Šë;÷~Ù8ÇÃuk«%g~PÛâ)¯Jð~¦àInHÂò%<‘°Ì ÎZ7ÏxbvF{Ü\µZb½Ÿ% ²M öE cüXŸY+ÉñÉì“*ßÞ=s*þ¶wØHOÒû¶¬*n©Õ왪ÏN•¨Ã7~Òé$ ä¶¿Ñ•1•O*Ó“@Fî hëTÌpæý³ÍSý,þl4VAƒlXrD@Pâ6£D,-Ü%EìØ©ã\ü:¼iá=ÈåÅÞ¿wweÜ͇É&>yâH×lÍ^º3~ç—Ü{ïý÷;¥ôªnÁ>Ä?f¶s òÉIŒ`2/¨‹ÒÌ^#/T\É/© ýË’!T÷Nóµ®=yrȳ¿!(]š­²QÀc×¾ßlø/öÀrzp÷z–|½Ñ·+¢÷©*Û›\‘t{çîý±¿ûêÍïý†IÐhøAâÇt.>|\’Ǧûo»À;¾‹ç%0À{¼Kg¹½û—Â¹Æ —‡&m‹î‡w’Úu!”h2º=mÝSêHŸ££ä¿f_¯‡Óµâ¯7o5JðN]ÖÚ£Dƒváí¾x’ H8\ÚÏž=3M§”ÈÉöAÛÛ¶KlB)qƒæå]~Me?¸?«*iÍ)^^>0ÚÑÚÛ • Jw\j/ ™È¶ÈÓ’5RŽ¢±t(’¼ÛZw£Åíù»ÛÃÓö~1þ~³jZrXRû® ÜÀ‘o€ 6ÆÛÛwž, \g¤^xÎÖÅ™ÈÏtõ…ä$ýN°®ãŒ÷”„“C7ÞÙ9¡M8ßÅÃG3n×Â_ 7 z_‰G‘MºcI}Þí…tƒ›û‚:–qU7*¼xéry^E¢™üLDÕÑ¢`p¥„²7:£¤:X€7wu%¾Þl”Ü0Ÿ].)öp´îkû—’åÏ ZVµ>FµÞzXÜëìtæôÙéhzÇÙ3o¦/®]ŸÎ_½2íȾÃPþR g-£îe«“‘ ?~ðª®mÍÇö_±œ³§Ž„U‹Ê6ãÛ¬ÛÌâÖ`9WöC=ïur-òûzð}%¼Ý¯G»î*;’õÃ.éå~ |ߣ*;ÛYòEÝ­›Çrñ“›×¯ÄkNL{Î×~¼êw{ºs÷qzJ£ò‰ÃÓÅ.žEŸ‡½oúçÆ5þ²“C§RŠWœxmú¨øžî"ÎýÔ©“­7zŽ&Ä<½baém Æà‡O6œ@ó[²aìù~6ý>eð-dŒvVáâ«MØä¼­ùÕï¯Ð²OÃ]¸ã4í<@ÙKô¯ G̉ž::5ôÜW]6ø.eF„´áUE{ÙÞסšnξБ‘aS{Ek?#»óé/§w®O§Î¾—¿eúדQý¾£ŽºÆ|zó‹ð»¨Ppd›=ª»ê\6´gÓO[hrC‘Ïj²¸ßØ6ñœ£'NO¯7ï2uØJÑûÓãßjqƒ— îë´87÷4H”ãKvgo. s1ìoÃéaÎ&£Ûy€½" Þ~õ«_)kk_ðž¿ñ³gµèDe3¤Cˆi§ýÊ×ëUˆö]›÷…Œl½Û%æÆ±¹?g:¦œê*Y'<)ZË(2n´}Öò@nÏ&ΙWÖl"ëÇ'³T!íÚ(,•ŦšI…êÞU®+×§ÛW?éà Ò¦öÀâŽéñË)Se‰F˜2·¶§µ~ÈÍ8ÉXimBUó\Q«˜Q>w#‡„6ÛClm¦1XAµœwo«?Ì3sOK§}{9+µJYë­Þûà;Ás­l¦_Nׯ߆ÅßøíßNÁ¼=àÁÙôþWË )‹S"Ž=QÛ‰ííªù®xœ)‹ˆKPTÜ eY#'xUõZÿ>öδ-#ãñãëSÝ "Sî8š(w£rLj8Ò¾=CCõ¬„”ÁœÊü•…µ;%@öðCB'Ü|Óú÷ÖÚ#iPfuíµ¼[+̯—…{~Ò{š2²»VÖúäÞÍѾJ‹)­|®æha îŒGß9dàΫht?*>65ñ4•Qè˜ÃÙß´Ì…K;.Lw0âÎU…Á²Ê`ÀÓâ{ÍÎþŠºŒ,pÙR)’pÄþ={ÐC°\èwŠl€/­¡÷!­mKUZ%ùÈäB_”ž‡7Ì¿a(¤DGh޲Ó]Æ9{Ú àØ¿gr„¡ ¼÷8({ðQÌþà¼jPX^+4³ ÐÖ•áÏQ!gýŒá»Âû>‹ï YŸ3úé~Òܰ»ÃI¬ª[‹{5O`žÃà`žð† ƒ'ÛããÛâ;fá¶Õ‘ä¡òðfê>|dy:ýλÁ[ˆZ¥¼ÿ»ßûµ³Ó7¿ý½æWÓíý¨}ÊjM±»ôEçñ¢FKKGÇÙÉŽe0?é^æÖîOHo<î<»^e‡Ã]²Š³æÜ³7•´ÜóeáMœë)Æ» $ï‹þT»©\”´€Î_x’µtë>7›#·¥ö¹Î™Äè7[G־ʈ§øVt¨’êEø°)ÞHŽìfÜö™î’Œ‘ ö¢ÐJеª¸º±¤ 2ºÍ~yõòÙ¨ ÿæ§’Í»Kh‘¹÷0yrhÈáÏ>¿œòz|úÖ7¿2[×¢Œ’ÄaÇΙOߟOWpoŽlmí«G7€VBIzœA/yGuÚöð‹Žð$8„¤¯6YµEZÉùÆœÕÎËý=Çv|vàV÷§ßÿý?˜þÛÿî¿+íõ/þ Qâ$Ýár+ƒ†ÓëÚåœÑ×HF ×–9¿3¥r=eÝáG{fꢻý)˜¯¦²9)üî‹_ â)®çÄÃÀ׋C-RíÚh¼ßÑ4gãÜçƒx‡O§|)7rÌ^k=e_9KVÐrú–ÆŽdŠº|•˜ñŽÑ~1~! ó¢=È"=R%=‰g2VrîCÆcz€µÅŽ[DÆeò_3OIš½0t­ð~z™o>±äÌ•`)¨#9ÇüG•­‹ñ¸G±&çtÙ )¹ƒ,´ßÕhÞ˜ú›(t'³ÝUŒø }‡‡3ß}9¡=‡m*³á2þŠŽÁXÅøúºJÝ­c¬‚N1Å`4 [·JTÆ7ãƒcÔºG°¥õâ î%!&ÃÖ·æ'×ñïx$^Ç1TûÇSñÏ`6ã=‚åýÁºÆ—óÞ’ÝÓT5»Ï÷YF—ç =°ëÝÓ¾ðŸ÷³û¿uøã‰œûä>M!WèHl…™^D~y~ànÍo»é¶1ô­>K†äÎìƒÎ?¹…ÿÂÆ‘ðÌ¡1ƒLÈHìêúÅZ±$,Õa¼õ»——uÛÝÐz ÿæßúÏÆ}u&ùÞ¯þzÛ÷âýO§?úÝÿs:þÁÙéq{Îþö¯–à{¼@j2)Ýp1ÞE—~^mtf†£B¿§JÞM¥©}›$“x‰Œ*xFžô@IDATŸ­ÿÃÏn%Çž š—èõ´d­¥‚ò%‡Ò×èwè•ÄZÉÄPe´¥£‘ ÅÃpŠþÒÖÆ^í ~ í¡÷f°›94ì\l†Tì¾Fê0ÒušÒÖ_à?rd[?Ï ¼£‹qƽ÷šA¤Ú8üçõå9±v8×I"ýM'† ¸×¯»ÆS=O0NÃoËð]Ã…áâ–’FwëøŒ_u=]ŒL˜Ù5>È‘Tû¿‚λ² ¯Öºý‡?>Â/Ž1Ç›E÷•÷NkKzö®Å*©ò¦Ù‰û—6OËÑäÓ%nÍ!í=oÙzr)ó¬—Ÿ9-ÖÇLYŽ>™û_NÿäGu‹gl¤œ}ÿøèVóÅŋӭì4k—HC?àømgƒ;²/œ¤ßû;GÇ ÆÓówÿ×ÿ-¨}骺^‡Š»é.*w8gÅsð‡½9Äfüe¿çÙ£‰€ÑÙ…m gžõž3‘õ{:F²Ö)Öí­­+1ì"øÐ™üÓäê£Ú&ïìoÖ¤›€.x\þ³ÁgtJà(:p÷Æãí•“_ÀB·üǼHöÚRö°¶éóñy J虽´9!9T  ²åèÄ£j¦÷Ù诲iè^t –½%›ÂüóÆÓµ«—FPofÃç®ô´_fß¼®¿8-5¾ïXº»±WÎ* æÎ§sèè?¤—}ÒÛ$:ëJ¶’nÿ àÇï¾7ô#:ÀB<ÌëKýéògêýή¨ºsý³‰‚­pccúôÜçu0¹ÚϪ»øçžÆC‡Þ°gïÒð{á‚hôI´¦¨ƒÍpîóKµ^]Ÿ¾þjã}$Ê/½¶î‘G¦ïÔ.øäñÁûÉ|z9å= í— l}í«_Ígd\:WÇ–ð»ãûÃ?üÃ*ÑvNgÞ{w:x´àsôª›•®3B‚à™Št Ââ·-/û^½q7'þêt¦1Ë˪w馈f¦¼JfÐoùHÐãèd“N9‚íO-ß}”o¼ø+ù67ײÝ~®^¹–cÞ©íéä eIT¹JG]M?zìÄôλïfïÕé+R"gØ–´ ¾xw’`ÈeËa³7ƒ7»b“(PÕ>~1<¥s0Få1žÒkËÖàÕsFR{{¹tù‹ðˆYëáÚ¬?Öö®U°Šùãñ&´¬OG÷ï«Ñ™¼ŠïÒå$°ƒ)z6šA÷”ëÉ9UýïåýÎ :Ï®rVfÊó‰ãÑ«%èÌ·¿Vëè¿J`ŠÞ½ÑÙ‘sl{Û’¯X0Í=t=½vãê¸ÞæÅä´n¦4f-öéÌ#`ÝÙÿg÷Fï|ð8w%;Lûôô‰ÎŽ:+pšURZ_ÿDxÅgßÙÁCAÖë½ne»Jþ,Ý•_ áEôµÞ^ឦó„`žó¼|Ù½ËAÐ*“ù5á:ù0ŠR¸—ãQïF›«%B>ȧÍpùʽô•ð¯õK&9>¼ûÞ™tö’WÏÅ£¢å¹øÓèHAôùöò<sµ4…Φ÷¾ò^~ûb Åvl*A¯û¾î³ô2UœèØ <½]üÉýáô«¿ñ—Kx?<Ý¿v!¼Ñ-sqœ×«ü3Æ$ñ#£‡;öÔ·ÿ×Ëû?ûeP}|CÿžäC]¾_îœ>ùè£q.ÛO¿Ó9uOç5ì7ø·{y¦qWŠ"%…¿|ŸÊî¸qåbÉ¢ÉlÝ`éK»äÑI$¦ÿ°Ý©;Oî•Áõ$ÞšNú`寨yŽ/Ð%ùÀÀDµ*ŸÀF~ù•k—£‰—éÀxU|!Á%Q4x5{ƒ¾Éøä]ÑY²MAä–’Çò®‡Çhxûtµ®Ïëlƶ3jãpL2Bׯ½ÅÆ=™àñ޹t |>;ÎÏ•à4*Õûðˬ4R/~º²rgœ—D‘'%ÑÛÿ{ï}eè¤x$™5K@¶¯Æ™VIJwo…’»ÜmÙq ÷ðzüëèኻJ»t9>˜?’~…^¢áÓ Ù-ø¹v`¶/»½¿ÈwmœQpÄ™I2{”<囨ßûª”»‰-Ñ?~ðªî :-‰OKVh¤[쥫ôˆw­mD÷Ù _={¸îwJÈ:®f—5¶ñýCôçºÙ6ëÖ]]ÓÓißTÀºÚ¼sñ©µ§$ÅÐI¿¸Yµô«tëdb,)û ?tç((ٷዹߨŒÏïÆƒ·šþÊoþÆ(TÝÚ=›|6Ú ß·®Gs#I/<»zíêøüÒŽ…é¼?m®¨ð—Ÿ|8ݪ­ûF>ð'ŸÜ¨‹Ò­xðÞÎ{{Å=»’«Íj/ÁLZ,›åæí7Ó©#Ù(â0kùÚ ôïÞ\×¾`{ò ŸTã3J~{ü*->¶%E5Ú*ÿt]Ю_ ó¯¦wŽUÅ~±B–;´¿$î½Ñ œ. ½¶wè|º ‹-åY~ª¡³¶ ½D"2ÝNwP•Ôk½ÇçN†Ší±‰ÅyR±|2ñ2…p˜, èÞÈþt[៩â5þ¶Gº‡¸˜¸œ{”`Ý8#þ$”¼)1|ðÁnعÆå“'ürÏ:o¶ÌðÏáI¸VÏ}1ñ?í) tô»÷“l;ÅÒø4•±&ÔUÓñøÃt›±'îˆÏŸ>aîyW›³?Ua Ÿ•âµk+q8.Kr{Q7»Ó…õ+éþkcŒÎ¶²!Ö[‚n’dðbþ12G z$™ÚXšÖ²»,’‡45öÏèÞÛY±¢è<| :¯mDol¹…|º ô ‚ÈlÁ½àÀ(–µ `ã…Ñí.CK;³PãËQh9¬6çØ\lþF`tv)á dŽGB¥¥„tÝ»¸g¥F żb¯Ø}\ö–@Áò*¨û›{€­fg±S2ÓgÕ¶3ç¤Ø•Òsè`D³—¡Zã@ŒI%=d±NLeæ*ý¿>¼·¢È•Sº¶?‰3™â¸¥³WµAHÖ¡„­>º™à»1ØOówÙë®Ì´—¨1àõ «˜, ÎðËkdö\ˆ §EH;ߺ_0^¨U "@œ›·° wápò¶pð9kŒ†Ô!Ç ˆõC/Ÿ¦(qà.î ä¼êó[Âk÷A͘ØpÚDîV3ã=ÆÕŒAÛMáµöMô˜ž€#‡úpÂv jAS^6ka–I˜!&¿p#©ªîÜóqJæÕ Ó“Çä0^ÌH½3=ýf›íœÎ“á]}´Š•s ¯£ЇrÖ~ÚZeÁrP˜=µôeÃê|ÞÍ€ÁÆ|½—òU%t2Zû3Ik;Úü9˜ó ÀëŸ~)þ³Öx–Ï’³$I³ì ®HìÛ·yºtéóHéStªdŸ ;£d%wžªätæxŸ ‰TÂ¥œ¯ñ.-¸9\bEáj ’e£çÍAƉ†Çš½N¦e¢52ÐÆ|Ù>÷èæ"âÆZçÎs îÊPÝžãê-Ÿ?˘îÙt?:¡Ž6÷rØŠþ0D%Ú¼­°ä<“”ô<UÒϬ³JUQ92;< ëéR¡ÎH\ü’°¥‚‰‚/±KWü ­h º6ô×ÝetˆçŒàö ³[±.fIÏ „r>Søí]ÕÕ¨äì¾÷JÖÂïñVަad@ûnpš”‡Ñœæ«à`‹¯ ½#€ø¼Êó95Á«¼¯ÎéíLrF ƒ{Åõg|ÝMŸ'†=}€.@¯Àû¡}›ŒA“#Þ÷¡¶vÏ#+F@ÃCë4Øyp(yì†Órp® ÿFPÉùu0ÆJß»}yŽ]cË_û}­¤¯þ4dfwðöF‚ƒñ'ážÓ…éC†ö¼·™Îø)Þ0­­Z–‘Ūá!–„Hë×\‚Í1;“ÌàúÇZ­^x†g©Ž³ÿwÏœ ÃÆ(|çW~eªYI”T’]ªÝÕ•gÓõ‡s¾6ëuúìýpP¥‘s—}þ´viÛ"°ÅßÔî­´°¾Tñ$3ºÓr÷»KΣ ÒçkÅÒØ™dAëwV^ôek$si‹‘g4 £<½ºs~ÑuônW±úó€ßøno>Ø/žî™t“F{ÁosˆäZzƒÙòha{÷ÃG’á´çI8K`-`C/p¶Ýb<ý1àç¼Â‰,£óU×,l¨ë‚q y9”ƒÖaQΰ{HrI$޵ÒeÙe½áÜØ‚p’¾/É€n&IíYŽk÷±ý´ÎïIFí©:¡#ŒW½š>þøÚôÏÿø\W5_mÿ¶º¥ì*YLm-l³ƒvl¯’iu.{n¦3¬oâì ײw¦ãl®uëlþb´–“f?çÚí*6þÑLÏj¿ùWšÁþµ¯÷œ?.¦m:Ø·IûÛ¶h•4¦^ýƒ}¤Ïð…цtAï=*@ï*4UøÓÓàèöÚŠîŠþ÷eËržÑW·dwŽStÏ>ãy`+€Î™²·Ÿ=Óç÷óì9é–ñ&8âúÁ‹ú°Î0Z™Î‚2ÑsxïÃÞêºÝ–íïü}n|>x¹çöqþæ¼´6Ýœœ°Î'­ÿyðöžûÀΆdˆûp èJ_è>è@‹¿ùÍoŠ2G…ïÝ<;»N:àåŒwÒË6Ò§=†­åéÃŽ»'y hV¥#ÆW{|èXœ ûÒy̔ĻvðÔcmàñ#Y%'[Ð~¼ºQÑŽsØs2ÂïQŸ>6lŸÖÒÉÂÌÎQ˜'ˆ¡Ï·ðRÅí:¸¿PbÛ#˜ÍßÀ…#(‡Ïq‚wñøò\ûäV8í˜\«rDëÃî:äqþ×l‰VÙõ}–üx]p‚Of$9¤¿ï£S”6·¾ã6ä¡ßɺ§U:~ô³O Ìî=pbèã èÓùÀòȽ­¾½}±'[Öà1®õ²k²¦7}÷ž]&£ÀÁßF¡DÎYVö™.ˆ T¾I†s’$ør÷Ñ7ðûá5`Ó?è]Ð|´³ÍÞøüÂÓ‡¿øEÕ“Ÿ—¤[{ô½}±@_g>`563ƒ³}€ ˜ã33;)Lg‹{öê°ÿøGÓÍ«×G²ÕBú‹D=‰Žûrì“•ª+óá=ÏïöEò»wn|½0½ÊÆx¯`®@§NŸŽdĠûç?û|Ðûw¾ûa{<©òxånÕJ9)g¶¹„ÚýÉЂ°Ž%7 düü翜þÓ¿ý· 6ŸJfÕÂ7ýQúÏÓüztL貪*Nø§9‘·ÅçŒrÓºûiá}vq:žïd:àŽô\3£g>ÂïÎÎø.8³QÀŽc™~ú:'ÿàiá gõ  üGn»ãZ}ß^¹Ÿl8ýDõ}ÏI#ÁþÔ;§ ŠVÝ;žÇaÏaå…ol ›ê4CžÑ½T®šçþ¢sæËACŠmVîÜÍ7—îÜ®–|°3ŸÇ39?I*’HI†ðgIx“m«Â^uùDŒsñ©3g?ˆkñ{õjºHÕY­OâäÒòÖ|tTqѱQ½k=&; }¿}(²$ÇfÉ_0·ÑL×®Ä]üón>N¶­€¹µ³ñU”ü‡ô`úK'КøÏâ}Åv§õ>À Üé h¾ÿÎéwâk[¦›ùîV©èﺈìÎ_µ”/˜­N'f7€'_µ…Ò_Î\ßSÚGUíé~x…ñ1tlÔëá9ñ=›§{£YÅJhçósŸŽj‡ªÙï£ÊÌ™k·ÍBJ¥{iI”PßÚÈ„ã§ÏŒä_z¸rÿAIºí^êħƒcÝØðx¥s´>÷‚ ·à%Yü2\8|¼ÑzRÀx[í0sŸéê%ÚœÞVà@Î8q†Ï¼“ŠLŸK£:ÈHänϲѮ\­à,|ÈÜŸ`o­¦7³ó­{Gþ?8Fç{T'žÏ/\ ¿—^zãòµZsﮥý×§¿öŸüµ‚¤«$ýɳËÚ#çŸ0|Q!šzœ½x%~W%2د`û¢Ä‰‚˜F‰í­àfoÅ/뻺÷7Ò÷ ¾|3\Á³o\¾4ݾ’ý+±ìPÁÏ]ó”Lí UAÓ•gÉ ³$Åhßÿã?jÌÐ×§C{˜* 3 ßȉ­Ð¡ÂÝþû³ºûò÷ð–܇¿Þ"¿U‡ºâD]>næ+Ð í³_þ|$!©B}§õIf˜ù® ÄõÞö”í'7ýòeOÛ÷•4ž.,NDÏw¶òã|~§Ï¼Åk>%ç¯ØÑ9*¬º“ï‹a£½»v†KÖ#A<¸¾¿¡…ü[gÒŸ_ã¡k•âŒÀ4ìCIôxŒµx}–¿·HÓ±s%·.Ä –uMÍ/‡WÎ/”`t®"ÃÆìMgPC+æ$köä;D Á™L·V~ïõÿ˜±­«ÂÙ³ïšW¨uþ³Fõ9›a9^"Hˆ¾@ù+}öĉwóµïþrDÔý‡?½=ó‹;qbø­%ž;vrÈ…ÑàŽä&]€?…Û½OÕЙëÆÈVA‹xሩôÝzÉ8ðóóã®ípç(wˆÿ¤3mŽöZÿÞ‚Âç.Þ -éóuý8¹œÎ;WQÇýé·þê÷¢|zµëÞU7¶Õü"wâÕÏ_v¦óýÎçA?-!fÚ¨Û^Ï>XêûŠ™šž 5ZÏf© pèf#¯U,YÃ÷ÎàÍtáQ~üiiúN°8ß¼Ý^kœÝƒô|cNÞ¾6mÉö‹ÅØ>ûⳑ¬p¿3¹õðåt»aë ù¸ÚÓ›ðã컇¦S'ëpríFñ‚Ó‘ƒ%RÇç–²Ÿ´æ‡ÅLç7Yy¨‹rݺïÍçO¾_uùj#ÀtÃ;¿#%ˆoI4ØÛ(=ÛàZUÎɧ' íœ~~®$“!·_'[êJ°¬ÓŽŽŠ¦ft²5{ŸNÆoŠÿïÚYbwvý®ð­B&â‹|j#qýG-äÚáçN°Çg¦t°âe[W’¿é"½T_ÏW\'A?b½ßÉæ>ïÎøÖƒ€»5±­à†‘»:ì-Lâð£Ä—ÉïMÉ- ëhjt-0ÏO#qføZ ßÅÓΑ¤H Ø’Yé`·WŒ}³¥“EؽI)„¼˜¬;Îùµ_ûõAÏsp©5²ÝF7ïäñžèëô©ccD‡àúùó—“×ÅbøQ¢k0f{Ó|ñ¤!*vË&{Sü)ãpà™‚p#^W›ÙŽFÇø`ÿ2Â'9ìQ,³e {±X›¶›ëò¬£âÆ3ß´­ΗDð¹oC 8Ä·'!Ä8Ùˆ9«Éi‘©ÞƒZb”®=@¹S­½FyÁËTЪ“A“ñó¢Ì/ˆ@¤¬ª¥„pˆcŠ£…ã­Ã5‹`Ì  jÙ„e’Ò´æž >÷ÁH´¡¦€ÝI©»ßiÁCÉÂp·«]§åÀ£G9B[C$:ë–zi™RˆÑª²Ìðˆ¡p cBf©ãö÷ùðg9–Ë6iïT5Á™c2ÚÿÊõ/rÜIY*Ø×ß)ÚOÞk­Í·i®"›ƒ„ŽZ÷6Ç@ßÖ¹mÓZJ…Õ—·WjGBIé9ëÝûiŸá0`ÂïLÑXXTÆh÷µ©ŠÊM² ÑÈwm.ž’1Í —]”ÂÞÞõ8=ÆËÌ!µN¤T vŽn<­3yrs"8 ¼½µ¯¡ L¦4` cÁ06è¤hf¤”õôaót3¶Ú÷£În=ÆÎu_×ôf,SÖó¸{ïÑÇGµp‚ÿHI ² 9¤_‡£”Жԫûô@x<žÝÏ3G¼N¹ÈyP÷—5§×ëMj í«žgœÀ0Šû¬50d9µ†BœmôU l>Áߟ‡„A c1ã`G4uw„ëUäv&cõjó<‚–Y›6+`;emÞØ9}ÿo±v†“Œ3£[d‰Qd Qz7…ƒèkS8B9~•€`” &ìÜ“ð­‰Ïm+Y†s ÌFeEÆEM çJL0æ4áÈ )† @‹Tèáüïü3­Vû®ÍÏ¡¤P:_®¹w×&`v¥˜=*{ÐŒvÁfÕ¾ð¸~ñg£Ù欲W%Kh_%k•Ñm{®6B/¦Ã{ƒtü¨àÇ⢵QJÆé»öQ¯Ã‡2ý‡ÁÝç(R3#I°x&´œ•ÃÆh $üsî n©º3èàt×pŽÄ/mc¬E0~ÚCRæ[ÁZJØZmó%hògø-žGFKÈ”Aä1sJR†bðÏ •œ¢uÐë+>¡@ .¡=†Øl3:³v¿sd?ïóÎL·ùŒl™ÂR”®QÕÑóá7c^kç«ÚF[äÇ)qà]ÉãPFeO&¿«((Ú&dN þ ´?mî˃»”¶Íª‘lä-¾ÛÞ.ý@¦+ešê¡290ìÿi¼L¡À/Ì(´oKétKûŒ6i¡èhà ¾xnX˜2±X@¾DŸ%ž±w¹Y3É9óµ~ï÷¿ æËßt¿¿<Ьi„“rçµë¥T< ¯Ï¦§x‘ƒ÷2`ÑXº÷2˜µÉ ‚ üÏ—äýŽm(H sÈ•a8ã×mK6{\H–Ÿä·Þ4NÄ|ŠLÁÈäÊÒîœèðøëÕk×_ÊØh ÷Þ{ïT{Ý–"°^RØÁN'ü‰&%‰‘‘BfÇûÂrúäñ¾×Îõö]h,öQ={g®D«Ã}v£$…ÏȉÁxçÄ•ÙÎ:Š—Y5ä?'µìÇÎ:Ø0|e#Ë>`Û¾£¹e­QŒ¶>íéy]0ðjˆ(BqÔÁ?ÞÒ<Ô dãM4ÞÀg/ôfíÑ_d‰—ý ‘—ñ]XödÜì-™c´¹?sÞ îšc{/'“î/+ͺÂæ°Tiw¢–OŒz`Q%ƒw=ºw·³Ð ,ý s\§p àÑTœÈn|*£{Wk’­º9šÒæîîƒ4¿Ö÷&9s0§`Ç©cG§7­Á½ŸdàlŠŸnKgX{S2JÆšJSNûÕçUªD2h)ÃðP°Ža‚&¬ãþ¶ûoüªçr”½ ’×[Âë•Æ%¨2VIø"4½VÇó¯%¥ôël­•<å\G£ø›ä84H1öÒB… ƒ §Ð-ÊÕYÈ=8{†AËr±=€çàóÁ‹C¶3X´¤Ôޝ˜É9ü¶kºŽû9æQ  &·U 9Tvo”G GE×çnãø¢‹Iè…s?§w¾ÑWº÷÷¦s0Â褖›#J‘¸Ýh<þÄñ£Án­¢•éÄ‘ƒÑãâè\ÀIªsýêNÆØ`b<“ŠWÎ F¾¤Œ;9ÚÈÆ¥ ²õÑ“ñá’×Z"zóöŠöc¯þ€'·8žéC>Ûgkw/IKk«ºSÅâžçYœ4à»l~º‹j[1 zd0Êvpïõºõ° ´A£öWéhC¾)…[à¿hÌÙ8o¸OÞ¸‰€Ñü“ß›þ«ÿú¿©úüW‡ÓS‡m>ßýê¯U‘v}¬íêå9›)[‡Ž-ÁÿRx¸·êø0—lUU¡GåÎ*ÃÍ…Y—-kðü±0é °Âëðãáî;Ý.zLwþ*|ÐÚ«dàH<ìÃd)üšÙU_ºëÉlÚƒgw­¥kƒûbð"GȦNgàɦ6žGÉËîÅ…;ŸQ­Óçµ””V³.OnÊa/àAÝúÑêLwÇÑhðØœ¾ÑYHâèñ,ÝŒè'q|öfòž“ÛþémCGȆ¡‚§DÇõG%Q†«ôEíê9gtàd–Tì,ÑÅ«¡ßÿøŒ5qRìÛ»Pbó,XLN=¾_ðæ^ɨ­ðç^ËÙ_;VÊK<{ÓþÂ‰Ö t sbÁ|;˜®cnù³ÖN& ¤œz'çXÎÞÓ§ßN0£]î®ÌˆèÊx=àG0dasZøû¦‚ª~ÆW$Íj#Ng†”ôTÙq.ö©ýg8ã>£JÓ U|ŒéoF°‘èMÞ¸%¨Ói3pVYkû¤ýz»‡Ã¢Ec Lñ£2‚½œ¼oœWÏræì(çÏ€Xž‹ŸØ³³xLãµOï¿ÕºÀmI=}pÐä‹Ù3âs½N’ZX’«3éÞÖ8*"‚2’°Ú³½yáç®ÝÛÏÏÉ;sžÏU¡jŒÎ!y_ÀýéýìòtVü`¥½hg-S œða„½+‘ _ãpß¿oyì n r{ø‰çmIUíC¶j-Hç•ð²=™%Q!äï>ÀÏù[’Ýgà^d(ÎØœõ°_ÇzÀ±®à8œñ½ ºá˜<8˜mtfc°éÌ:KÈ:g2h&»NóŸ@º¥Q‹Á@ i5çàÍ‹U„¤ÓžùÚ4}ã«ïçœñßU :¦l˜N?ÙÏãŠÙßè…2l]êz_co½3‚ƒmÙ>Çß[¸ïô×ÇU&d¯Îâúõk­oeè=/:»ƒ.ÀSš6ÂÅé¸;“ÿÿ—÷³’ß/°ž.'øzŽá~ÿ‡9àÏ¥‡å;*i´JÉMéåäz"GÐ ¾7ƒù:³ ŸÔùäYIŒËnu—Ó ù/RËÐ9yU>E—% n ‡–$·£ìéíÙñ:Y|òÅ…éãO?›^ÄýÚuv;}°–­s}Ý©[G>¶`÷(:[^^I´dÉZ´Í–…v´2zDÇ m¼é%¤Àz{åÛœ .¼æœè*‚Ñ•êÐá ÚãÚ`ÿäg?ÍÉ}°$çoUýy´„¡Ý#±öiºåèl-J–0¿®úÅ•›ÙhªÑ«H2(ÔË‚ä=¶{f¿&ר+í¾SÈ®ÖÅÆXˆ7h/Ü÷zÕœm/~>Ƀ‡vg/¶• 2^…ðfAVNØéNÞÇs#óí½Ö3:&¾1»›~4“ÓϺîÁ†’ '®^º8ÖNÕ1É3VL>i-ð›0.à+xðW|` ½+~A~n­ûλ[vd÷AÑP`´ž^Î_HöKLTò?s ö™jGã-Ìú~Ý—dGA²A´@ëlF¬ÎL³1\’­‘ùÌ÷x;»›Î½»³“˜£c‹ÊþQAÉñÞbt…ã&cF’ª~ë©ô· äéôÖãÙîùÝú¢/ ârë&áø—gúß’Ÿ¿/òM ,JTÔÅÙ<‹NЙ-è%Éüöµ+ù´JÖ­õýxœ¡?kÏ×*K>’ ¥‘.‚{#I+ú_ssÙwàh”fœŸšÁïïqá]²ª½Ò©jšŒ OZµñøÿË*5×Ö‚uÎ~x]ÐF‚ß+?ï•çGp#y%©ÉÌ[cìiÙtæ7ò°d—íË—«¥6<}–?a0Ûø‡rCg!ûüã¸÷.÷™åøgÙm£b4˜“Ó㼺Ƶ­´kßêcÉÙhJ@ ¿I/H&gÒÍù¦;sÜ[Â@Woû“èŽÕ÷ãMW®Þ D%ÁsøOÒÏÀKŸ¾äüÑÇü5ׂƒªÜwß{/\ÈßÞçt;ÓA!XÝ•êYÉçÏ«Ê·âšø]åP’RŽ.Ø”Ÿ -*¨aã±’Ý*©‡–Áß"ˆÉ÷tãþµìÌ«á›QUóUÔAŽÜY¹ÕsM«“ÁÁƧ9'IcS#O·¤¿›—.W¥ûîàxá£_Nwªžüæw¾]’ËWò ¥ÄkÞ„ º•ã6^CNù‡ÞÜè‹pƒ’îÌÆ÷ Sà öÐ;gy›?K‡ºë_œŸþ08nüÖ_/êä=HvnÌ­ãßæÅÇEg:ÒLâÍñqgùuð™ù]);‹ˆYAoŽBŸçâ#;÷Y¬sæ Ft:û¡÷c4”ŽÙ>Ùݪâ§üjó†OÀ 7g•³ÑRÉ ôþFxÒÇÇ9ç¥ìoƉ°Y*HiýüŸ’ew4Çþ~ºÞ“ǵî/8J/W/†ó,ZâKÙQ‰b>’¶Ä f£ÅØS%@…{‚¨ºÍ7b”¸s²Ÿ9x ÑzmJÑ Ý”u"yžEרŽO_3V@·K´¥XM"›â Ÿžm·6,Äó%u²^42O‘p"*Ö™U ùE‰R7F">%1ĞĀnÞ˜Ùzü@Ká¨"ºkW¯ŒÄh2½/¥ÃòéÓM_DF¿~šMŽ<¨“Æ·Þ?2’‘÷Õiòÿ`gô0ÝýV{Ç>‰ï¶‡];ñZ3ÂuíP€À7›ï³¥¾z±y:}0|­rÃxˆGùb%-Td¹\‚ÆëÎñŸ}x/^trúõ¯¯ÓñÓ‘ÌÇž„ GÏ U½?­@Ë—d ±DכЦ_¾|sÚžœd£n‰8\w•Úó±Üݶž¬’9]AL† *1ÿ=V7ÍÃûó«åkÜq°µ\»_á_÷YˆÜ OÖëC¶.n1Rea:R"öþÝÉ‘ûTÄß$I]»òy<©äÄöûàQUâ!ñÓ}ŒnOŸ)(ÍO¡e‘ =_B&Jÿeï9C~¡·vÚ%£ùèÉA~ú[É(ºŒXËî|ÂüTä9] ¿Û*^”Q0â¾ü§;ê,ȶËSœf|Àü¼xm7Ï&Ú °ñÐ(ÿÔëâPb2sÉ :»¼&ç׆ÚgÄNû¸=ѱ6à{&÷ß$×Ùpä.Û‰ÁŸ2ü/Ýtg¾=Ÿ×Í‹Ž{·D)zÆ~ßà§Ð%ùþí›ÅwÒiKÚXYY>ùä£Q¬'f{6Þ·?¹7)Á¿ºWx¡õn/ äQ£GÙlÅšâ´mxìßû!|0ëèT2ÿZ¼›’ŒÀÇø8æ¶4J¥¸™»3>²@ St(H3å¦‡Ü "NÕY;#@+p/FÕsd^;0óÛó0øf b†hè`ðmªÊ„ˆÑ&0Êa €Ã9Ógã|ƒ)90J(¢ßS¡8r Ë®¿u³,3 _‡)´gÌ´=·ÎaܶQŽ4̃ÂFàpâS*‡£ÆÏ¿ûsäL;03ü(ºÛCFÂ㈠ƙ®Åà9Ì[¥”k¾–R†£åq÷¹YÐÁ æ«8yP°oÓ¦û£ ‚–3?ÖÀ›?‘â/;Z–´jôgUX;žÒÕÁŸ¿¤­ôãzœ/ \ª%uûSQƒ™Ï£˜ÈËÑ€óú[_y7¦VeßÝëÍŽ†|fkÎ9ñœ,ãs)[ëëe(7 BıgÕ‘ ² Š÷ß?ÛÙl)‹ã\Œ;&ZF‡ ¦}¯,0Ĭ*s;žZ >YTFØzJÞ³YÍ²ÎØÞT[ÄÞ ùr¶$À6•²¥Ìà« ‘¤ ){j³m{Õ8) `€h8Í9-(±¸¯ ¨Ío¶NO[—ç½(¨ö¨Ù@³‡‡‚]:€;ªïùfŒ&2ÜÁ 8 d—ZóÑcÇ36 àðÁòaÛ÷RžŽ;™R3k’&|ÞhŽ.tO³]UæëÈ0œÝÝ‹bø@+ˆ~–%;œž­Çûèj8¦Â7sP9Õ<å8A”Ñ …C›u 3E§à1–·Ðk…Ñéj3˼L|æä|–6>¼·üÞ’R¿7£r~aOÙH÷¦/®ßšÞ?ûîôõ¯½?}qñZûþÁ ÖºÚwŒíikZßx4íŠ~„­æ £¢Â,jîé`¼®Iáļ7uýXKxµµÌÑ;÷J̈0gN~¨[šg o‡”cJ‡6a!˜Æ) }ÇßpêÕ>ßìãÑÚ¹ïŒÒØIO7#MûÜç (³Š-e€/¸þ\½v«û×FóÝÓ9BU¡•(Su8úϤìl)ô1Ë`ð0KhRP›§A9âdñ5S=oKçëþ+'?Å ÏÒþŽÀã‹UõÃÝáȰVtÙç=‡R‹_1Ö|_c4¹/e­Õ ÁC˜Ž 8Xw³ÆaR›ãIñŸø#• ‡kסÅñ^Ï0'¥£pÃðDJ²xèc­$É#µ{PNG¥‚J‹f(¢ÎöuÏÑB^eôZ<mHh‰ãù²²âe%µ~‡0Æ[{Öhs”1I©dÄ WsV;‹ÅøÒœ ®ÓI$0Dë3aM~d6ÆC oÍÀÏhå„TÉ…ŸïÚ3ƒ‡)8E`O-á‡c¿ lÛ®Zv$™µ†žÍ]ÑYàÆsu 95>óÁP)›“p\õûã  =O%Àœ3O°ÂE†ª–¼×2LÁ‘À©õ8ÃOÿè±#ƒkç÷¬ÀѽÞßW`oàn޾#‡2¤U¿¦Ä<ÎÑôÝïÊfnVÏ…C²¢)ðÊLž¡ÕUåm_ùr³ ;Ñ®¥œøJ±‰‡«SÊÁd 'ÈÃxá[/¥ð’oÚʽwæøxóá‰@¢ä¨›×›1ÓÞO—±ÌѺ‘ÁÑn[«¥­p¹ó `ŒÃ )èQùHéÿx*EŽ{µ·¸ëîÏ£{Îuívt·JK‘ĵÖ`#GñÝÑÑ#~mßeŒÄèç}K9“ú»À]ßÆË³Ï~ý ûïlöŽÍçÛœqýÙ<}ç›»’w‡Ï¡µ;ãm­žQ}°C[’I Eׯf(ì0ÖA¥÷Ž z†?Å{o2WG¯™ƒ'ã2þ½goÆU¸%9¯Š–s:†'œ~ÆâWÂ+| ïã”ÑFk1ÚYRɺÀgó{_§ Þ _ɾH¹¥~ø¼ ý™ž†·q»ôŒ™^F‰Foè!/ìô9Ž üOœ—kâ´x>~‹á«ègè)½f± ^ݳ Öãªê`ú½tôOY¦CŽ€lhÅq×}O'|N‡A• ~/UHœüžmà † hüæ^U”vïwáà·0WðlTs„ìd<ãGÐ O»ÒQF#€ßí˹ª=µû0hÞ9yl$}£Ð¯hyØvñ¡]UøGª ãC¯_sü8‡à ŸwïGáå‘c‡Æh™*iâ“è’{òÓã<9qµnýù~PbóÁA¯£š>xÖªjœ [ØsŸÈ;-— 5fÇÓð ë~P¾+É›^!€£³=ÓyÓ©ÙÜä·’ì…¹…×ÃeíñƵÎ8Øä…€0tÚžŸ/<8Ïd¬Ï÷ÖÐ=¶WX‘ ÷¼³ÉÆíüǹw#ú“`Ùp2}IÛ#Øs‰<݆Fb·VŒíq$3fÓ¡c²‘ƒ•M ‡^¬J¾éÞÛÂ+ëWÁhÖ« g’Ä>Îo¶ë«À>Øy.äópVÚŲ‰Í$ï¿kMì”\}c¯h¯ÇÎèpÐ2›G¢Þ7£o~ˆPlÐ$zÃù”ÈUûd›ø?"Ûáb7êºø^¼éÚ•+ãZÊ®Ÿ»–=ò¸q2×ãçË%ìw‡ÎçMÎÙ=uõ€?žãŒþ}¿ÈÝgžÖÁì‹‹_LôÇÿbZ¹z£€ÿÞ*F ‘`óZüøü¹óáE®dï°…ƒ¡³ÇÇ€iT V:féTõÅÕ«Óð“ªK›cºT—:|3¼Ý×™nñ@ãÓV£ƒO~ùË 3nŽd¸kLk!ýÊJ#O–,·yïôáùûá{-ØwÎæ‚²Ï/>» W5šì\Ú·?¹~`øoÑe­°ÑŒÀþ…‹ûŒŠO“[Óéö¥ä‡© k87_ëâ'ŽM;gݹ¶ÅÏ_Wp¾˜Ÿ|úét¶–¸JX¢ó>WŽd|ƒR£ƒWpÝ>}1t£¤F¥y¼Q %-æïî»ÎI âÁf˜æ§HÞ…§ \tCJj„Šábç°µkö—>ã;%²%Céb|NðŠ¬Œ] ~,‘m› }ü3v6ð:ÄmËŠZ ]lè|=®ó^ŽþKxÌŽ3SwóRŒñÝ%Pô\CïÛ7ŸØµºá«ì™x¼j9©-ù3ài›éFÛÓ•ŽÆ/“ìl¼¯]†G³Î É ×ñ™öƒ7£7ArT½ZÁÄ–¸RSåüþΜžHbŒnLK´&ëÖ”ôq'}kG0ðY´½-þŽ§Íø§$ó‚‹ÙÅkÀ°vm†Bë#OøÍà}™\í(zu-xNž|8ä¸g¶ùþB~´·ºñmÉžy³I1H݃ÅèÒï {%‚îˇjv¸„\öñÅè‘âäÉw†MÅG´V…áÖp®òk{FíùøYÁg2û}Ú å î=Þ´±æÙq:ôv'GI‰³]ç¬ü a˜7ÍÕr>_àæèùóx€³‘œG¾=NïPø2ù‹ï+0'WgÍ* KX®è¡PàÐ áHS¡aUíq¶Íµ~›ì6ü§ùwUHÍóýÄN‡®¤ºÜYðLÙ÷|Á–¾¥‹ ÂÌl´:nòÂkÌØ)àéwzYîìÖG—À›Ñº]Š+‚í+öW7ÆÛö--%_v¾ññO~1}úéG‚8“lÝ9~÷lüŸ-§]t<0¸ýì§¿Èw»’®z`¿>ôk£³Ý‡?û°D¡íÓ׿õ‹ìÔcù _åCœÅS3ZïùsŸ¢µìCáù™Ö›“­t£¯»ùw÷dO‰+°µù’÷t?64ûæãøørղ߸Æ7óÍŒ$£“Á[ó/Dó±S÷GOÝ™~üý1mm Û»ùƒæðèGŒä¤Û Bò¾à³dî’”ò9Ñœœ• ýk%D{®SL·—”uýZ],.NßÿA8ÉNÌ/=_!W¨çþ¿_3‚ëÚ®(+xÔÓG‘ž7Š*ÅE¶Fûb=ª°¦ûÐM­ïE\黃ÇÞ䚎ůÅgùñ%çK^ Ÿé ¤è>ƒï¾ª›Wg‚žÀy+û:˜ðÁ-ÆÏð]c}øBª«K89ŸÝ’m ©y[Ù÷kEý¨6Ï£ëO{ZŒWþì‹ %BW]®Lvzž˜œÅ?u<–ì?’(ÒÙ<  ³ ý³cW:ø$hûñt=ù}öÌûÃÎ|VT²:;§Ócè÷·+tÐñHâ3»—>ÊOz¯¸9@NÞ*¾pýÚµ~~Ï2’2|våÎJÉ‹uüHÒ§è^è¥Í¿Üò¥A×xýOq€½ÐE޽ÈfýÍßÜ[pziàìõleI:Ý<¸»2Æ;~¼£Þ2?ºwèµ’ê— ?yÒ8±ƒ:,¤S”¸³òhmúÙÇ_D£án°õøñܦì“ðkµ–íO ®/7ŠìÀ®¹|òô»&‚óËbd/Óÿ¶E÷'÷œ‡w¦ÍÏãKïN|ñÉôdmëôÛ禋7nLû¡ñ¸{ÅŒ.œÿ4¾X²~4ÿ*F[‹¥ªÛ*ìܲ°z´éð5¿RpoÕߺ´üà—×§ã'J0Œvî߸:hqÛŽåªÎ× ØÞ©Û«ÝÆÒòÑG¬ˆïEzïË®]1“¤f®Õù®Š>v‡s—[KçÖ—Â.z¬âÄNOa£ID4nbztÎ ÐøBôsˆÎ³ãÿü O:ׯëÐÌ'Ó~CŒa{¹çz¼Ç[‘büRÜóE|°xMzQL7™]|Œ‰Å¼ 'Ù|à›·5æ >§ßüæ:xô3ûÓË:×;óùì,öúàƳȺ›/“|®;±D'Éç3z®etÁFä¿àûØ–oEqxÃýlíó<ô#Çß™VâÇù|ŽŸêo.<¿Þ•D°Z‡Îè‚ň”–‘¯¨øNº®®*³nAt´a:„à• “¬\›—1/nØÞcqÎMW…Þè¬ãIÅùâpËô± ÝsW+ßµ3Y˜^°  EóS‡±ÊV Ä!­ÈÃ0:ÎI‚|[̳Fø^C‰ï”žá@Œ ªtà~Nn7Sx3èCJÎÅM!íìeÖv») ãoU! ãSe´È/žÚ\‡Ö¡¬Ü¼Þfß=;¤×fF¥L×Âm÷fŽU™œV}¶ï‚I>7”†v©½.ÀËpS O9rÿáŒía†Œ#FÙÓ˜6c›Bµ‘ƒÏȰO1r?]BB?Œ5‡×^Þ{÷sò ¤2§¨ÃX{Z–ïÝé`'LYá¤hëñ]gPÂ;»Ÿ ¶Z匠  @Ê ¶¢eä¼Nùƒ€eêoB|F ;2 g†›ŒÕ²NdhYAíÏqù°üHˆxù¨ê²YŠö«¾¥©„ïS88p6‡”=º¬{ 1S´l²[‡œÙj0PU¼ÌžÎêüªÝ,˜ïJU‡ “x>¥r]0ìîvZml2¿»’uí_Ü›æáãÚŒTñξþÒW eó®9Ê~ åu ¾S–_¯Îoœ¯÷9 z¡™ÞeÐ0ZÞóÑ’©„CyìÖ"ÆZÜonïCq 8 )›cÖL`?}twdÌ0É1CëÛ‡+×g!¿Ï©}GI ¶7­Ä‘Çu¸yýFBißÀW[oãÆoOŸg,¾ÿÕ¯AÇj½ŒŒÖ-Ræ‘ùÁD0ŠcqW™;‡Ä8; ÁNÂm ]¿/|õ½‚);fŒÍ)µÖ× ¶9×o…SÚqÌR3§åÎýxB™]wïÏ‚ÏÏw.@IDATCÝÕLemÏî=é9 ‚ûÅÌú8p)xŒ{†lŸù=kÓîÚQùYKóÁ:§„[ke„2P( ÖÜí]ÜàŠ­Ü¹?èf1¥Šîÿ0£Yvéb´:„D0ß×LiÏß}Wf$–­Ã¸Ã¯(az—Œ€(¾Ã³¸+£)GËý;×£¡× ÓµÖ©3…c9å=L ¬áxë’a(ÙàHŠžö*\5/N¦"‡%<…MÚÖiÕ4ZòÂˌłœ‹ÇŽe0˜ÉçzÎTdµŠ2ξU<´{hiÂiÿÌ%àî¦Üà û ˜²A¼(P'J<º‹«%ÜrŠw–¬„ᤵ2~´s{Ý9:ÎEíÌñ8+SpnNk¿f'µ«¹èŸN54|æ”Ãsê$3=‹7ÖݲǗ€Ï[ÅŒâ‚"”SÆÉÞý‚YŠPsrÞ´æ±ÆÚöX'ÃRâ §M‡wÚÜ,lÖúYL R™ŠQçpè0&ç¢)ÂÚÚ(© 1Nä73Ò.«øL&$Èܽ=ýô2Ö„vàƒ yv¬y锃'µù$Ì9ì¶l]-QæúÀ}n·Íse¢§\íi»‚§'\R­¨‚S%:çèâPå8pèH{ÏÉ ÁÆS34ïå`¿sW¥wgœ½/&¬¶Åóeð©f}òHv®¬á® 滺¿ÛçeFûœý %J¶Ù5-…œÃô` <ú€‡Tc)Œ8ØW¥†`e‡Ü¤Àx¤©Î†ñÀ¡û"šæ AŸ猕7 ¸‘Ÿø¯êZgˆ’m íûÞBéÀå>'ç‚1‡@ž®Bƒ©·3fk_èo­ÅõÉQò½Ïo~ÚŽ›5A$øÿÓkèao÷Ý÷])®œUÏʤ¾zýZü°ñ9´rl©€Ñf¼óÛš¼A»ÎÍqÔ?Ïy¾÷EÙÂÁïfFóuÞ3ÞQ ¾û pÌ…#ήnt6†-~À¾žr¸ÅJMÈ6~‚CikŸ;]‚ º}’ÌÓZ]g3é¨h ×9Ò$z8hFþlüKIn=ýi$”„ ñÉùø}ëmP„ˆ/ÀCÆþ?ã§[·Ö.-xpX0è‰kp~xÿ=WÞgÉwô&TÐ¦ËæäÃ?ÛO‹×õá!CzP÷˜u•ð7Á%p$_6mÂC9>I9×’t\z+å\P’©5Ä[­E5‘аS'ŽæÌnÎæµëë¦cª¸ˆ×Cq(§±ê£ØLùÍéäÑæÀå¸ÔÚPööž]‡ÃÄÏ÷ @ žÓowÀü—~å›É‹@㜰ï¿÷^÷2BâV3VëPнµe¬?~4nK^p*MÓ™þŽ#q’¾sü`ºoõæPÊèÿfz…ŠŽ‹—¯¬n¾W÷±?m´Îœ9=Þ»ôî)aáWüØ}8SGŸU¿Ô.3¼ùÊÙw†SüFU_9sbÓ52ËïÈ¡÷‚qI“9ÝæX8R0ßù^ÍÙÿAŸ;Ø}ðYc$ŽÕaÌ$A|ã+ï±Ï’…ªÚß=u|$¨6Ú/@Qšq½gº¯`û‹dˆD'ç„–F•X0?ÿÑÏsR–@ŸddêœÀ˜¤c¯&¿Ý“£åPÁ¦O>ü|õå:Îì g~¡i6~æþ’¢pû·0{ìÚÔ<9g' ¼‚ßÕ,©ãÍ´ÿÐþiGö~=‚xÝžïdæ ÉanÏ@É =‹1Æ!Až¾5j ëfœK¢„¦Gèö¢ 58›SFÞ Í÷d^°=&Yl±Únm^¸„I†¾Eq–û»}¢2 ‰xñNXv¶âFÏUõÀé«Í=Xha gÇ+º“ÛY«Ï Œ‹hšœ `ƒwíN7Úóôaxö42Lê~›³ðJ/:ËÐ:sü†ü-ï““ý¶ì]üdîB¸£sŠd0¸TbÙt«Ê¨o÷{U’›Î¥ÃTPêhôô¸@4œSÁ¦e(]‘cVàëv#kt(3ƒöîÖÏ;uêÔÀ‹k×nŒà¤ñ*Ü+¡ä^ýãCÎ_î™’zŒ[á|Qm´|(¾Ð3®<´K¸LÂC:Ðî­ž·r§1Ñ Öw¯„rûä‰ð?ûõVk8yâx¤%ŽÄ‹’­GÓûT«ŽÿÂðÁtÖd‹qZlQ0w+ZYÚ»J$r’Œ¢³=K·ïGÙë9*Ÿ=Ëvi 3ڙѧªFú?Jçö,òuÐUûàd,Ò¾võ<Éã_\¸¢ŸÄ´§ŽD’.éúøIù cßô¿W9±£äÛ7é)#¡6Úmk%eJúâ3Ê?m-ä5£TëÖWÁ /b_Ù8ÀB÷¾Ÿ¦cK긇;ÖGß]¹=}÷{ßëúYGVA*øô¤¤ì§ÏL ,q¶Ît¥.ìåÝÿßÇ >¼lý¯âíדéÿ²Êó7füåßøÍ;²—– ~(!×%tï®k‰†!Y­².û;^«]öþ5óñ7ÅÎáû?üÁôáQYìüA|=; Br$Žg¼‹Õp-gô¥/.5ÚâÓéо¥º‰Æ¥ûu“tbƒþ‡? R)?€b宿Zò‚nUl`Žä\?˜ü¡w<{•OáEI‰ùÐ19¹Ò˜¦#UÓí?T¿{^Ïo÷¼ýI”ÙÓ`Ó]ÏpØfƒóGu;ÝäüÇŸM—ÏŸ›¾õÝïNïœ)€Ö³ùF:úسŠUSweÏ>©ÐäÓs§wަóÆ«%ùÓÉHã{ÈNüšd‚—æd¢¾Ì™¬ê{ï‘Gøã ܇%ú[è;ƒlê,ñ ¼J@ƒŸ@׉S= Û±oÑ>ÚÚGFw½1$æÂÓËØZÆsìiÏ’NUú›Ä5¶#}…^ç¡­ñ6zeo„3ùøòm¼iŸ&Éi¡§Ñ”ëÐâà Ѧ=ãƒ`Ìÿ°ºêœ£6g&áÑlfüÌç1+h’T4“íÆež={¦3Vt°#™tl莳. [jËûÞŒãá3J&áu‚†3ÞÉvP̆å4/y¢ý,vìäAÛñ½íÉÜ1F*®ÅÂÞa—Knwz Ö«ì§ó9K3WÉ4{E5üyàmùæù##©ÿ–Ï‹ï—},8ÇY¿uËÎ*CŒ~aÿ ´®E3Ž/uFî·_Áÿêî«É®,MïûN áÞ{ïÊvu÷Ls8ô éNüº‘úbºB 2È`phfzütuW—…÷H ‘2‘ð©ÿoÅ qÄ`ŘSÌsöÙ{™×>¯Yë–Ògä z°Ût žLu}A‡°"òç]2»»v¦ƒ`b­È tÉ~VY>|®èˆ¯†.¥ßÙçªù6ä>~64Í®€qÐ4'Oïßpvò›lnßÙe°bE3|°Q±é::*t½Ê_ãßP@}tg„ÅöùÎn…ªtLEDϳý|O­â«ìl´ö†n*‰‰e ‚m| :»íþ¿éQ¾!ybä˜äœÑæ»oÑ™Úγù®\¹.ú°¶ìK×¾©(ÇË)›ö®êˆ×%¥aìì­S'O ¿N0Ýž=|ü°ï¼é¼¾úìq{&1%Ýþ­+ S*8R@1YøÅÏZÂQÕíù²#‰)}ç(:<½3>Ý΢58c)LôQv’à¯ÄÅ[W¿ï<äÓ‰3uQH–¿¬3X­~;Ö¢½ËŸa§ìÎîqLÊ¿þ?ÿ÷éÞïþþô»¿û‹¨JZ««#ÜL¬ß³õG×Ö™¯xVcÄ6ÖK>Dœ_hŒp ´Æ—×ÝÀÏãY~¸úÃHXä«£O2øÿóÕ}þŸW 7ßÍ÷1þ§NN?ÿìbü¬Ë‘Â>GfCuß}{J,O/üpãîtû~ÅwÍ'©8âäßùSG“BɰаØ¦ßïÕÑkæ¿ÎM‹¡,§ûÖ1ÅqµìsEeìü’4‹ˆßÖ7z&Ðî”/%¶£â­¥èçÁýÇ­!¬Êqu¬|ú`úåŸ}×çJÀ-Ù?¹@¼îÈ×—ébÇ€_^;£;k9KH”È—ÜjÏaTŽX{]’ ß÷Y6àHŠŠ¾ø•Î^\Ìî‡Ï6PÔ¤wÍV0äxú ¦¯ƒ‚„E´Ý“C{¢×íÑöþöts6ÐR]=$SfØ‚=×QÅg' ,JlG[ì}ϲ¶{Â^~öó/ÂëîLÿò_þÁô“O?¾° ¥Î ©¸ßý/F—·?ø·”Ì;Óº»înñ-]hMv…ÿïÞYbxsÝßѱ:rxóÖ#f$×5ï…Ö{S¦˜É¾q‡ä¢’£ø¹+ÉzIOd.þ<˜ÿC¯>_xVbåòðmÃ|ZÓY<4ßœŒ{.»—ŒIéÑ#Ûú¹Ôûtœk` H¼…¬Âfbx[Z¯WÉjºlèÚü{Ä÷—ìâQÝVž77É®ß$ -,d4¿Á}ޯȵ¿Å>¼Úë‘X ˆ6O4^1–mý±b›“Sn4º{·W mzF>ßñô9ò ™ºç»o¦¹øh£Pòä!G’Ô5wÃ\ Pù :\’yì…nëïN»Ž—@Ù 7m1Žî5ïÌ•@4_Ížd΃ >W«ÞКJëÁíOr1™FÏŠ›ŒXp8ã誘=öjÎ1âÒ»ÅÌZÓŽ±,p”A"+o¿ ·˜]ggšŒ•q*è(K…a‚ T«/œKS&_Û¬B ¥l8ÎŒ‚ £ÄFû&)ÀyÆH& õÌÃjAÙrƆ ѱE½iÓRž3ÁÙï³Ì •‹œVNm·Ë`É`ˈàcÜ÷oº¦‰P*@~ +SÅ¡?0r[÷ÁxlAtY>ÃàA=‡ 30ÁwµÚ™ ´"d@‹v¿kpŽˆlÚ Â}~1F$Û=‡Ã©dNtsvÿ~ÉùØ‘áßy]¸¼oqHl"g\ E@Âu Lûº@œJ¬…Ö}wÂU[h•‹"ë)ö‚ªÛ UF°à]Ä.´¥søÚ…i!#A _ÿrFÂý{7‘ï\m¿OŸ ØÍ¾zãvk[ÅÈÞªå_´í©Š& 70\/#rT˜4Ìx+A¿œcCéíÙ{xd ÍÔ†˜Ê$m­qÁ³=Û[ÓFôt¹n)?m6dÉn.@®šÚzÚ3•i²¨FÕFwdŠÃ§ÏŸ`ÊÒ“Î’n,öPÂÄBŽù`Þ–YP‘ñ¸R÷@gÆ›ÕZ~ÇXo‡’¨Êµïl`z³Pü65e¿aN–JÊ÷å“AËhèx`®ÌÐõײeº½( Ï©õ¾vÅë9'óÅ7v ‹âÎÌHøgh°â/AŒáE‹Ë ™êX@Æ8ħ‹ƒ¿ÉkA­ %X’ÞÔZFׇMµÑzݸ%¢W‚üx_€W¶.Y+°-;ökë±Z%šláŒÏÆù¾¶ÿ”39"à³'%ÏAZZ óz×5Ú(K(†¿‹—G•rk1“l ¡yŽÀÊÚÃxÇy…ª©Èìh(š¡—ªš½ç³í]=xÈwÒ“"Ó Ãk=#ˆ t16IJkɾíi$ªœ>}¼sn.ã–N ä8˜Î>R-#±a±ä——†Œ<{úÌñÁ'ƒ7›‡½»¨²ä\n¯‚]öú¡CUg¶Ž2„ÑÕÊêÍÖ/Ç5§Œ³½­ÄàŽõvo4ÁðÔvmkû®2þÞýÅä@gæöìTŒþ¨•ÝÍ `¿¡$•·d¾2Ð-ï:ša”–`”sÑšìé™;ÒöØ}6xçpδè@u÷F3£bòÛï2ÿŸfè[?8§ÿQu :VÐÆUtš¦kŠn۱Ƈ“Gñžý¶ù4I€ÄnÚä2þSÙ° Ý÷.ù66=ÓÚíÈ0~Mö©ùŒöûÉhàã ÃùAÏ÷òØñàñïïÿ3«ÙˆþKFòŸ^Í&R `ØJøY d]‹†Ñúž*Ä2l–Ù|0PEkë¥å»#8ÆáÀ¼-áMâ"†Ý£2ÈÏ{wng¸×þ<»Œ!/+–ýµ^'€ c0hؖç‹Vy’˜Øß=ÈfÁM‰ŽŒL,ÚQÕ´¯<äÜ‘üR‰Ë60²£€)ô1@G;ÏMÝ€®½;§rTáF ý869 £r;þ|ŒI:M’SîkÅCÖpØ-,º6Z¶½=ëpàû‚ Qic’aÊ`÷]À§X†GRTã¤ä'Û•þ™ÇѳsòÈF‰-;sÐZºaã’ùª­u%<ÓPw-IU¨.÷sôÂéx°¥tGMÄFåöæ@ʇ9vgÌÎT¿®ß¸3¾+h¼Üžÿ}Áè~Œâd<hvFé¾ì¾ûUa¨ªùüã²ï6Lß|-9ý~:yîä°‡¾þöû‚ »«ê:7‚f«ÙTŸu­Ö‡¿ýæ»Öc}ú/> Á·ß_©zok FGÚGÝ_žNç òíM—©`ßÜžŸ<}¶ ËjÀÀÃéh3ÕçÙ€C üã§+eâ?H¶-¼+a­`ÜÓÀ†ÏŸI^îžnÞ¾7ôË'—ÎŒ¤XG,í :rd]tÚPÎ&_¿y7î/1©ß ZåíË>Øßò¨j6ºíü©cÉì­Óè}Oÿ뙫%ü±DëRvÇîžqêäñôææéÖWß ;P;ûWUîül-nµx£÷Èn2†>œøKý½æFªÛ0ÏQd³ °vß9ý NrPܯý߇{l,1ŠCÊatÝu‘Ö - ;žUu†/Uð·ìM&³Ðû¨Šn·GÇZƒFÆ´O2'ƒ³ƒ»þ$GžfSqF‡O×xØÃžÈ£A3‚wKüÀ÷ —Ùî¯r4éTká}~ƒê `Rä֔蘠&C&à=SÅgƈ¾ÆqÍ]v?›'1ž‹¶fëö¦G¾a ŸQ ôUsz‘¾ÚP›9À^twsÐ6‘?DÑ9ë­±nlµ!zÞ8óÝóú]¡ç°ûÆf@³z(½ÄwÀë‰ßžÕ½{œœ%j•„Øí…n‹¶†¯²~úûT±I½uåûéþÍ«éû@¿U×;˜E^“}ûëÞ°'Z”¼"#ÿÌ™³Ù燳“ñј="o?þè£Pç)€Ë¾Ÿî™îfç[;nmª1:¨]ºpa|Oð`gíùó C— à£íƒ³“§‚ä‚Õ3REÕáÚ: ™ó¾tƒ³C%øÙØÓÉ¡ñû¨’nM?¹|qŒ{|vòd (%²$óµŠ=vä'c,Σv>»d Á3”¥Ú-±5´Ù;–œxŠ×²ƒŽ¤ËŽŽÀú¬*çt|éyl[‰›Òa‚Æ:Ð1’‚% ­Eoæ.+aM˜º;HÂá£FFmotýàÕ¥dm­Ñ!:àÇâ)ã³ ôÔa rXÈì³nŸ>t¡7¶zd«½|™ŸÝø*ü#`Žî‰¿^%C_$+%ºï.©àþË»ñõ2¢9ïå¢#¼4€WÕèÉrö˜ŠqàÕB:^ ó¥Ü_rîL®ä¯ô.’œ¬¥®„ÁQ¶aóQ¹hþÎ –¨MÆCÛ’lÑû÷ë¬Tóéò–:Éd/&ÿ÷Es?ÿü³è®6àÑ‹–³ï§ñ³±8ÇR i¿öu‡ ¶$êõ"¼±à“¤ÍQ €ÞÕ¸íÀœg/Wò‰ºÃ«ñ³¸I7ÛsñݯûÕà3¾Îûõ]Ñ™ D~žhm·µ·žùa-ñøÌ&@mM/Ÿÿÿñ"cøA+ùì÷Ü™¾üò7ù6¯ê÷Éì$ ‡ Ùty°/y ˆÂžë‚2ºêp&S7ìïg­Š=_šþü7¿ž¾ûÍ7Â*šP1µo·cžCÛtþ ÿó¯~õëiS4p°£]ÎÔFèa²a{ÉQ£*xÐr‰ÇéÏýa»j¡‰Î··ž‡ ÚïMÿž;vúã?ûóèñE²¦äêWŽç Ûª+É«‚Œk%á¿çøAº*^8¡ Ñφ®Ý>?NkåÏ äª\¢? 39îìs!à“éþëÿá¿ïhŒ3ÓÅ ÒñáQuJR§óÒ“YÇ;è«·îL‡[?÷’•ëÙÄxïݳ¿1ét%AµÎ>ýK^`xsv`k%À3£ÆÕg3Û¯÷¢${AO šþü¶h¼D†òsà'ÄÉ,xMG\jýزÇO-б»Û¦›ZÉùÏa•ìrÄØ^Ijíí¡oÞîȽý Rv”]¶â çž‹oð,ßÓ?³¹4ð‘\Öh%¤XšoµÈ$2Š P5ØÔÚ§tb7ó1æ¾7 }HâŸ%Vo­ÃåÉSGú¬ÀB<º)þP ’û_wOÝg¯õ©Ê»uw±âì¸xùÁ}öHÉàÑáè²Ô:²ë'@/°ùÄdà_²?”ŸÐï|Ï|¬µýr³µW(‹ðÉeìÁV¸5ß’ÝV²¤õ´=ëø³á+å7o,8wìxú¬ï.g·:JstMðŒhËʰ—ék6üÙ±!~ï’äà,1«€qßa›ÙA¶˜*wÉ“l 6Ó ¾Gþ¡?ßò¼ýÕé(räèÁ‘wrÆýîðKؽ76*N i_çû>‡{˜Üäã'™{_"[-¹hÌýÞÿ¥·òÁ¿¡[vÝ,aÌÒ5öBr±÷ :£'á…Æ:x zQ˜Ã¦B3tì‡.® üq‰ú‘Çã=¨}oȟ잯+ZÅ9žKw§…ìþÒ_þů üÞš.\¾”œ::îZ^ll>‘Öúô‡½£}ÎŽÒS7t¼ÚšoIÍ¿"£è¹ÁÃð§Ö m•í_…xîëdè–p˜‰£c$ÄÒï’vÆ‹.Ëpl\…ÉàUÄ›;ëö°1Lfñѳ|‘¥éÞb m á•ù·oUýß~öù§%6ŸŽæ·GO·ž|w\„I>Å|søòÿ=ÒšþÎßý‡É=XZÇ"${ËÁh.ÙùÞÉ&AY:ÐqnãÍöO_d%×íÈVIDzÿýñê‘|ÝîI0oSmõþõãÇhˆýpøhIœh®u¹Ö&Á‘,9âïKL÷;ýÉ2h…¿mÍüHøá òÑø;öÂýáÇÏ[ŸÕ03ÝÓ^T4 ÃQd5’T±ªÇÿÆÂ¾‘˜2—®Ø\pw–ÔXâ}˜S›8‚‰Žâ¹wçNëSKíöu.ìàQ¾é¯û]2BÒnÇcd—ê`êH|¦zžƒ šE…Ñû87½uc¾ÊÇ{\g¨åÕdI:Eåú…K³=4‡çCž³ãè]qª!ƒ¾è l¹ÆGfÑðäÝá—ºKHŽc¯íh]ÙSl{¶°‘ÑøtÍ#{ÎÔA’eßëÈ I´dÝó6«"÷ôé3C?>¸/ŸºB¨®1)œ8Òçb}‡³¿%JH·Kgg˜çÓ·oF›zImxèmG4ÀEf·l ·…Kãt £TØz  ùÞ‚—äÃýÇùÏý·ºþ[ŒëD-ÕnoÕrþÎí°êõéøÅ‹ÓæŽ4øþÊ‹éóóŸN—Ïž™~ýÃwÓÕë×Ò É™xd>™q;]¾–\XˆF²œ‹½;>ÝÔ¿ßvV{ÍK „Ïbgä„)íèUUo/¨¾isvrôùèE¾òþÖ¡ûóû¡ŠæCç?žÎUuóÞ»dgsLl+Þò¶¤Š½{k_Rˆý~¸T²Jv¨NÄûóqÈBgŸÏUÀ±Z75úÝúUE:äÕ›‚%lÝH`ÄׇG(쀃èÔ²ýõ¶l”ûÉ{—nnoÈr_wEùt®vþ;?V¬‚f;DÑC·àcêèI¶ø»˜^²?:“E[—’ctað“‚ÇGù#d»":K€œßŒæºÝø{ϾpÆt÷ËŽ›UXÂÿ6¶­ºL7>v‡onm%ÿÂðé»×ñ¯c7ÞFç|¿ùy]>VG‚“$%IÌ’ñ…÷CßwïFÞ±K%ŒÄcùëoÇyæGò ù¯d Ìz©ÂÊûáè»Ò!‹+é×l?>®Eœg/ _/þ±.æFÿdUŽû¿/é7ñ"þŠu²dËÄÍ)Íí´o%¸miN°G8Ö±’Ï<¯zµ]ð ö$Åõ‹ì&àé›q~>â¨JUðôhF·ìÁ;d1º"²a]áq ¡Ødˆ¨›7ÍZsÜ,óŸêkÎÞ»äîÑ£:keº¢i•® \û=2>Kâ‘AßVÇ#ÉáöS­¯–£½Ñž¬A!õ_”Åßãÿ¼òypªýËr0z¤ ÙéÆtå86B¾t¾g—xYà|ùâùÆÆ÷÷%ç$ߥwÅ¢%¿3&ïVáÉQ¾xálbŽUÅUð9üóŸÖž0ùrõÆÝŽR÷n.ß|õ]2¶,ôÏ?Éx6ýê×_uæù‘!;¯4îÛ¯?¹|a€ö7nÝ*¨\«ÃO?A¦o¿û~:\pîÂ…sU’-WÝzoúøÂù@˜ÃÓ•›·Òg¯ ÒŸç\ýöÛ{À'Œ€Ù£ÌšÏဿ‡““>_bÓ‰ä|•Ùœ^´z«JÛ¥¥³}f¯]¿5dš¤ÉÇU»ëbƒF%r\¿u?GóáôIæ‚éßýðC²+û+ûQwµ Ë©ªyUå±çÖ£×cK­}üo¾ù.:ëÈž*åÙ}*àwÅ'’ªTN8‹øpöîÎl+útiÍGWŽäÓëWuéoº¡ ûq¥ÎIÏsæµÕׯVp1¤\ã¢kðܬŠ/>‰ŽðݹñA·²’\ŪàÙ6äµ€0~°zÑcŽ àl¾jì#Ñ­{ fKÔ]ý½Òؽ$©J{Ñg›s\ÉZ4íØüDþ¢{|ØÅÉ\`<ûif¿‚´†o2ø2[¦yá'r.€k€Èì„ÖfTc4¼ó^67&uß^öÔ€Åü<ϱK<˜_­äW’A[£ßbÿ.ÄW›šŸ³ÄÿÁƒÔɨZ7ŽÆúáþo›ó|öÜ»tä«|­´ÍhN×x€d€5ÝPÇ3ÁvƸO¥ƒÆïÉ›ùdÈY°HÊ:ÆÇÙ'󆟔|e³tÓÚßžþ—ú¿•`wdúgÿ×ÿQð¼JŸÀ©vð­ ¹VŠ]>?lú~¸ž¢¾æ•ÎX[Ð’Ÿö–GV,>ì©x”­á˜vÍHxjžº>èè‚ûÙÊ+#é]e¾`<^8qâÄdO¸Øó `?L¨Êw²`ZAP ãî]Ùìév‘¤F|®óÎBÇŒ4ncÚZ0‹Þj©‘€/pÁÏägâÜž½ÁΈƒÁ göß\>†Ö qºhåhî]s‘üµ3[WëÛCÑÅÑöf®`ÂR~›–Oàˆ¶ÀNö W^ç{HlgÛ¦q Êð–ë®0þÍA€wyùq´PXUsžéF¬ñ±³–° /41’g«Î!ÇD›/ÖnN'/¸{aÚÙüöõ™=XÛÜf’!æo•aF>ïX¿þ7~ ù—ŒY]{Þ¨ßL7¯ßžÎW-‰ÞÈIÝMÈ¥6ÃfàÓÉ“’×>È-¾í›|¿{Lñ—¿ž®þê«Z‡fÿÅ7’U áIzè}—×nÝîlÕ«“ã'ìÉúCɲái­ Ð^Ø[ ¸‘áð¯÷íÝœnÛ;®×þÈ+Àï–àï8ÄmÑ% úÕ»däæCµH ƒ™Å;’ Kx.Ø5ÚOG§»òöØi*ÿV—«Œ¯Í¶³Âé¯;ÉSç§ï­HcÿÞ’6æ'5ÿÇKéÛž¹öÍ×£âÜÅ Ó¾Î{Ÿ›_ë2º4nG‹ˆã Üjl ;JdZ+qE5 @Šù=¼{»Dìü°A{@÷p¬ô ûpKø×Ö¢U|Z{U–‚Ÿ°T÷ÑSô ûüŽ$…xPò)™¿Ò~Á˜œµÕ}m–Ì´7¹“MÏ8–n–l'1³ r|"1p5¹dai’nìÿð•¢²é"Y㢗í1Lèƒî’èÅ_cÏØ_<ϧI2րΎÇ›ßPÙ‘®d#}xVá‘ôws’xö0}Άw´ÌŒÁoTáOf¡7 3+Ñ!›Kÿä}ºÎ5ügÁ¥Aóƒßú΋ÙXg~$ÜvVÉŠì ¹Wþ+FÍÖ°îc^] [Þ"°(ÀÁg7;3™^X×˹üùáG”N¤]ò·`oú¯¦®]»QbÍbÉ0Ý;óÖÝŠ¹N77Göœž6Ñ÷é³ç² H%$Zí$OZ#ë¤è–˧Õ Á¤·ËØ›ìJÏÀ‹·ä„”…ÇÖ… 1;Êæø¢[ÃôJíIþJ\ÞS¢áŽt®®£kª›³oç¢}Çw>J¾²%ø"/ÒCß|õeþâùl¾|•Æ•¥…ýç#·Ùõéˆlôƒ ùt‚jû9*¡¢µèÝwBšÆýÉ t [âß–dóÐý}ŸïCÎÍ9¢ò¹ý’È£û«b$qñÉbVxj¹9ïß]¢Ìëdm®Ñ[IXòe>¸äƒíùƒ':´®'íÕè>´ÙQ‡É›ä-Øh¶¥ÇÐ ZÛ)”7t ºw†=»\%ö‹®hä‹jû¦8Z`–âKñÝbÈŒ™í()¢`+±y½º>Áx­ÙË%8:l<è¿qÍmp‰¸KsJþêFp:ÌòY´÷%Ϭ-›žMA×â…f|˜7Cwïºh_¾ÇŽÀëë7GÕ9Œd[´çXÓ»<îêœç…tª#B†¿Ø^î謗CñîÓÊ0¼ù:¾(¦mé\ðüª::ÎTAJA,5ܽx*Ì–ý±lûX²ª8[Ãøí»„föÀöh mêåâç+éà¾ßf ú¿W±Z [,NtóÊ·ƒ¶ØÉä4¹'–©óŸþ¯ÿsŸewç_áÑW÷¯êê‘,•-ÿöY EÙõwoÝŽ†ºª{[ïÍŽ0nìhðu|͇_¡38Úˆ¦ Ù>Žâü"!et_n^èdǘ£ãmÒ똮­ftÍé¦Ñ9¹>óæp§³ù—ËÓ—¿ùvà§Ï_*ôû…:ÞM¿ü寇±PGîO>ûi¶ôw%Ê\/YêD4­KÆ kçsÐå’ö"ž{À|ìWÝ/M ö¾)»ìE±›÷IÁK+ÓúÑ|%´”¾jnâXsÑz¤šH“LSˆ,aŠc,«üòe•Ž.Hˆ½|¹#­sŒn?¬­ÁÞ”ÀÁqŠ€!ŘënW âýðÕµÎØ8]P½Üb!Ná0Œî!PkÑTà¤r‰/Bª ”Ú¸4lîV¢12vP6—QÖ® MÏL±˜PnÐÜ]>ž#¸.à Ʀ÷}UNœj·I/ ü8Ï·4)«œä¾Da’€ÓêùÃh 1Žà/&š7„¸ «†52G&VDh Tlš%ØPƦ,±  è§KBÊÚÎ"’”dÏlÏûÉ@Ê`âÀ[#Ïf€=r@?ÆÄi–QBQ1æÒi‹­SÕøæöRﲎž–LeÌíÚ½¯VÈl“`PB³ÉXc@­f轋Ðö@äŒËyÁeŠ©šV­d}3æ?‚êönŽªŠ7”loE3 ¹Öƽ†Ô5À~4ư•Ð6±Ë­‡ h?º} ÀÁ²Þ­†è§9 åÞ}Ξ:2ÖeNÅxs2g]£ÁÁ(=WËÒñê=ƹ€´=¸pf°s«¶oïšîM©Qàh1D¨«" ýN£ºwÐ2ó‚¼H +ð@áù>þ`èr Eë Hü¶À‚¥`šöˆö™Âà˜Q&Ïret H ™PpÆ·`ø2Ù?³°¥=Ü—Á6?ÿh!4ï®áüõO£¯Jĺ+7nO‹í@Áípèl8B Ï>?j¹†pâ5Ý2À=)·À>~TÕøp°Z`žñ%÷X÷g·Nö-4µ~ŸÒâ€QÚŸs€ÇÊÍø[fZÆ[€éµ@Ñù[‚Í+’ëé>2ã‡Ñx´§|‘’GC#ˆØ…@>mÔhHûwÎÌ«õçÓÖ„0¾™ßðæ?‚ ÀeòfS|±"Ú–ÂxU÷ Æžç¼};«ÚÚ”•ÀHÑRfd ¢Ã掆]`ÞûÚÃ1ˆÈ8Ï•Äè6y§:ƾY_/ë¿y>Câ}gF»»vÈ’¯â½ï’£øÀ¼©®¬ UÑÌ(Ù…¦¶¤©Gw€|²œƒãP2p6V@1Òç”±Ž#³=Ôþxæ4ª|’!ëýmçâãÍññ¦*I(Orho¿ãAF!·ÒÚšßÞ:Fpì|Ÿl$÷ð‡˜Cò$~ÔF ¼_/©¡û¼I6QÆ'²óG;þ‘Ð…ÒÉÉ*ä2çHB ÃQ2ð˜“4ºsë^€m-›KÄu P–aM¦ ak²}û̸`Ó!OÀîy|éóC9A(ž±IÖj!©"Kpü]zÔù§Í ˆ@þ˜+½ÄÀ #ë´µ"Gé]RV;G[Ü=é>ãv ‡Ê†Z¢bè.rš^³f#GnȨÆmr‚8ë-^ANGÝ'€¿kíMÏa„ªzT™†×{&ýWšb¯ôN¼è»xqÂdd¿hú§÷r“ñöÕ¸%¬I’Y_ç°¥»@ߌ3xËÌíÓþ7ÎòíwÀðŽörÏÑ·ï‘Å@8ßqÏ…Œñî8ôàW´3ËÈ&º…ÞÑÅ€-˜ mÕ­ñ9›¸Æ_xðoÒËh­«9ŽsÍZ'zè±övÙR‚_÷ÛQù}80ñÙó'#èw çõiôWõw8£ÿÞÝ;CïHhPõÀÞbà³çÄ;ž„Û;ÆÀîΜ¼Å]ÑðH¾éI\Ž0ðÚpÌþ¼2à?¬¹óÑ%0‰®ãàã›È/]õdÈ*vÁ°Ë¢)vÉDœq|#‰SF4`M]8÷l´j¿[¦!kT»ošOú­¾Ov¬7ÏhŠ¡3<#q£CôÉ©ÙZpú-ý¯{ž`™§bE‡ isØ-wÊúewž-X.È 1Ëb õgñÿG/Tݹs­»j§“‚¸fvƃœi-øOÈ¥³oÔê™c|´¿UHhk®‚roz[‚ž õуÙ×4dÚÝÎÿⳆ®ûÃ_þÙС?ýìÓÁ/ß|ÿC ¯§‹çÏ·¦ò¯^™²¿÷»_èT…uõfÙÞµ"üâÓÑ20ýùÇ—FÒ×힣òü÷~þ“‚M»;cüÆpF?ûôãQÑñÝ•«é£ÍÓùOΤU;<›>ÿäãì‹=ÓÕæ^eÿ}R^ÂÞÍ+×§‡÷O/_¬Úüy•wúìܨâ¸^€\K¯ŸþäÓô}íëM{“}—»önA³k7ïÔúU«Ò@§Çµ±N|tùÒØ“¿üõWCV8y"Ç´NeŸ9q4PàhùÊ8'ìÄñÚyE#oÝûv> Í> È9y|ÿÉ2GzŽ}½@÷º÷ö²’‚jèìè ÏÉ©Ëùc§»×^. Ù¼©3”³úú{v| üyüIéŠ$IP›íÅ~è´Êg sG`9kÿÑåð%Øtñ(øÈ oFÝCo~ÄÑY¦Bü6 à>®åíÖè{´¦o=g³ ð,‰„ìn[³ €œæÔÁãÎT§û€ClÞáybß¡«è@ç|Ïœ4ì^é…æÀ_ZÏöð&û”Fó¿Qõ-j­ñ+ù@gxuËq_ß1w× ½ÿñM\5K”Jf÷ùÔÚÒ©t•1Ú‰»A#u °5 q¯žáæ}>kÑ={.ÀÊ:ÚǹæC6y’êØyëØ¿Dª}6ÀÔž'‘vØr}î}¿›§Ï-‘ŸÐC[£trô±%›Ž}\·/A›O?‹/¢—§#qµ§¶·[QÈ£É_¸w9w´>VRÉýøšop¬$™å|àë±xõÈá* nܶ÷‘äˆIÉzøä‰“£ A5ÊéÓ§²³ÎL7oÞš÷Ùì"Éîâ/²Û‘ìÕsº7è,G6½Ož<~lTx=nÇ·ï'‡mN?:èS`ÜysgÎÏÖÉîIVâÁÖ;wî;×y‹d(»èdAÕÓÇOŒy®Åkùü:{Ã<#xe||¥c¹ãËÏîŽõpÍ’bÑÁ¥ çG;T²Q•Ù¹sç†]®ÂJç‰ãɃ%,/½Y`pÞ^×?™¶íß=Ö>®MOžŒÇUŸ±Ëé”}Éù‡%èÄ •¾Š½ÛU(]½qk §“/K æ¿!ÐËzHÌuñ¨œìú%ó kÁ]M ¾êv/}B÷Ò³¿g:”D/µÏ)><ǾSi„ïÐÜèÞ.^U­ýhCËŽ <Ø ôl)¨àßÅ@¹Y'ZÍ–h¡¯Yäƒy ~áM6wù¸3»®÷g<¿•îÆb‚ÐÆÎÇÛ•ý½³ªã€¥Àäs§ÏM¿øÅÏ»7?!™—nz÷a|#àÖwñ]«ëÄèð’Liæéùì‘ÆÆ&yY•ƒ^–ÓÉl~¾ß¨noj?óíØCkÉÖ¡_ùÖ’Úà)ŽÖÐ]Åyîϳ5Μ=3ø^{ô½aUöÄ^ÁZše”b®þ½üæÏ™äúðî»Íy­uàƒß«sšÊ)UÐ[ ΨÈÓ¢YBÒÇeÃF+lçÄÒ#ìA´˜è!üœÞÈNÛ9ð ôüèArËÁýUÇîÍ?vÈM¶±é*ÁAôµî^Ã.èZ`?ý‰?‡-Þ>ĆãÙd?Þs]¿È Š|„›7ov‡Yô—ñõabænÈ*¼­ZÐßÛ£c´¤ &ÄúdÚUÐÖßÅ›c=Çâ¦{{žEž1}Û:“Gì‚~ê|×Y`×^£ë‘Óï’|¶¥çoÛ áèÕ玹ÝtáØãWíÇ‹Švàü$ °æ)ÔÚZÇYWX-ù[еçã¥YÑÐ óüa+´nh ö:³p‚оè†ë/Ó#ÙŠöy´{o?,žµ¤Бuø‚‹.x*ÉÍK‚Ƈä6$ÌÒØŒW²Í¨$o ÌG‡MϤ[èà]»+ÄI¶ÃŠð»1ÐKý¯‡ÏŠÜlkÖÑw†½e6]g=–aÜí+Û>k_ìlѱu;“ª~®BºÉØ€$/G1ÞÉæÁ RÖ Z)†Ñº†7ß„7ÿéŸüE‰‰ëVq6Ràà þ®Mßýu³.f;êlè»ÃØ›ÍsªÊn˜Œêjö×RI{ô³¹zŸoÉ·CoüLÇF¼(²¥crµ¶Ï]æ—6o6Á+6Cã·ÆkÉ]G[½mß.^ªŠ7œ &×l;u¾ä^‰y+ãÈŒ¥‚Ÿº¶,$ó«È“G—¦·þÝô‹¿ý·šÃ±AÏh¬•½#•¶u ØŸÿÁ¿ªèeWÝÆ.~ã+nίþᇆ¿!©Í{ª—é@IDAT5ß+xÖ¨ /Ýù>}ù¢„{°M-æO©`Õ-ÇÜ65‡×®OgÏœvœ-`gÿÝ D¿þg^.³Gæÿ®Ž»ƒöû)ZóB/ý¯§ó{`‘aÅñ•=ìÜèøÀ+à™ïë%ìák¼8dw²™¯“#Úºe7ôï‹êÐÊUµ­‘Ñ¢58ù؛Պ³¢?:†Üh¢ûÙ5Ÿ&{^–HVתçUR¿+^uýêaŽwª€ïOÞé°¹ÚÉOÁÌ­µª—¹6°çdl˜Å»:ô MI|Uœu·ni¯º ë(§Íº$hóp6¸ÎmxŸM6ðÂhDò õaWâ;ë6{Åñ=ìðYcò·µ×´öd~düè_Ø£]¿¤ÉIÓ:h’¥³×U<‹õÀBf<›¬Š¦æG ³àpöÔ³Öj».ÅÑØ›uI è‹­ ³å f+vÌÃÞy±$µTÕ_y_çŒïªX 6ºÒ1»›¶—<²fû*÷]mtÿF»„F&K œoxŸ ®õºjpmÂwÅW*¿üö«á¯­Õñˆ_fouÉ19÷V RðG_*9ZÁÃzrgüÂÖ:¾v ì‰CéÐŽY˜K 5G„LÞè:ÄÏ ãÝ—ÌÞê1Ãï[m~kUZé:ò2ZßÒ=ð7cb£¿F+ùrü¯ÂIF÷¶ø½OÛæÓ(z~ÉœÝeø:Ñ2™+~‰î\+¹i®ûë¸B¿ YãYo-ßU¸ vý77ëÄ9˜Oùû§#Úë?üÃ?®»F8aöÃ-õ{[Y"ÌÒtëÖÕQ”¡àbtƒ(Áxû©céš NÜœ–;êÓ1ƒ;wF­‰$Ç‘W"ý† [6îæ-%@½Jg—TÅaÌH h@â8©ƒ±/lVô(©/\)Ú(i9ÖæLŽÀåXFˆÍ”QºVµËñ”SʼʨçõÞ= ò’`ßÛ„‡úãˆíýƈóèÞ~dºüHÝßâk=ùïÿìëŒìw@B-:ƒ„‚æì;3û«o®ç¼>Ÿ~rùt†{gq6x†À€Àá•kµÍà=qÔy@q UÚ¯»ÇhAÆ€mL¥—´I”=pxf4DM)˜ŒîÉ[W†ßT;Ê R…Â!µv o‚º–pòc¬F£å]í¨µšÚJõ¾àK¦4ê=~¥DéLh½Máy½mÌŸ±R( A­î8“ÎÐå #’ Õí †ÜœC¿±17ÜAì£BpŒ'`7†’QA&›»×ls­ß‹gÀ€Y–ÓÖ”†,î'e毥ÔQ+ÆA赌oÁ€þÈÊ꽟ÍsõaÆp–ðàR³Îôë3Æó³gÝU¦çrç>P2P83®)Sf8Ë=·õ57»q¸-Rbæp¤(Çƨþó?Æãb¼®³~Ýq̳Kc«.Ác‡ê¹2Uh‡àE˜Xƒ×TL="ÀKc´†žÇy0F˜¬:´ZdÁ[ ¢Ô-9RööY„1ò% Øæõ» Gãžo#Çø<Ìzâ£gYT)þîÖ†‚L`1˜ßzP/ëd0 ªîÏ–Ô@ÈîÈY‘¡H¨Õ EÀY€,þÝ{°%‡º{3ªg&È´±ÇÎ7wüÀ{œ ` l‹Æì-e¼5š”¡c/ßGŒ9-mZî “²·Z´ôÂý£÷hf ñ¶ý´n‹UôÐÖN“²Ê¶døîgZ‚±Ïë)OÀ¥9”îÑx ¤¬Yj=c[É" s_Øà|Z]‹–ó¦d'` Ç,|ÇÂVrª^fTËpÛY«€ß ¼ÐY»FF•–$O›Ëë€Ñ23´äüÌ€*ç£ÖZ -·ŸdcÔ5Œ“-9êŒpëhúν2œ£禸?:Ñ}àÄ Ù%¦tOŽ; ªjM¥}'Cb¥½ ä\YW ø²§LBÙ§x•4䪵—N@ªõwÍ[kkÀøàHo-)†ìLI”·vT¸ƒ%¸ñ!y@†W$9,øøM«sIÛGÏU'ÚÁG“>Ù«›|†£_©@#Sw”ü" ÌÞûϸu.X}QòH†Ï,+“£•<~°Ô Ï«¾â$jsf V[ßÈdøÈ:Žn'Ýw&ŸÈï|¥!Kf†ç \ ‹’Å=—c(á‚Dg `ÀP½Dã–E§5 ýäž[3^è‘@¾Ñ!Šo‡´¬Ë‡}૾ö£’îº~ J4Û£‡ ”¨ÃÇŒ^² ßE¨ÃNwôñº=ÉLôQÀŽà5ëDGú×Ú&͆<@7œF¶r¼1E/KV1.üL6ŽIsôÐzàWK0GF$¹`U¤ YÑpZí§€ïÌäÀ¸ÛÇ=mûžƒn´LÛИǚ5"Á_‰r/ëB¾ØIM€(×Êzdh¹ü³ñ<{û7åe¨-ñx­¦wèG2ÝóÛ¯§[7®&;uD„:ÿR÷†õéO~óÛ:£ìë÷’ßrì£sÒ%—Å®ÁÛèX '- ζ]cïFü¾ˆîF½¯â-}ѺDTjÍ×YžÐ5x^`}#@ dKl«;Yä÷……ÒÓ¥dzùyœp&,9žˆin\³¶¤4ÉCoÓôJ7t0ø5:ÅÿãÌÒVÇ{D²ßXƒž0œƒcv¶]öÎôÍÎU2öµýViá Éýû P|蔢ºÑ1Dª1TCn(è¾+=)(díÃ$·8#=/ÇóÇ 0©"PÉ·’3x¸ïŸ((#8eO:zllæÝª£ÙDz•W{ãúµŠ$WU’/ Pœ|>X`çî⃑áûñå§UÜT½SÇ–C‡ûϹÇdÐO>ý¸ÀUmÎoÝîÙ‡¦Ó?¯ê½ä®ï®\{yáܹÆZ‹÷àûs.ât Î Â= H¹[À\Åú‰ÇÓû‹cï~ö³Ÿ&“·N?\)Ÿ 9wîühãö«¯¾­à._ºÍ°Z•ú­lî*Ñí¼¿ gŽ6çõé·__ ˜¯jý£´ÔÚ_8wvؘtˆsO/ž?—s]Ë÷‚Tää'?ý|€!¯š“ï‚$wî×J-¹ûÑ…sӥ˧›%.Ð~gº—ýy°x£3N¦‹/Ô¶ùåôõ7µ¡/H§â}$ZIdÐòû~HvÎÚª½H~ܸy{€"—š@¼d€S}ÏÞÞm-“pöìÙf=,ðÎWÑJIÙQ fw>dzgÍg±ÖmÍW¼÷ë÷þ¨d­ éRGv°ÉíQQ>ä.P')ØÜÐ u^ Ù:§ëDvÜð5¢w/ró}<…®?¸7“µ½OŸlÊ®~“7ø+æžóK2°¼)>1~ü"ÙiÈ|]Ã6ÁS佟Â×íÐà†Üÿ5¼~ú®»û›/B– ÐîÇïsÓ»§µ¯ñýÙ‡ü/úÙé-Ïè6ãþ3;jÆ[´EÉ–Ãnï÷®“äæÅ÷b7ƒ?‡ÞnÉ( g©’­IÏx ðiØ)ËMΟ»ÝRK¿äãÆ¾œO<’;è/ë^Rr¢$ Tå û´û ýظŸ¯ô[ô}üý±a“Iž1ڃɇÇv&ðéÆÕoGpØñY’aVVžF³çó¶Œó†Ùö‡Jâ¥w}÷VÑ{øÐžD?ø. ßݙخ‡’ ‚5øN?Q׊ ÓW¿ýf$PIÊ9PÐøë¯¿m¬ëÓOJ´¹S¥ðW¯dÙ¿÷÷~1}÷íwÙ‰Ë#)E°~¸2ìÌ>¾8ɪÏÏ?ÐwhIKOŸ:6ëÚ íIß:?zô¨õÉéãËç. ~OÖIL!#ð.’WTŸÜ>’¼¼uóVÉ%´T!³¯KrR°ÿóO/ŽÄ×'Èðs•Øsëö‚Û%CGOZ¹nÞz¸#¯N矬ÆÛWGUûGŸ\ @÷;Oòxríã;Ê¢1-9ž\pþûÛŽe»p^þeküÝðÝ.5_XÄÝ‚âÚe_Î3f³E‡èäóøÁ£éïÿÃ4;yløxgË­ïðßè½€ôÇÉYI>ìY:$ϺuI‡¶®±ß4—ïJ³l,(Àǰöj…ã·¡³ûÞ ‘¶—}j—­­ÑÏØûqþ+¼'ÒOt-ÿk–Xï_•Wð?…+uUšaCºSÒ4Ƶ-_ªäÊæÆþö|๎HÏ›¿Â‡Ýñôð)+=L.˜ŸD•wa H–iÌ|dƒD›xˆÌñ»¥Qú|Tz÷s¡ÿñµûÑ|°íªu®%SàUZ1óëaü¾œVPw[û<×Ü­÷«‚£Ë%©—>"v‹>ÿƒÝÓ ø­­›ÅhEºd`BÆf^øØøÙ1ýÙ«µhߨ;ì íø}­À"èÑÝ%iÃT¶wþéÆ3ìbOvµµ%×Ö¢Érmu™³¿Ž¶ØRqSÔŸÌjìýö®ý"³|ǸCòÒ‡sîÇâñ6ªÖÆ ÐìþKäð·€Óx¿ñ˜ÙÌo¶7èÎ銇ísjrøÙsÙëé˜ä†ðÄ$wÀØžJÌoðVƒ¿®à öè±”hhuÔõcÞÉFD ÖXàÁ ¶Õ7aÖÚÜgtÔß]7ž“<€ñ]Ÿñ×t⎄¨ë%;|G 1UÂú7fχ×í©ðÁÞݺu¥“Qg•‘Ó½Ö˜•œ¿‘hD?éÀù·¢³^tì×RI 4ðÿŽdßZÏå? а©Ÿ(Ù¶û¬&Û•0<&¼#Þ _Ø‘^?Yb"»Á^ÎTùG ì=y°öËæ€Va·æ»œâó§%@yViÏ–”’üMIŒpŸëWòëÓm'NœŒ~~¤£F³Éí/ÿø¦?ýwÿ¶"‰õéŸüOÿ¤dÈãͽ#%ÓŸ_L^KÛÓúÃJùGpÖѺ@âR6Ñøƒ?˜¾¿}­sÜK0:v">„ùUtÒÑo7oßöÕ¬jtf# ÿù?ók[œçŽ™ÐZ9Ú” ë…ŽìUœ:ø?I³>Nç;úݨ’ê?ØrÇ—žCÃÛ6g—¬f_¿ ÃÐ’íJþyBŠüz|:ëÞ>Ù¾?º{Ý`Ü&¿(}¶aË´<lúòWU`Œ~íÜ󿸒þÙZÒèÁöàÔà#x¤¤i4ŠÆÇLúE¢±nÀ Á\ƒÎ™Ãn v5VóÞ–?îœdI~iè 2u=æƒaݼ]ïI´Ù=$#‘«x}4Éî~|@ñ­ñ~s²V®õòÞÛpiX*BÎ[[¼Ç×Â|»Þ׈W0Ѿ. hÁÇÕåJbï¬H'”n¾Àq÷5±‚-wTγ+U–Ïų‡÷Æ­í ÿ ×Ë_ݵsK¾ù³iß®bg ê²û$:è 7¬ •;²¿îGû6Mo|3-]¿“MW¡Xü¢ŠyÁøç+’«ãBŒZaÃóé^ëüÃ㜳ŽýØœŒØXÑñ²ÚuÃ~¸ñ¸qµÞÙ¾‹ù%¯úÎÂö:ùÖ‘õuzGÇâŠӱ‚û’—³Kæz†8a]ǧsÉr=µ«úZ~õJÎߎ֦þ:ÉÆâwwoL»O¾k;0í[z >Þtyõ«õ‰ß³këtòÐöÑ…ìuMŽ_oGUY7Äž÷§Lgîhn´ ˜;Q‹üWÙp‚óô‚qu{½­n³ââUdüëì>®‡!£É¶çíi/¢“ìa´ç;|ò ¾F^Æ ãy¸m©ö™ÊZ>ÊæÆÀë±UDmE£=ü‰æt‰õ̵ìèQ@óãµ}c¦æ¢?Ïœ%—÷Ýä¿u£Ø%’qý®Êóǽ£/ô6ß¹ô;>U¡î¸m{”žŒ•È›nΛV§Sùfÿøü¦?þåÎéöûéôÏ îŸë+æS¢Õ|sý싟—`õÛé«ßüfÚŸòª5}áûÙÿ«-Q]!jQüÖPµ¿ãbÙ€a6«ñÑUüˆ7í%?EÁ¤  !ýº„¬ë…›lN)kãþîmÉ2u®Œcà˜X›BÐ ƒË’ÕÞš“ÈÑüâó§_ùÕ0ºç7V1kq¼uÓfí-ž–á±bsIèµ`›–Ô¨]ªíÅ®˜xç‚vP9Œ£Íù,xü¾GkE+àÁAb8 1Ì17#åá-`Žèlµd›A;®Ü¼;}ó«ï§K§O§N'Ÿ 4}ùõõ¡/ŸOiµ`*[ŸìRŠ#ƒ6f×ÎMÛ›Gõ@iK‹!çDoÉ1™¨¬ÅÊ œ6cSuƒ5©Î]¤úÀßÎB÷ž1«õ 4fÙ@umòÁ ×êäŒ €NraH×3Š1‡'%ä> ó¡dè*im>!+¨£ÒvkQZ³;KNÐÚ™Á³sxšTÔ¯R’cü ïiƒ1¢ûc sÚ•"ã˜ÉÞY-è8×ÚË`r âzV`mKk#ðéü EÚ˜)g%qÖ)+{Ìá&X¼ï„1Ò0Ùâ2$Na6üÞÀ¼ŸF§„1Âb¢Ç~Zæ±G#³¦ ÔPrœ}ã¬çî…è Ý3¾8¨²½µË±Ï²“ Jžò"‰ögï{^†pó´0¸–bÌ‘ J3öÝ2ËjUñä‘gF7=GL k8Þ&Ô:€£çQèþŒöÆN‘qºœG¾²šÓXì· „u×—8¼ß÷=—bÝòaL-MdË´(ö}o™8‡:£„ø¶l¯»· Pk÷âÊ,?¼Ý:$ ¾<,ZFy™·„í gŽ×Ütû&¡’fj !Nu†‰sоýÍŸGGZ„°h%åË0HŠ‘/Fë`߇ƒÑœÐÇGå«×óo\ÒW¢Á®“˜£ j©•‚ùã5ÁŸ±‡íûhÝTexK42áŒyíéÈ„ê>žaݬ«¤ºÛÖÏÆô€;mÑŠŠ3áÈD§)³¼ßŒ»éÝÞKÄÊ£ÏG¢H+?2_[ËÍ­•=Pp,æVÐ_ÀRÏt~ ½tݺËfàvÆecûØšÍoóÍ’I(àŽ¿É }4þîÇ6z¯µå ¡còƒBz¹6 ¬£}—áL¾x6Ù¦úRbˆïŽÊºþæ> c²²Ç´žä‡`æœhs>Ë2U5£µsÖB:¸6Ë0M’U5§½\kïÐ7±ç ·7Æ€ö¼ïGsºp½?h¥½Â^’wÌÙç²5µúòMk…ÿ$m¡›¨k¬š|Ò¹TƒÎÊÌt½1w/|LDŒ:ûùbì‹Ìaœ½í·A/­žÁ{]g_|FxÎËèÎ^»—1Ïdb†b÷_{¡š]ödßk½3žÐ¿i“»äÛà§þ¶ êÑ¡ÄX8Ü]HÛõ2|ýéÌE>-^3ÙaÐý½ÚÍ:y¤K3ƒcO®«¸µˆôÞVÄ[î×èï¼Jn ]ÙÍF†pÉr3Bkí }—Ž9¾ëи¯N-ÈŒ—UUq$(ÖghvÈ鿤íbר³ó';—µŒðÕWÉìœ~G̺»ÌÚ´š«€ñpn¢+`õ_ÿA÷+A 9üMy*Ù?xŠðˆ.U£ë=x¯ J¨æà”}þ“ÏÓ&[“¥¿üåèòù* Ï&O7å$<Ç€Œ1ÉMk;œóöK•áLŽ dÚh:ªjȲÍxM_ô=tk¿ð$šžÉ‡Y¢gkýåu¼ ŸYË‚*óÐÍËW9¼ÙŽÀïµdµ=Á£‚áÚâ=ÈUé²ü÷ÖV·Ñp© ©kè¯Ñu »¿ô0ð¨,îx9òツ §Q€{K¶šÊ7:K ê`Õ•Î8¼WIÕáHúi*Ú¨mÞT»½ì[Ù¦8‘‡B†½9ÛnGIJ*5$–Üí>Î%ÓæX•¡qsµö¦÷‡Ù2ÎÓjOC+`aa ˜£ö9™%@pêÄñÑ6û~`•Ï%ÕiÉ(Œ??%¦Ú‹'­©–Žû ²vÎVvû‘è@‹ýÕä9{ÕÐ[mž­<ÎÉ1-1E;në*'X.@Å ŒT{¿,õÚ°¢-?Z4â3ò›Ž'I%!šúcø2’]éAÀÛ¥}†öÙÈ®#Wéÿù0¼ÞTapœ{³gô”îË9œ¢=÷#St:¡ÏÈ:œ>bÇÐé[úãhàiüïáX{.]âÞî7t^Ï ³›ÁÐw| Ý$ v"Àì?:ÔtH÷ ”ûßl®]‡Ï=”ÍA.° Ù™3Ý7à†ÞëÞxfèÝÆ7^»{{';ˆ]18Ó_«Ö§{±‹ûÖàçýû•ŒqiºúÃwÓßý‡ÿhºüñ'Ó¿ù×ÿjúò/5Úp6³öÕ|<—®üÌO&ZóqÃ~·*„ø0ï»ÆgËñ Ûrgô²裵¶£‹Ø£ÍrkyóFàYô'¨KŽ,•HcNŽA`+,ö= &º.ܸq«kŸM—;^I@àëo®ViZÒLAëùøòÆÍ›ÓÙsgºöüôýwW†=xñâ9ªcºyýfsx7}ñ“OK†ÝɧNž˜®\¹:]ïsI¡}üQòÛ:Û«0?1äü£G‹ÓåK§Ÿýô'ã¿ò™ïuûæ­‚È{Fâʳæ!yþÂ… cL³D•}v9÷@Áúo³å7„åøìv2T·ˆKÝ[þí;·ú½¶ËUëßJî,?Ý8ư=?ôáý{ÁNL¿øŸ¸î˜ŠäÃömÁqG<|÷âû|ÎTúY•`µiχ=T‚Ðþd„ëçæV@þ¢€ V…Z¶^ºt©õ|Ò×GbÑgŸ§GÔ$_ö•Luæô™daÕõïgKF £n¼g/K<"o¾¿zmèUñ¿øÝß•è’l5_½^TF& øÓ׫ðâ?¨xEãªÖ^½ˆ³‹U,í/áÁYÆ÷éõ½;y×à³ùf¶?«. XºZb-»?’#‚rÃÞM>¯vñÖøXu' ŸþÖ•DÛ~|&ÑŸÏt ÛS…>½ùð,šKò2ð³vסOߎî€úd…uC»ôÚÒÁ2vߨjŒï²†œ¡GBJ÷SÙi äÖ¬Õe“ëxM1  Á=6á5ó3§ìrS€wTUö|G_àY"dhï ÏèÔÜnÔm…n½sûöô“/~60¤¥‡Ï£¡1â;6#Ú³¾€XAÉ!_-T/üíùÿU¯¾÷×}µG®sÖýªîïÞº“ý°m:š>L˜àþM@ÿoêðøWÓßýÿ`:}î\ëO.æ·Ï¹¶…‹ÍÖ"ypw÷îÍéŸÿ³‘ý³¯ŠóäP²X"!›DŸë€sëêÕÑ¢”Œ±ÖãØ›fOR™è|ͧÆ* ·lÛQÂ]Gr­>‹ï´r-v²\ÁË®ô“cZØ@*Ä$êKà¸EïI–~} ¸ê4&bgô»ÿžS5ûÖ‚ó[ó›jK½¡ê¶ÃÙsï³Ãv”¤¸§ ãø„¾a¿°Q$}à·£tŸ‚³ÏœÜ×qKkTIù¸d^q…ϲ{FR~s’´ÿ¼ïðñVמÕ2ºàVôË}=,仌€k>ZÛ[ÐÑÏ|[þ”Da‰+8Ô¹Ö}Fç;qKHÜ·phú«¿ú«é«Ž7Yé î‘XŸ»tñBIÞûòÝ%ŸÌ’=vØd{/ çuAU²sºÁ¼U5[cG€a¯Ÿ¿œöäÛîŽßó©Ôù|gI[†ìFgŠ)¿.À·=&á¯ðmt™ÉoáUs!⺾3¸°y 7ׇì¢!»ãK2Ððù³ëÙSd²nvŽEcÇ/ò‡ue[¦`ÃbgvZO92M{æëPÚ^_Þ¶oz^ðùéb±.gß$?²Uå¾T2ÂÃGaMñ2ßqà­­‡BÖeµÞ?ÿìÐtü`IÌ÷ŸMOV£‡dïþªÜË癋õùöÆbGÈÔ9u×Æî §È~j½ç:oOô{ò`vLØÑ“>ßR€Û÷ž‡YØR'í’uQ}S¡]ëµ­Dx‡d_vñZ ÃÞ:¬9®Jw×m p¬’¸ã ²rG±‰’øßº³ùèàâØYsãH†sŒÛe®$¶mp·î¹¥ä x†"UA_>ú‡ÐAØð8—„Á¥wã±â'}æz¾ÌüJˆ%õ«?ûóéÄ™“[{_ †nÂkt"…/ÐcSî÷ø<ºe[þl%œ•. æu±’†“Œc¦ƒúoÌ?±o‹K²çºŸî£Ä Û¦¢ø¡¨æ´9ó§¿÷{½>Sr¤8Ýà™lD~9y”Ïγ;\èî=Ånµj/‘¶·^"FÌ•ÜYŸ.Ý8Hæ4 ¡Ëß7F’èÛÓ T»vÖ–¿õKwH¤Ù8JâÅ?EMŸ;Àê„àÌ™amþr†3p€8ÊÙ²³GK“²ƒvæ$nßYÆKŠiC›ª¿=CG@qa«mxŽJ«6ׄ×ó%<j±‘ÀAŽ`uù[k…@ábbŽãÏ.F”‡EÃô㼡ͽ_¦ CY{§½-Ì¡CûGàŠ1ªâ–Á5Ãæ´+£ÎF„ª{3_ÊH¸ê³ûŸM_]{P B¦³cÞ/MÿŸŽl%ĈˆdŠ1”-1zgß{QkÕ²ü%wï,–qSUoÁ­€(• ×oÞŸ漩&"Û­Q.CgGþîÚÙWXFKà_ë̘aüqpÝ×~l¨Õv™©ËP ©µ¾ö‘£Úuæ6²-%$)ˆ]9Cö`TžôªcăÃðàp®T¹àŽÏÞõžñbä•~´žÆ¬*mlßÓÚz(‚¾ÇÐxñ&ç¸ê\A«µ§O¶… Hmr3ÁˆÑ¼F¶bׯ­=®3E»Ö{²))hãÁdk‚9Ì Ûá0´Nà hnÃàŽnJŽn89ÌVŒ Rø1p÷}Û=H‰³»'§Ì>Éc úW¤È7aRè½ï¼é^„ڛˀ@’*}U{}Pìëe¨b(Æ4'o<·{Á9˜ø@ª‚àmÂÀËø8$c. ‚Œ²ò€Ñ«g¯>O¦¬¶ÜÕž7ŽfÇ ³4…´©ó Öc8` %ÝÍ'#¶y2ÐßÅ£ëo«*ÌöØY†9IeøÜÜçæf¬öäï]ŸáE¨¿Î¨1ÿq¶Sx>Ú2Ï-%Îçš,˜­º®•£d^÷ýW.^vïWõ~ëßÏ“ûh·5Š8­!>d¬o‰ß;b¡yuK3æ”/_õÌW ú¢ïÜäγÕΪï?r M[ íöÞ½¯2!ÙØÐ=Ço1hÍ­õcB=Øà7ÂèiçÃ= C7öEת’ŒýéRÕ2½?x+š )xüÙRò¬›çë2Ø)Æa´õ¼–´ûÏèÄ÷=SExƒ‰.8½³¶ßøG6܃œêV0è8%ÎñÀ»³g¶·}LÛÔ¤!}ž×|íS‹Ý} h´V®·>(VJݬð úPIã9Œ¢F9Æã: º+d[Ú®K®tΔ}Œl30i -ïØG½£rgÀJd4ÎÐ~^åàžŽÏÐmbS6гäïÞ¹ Esò’q@ÎØÛø p´.Kƒ?wWæ²å}ަ INÔž@v¤î‚ÿÎàÙÒçx“M¿± ¯¬ ­›=&#É']Föµž²¼ÙgΔ{žÊç9c9g?òk­Í$2j¯OîsnÜÛ|T=d% ì?p`´w‹F2¶uªCŸæli³ªd€Åï •¸!Ðîi÷‘ /x¹¤B-ÝéÜêË]* £ã`¢g@“Éxw¢A/üa\ä#Ÿá]‰>ltz:Ž<¥Xi3€Úl#l2ÝçhìŽ?yßð*¡b8¾tSò~èwNIøœdKDØ×|úÚ½ðŽ{ùeS>׆íTc&õåô]2³]ÇýzO’èsßue×çÈ7o ‚ݰ™H^dï±jSg>ìû"‰ȽaÃÌ&'‰ñܘ³ù¦‹Ý35—®m¬®í¹@tthÈ^cœýÁÏsß÷­/<’RØÆ‰ÎºË qsÙ8').ƒ6þ²>lo½‡.n2‹‹‹Óác'¦S§ÏÔöûnå@‘‘¨<Ûƒ.ý¶Àì93§\ø±ì‚ÆIÿâ G‹H\s†7Pâ…À¤ÊööôœªÇoÝŠV×FàóÍUR/Çõ{óÿØÒZøîMލªön],¤_OŸÔµ"€<àÍ17ûZ÷Õi ³mÛѪ¨/Lß~û]A³;][»ôÀ{-7{–À¸*ïhÃ\|Î+W¯”°rt:ßYr×:va¹ŠŽóÙæ|Eë°­à”1yþ×®ä?2¨ëÆÛ“Ž'Ó¥øáÆÍÛCžÒKæýÛo¾G#\¾|qºzõz‰ŽH?|öÅgÓ5§wN?‘:š $Mm80.áý~Ák× NŸ»xa«ñþ=´áÈØ+4u´1„ù’Æ´{ß×s%òhµ~á¹tÜÉZ¶~ß^¿óá[\»zuл—€?9!™K%¿÷»®¶N'»w9’ Θ¾°ïBkþxúîêµüߟ¾¼ß/PAv®»ˆn¿þê·á‡îEìò`ñ…ÀÔƒ‡Z÷Zv¶7½Ù´›7ÍÀwzrOÕíZé¯,¤ûξh¨M8«ýI•ÄÝrèžœ¯Ž"ºÍ|R²ÅÚ8ÿ–|Å»ž 'Ùþc[A×°g@VŒåþ’P°æW ?¿û°y㦮×2z÷àG¶Æýª£%,×÷`‰d ¾ÑÙ#Fœ6Ò)­Þ÷‹7\´\Ýâkrƒ4”j½à:}%Y1ó§aGdƒ¿Õ¸É²&9È Ç.ð à-ÚþÉ׳‡2V³ªš»nß½Õú””uåF6ð¥ü+g«?ò~~^ûrmѵö.€ß= dìácÿ5ÿgÿÚW÷&ëÖªº0»Z•îéô‹ÖíKíûh‘ÝnÞJïÄ{ªÞT‰ úܺ}³`o‰€ÙköS’ÝáÃÙêáLÚ±¯f3, Þ“.ÄÏô¶M½“lùÓ?ùÓéÔÑYàgOÙb‚O´o^/øžq0;ä³?ŠwsÌç6·vײñòe€¬#ž?Óz–= s”Òú»»­õ–€í‚Ž%Ë~<¡KÃâðíãì»Ûß$k¢-À7ïä?Œ–DâáxÛë°‘o·iûîÀíõéûo›_½>}ôÑGÓŽè‚?ç(6öšJh|>äqºjwüû2Û Fð›_ÿ*Ûym$àD{ëúw|¡æË¾à ïjý˃ÌÒ,™p¼Þ¼¡ÿøäqà;t)°ÍŸ!3gŒÞ„~UÂ{t¯ŽÀc÷€ƒú>ŠâS£ßh;Z3ü`,dŒý}¼L Fã“}«È ‰~Ö H=îLlÝ.é!ó2ºO°$ÚñÓáªdƒgó¯¼ð•ûø¹ºž1ü^÷i´Æo¾Æ €»à1˜ØBòm:­³‡7†9#'ûæ;Ää XÂä#ÇÄ©6ÛZBÅòóWÓî°(I§ÃÎhmÈ©QŒ”Í3¹ñƒ §Ӊ̉A~ ™×÷È+r‡ÕtË&ò»ï>Ë'—|,À¸wØx¼¦}ãóYÑì@R´ óæäÁÁä’Ä–?ú·ÿ®½ ߌÌ÷ì›ùo¬E¸ÏZÁÝ·'Ò­‚Ïß~ù‡4½™Û™.ïXœ#aŽgÏ üŸÍ ×"Ÿ°ÖaG‡ŒÃéÝ›W®`›²êˆÕZ ;.kLhöáƒÇÙ:7FÒïÒƒÚJ7’¬›¼;z¬ÎÂöu7ðx|ºE ¦ƒWÂö%ðÁ†n”X©ƒ#³\k¶¶—Oò[íßZAÊî‚ãïÓW[ó‡¬çW_UÀ˜¼ç Ñ©*sç딓/ó~ëÛé/þô—ÓŸþñ/“ãík{u·äA¶ÆÇ—.kèö•’³µÙ>ž|~Õ܉‡¿^{Ílå óùùbÛ³¯Gƒ++ß Ì—€Hö—\Üþè¬J><-‰A!Ú‘@§JuËBgÚß]t¢Ûû{µ“Ž$ÚÀ9uIͱj=:?½nü×ÝÝSPw³æeÌñ^üúų‚oSUâ7uãy_Rù™êˆÎöLGŽŸšž>¼QÇ.…›-6¢ùGEÉžòåf²nè‘s†áYx&!af³šß?dWvÞ·ß]ËT:늉w…éì¹СHÜl!¼A÷‚«W¯ý¿‚“ Ý@þ¢Åž™‚ësÁáäcþÖÆ§€mÄ$Zk¼M¾[Û·ïOçJþTè·Ržì>7º—¨5’«;Y9°ÓþÅ뺈 yÓó†lj $ I’”OÌ Y,!|®ã ÜÐô µ\2³ço§ó£cê«lï»±JPÀ/úÆ—{:jïi4¸íБt½®mhÕu®ðºŠÞî/Oß>|7ÛØyàuŽ]ßðdÚ9·¹oúrµNäfc;wùb÷­ Ûc]{Jø{ZñPAí÷ùIo^ë®:K<ÒIà§+¼Íç\©hqG˜Îªκ :ßtÜK]\_?ë|ö’vl˜®?XéüìåéŸü¢}n-î>©5}ÇÉÐcó%c8êíÕòíéØéO’{%´ît éh„×­Á}g­ÙO_MG’­# óõó*Þ}ñZ,³unMèûŒ.xÖé[£— }ÆnæßÂtA]¿3,N·3º`GT¸Ò£býÇ1ˆGm-éqc<Ç7‡1ËGWí”Å&=W|l‘v™Í€aˆ“d¶Ž®M¶·Ï:®òUÉÆ7Å:=lþNG¢ø &€v%ÑÄúco6ÍÃîYû³ ¸d¹aȾy1è¾%Ž7Óƒ}8î•ðEò­EÇëd+À^¢ ±FK²±±Fwïx_Uô)²¿ß¾q-ùúl:wá|vh]‡ëvs;Û%ÁîYZÌ3°@6"¿ð矞•èóYrÕ|É£l/] ÆË^"¿uSX-æõ.¼ÁsǺ•H’jMéhWØ€%ÚƒÎ%ª•R`à‹”Ø³Õ*S*F~:rl€F/ ¾©*T¤Þ™ÃC༟¯ÝÉ©a ZlÂ~®LŒQ5Ù÷7´k³€† £V¯Í¦HšÁ´E˜oÞä'Ô8[V*àU ½±Y]ïëÞã ª|—†¡€K€[­ÍRí¨dl”ìh÷yÿ>˜²Uu1*T ·eض¶ãÞ''ñ1ÖF’A*€UR³×,ÀKɹÛB ›Y‚1˜ ÓȧjSý,a`¸· ¾6ˆ—Jø2šlæÈTIØieFQ©|¸u)aXX[è9RZQRìÖy{k£/Ø~§Ì¾ãµ«Å»X€ { #27½å.[&¥Æа8²ªH2’eÍbf{çšÿ›º;é±+Íóû~ 28‡`pžÉœ*kRuKjɆ%K†¿{ã•_À!-ä†{aØív«ÔCMY™ÉLÎcÁ™ FAÒßÏs“Z†´i-úVE2âÞsÏy†ÿøûÏþŠ­5‡‡£¾¿v\Ào},”u%¨W3\€Ê*!Ó¶·-]—T¦S Ý´½Ò’ƒ“”!éÁ­ÏÛIÁåáذ”.ÆZúš5LPd”¿~^ºeÍ*¹ÑËëÚy$† óê»3áÚØZßá uáœÒÀaBѸ1ƒ ¥Ûªç=Î)víÎ×þÉ\eS$;Û«vz tJy #¾¹¸z±†}©ï-Í¥÷ Rsaœwáƒf-m<‹’œ9=«wFö¢6Ë >Dc”œg’g# Šÿ͇$ƒwu°–c,Ö½û=x¼=<@4€CK ç̼ÓöÈ}ºïXçÆˆG[ÖŒ’*¤Í¯½¢lÄß[Þ¯MK»ª¬™Ë‰O.l]<==–=¤e×áóhykz¯…úH|ˆOã#ë[Öc??D{Ñùh]XbMÃ4ŠûüeûLnG?ìÙãŒ2FŽ€ð¢ Üt3VBx&oâó!+f@!ˆËÈo †€TMP¾{[«¯Ög(œö‰ ‘¤C=íRÀÛ¬òÛ †‘Ÿ¡:sLΊiÛkN‘noNäFŽ&ÌJsx+)ÑDtÕÅ‚Þ^ö‹ƒç_ò‹ò16/{ƒÆ>:2b£ð3#ƒC-ЀŸÞµîå—ô{7iŒ³à4×w»1ÀaÐzs{W†úÅ7~ÐäèÎ mf HŽ,TGqŽDón.œ.Ëd8S½G~ã@†çzÏ"çÒ&Í…“-˜=Q@eûF´ZÁøÀxóÀXƶað¾1=Ôõ3žf8ï:í”=ƒÒÖ~ú™`P†.yª²(¤ð!»áœ¼ù¾§’XÀØ^ |Ęê9 ‚*н5íÊÀÅOóÉ-wîÝ4ó³çjÃX&[øeüp»öÐwyòù¹2¸ DÎÿzH°ZÕɱåΓïÜž÷ñîBÁ×=ÿEíQŒ)NŽÁÖpó]A¤ôð¶À[sBóôÊû²Æ¦Õô,q ¶fz®ñ3¿ ‚ fkǰ×úìI KyµÑ(h&7Bu ÙáÙÝýsnÛ]é5ûV×óŒìoÑ«ÁrÀÏÉüµº½sãr4¸|æìűfOrzŸ0TÉø³Ÿþdèl2e#ž“€·kÁ?Ue|(ì×ÅKŸ ðÑf8R À°ƒû£=rk$Ú䄟:uv’‚“¾û¤ û¾Îpdߨ8Úˆt¨Ðþõlç¡$ˆ<^}:ÀÉO?ù´½©Ú:GòQï-ÖF7.:xP°Û3Grè|úY÷ª%V HkØS'OÛS`UV<@È(ý:Ú=x°³lKÒ"T¸p8>ûüó$Ú‡pz• ¥JéìùÀãà*¬Ùaªo\»í–)8#¨Êqß™ÓD oíž%ÚÇœ¼Í^KÆ]Vá½7à¯j¬dŠïhU¿¶(9¶Dû«ž'3µaÆëª>Žw¶±OÈ2ƒLa°ßwžê-÷•0‰vUnZOë´T`ž-+ áYsLx×Þ“} ™A3œ(-úU«b†L˜²Ü‹-)ÁQ˜}ï ì'…öÚ¶ ÌI˜ÜÝ‘>üWäSë¿/õCz_°FÖº{¿ª“̳2ÕlfA§gÑÓñQÙ³;¹ó¢ux7íÊÖ^/épý¹€IA™*.U8«DÎcZSeÂ8+.úÛ–#býÉÝq.bŸ½{_÷‘ü]¯Ü%+±“9Pæ%‘„ÎÐòͽH^2Ñ$íÉ{¾„`Ý‹î?tûˆ~(`{lºÿ¯ ô$¿|²{üÎOs¿Š÷×»¹öX“芾ù¤Ù¿ÃÆI6¨´×-ç°Yèp69~ÝVim[±AáMÙ=Éɵ-¥=÷I¯@IDATv”ïK–´Öïrêw&#FrABF’mjÝZïâÖ´§7%§´îÆ®zit!éïÉ_¶‘$p]'b%SÙ6Æ/ã ×bÁ€ójKù ³ù·™Í¸·¶Í·o\%<ë€Ð¹›Ù£ë/ÙÙàí“ïÔNŒ9™Û¨º§}{žï=Ú‘æw¯7÷ñÖêjU¢=ž¿÷›ßþ¦½XŸ~ö‹Ÿt¯×Aã»ì¯¥oÁîwO²a’0«“§Näûx É"oòu³ó´…O·“ Ú¡ŽvñÝ•®Áß_½>ö›RZ.`´;ùpùj-vòÝáDCvJŽ]‹0¸1”tË•ÍgÓ/~raÚ]ðK6ý럜Ñ9qÖA©JÍì>¤¤@m—Ñ¿÷”P¸%Ùùa3š¿€£xNÇzð/ùË®Ù1»xaè=>èÛ €‡ $iȼéd>% À¯Ò àcFü9|²ôË&[¨kGíá‚Ó1³ ó¬[>@;C/÷òèCmlñ]O7[;?}0ÆAþöëX×=Éh…$ãšdÜÇ*dÇ(ŸÝ<}•-P‡þ» 6°ÚèE’ ÊX\¸ û?á³β7R|þŸâ—±}ÈåáÓ7F4ïí®oÇû›"Ž‚( næÓG+ìÝ]ÑÛ¡Öä}Ì‘äÖžÙP²8ŸaœßžmFAJÌc[ÓÇÂ)6¯—µö›‚ç‘'lˆaÓŒõš‰a´Þú¾Ï­¹{<*\±FÆMv¡Cóüè§.4ŽQ¨á ãþ'qv_>ÅŽlR>¤„){;WƾdßA‚¨±9ÂB—³ÉÌ ä(ÀX­ßð9{èÎäMo¹-Ùí$PºçÌʈk=l‰‚{ö½âõqiÿ±}µ9ÌÆ¾«ctì`G 8©$ÝÖXwgS œ4Êï×!Õ“—Æ´¿ O:žam¢Ÿ<Ôq4¿K_Ð#Ù!í`»½ “’Ü|´N1G;‚kO+¡"t«à5_@Ç™áw7×Ty ç÷†ÌÑu*OiøoªÇás°dIy°Ï¹¹çÙ)S"9¼˜ý/ÑEqÛFòÝ%míémvùBGÉ|úù§éWK|¼“/œïÒ*?ΗÐÈüèWtõ2z|ßܬ›Í_ýå_””'àßõ7¯÷ý»U'cÛc÷z—}ðçú<êv:a3üWå2*¢â=°=…G ž^ùæ«éL²u?ü¡{}äÇqÃÿï0G/N_”ìµQþÁ­«ƒÇÒ¥æ7:^ô´ÇÉžAçÉ3—7¾Ðu© ¬±¬W@t9ž’L/1ÿMó„ßÏ%wØôìôöúÕÓZß—,/‡A$×é"Ø-Ïn¨³ñ8IÉWîí84w5¶|Ì×çñÃæ;?=÷³h ¿deëó¤`¿ãÙ_d᱑ߊŸF2sòPL°=¡Ðͳ¤8½ãà$>3vt¥k)ûóLI´°r6›þlïÁéÄ¢$WòzdÏŽ’§1i1”YŒi&Éa×± ý-z¡-ôH^ œÅ5í« ê;Ùâì5óÀ{¨DòŒ@ª1 –?[·yº:žW,¡R÷yþÁð_’C*ÿ÷u„pl;dú‡|sY«‚z[¶Øùã:ÂÔ çÁ«üŠâCmÒÍgé@=ùW¿›Ò¿sKÑköÞ£ªÁ— KôòÃtùΓé~-Ñ¿»ÒQLg¥³$ŸíÂpÛß÷ÉÁû[ÍÉZÞÍŽ£¿adƒ®U3yd–[¦OOå3uïº#?õlúþîÓð£:s«R ¾–½ðݽic!Û"{‚þÕÝl¢ëϦŸœŽÆŠc®Ô)øíî7âivâ‹’^ææ¾ŸNòw¦go+j(yäÝXŒ‚É©`üòrÛQ«Ó,Ù>S¡†jwX5*)äuø‚¢,¶ YËv¾WB%›ÁøÉ¤‘„Óý%Ý 1{&Ùw¾±š3kaëöÎôH7‹%ZÀ$özΠ³¾ƒ'é¤wƒ¿fÝÀu©bÏà7~z›ÒïqWë}&¶;vûÌŽŸÍÎt”±H z°ûHZ'ÜS×R1 ãK–Çœ«%OÉv éx–­“e?ÑíéšmÙùà;[Ÿa7±KâeǽdKtÝÁ6ß„I'“qÉÓ—w›ûöì¾Ó£¸q]‚Æ€ zÃÔFÌæx£ÄT~²Äû•{û~DÞæÛwŒHfËlâYÇ¢IV˜¼žÏÖz½UÜ¡9$³­%>x›ž±­‹OÐg`Ô²}( ­Ü AçY«Âج"8‚é€Ú«ÉmN³Mmá–»{8§]@`Ú£ÌWÌXSy´“•Jùo/ð>œ¬’§]ÞÎ àmP‰€Øj€t@Wä0218j3ªdÚÌV²1|µ™UÕÔh­ØE£ýÕPèÀ~™ ‰nä\Ÿ÷g0%œçú@k Ä@NcàYÄQ‘ÐÄ9%Ú-¤Ueï)«… e¬c,°?€Nà` Uýy-c8ó Èý‹?•‹o¦“O̬>.X\EÂ"ƒ$¢[Éùz^ðxWë÷¸÷?9ë>»G‡€µ•kc=VP½ûh¡úþú½²ü–ªÊ \¥ä36G,ª’ÓÖ»èIëRÕ×£W#HÊø;x(!œb”b`̨5ÈÀ°à”9z>i±3 @2‚¬-d°`<[ߢõ.€õa€ó¦V-)ÆCeR^:ªÏ÷e¡÷/ø.­}Ü·ü3å…îT@ʆ‹(k§±s:1ÿCŲv)®1¶¾Òžµ¢½1’3º^`nµõšçR¿ÎìÛÖºf#y"š8ÆõÃÐhí0%!æù²S´P;#C+GÂá£âÃ/æ‚Ù†±’ A÷x‘1!U{´…C’F€wÑ´g&t8y2ûýpHñ=d¤ '€ÍæsâÈÖ)@#AªŠèeÕ,Ûjײ|jŒy©³õ–ŽŽŒ¸kµ‹<æätá“OGVûê½[=®¢-:/ˆ8s²3„¢,ú À ž0ög­)n_\‹¿÷6gÊY€r=EÀ©°.Û “­øƒ*v€2²vúM*Ù2sÐ쪌ˆÝñÚ£¨µU¢È¥Ñ*(¾ÆXtÊø¥œÐ&^ÔyÁ^F„íçLí  Øú·Xhl'­µrbí ý“û*Ñ@ÁlvÍßݤ§¿‹V8kèϹñèVPƒ Bsœytbƒ8½®ã‚Ïz&ÃzŽÚg “™ÄÄÎ@3ûMÙ‘¢ž)€Ý¤’m*"ɽ-UváÊb8¤}W+"m`Œ„!RaÖòðá£Ï®À¡À˜è‹A&õ™Ú÷¬ú÷R©i¯F—‘ΚVê>olÀj‰J3žIâú;|O2e´/o®Z -.j74s8}’)fÙÃøˆábÜÑ{¼cŸš´íÄ[ôÍîÇÔÈ4m~ ̳vºÎÖ$Ÿ>u¬Ïd†hL‹ÆÌ[;8¼w°€ 'Ÿ¹šLÑ~†Ñ3\cóé·.K˜üb¢kzväû.p“ñ£ê•^yšãqý%àÝ”lr.šß×(/’3ÎWÅÃ@!ÎÅ\:Ö÷ßD;ΖL ,0¶m{ÍJqzKÝôÌך€–½éYé–@+Æÿ– àµl]û”4h†löRA¼µ–Kž9Ö½µE«o“dèÖœ  ß?¢k`oéG”À^d¾ÙX­º: 0°Z€ÊóœÛ4Ö»VY»r8}_•ê«Þç lnÎ*Gæ’÷C.QýíùÏGÇ•ÁÉîúù/þ`úü³/¦›×¯Lò/ÿûi÷‰éþ•@ÓÕZ»ª“‚´k/ŸŽ5chsà÷ç@ @rÞm–D÷¢¶IÑ÷øpO™âÖ{´‡ÖT#.XÑj‘aL«Ø°»ŸDoù5}þü¹öPP÷uûñ$€`q´‚cW©n÷}É5çΟ®Ú¿äŒLOß±ªöèÀ“®Û›“íŒv¶Ãà½xÿPÁ€ýïm¼)“Ÿ,9wFqWïÖ’õ@ŸŸà É“œGI Ésg³ßTEáuzý­<\²{éðÑÁ‹Ï Æ<¬õ×ñgG‹`û£tƶíÙ¡uRyü¼ÌòàHVÍÿøi•V#(Ï&OÙ>—t²o1 ²–dsÛvå¸Ñ9ÉÇ,uNUáþÓYæy«§NHî+ÙK•%ËU”WG?~|d ;³¥ÚÌW9›¡ ;³¯‰%¯ô‹*ñ¹¹€íTí&׿OK4Áx˜­B>ÒAÛKH Œä ] ¬ŸÎ8lŒ¸¼1—I[Âbv”,¤{€à!›HàI nðt]pv—PA·¢§-éÜí+ýõ:½ŠŽ²éý KV$iS§âžNRUGö=.9ØŽU½K2 ˆ,”é/Iè镬‹®þÙ1ìžmI8nÎ5vŽòÑ[°-ÙÝ‚½tÄ‹Ö×y𼄗¶·-Šæ@Óc‚œlŽ÷[;¯Ìt]¶õìŃTÉó¼´*I–¦(]k¯ÙIÅ¡¯_õý=Én2UKOÝ!t*`¬Ž ïaº±T%eç¶ÖãøfËQŒ}Ózà?2H÷± G:yJÿÐÝl‹.×ìíœéÍÍ/íÙ z0À°–‚GLåñÖ‰ý Lf3haº]ePzžþúžYË>”ù¦ëÈéV u4B¶O7b¶l`˜º7û²m~gzM7,rf¾gszÑËBŸÍµÇì4Ê!v, ›ãMºl_6a${ô޹äË×í©ä[61¾¾BÏrVÞükE7ͺì÷ðÒQãh»5CVÃZÑ ÷ ²²Öm»áÐÑö…oèû€‚péö ºf iÊžk¬T]¶ƒ±Ø+`1„qxø8 ›Wkhd"‰ KâÓRÕ~H>±ŸªÜð‡`Ûf1Ù½c{Éé2•3Æ"ù.›—,#“ÛªdPzp{çÖý®KFòŽì#ñ|Ýiàè3÷eÛ #>‡ :»/m¯û‚ª rO;èf8ä^Ël 2ü|G]Hâ¶'¯|U4î^ì0íJ}ö*´>NO•Ì#ÁoÎ*™Û‡t‡Àž—vÛjgXB·®žemT”ÑGª6ØóÇKžÅÍÁ|´†»èÍØŸŸñs}—<›Û­gúBKtÉäd؉öìU‰èX{4@þ~øP‚wþ¢€ÌH¨jÝÏ7ÿò¹ék§— 0ëâÑ™‹uä9T"Àá-G‡-N¾¨®ÿQkÈïâëDeý‰Ÿ‚·²Oœ<=Î ²¿ž©ú~sóÞìï蛺ž ||ãvvlÝÄ–—†- Ø–°eÉ€M‰#ÏØÈ?`*6ÃAº° î‹öÉW¶™®\‚SãÜÓh†î–$+9Æ@Ø|ð™ˆ>šåÇkÝÇ1.‹UÞ Ý‹¼@ü>5š"µÂ¶f'®+™ïÒ'ŸÕiäóa÷ÌäËL[Ÿ†ú7þúèó𿾻reºüûßö¥OœF#Àëm€Äð1‰Ëo6^ÖÅáÖôgÿw^úÞÑ0|Ç÷“øwÆ1ƒ¿ûÕ_•hyµÀzÉ_ñàáÀ^kb¯žÆ‡ÏòATn?á[§ñ¥dv$‘¿è¾ªa@Ï»Çú­Û#_ËeË®Ü7öÿñÇ(²q4®\zÛQ>ª-uÙÙQ°ñ~>ôï¾ùfº¿º2ä¢ æ±Õñâðc³ùÎwÅ·×_O_ì3LÅ~¢í{wï yqâÇŸM7nß™þôÏþ|úûøwëê‘|IïÏÏ¿þàÇçKr¬CÒÕ?/žVñœ‚ö%p»ÏƒŽh8 .¿ŸnP“Ut¼g®ñ®Ç×ÎIg–Ž}‡ý鈒íÃ쪺Î׸ü·—|ëI_Ýu²Öt%y;Àß±ÑVc@ãý2èùQ÷X«{—kUoT*¹¾ÁVè{}ö!Z­v-¡#g²Aé¾³ ¿™ÃŸ¦G‚I#ÃGþAcô±`'ÛÀ{0ˆ.¿?0|'¹Äöæ‡Ò­Yžãï4ïoöàûN{@ ÄX'É[‚6t‚D­FŠáÞÖýàÁÐMö¥ôûL"ª 5µn‚ûèQ!kt„h¼d£ŒÀBLÌN€½vZþëÐÉO?KZh‡¢ÝÉ<[¦ä³}ê½’³ßl¾6ýø¬J~Ý%×ëì¸5»’/Îvsìä›Î6µ—l¯?­Â´¸ßn¦—­o6HŸÓõ¶McÒ¬;i ǼÙå^C4.ãG?Î}µ5§þF_ìN{Šf¬±5sñ®¾¼¤r¾ûµædúc«£W™èPçyK&H÷ÜV‚#LÛš H5ÔžÛóz>Þ”àËêiAûO²=ç—Ómhr$‹õ]§:~ƒ¬·Fè ÝÌ Ì«îuí±qÀùñÑGtÀŸ?V²T’—g>J^,ID{Ýmu‘ܬ²p¾"˜–lÌžÉâ3à'G¢ ç8ÿø'Ÿ7×dY<.Ñ–ŽP]l\÷2?Ö¦°U|Ÿ‰æà‹Óï’¯nݼ5ðýQ1ÜœuMpäñý%ÿíˆÖÑ‘Ä7Ø¥}3wÖÿÂÅóÓ§ýû<ÿnWöÊÍ•è^bžŽoóÙZ£«é°sÍ£N{ÙG?,GïëåÀ(»{§€z~8Ûd[ûù¡õ{›~_Í¿ÿç_ÿwÓÿþ?˜þàïýÑôë¿þË黯þª€û‰!«¬{¢Eä_^€V" ¿eÇHxnNu4Ù:Á#ß³ïÒ·#°fïÿ=/²s!û†ïÇ'“h÷Oû#ìô{íö,!G_gw(tqÄ«@Ö–t›Vþø ï¼OÏIFv_øÑû’¦U¶²‡FÑMU¯ë­+ÿŽ}JþÍx¼€V~¢cÜ6ŸåJ„ÝINíf=ûemì¯L¯wŸ~ýëßNk%pJ¬{ýºó°ëô7Ñ—.±ld~Â"Œ«ucóóK’@ƒ¶N:bPu½c;ŲÖðE랊`Ì»Z'<°/œ†ÎÙÙ\óÑ߄żڨ("Lòâé|‰0 GovÖ¹§âàäÏï¾¹=}qþÈô݃ŽÈ Ó9ÖÑ¶Ž–[­šü|k—±3hâU¸ÎÒ‘{a8§¦+%’Ñ©ÆÖa&w‡O‡~½VÌnvŸ¾ 'Z³±/-æ¶ù 4Ú÷ÕŸ?ñ¿b>\q?ÿ:FN`£Éh‹áŽB`#“kü;ûÓ톾Q¬J—øi«†Ý>|[7$…f$h㑎§K§dú̧ífÉxñ®q|hÏ]@iÔÑÍŒVGbTvþ8¾™OÖs_k-à ãô$\.›+™üi±=8ÉÀ@’l4–h´þòc#ÚEjÑ~“rŠ‚/áNô•/3ìJŸmÒî=<ØQw¬nªd=: ïVªB_–Ô]7p±>#Ÿ@Œ‚=£ IÁ‚ŽÔ ,$e†d+®…Eæ?eG¼)ÆéùÖÝQ†b×ëa°ãhâ ´¹î9?ŒÜÀ8RéÐ"Ì(`™5Î8 Ò? ôž–ŒÌP=VÀ’ A8}ÈÁËÐ`8®„|‚©AnfÌp„v¤à‡à¶ø-¨év7ÂkA¼ a¨ºßÖ ç9çtt¥,;Œ4J‚@0|Í»—¬&FXæGß1Þ­÷ŒCæŽ3+µ¹DÎD™¯’[Q>£ú -.HeCÍ#G€xo##n¡6J ¤j -mpf`Â?­uÛìÚõ@Ĉ@ãܧ¡ŒàßÖ !QÊ éíåÚl6g¤¹NEÂÎ6í} ë°=âUM´sÁÙ·e>bjKMIQ–Œ.-…<Ðý8ª11ŠñU•=Kò±Û§˜dTÔþ#ÂÒ†gáÝ\Tùí-¯Uưž-™Ê„–{ìì°íøzŒÓ9·`€Ýž=c-ú4ïNé`Ô• ™-×»1vc’v0Àv±`Î,xÆxËMÈéf(Ìz¶ec(ŠéÚ½wëÄØÄ 'khc”3Ín_µQY• o_ªËɾ¾RÀ9™Q°¯õ;QÀ¾2Ç(ËúÍ‚€eíù2\S:WoÞ™ž5ÿùÀ¨çñÎÞ=[ó,è{è2#´|ž}<É^ ¾v{¥ûh Þxšãbßwæ‘@%ašˆhNÍ@÷Œ‰'OªJhOy?¯xHu´*´ÕÕ‡³Œ¹m %AHPyšr¶ß—ýIæno½ÉGÎ.‹Ÿ$ƒ0t‡AMpZ8€t–@ŸÄ4:`a;ÝÑVÉÞgÑÁH8HojÅ…öÃ8Å\û!ctkÁúQùÐz™Óì@ƒhjcÆCw÷}ãЮ ]ìH6KJ8–þá8·t¼DàÆrgÑ{*^eezk¯Š'Uô?_µÆ4Óß®º÷"Zä’4Î »†PyYÖôï?Ìq¸ð`?L7ro^¹<ýüþÑt¤vûª­µæ¸F£µvgL=X}PÐ9âÉMÝ#¡åúkC73‚âUß±ƒ^”]”`—Më´F•ç©eª þ&S®ß¼%W\ìÏZ‹Gn4ä¹a¨âs‰^wïߟÒ1µëe§I¶b0’U:h5pVKV-æé•¿ÚôÚßO?½ÔsJ°ë-ÓóΙàÐû2lÏœ:=ì§•s-Ë´^H>¨P~Ð}$訊\×bŸ:ë—SìœröбææÙKÀê3§OÇ ã¹÷k/¼´´<=!ÞÌ)/1 _üäÓĹsçö૳g/Œ}S)®Õ/~qÖúëd׉ªçЃ*,ÉkÙæ&@{øð‘ìÉÝý½ÚÕr°¿wå4­u70øÄ‰“usªŠ¶ìöû@Èû]e÷>IÎ!TñsäŸ>>Î=Ÿ³Z ½xiãÙ£éäÉ“bŽÖÒ7oÝ (*¡ààѬÚ"Z{þÞ½G`ûÆ;ûO `ÜùÀO³q>¼ë¼Ò‡9œK¦ü°gßР_8Á[K@xÚüödßèøôfµó sž²Íì?ïþƒp‡LbsV™.ØÙ–Ü‘ð¼{Œ®Þõìek=¿x`y´œ~­/Pڽعǭ‹ÌäñÇF²ì~•fÚž>}²{ïgO«B=U IâÄãøAïÑ£'ë“ü—‡#൴|¢ªŽûÓï¾þ~ôv-f¿ÜžÝ.á4úæÐä¢À,ªZ @Zï/-Õê¿ýÍ·Ÿü†¤qôÎ}B÷xœ­µ±™­•íØ&fÀ5‘ ßóàt‰ké¼Hð?Ø£ìwâü^6Gkû¶D§d9ð~v-ÑâÙÌ9è;xAvg/Ç—«ñ²çs9úÀÙü™n`l3ükNäø®·ØUîþlX @†nN…õvÿée^³$¸¬í>·nø XÈ]k¿iÝ›í!ir­}\ËG 6.ßdz$û›ÑÉ*G çX»Æê–´Óéu€Åá_æWI¦è=Îø ,#ç»s7ý¸¶-HïZ·@ú4 Ù‡nONÏôj@`/[SÐS Ãñ|7{`~*‘³=÷õCžó¿†N&iËÉ'«v—Ä–¯"‘cTaG£ÆJì8 `¾ Ïãi_íJmv P ­« t„ßMŽ©»b“9¯ד[ëšúp2Tt‘%Ø Þ×jŒ wì‡q„Éd4ßeZïѹkkU÷Ïë’ѼÙLsùúëcžùÄíí³üHm-Ù#ÀbúKBÕ–èH;à'É\喝úlUaC‡ìiÑÝxðpIßñW5t×þ£|¤Î¬Í^bãT×»—³—ùG“ù‚âtËÞZ .ìÞ—œ­R?_ ÍÉžgÏKÔéwv¡ª¬Çw>ìA+kAG<Í^Ù:h 8£: FP,„ïmÇEíÈfFwÎ_”,´¥ZAíedÚL#ù¾r¿`èóÀN¼]$°óóú‰Ç%?ò÷µÐÜ·˜Þ:u¢cJ:>:hÃŽ¤ßá o²ûØòûé‘ÖmJ ÔB~$®Xg_ TtR{{ä Ùº 6Γ'Î êX%6{ˆwgç@ÓÄZÀ£î5÷ÒpŒ½»ð¢ {ïÜhÝc>”A‹ôþ£ôú³hN‚––û/z¾ öƒûG°ýè±ã¸ÉÃh^ÜžÏL¯Á+V²3î?¤+;¾®jj+øl׎œÓ [²/Ïž??º„< |\­ºZu¡V².UÃÿú׿Ê~x‘®;6ÍE·è µ kúæ÷_E#ÙAUPçóѧ ÌÇkøNã·¿ùÿ…dŒ7Ökå à±yÙìæ­ÀÓBû±¾V¡Ä“ #þ›JÐÍéëÕûÓ'_~Y»à¥é/ÿâ—Óƒï¾™Ž¦ß¶%#ØDZо.9ûÞ­;ã;xÇÙ›º¸5Ãi®}¯bó^úNb ºÁ›ôóX…Ö…\´OxòZ²®?ìºh~šãÒ`’b0®„zéÎí»Ã/¥‹ÈUIed7ù¸#ú‘îÌzöÊ`GÛ¾7Zh' “ Fâ (À¸_µæèôÞs/™ G˜ÏgQ]û¡µ3G¢Y…·}åêt äJ8’P+ß5IFÝzÌ ØVÒý·ÆG¿¬g³ÑkôÏXOÏîÆù+釸aœG;ÈDpV 2…‘-b _7nIý¾¯à¤ÿ÷%Ȫùdôf]Žž<›U;²QYÍC²³…›áéI®ÖRÐe­ÄKòަGÆÐKdñ#NµÐß0N;¹Ù>zßøpô¬ŸaƒYˆnâ™ôý›ø˜Íê¾°…-Ík(BAºø~KøÂæzã^…†!&×»tØêlñ¹º^JŽåC»/JâkKà´7Û¨ÛS¿ ¿½À·’D žOŸ²ýÉ.¶ lœ„ÝIT5-Áiv–oú,÷ã½m=bÌöÜZ›÷Ö}í›ñµ®oëº$ÁÈœíIÀ¦k­»w/_9Ì&›l$O4X€õC»Îß”ž­]6WüjßßnÓHul¸XØ>²ƒ·zþЋðYà½jWÄ0Úß÷<û C ó$¥¤¢ö–Ù—0úÉQ¾O_ÂØ€AÜù}éù ‡³§¯úLÓötóÀZÀØ–d Ÿ°¼L÷ ,HNøh²GpÞ{xi-"ÞZÅü _’·¯ï¦Óƒ»î(:ìªsúwô˜D–äóÒ™³C­v_´ [£ÝÞ¿’àu¬ß©Ó%ýÃ%ØÚä‡Êl:†íÆîA3+ùìéñLI¼ÿhËñBR¤û°’wÃý肞ãõ¢ çÉGPP0Æ•ÌÔíe¾CÉVöZˆä‘WùÖ•n¶l¬=ÙäÀÐëÉmÝZ´dn…rû¥{V‹¥´§ÑzßØèøµ:™¢162 lÿbÉ]p™xäeúòÚõ[£ÓÍzvÍ,Ñ)y¯ãÉìOÜÜø¦*öÏ’)³ êÖ­3œÚsv—T`¾lUkµñFå{<Õ=àŸä¬÷&kÿ!É~?ؾí÷¿ïÅ~<|ôdj)Ÿ,4v]ÄèˆQÙÜzàE|1+Ü;4ù6 :?ßfs°äé®åŒ1ÒUí÷ˆUÄãö®‹ÇËßm<›¾þë ë><ýØÖ|;ñóxK6&CÞfwÌ…¾lïžrº~ízkµ:9ÝqÑñ®ù¥èÐ=’W;´J3¹çüy|kLiƒÆÓ:ÆÃ># ëT^QÁ«*Ÿï ™G6:âðq]¢ØDƪc¯£ê$âÂþfE³®Kôùñ°ù{Ÿ%Ÿ®\½m´â‡ïâÙt<Þ|íØrq4Ô~BpŒÓŸôØ¥OÎʇùÔŸ}viv¯èuGë£Û‚3²ù=ô9Ùaœl-6?:ôÃOá Ð…’NÌÉÑSg³+דǑۋ»‰½¬;Äó—|Òªƒïd_ roM¾ÃòÑÖÛ0l­–:üÑõ¯¦Ç÷n \j#ú¼q»tçL®zxµDÆëŽ ÍTìð‡? {w¯ß®còéŽiÈϹó0ü91ÇuÚQìAáNZç¯<*9«ud1íÝñ~:w¢D”æò*Û¶èèãGàì%þ½¯¿®ºüPº#_&¢ß·<·1ýñ_üfzÜŸ>·8]ˆùíÕhæ'‡¦CûÒ5Ϲ> £Ø¶zóæ´ÔxŽwÔåýëµt³D‘g%ãonÖÙ.½bGFEºï‘³Kágì‰'Ù]޲yZ’}‹_NT«Ã óåHöƒ“«3½º#î.éÍ÷ö´Àózì˺–é®5¬¿)ƒ“ñˆ€dØÝ)}-|e7hü ÖúˆžÛ&€ž‚l¢‚]2â‡²Ž™=Р)ê— Õýªþª¢ØÑ¹I“H.âÉAmÕŠÇ&4Ë.KÌÕbB¢ ôþ)•Œ,Fœ39Fˆ…“ 8ñÙOÆ8eD”ÁX;/•|-ÿ0Pߦƒ¥µHöŒ{ص™@m ÛÃYk‚®‰ÑGðž2hcÌçckYÙpúé3†ŒáCcÖÎ|%a0‚>‹@Ž‚Úéa€›õc§~Æ:-$¼9ã*"šUn÷™ujÜ2â|Ï™w EY2‚ÞÔúš@Œ'ñºç (ÑJT6#s­€àŽÆ)‹FG8ûvõ̲mÆß9²_šÌ¨æÆAD‚Ñ=«%…×Çõj “0.Ì©%Èœ%/4ì^¢õmAßGäœý]ëÚ»OBÑü|fldð÷ì±j{h{ɘš)@AÁ¢Ý-øö·õ¢d=ßãÈ€‡™Ñ¸m#P €ll„mŸŒuÜSûb ÕÚ|”éëΰТUŽg̜ޚPë@1KRhF1©¶Z@¿˜±À.ºôb LÍ&zöycKøÙs|¯E‰)ÇÙ½Tel–ŒO6æûÕõ~çÀXWA³A/ýN@ÔTÉâ[ìø€&ÚwjÇ[æú󌅅ö†À&äœ %¡†bÏ&LW‘º?#òàñé˜*$7;çd½,±· û×þ„ÌýÕ‡ÑSG ôg4¾(ãjk}÷sV3$Té½(kQ á&á@@À£€dÊ3-3¥°£¹Cº¿G–Tÿ ôíj¯G"Kt< éîaã99ª¹ª<g!~(X;$püƒ,ºVbÈùtñ…Á Dûœ€Ù›7U|2ûBb"ã½à]ãeH[#ãÛGæô;ºÐ’Iö^«Øµî±-Ã|­5ôÝsg:ö" £•Cƒ§=wz?«–™+ÉÈÙ[—¯4%+<¼åÉ(‡“êïøDW¼ó4÷™1¼öºöRÏTétÎpô X8’RÑ"¹¶vÉÁÆžíÙeâsâ“Ù­ƒóe\½*+>fœ–Û£C)N`£îÃɪ³ÈØ›dî,íòß9œ2¶%·À’k;KÐ:5®è­äц/ž ßÈ®nÙ½Ò9Ý<’âסH[ssP‰£ÚçP{ÒÍG;/ŸûDsæšä*—œ~úSÐ@Pßþi•&ò.Pm¡ 'åMï¢!g³8·è£þ¯3°¥õ‹†™nàO–F¯¯Ë’¥ë(v_m›ó] | ‰­ö· 9çÎ2É 3p·«ûÝÚ†ç '§$_‘S€ÿâ=£u÷T5ÏñŽ` ®UÅš ÄÓC>GŸ‰èk×¶<Œ"ŸìÞ[-[¹±G¨—mŒ’ÖvgŽêŽœkΧÚUlyYw–dQÉ™Ž/ž³ê­¿½¯ƒà ?~bú¯þëÿfú³ÿý6Ê‘âÛ*¤Þª |_¾ÿ¸5ꌼG7këZ°5@óeÎÁÝ‚Šûgwݹö}s­zã=Açç¦:ˆ„ŽŸ<žÁº^päö@+Õ8£ÛûÌÚÚŸ=‹“_Uq¼@?”s¥M§ÖèÚ|â½O>½4ôÒ‹¯nÞ¾†ó¦B[ö'aœ<ÎvO&®ØÅ¿üv“›ÎÑe¨¯zB¦¼lÏUažMÓÊÉKÙúô¦€»z’I>ûôh¶éúYƒpæä©î› Ξeg ¼:SN6ù½|Ö1Q’ŸhÏ=~jdðßNwÍù~[«Ôg/œ•þ*y[rU‘*3 M¢]׫®7þ'ÓŸÞçµM®œêè“'Ï ]úòêµÆSp0îÞ­tº–Üløuw8JP ­iO:?Oð\ËÏqÖ}6âð?¢¿¹ì@vHêyäø“æÅ®ç‹ý6²«è‚™ó™NŸrÈ9¶ŠÀû…~ÔÒQ•ÌÐûýƒx‚ ø÷¦DHö3=küZ¥«ÔÅcï~ø&¯[_v%Ì‹‰üz­#¥:{ŽÞ˜Ù2¨Š•$i8'Zþ–gv/G'u¹e’ ©×¹‡÷iœ7­ßû1é~ÚHèw~¢qû[r°ß߀d÷;Pd6—™=žÓ½—œdöݶ|U7‚ÐÏ’ìú…l þ»*ùìÛÕscîÁß@-óêka.žo>´GôÍX‹Ö—ý4 Ͱô?šÑ`ŇRõå,D Õîdç³ä—ÄM>- SgI1ìK ñ8ê(š!C÷@+øº’ÀK 1'×-ÊW%3%¢a㎡)-Èõçµ g K!ëŸÄƒÝÀaûÐ0† æã‘§èš ä;°ñÙ€L¶æöxø@ Zì!ë‡VøøÇ/¢Éíµ¾Û[›Y~€qKüiÒ=ªÑwço/wÆ» Àžqà<£É™‚êì9ëϯ: ª;WWõŒ@´soÝãCkÔ–Œª£Þ·¦Y%7VEð·'¹Œ7ÙÍ€à¯óU±Øë­ÙÒ¯³_^¼|Ögu. `D'’Mª:’@ˆÙZ‡ª°]ïþÎz6Oɸ‚ðÎm=ŒÝ¿·ï§[ïw<Î΂“öt­ ¹x ¾Ó\þ¸Ž kö9ZaS½Íߥ_ ðãF‚Ã\É–Éi{ûìiQJ¤b»Ÿ:Y =¶š®5ŸEÕvï²%£}²ÚßQõ=¤iŒ oŒ.MÑ=~9|øL²¤ÿž›uÕZél" ^·ŸaWεÿxïu`åTu0Ý#p/ ßÃc'OO'ΜkNmU<•NûpzЫê¼{úÜÙè:šm/6‡1$eÛœ?aè+²Ž­{²Vô/] Ä-ÀÕ:IÐtþüÑíGÁa¶ }·/[^B;™É&uFê³'«ãh#´ÿäáêt㻫ñS¶OA…‡Ù¯_Õ »vëÖôGú„þú,M²Å×hé£}Œÿÿf_dŠuuüa¿D7;¦=õC>ßlï“7µ^Õ5ˆì$ƒ4×þÿþ&/ÿê—ÓßûGÿtúÇÿôŸLÅOI7Kê@ﻓ p¯³UÏÿp‡éÉÁ÷d,Ö „]·q¼ ðìtò‘Až%€x¤±ñ6©pè°Ö°dN hú Ý,—ŒS"—$;ÐkΔ×Í@ÒŒ 6—þE££=q´ÄGÄÓ’-Èv=mcÉ^ím¦óçÃýØhc•ü€¦ÐêBGë ­K=/zží'7=é\@’oiNO Níj.»â]%‚ÐQæêH‘°,a¦D ò›¯G—Ò¯³ª¿°«æažG‡À(­íðÉúW€-¢!~» 2»“¼ À= Î`mÉ ûêžÖC sT¶¶ü±‘hÑ=g0þŸ'ŸºÏŸ7Á_@:»˜ÿn¼üwãe?Ó±:€Ž„£Æ g 'øfdš÷%ÒÂ-Gxc£Kfج–ߎ sVGaNóå-²æòOÍià}Ç^òkÍÏÁ,€ÍÃëA®è® [0G2 f6ÃG²¢)6²îo/wÔm¢±9"ˆßJ'ãc­û.šëÑcÝé A;kg=†mÓÞè†2:ŠdøÞà¾'‚Öײۢ_>éû·é¶d‰ åGÀ~®D%G_nÙ2kÃNÇ÷Àîß?í› ÃB2ztJVµ*c¬qá¿<æd¡c¢¶–à0 òûYƒ‚…ëüjAUÀÛ·òˆß0ªúã;üg„[Ð#ïÞ—$ÖÞÁ¦ÍÍúZ|4„Fÿ¢•¬}œlé(Åd/¢3‹û¿ÊGƒëŸ¯‰é`.‚lö\‚þ¬ëeÁb8W´d_Hв¿Û{†uÆ{«ñÉk8Û Î%8-ÉZÞº‚ Äoôô<ÚBŒ?ú`{I"XzóÓÑ“Mâxf‹?æ+±𳺬±£Ëþí0X,Z§GŽæ‡YCV’{ l²©Åì+9Fî †òËtkcGlzì{¦?™ëÖ0<{[f²ýT>wû@§{سöåå‹ñdØfë”å:èÛ3GB’‰¦Ðºk½ÓÄUà&+oîõ¬ù‘4ˆç—i-÷L¿û‹?m>a“ñ‹à‘µÐuÅ:ˆ;lÒ1: cpdkáY­^c‡?G™À[£Ë­ùøü·×ÉP݆ml0ÿ¿/÷Aw3Ú‚C#¹½{ªÀnOá<ö×Ú(ÂRŒ¨õ±n–¯êÀ{ulÐÎÖÄhZOëóQnÚGçš“Ëä¾D¾µpfûsÜ)9 õ" _—Œ`Ìk®›ï³y¿½ºVÁÑÿ6ýþ/þu>ðò´ãW_™þà ž=ÅÏo[z–.uà=œ?ÚEôžú ëÉ6übßøÏÇNVÐpýÚ ù7ñöâîŽY çVd¸ž-¼½Äùqd[ëcX]ßÝlž¢ácÇ–›sU¯Ñ&AçSûO{½`ÿ¢åñ~³X½ñٛѿ. MwȾKÏfÓm½;Ž-¾;¼thØ*Øél¸ÿ,fÓü[?ãB´hK¢ l|ø|Zçy_W=ãOã _C§µQ¸½¿TLWÀÕòEð&{v±€ù‹pÚŠ¶ §y_=˜ݼÁdû7®ùݵ7/Žòa³*üÓGJŽx3ý››O§—ÍgÏüâREÑŠÄ‘•æNŸ;=}Q+öÛÅ©n?\›öß3Ü–S7ægªXßCg8vòY@I-ƾ}ujMéœò,»=™þáÏ—§“‡ÒKku Hg¼n,{Jò¿såÊô¿ü¯:ý§ÿÙ?šþóö_LüÇÿÛô¿üåô幃ÓÙ[§Ï/^˜ÞEWWn?šŽîoŸó‹žU”8ãÊtòÓŸ„ýîû¹Ÿ¯ë²b¼%ÚÑÿâ—Ÿ×?ëØ«0¹“ÅLtÃzþ¼¤Þ0ãG«%Påk› ¿ÐžbçêF†uÉC‰äÖe¥ ÆÚ|ÈO¤ĺFr|N±·ì»´4ߘÿ$7$ +$}Û˜tÚÀØo²÷%èݽ{{–4Ûw¶ÄëªÃÉ|1ˆ­É:ó±•$¢ d Œ¤ì†-Å\Yµ­Øœ.y°s²&m~äÕðÿQÙßû š%Ív›Ùçé6~'kśْ˜<“/Ö1—îaÁ$£Ý-[*„$ÙºÉ6![kW݆?“º¥µú¬¨[Éu8mÏtÕ£¾ùöûüj…)Ù5Å9߯ó[J±Ol ëýä…N®îTIÙ¶(‹æ¼A€äB›Ëy·µ§¸R*cîÛtùz• ¸ çOŽ óËHó²¹;ª0¦¨TüZ¸äCŒwpKŠz] dµw 2ÑÑ¢ú€•)B” Æp2n)ønSBTb ð™Ð즜)Ö”± 7†ŒêÚ1é„õZ‹2žWõ, åCÏD°²Ê-”Mçä1 ü0T)oÂN6Iä>i¼î=S`ÞGX)uïeT#"‹ñíl€o䂤΄á<0Š¿„·*s£|­£†b·¹²=kÓúŒ@g­·52#.•´£=Hkº?BÐ:ðl­8cÛ¶L³‚ ‘ÚJ¾G@[2ëîÝ=¯=ÆÐɈAÈŒpÙžÝòE+e4F´œoû=žBbР¾Ñ|fP1”EÌ%ª-ç»ö×ú†´5Ÿsà‚šòЦ£ a<5ÆÌàñ€ Úi-í¯5ÕBÎ>HÀl ã7tÂöž#ð=Îi.²ˆž2  ¢Öaˆ0:ÐfŒ€éM-„€Ò[ªpõ¯$ £ÛA÷y@ÍaSÄÑÒ*Üs0 Î*B6›¿}—„°Ycކì´ÂôyíR¢œd5%Êø¶æ½U "øçs ¨îý¡VìµÐ%”ã!´‰DÂ$0ÁYöžàÛáÙ`cŸº7¾Ó:ô€\ža°Q¨1ñ´h´±e_ÕZ]<žŽÔº½„œ±nóñêFgÒì>XÀ´÷¯T}þp¬àßÛ„<ðVRÌ–wçòHÙßá5{–;ØÜrŠÚ`kÙãÌq€ü±ƒ‡_´ý¶à Ð,úaØR?lÊÊ g"¹¢•þ¡*$´¦yÞ> ´‰w€Õ®ã|:—‰¡7Œ†xÍqºi!mFÚђ5&Ìñ?Ù‚‡p:ú&Û†©ÜüœÓǨÀï2M ñšPOŽó¬¥¡©ÅdD÷ ñð0v'£ ¿èìÀÐ1t%ÈOÉxyÛk<'>ٌƥ!¡%÷&Ï(3>ÈsÏ1@}ˆ"Ûé@ë1ëÊ‚ÕïãW†ýÌyÊ@j9ד7ÆÜW‡l2˜ð7¡L‹Õ}f:òCçá’C6´¶d:Ùj¯$#¹‰¿…ÍÉçLÏç Ls²ÞÓa­G³müDÝ»ÙÚš±_ä²j¸­ôUëÙ­z^ ³æ¶V…Õ¡ NyNß9GíÖ½Õx:YÜü6sd÷ï^_ _Ÿ‰­µ ¨uñÐo˜S7ÍÙ<5îm¿þ¶¾ðS2ôÄž*>¿ÿõ±éÊ忚ö9=À¾ùŽÌxS2É©óç¦G÷®tŽÜ©h=»&ºÙ¹µã=¶h-ëTòàÆtrI‹­ªµî< ðÙ7|[ÒñöíÖÍÀÔ^àÍí[¥gÀÃÛ*Kþ odß¹u³@wm|RÚÁ@¼ÎbïG°\†ó¡ªØ×:¯ìñãZ÷ž³¥t±¹{÷îØë3§² “qJÐ"·9­£•|à„J`4x(àT Yàûׯ`>DzpèÙÚ‹IU=¬¬¬fÜîÙ»èðnj6ÍHÙ7ºg8~ÈsŽ °Œ¦nܼ3ÖW`‡TvÛ‘åTõ%PÎ1?ZÒÙ¯ëWª®—Íí¬o:üæëà ß;Î ßQÖpUTѤ0x{¥ª=€ŒÀªsˆU4—žKÎ*ž¶v@çoóí7ÍkÇtéÓφ޿yãFûóaè;µòë'NžlÜ[Få:[ób4BN©loûÆZèjtëVq¥Ïž«j/àâÎ{%X<Ÿ.]L¯CLÁð%±~ñÅgCF¨ú×áæÒ¥ ãó+ß_i/_öùçÙ.»§7®ÅVÎ&‡j™["ƒÝ¹ó‡#ñ `;9ðÙgŸ§ë\sõÚpT~òåÏ“¦ï¾»R»Öζ¯:”¸1¾ýÑø‰“'Ú˵:Y=ó×U‚3©]ÿòÁ府/ØT›ûœûcOdó°½–Tqáâ…LQBÃ…sçúüÌøî;÷G'Áú;µï<Ÿ.ðƒ~œÉêïó]«jàëËß&»×GÐAàS—4vºÀ"GJ%ö¡l,À¡D«}ûv àåa'vÄrûLß¼/1ƒç\WôbÈ)g®+­‚7ÚTå4Ÿ:Y•~´ýàaÕ®wž;]‰&Ñõ¹³gãyUU£;VåèñéÙO:}ûí·#9‚#Èæ`‡X{ˆÕkÑ =OÞkCÍùµßì ×ù ¨–ì³—3ŒÉ]s°gCßÓ]/Y†ì8gk÷ëе@(t8œç>Ófw设¿£gdޔșîJ×Íîé>ÉwÉžaÃuõlºÄѨæ`“ŒòÛ«8h ÀxíæYí.BkáGé«—\{Ó™àÕvÕ}C—ŠÍ·_ˆ} ÐúArýáJ/JÖ:ufw`ÓãQywÕ)FE¡{ò¿ŽvF) }½dYô³¼||È5öå`ÏxÒï÷²u–;šM º0 -²Ÿ¼pôÍÛb6˜Š>> Ä"zmÏ®ìØü+2 ÎÀ˪ 2éîBvJœ ¿žÕ•$q=è&lnudY}0hV¢†äDE ëu1`ë1ب‚D{’ÓÀ:•Àô9;•Ÿ¬ñÚ½‡­éÖªƒëXP^P̘T— %~Ðt¾Ù|ûÙ]#öYÉ#Û’“»:ª7«uT¡{¶´îAi 5ýíÛw†žs ›x›Žã¯±EÈ ‰EÇ¢{ë·Ú-%m^OšçFUÿ [Xï<›ž>¼Y[÷SÓ§?ûEܬ CÅ ðr®yn/Ÿ`{ý‡|¹»½Òn~9–\äƒê$ÈgàGlÏNH‡øeý÷­lü~Êç’O/ HhCôÄ©~ްvNf¶4ùW¢€uW¡ÆÔ=Ò}gIAŠ|«l÷‘,Ù³aQ>§™Ã§¶èº üýnQÝk{Uß#ðب\Ïø$‹­ž¹ ý’>UY°´ôÁîè†Ï(ŒKoÐïâG»Kô>¿nPõÞÛäÄÌÿ¡R¨/Î|ÈÖ§=¦®Õw Eµª s‰üFrØXèv(ã‚ëèd=$5{o!üïðË%9õætî‘Þ'ËuÄ¡G½è6¯Ñ2;C†¾Î_5&ÿþ[¿¾qÒ³|%<Ä¿cûVûüt+|?~¥µ£WèX÷§wùZp%öÅè¬)!(¹ƒ÷}#þ5¤¾»gµ‚${±!ð£ Î$yÝýàkUñI•c\Ö°eo ŽÉ€_)žšuE™ VÀ̺ñ¸&ày|aXùAŽ´t3}ØU¯[ü ‡0k ×ÔÕÆØ·§#áÌìWþ¥$Á‘ ‘·£“==¯ Å™¿>KÅ!ñ‘56®ü%«²TÃmíY€&àD[æJé{ô>]±ãƒB±vü~K¶DãâRÚkMt@—Û`&ƒÚšÇ–¶vh ÆgjÍáo÷8œ æ(*‹–`#¿u¥ìå ýâ.>ƒË »£…,îXÀžQ° ¿öpoà(ÚgãµÄ*†Ï$u f‰TÖB[^ÂMélBæ8j ^ïú]ÙÝ‚ ìôу}´Æðü7°·æm#Ó©ž·=Þu½7Ÿžz_«õùí¡G­ ù±%ûnjÍ­ž ¯%Ñ9+±>Â6ýú·_ Zl#8ÊÏY¾`?ä ; ÃÆnÜœáWƒŽÓ/ª¸ŒO0ãÝæî’µ­ñ ×$3£éîç%)‚'Pð ´§°(sÇãód´ƒßet—´þ’Iàģȶ@óÑÉó’äà8ºê©@`gôª(æM¿+ì‘Hãð6‰ 0÷S³÷}•¾ìÜ™ÀïŸnÞj ·Lÿð³ŸÎôxãÝgºŸµ™Å&´ºæ£]?&Ó³f¿Z£Ö¡õ²®øw&ë­n‰ŠÙ¤üRüMý»^n7ìÿž…?í˜ÕÃKòÄGlr/ûáxËñCß\½›ýV’]üþ®bÉA‡;ëþÄÑ:a&‡=q&7g‰ÀîeC:[úIó#Þ$×%°ÉF¢aò|<«9J^8yÏ|Ï¤í¦¿üå¿.Ù÷Ét==)AÀšx–„Æës4ä]íƒ šÛ)ñß´bä6 Êùâ uÍ|þ8Û"£ñž3'&«ûéÞ쵚¿%Kt6§¨cFNëNVêNgf#IšÑ-påa˜I62ÚÀnÆÙCÇ~Ú¡!ØyKÞËìsÉ)ûL¾« ì³æ§°•œ\¹]·¼ü]ØôÀ©Ók°~º#éÄQØÍâÅáÛ_̆ƒ9òAغŠöȉ¹m»‡¯²|âĈ­>)ž=ò´¸ÂñãµËNw-îKßž\/Ðúª¤ÔÃî­+ǽï.7ÇtTüÔtóëoTqU¤¹®¹º~ó|súÇÿà\t´gºQåyç4§Ÿ÷M×¾¿]”€T°ZœÏÃìë4Üz¬äzsíãj¢ðUêO«ŠIˆÉØáRëq·êð×Éÿåäëù’|W*vh½µàù®“¬LÿÃÿø'o°wúÙÏ~>ýâžühúçÿêO¦ßÞ­@£±:ùVã‚=¾¨›Ãý» Àn¦;ß=}P²ã±üºŽ"ì(š;u"IÇd?êˆtÉž’$öW h¨êH@О‹É„UÕa‡ýÄÆ"¿È¶÷‘p#Ç`°»¥¨Óïª×÷÷²ü}<ÕN%¯ÃÞ£?NŽÑCðb<ÉN^/6°£„Ø—G£»ŠqÀ¨./ìý×íAþVØ›Â@ôH¶íÙ5;g·ÄxåU­÷éòËØo·¦gº'Y` ÈL¶; ³iÐ[ç¯#j|kýN‡DÃÆ”^¡{î_]íf÷|(Æw*}ã{ë¦æžî!qG‡¶÷Ÿ.›Xz[Gëõà˜î¿ö*ì!_z{ºÔѤ4ºªFÑyæ¯}"wè ¼Ï?‰{ö­@yíõ#ùÒ¿ó¬äÛ œ1ÖÌ #7Ú“Í+‹A Œ-šçør2E%’¼Œ6¬ÿ[S2í뀷ÇÙ¥­ñàÇÖ ºV…-ó‹ñÈES›¾ÛöÍ2]m,%Bi0PÑ?Y•õÑø¢™Æ‹¦üXK2bfð‚î”Ü,QÄ1ÖZßÖdñ0í½5øá+¡Ç~ö\Nšñu³~OÉI‚im6ª‚hŒåþdùê>²‰ßf âoÌÔÐûŒAÛšt¢n7äù«d‚yŒ$¢Öˆˆt›æ’œm¼C)ö}"þÎjF±“ýx´‡µr﹞#[Õû Rû6_RyB×H¢¼˜b…Éæö¢±ÈÆ?´´T›¬'#è5´ Ñyë1Ö–ÁN÷0{Ÿ3·­LY‰\‚Ò#Q§±3(?˜SÿÃÃý:cœ°Ùù`Ý×$¼uóvÍ‘ ÉÈ'ðÀh‡®X¨Çœ^fø½ŒöWéÔæC¯J^úHoÆ8“Ɇ ¬šsé×уÊ<9îÀÆFÚþ¤; /Ù} ­-œ¿ ¿¡¶Þþ1^IœnûŠ~í…YŒ#D£_Ï@Gè—á`ìYç‘ÌÖx—Ò­áÛA[½7d¡{·§³õŸ=›ýh[|ýÆ®_+¢ë˜p¬ÄA­û%s S'kœ<ÔúX%㇞¯ªöPtéˆ]?®·NlíìèRö¤‰&9œPºúÁ꣱'2ÎVys¯äŠBÇúûXI*ü. ßž@ çÁ£ºHd÷b¨<}òDÎö€œŽR({ù›o†ÌÞõ@èþ¾@l¶¨öžíœeÁ{/ºu+?!Zakíè:Ik±]®bßï»#Ð’bÏdPdŸ »º¨G4KßrÌ%ÚÐûZ‡í›çqPßtCöŒ½~EÅS’ý€)Û-2µZf ‚ ¿¤«¬e¯°ËfI£Ù]çcÓ’yèõþ&c¼ÇÏã;:ŽÁ¾£Ý¥¼4}óõwuJ¸-M¦ÌZh ¢¨Ø8}æd´p«çt&Zkü¼ºîêZè=­Ú1|Z‰®øvT4Æ¡ó› ߇}#1LÈzYg6¾  ý¬*ùÀÁ­ñåêÐ{ÚYÏÍ'KJ á?œ œ“üsëöÝéóÏ>¾øòÇÓ·—¿OV<.IäTt}¡N=wò):žæüźCœœ™Énûô³u[}÷vÇ.œž¾üù/Ò÷âÕ¾·g|~ûÖ­dßWâ«îׯ~_àüît®{]ºôùtùòådʃéü… ÉŒŸ&nFSkÉ›³£Ê·}nNgÏ]J.Uå<­Š\’•Ö¸’gœ÷}ñÒ¥hôàôÝ÷ßÅ[ëîp¾û¼Ç€H¾Z®ò÷`¶Î³::Œ¶¤—>ÙÇÉDÝz~\rˆjW÷;{öìôÙÁ¥éF²Kpx©Àðg_ü¸`ë£éÞÝÎM (üìó/Ú£¤U¡:Ü=ô¶ŽÉ_~ñ£’O'/n·§{§ó—.ޤ¯çUåKf:V—íùøñ, ÿÃ3´˜Ð8!8™MF8¾DBï>\•ðõvÎÙ¼'(%QI¥«vä÷ªz>œÌ?¿TëÕd€`¼ªÐ#KË£‚PËs²‹ËU,×ÄóWÒìuíÕ÷(dÀÁnñÚä‹éÈçÑÐrsáûš/=CVðƒ$±wñMî³™žš' Ú¥`*?=s-Þ” &étçî: 5>]ñ¼ä+‰ ì@­oW^ઃÒU|KI(‚Û“ë‡U½Ÿ÷¤õ˜J¼z »a1`ùQzþq:W‡œíÙÍ/ øßxp'®R-PóYr÷A²roÁòGï&Cfk›µhí~™—ùŽ%^ýfÈ¡=ÃÖߘö6ï½É¡íûêÆôâ^|Iož®‹QGŽüèJ| :;¾)Õé ÙÖ6üÍ¿’gdX¢j$KÓwÚg’yüØ7Uh²)°y/ÙH'Ñfø}Ï÷Ñ ŸaEÛ—]eòõ·—§%¨ DÿΗ²7=ḗÖùÜ…3㪇ÉçÒCozîídßÉôJ !yr?½Vuvòìzúùxüt 3ÎotŒÉlÉÅŽz»žÉóHo.£÷ÀDÇNj¶ _üäü8²ìU ï'Ÿœë8„’ ®ß©åðÅ€ã—%‚~?}ñ£/ ¢¾›¾þÕ¯¦ýø'ÆüÍo~?}z±Ä½xì¯ý›éht¹\µÞï÷Õ¨?}þìômIEï{/|þi݃êZ–ì¼p±gDo+%’=P_÷/ºï: ÝK¦JðÂ|Z3?×¾¿<ÖQMÇ>Çl8ª&0dès{k ù×êúB¶…ù3’£fÁ wÐøŒGßÂéð ̈î‚=ŒŠèÚñ˜mfº²÷Û{v¶ë$¥¤´‡M7ÓÑ‚w|YÐZW—ý®¢PQ¾7>E£â=ºpd"ÚÔE~td–ndª[ék2VÏú ý*Hò’ÉÝÐüGdzýw„]ÁÇñ†ƒïŒ–ÜÖ2jJ67»Ò÷„±Á©$‰Y>¡.|æÉ¿tÜÔàñ’ØøÈpÀ´Àœa±ý1oA¨édÁKûKZdK8J@g6ÌÓkÙ7Úî´Hñùê^RÑ8:ºtÝ%Ǿ P€%¡PR6ßã¾O²Ã{F‹MÕá uæ§?mìŽB±gzŸ­b^º+YϽEÛè6p¬‰§Ðß‘‚‹’huGa3âwÉT¢q‡®¤û›ßŽlœ‡ÒvÞØŸ†X+’ɇCLkÙ‰‹­œ £¢¾þÐ}ëTÁk´ŽŽ4cg9^Á ¨ÜN¿£G=JúÒâýpüízÂo)Ýõ²gJw¯6§ fϹyónI„ÇÒW É’ŽKÏ¢ƒ;½ÿ®µ?q|–˜-aYð~)OÁ-$wñý“ÖkÏôÝoþÍôbõnMTàœÉ™è  UŒÝÇõ’N¾ß8f`˜Óp¨íñÚÀt“ TÉ©O?Ϭs\6{döï~õ9NáÆ'S¬µ/âƒa»G/6/›Ÿ^ª©À†§oízßÙÏÀs¼Þõ]É|xM€LrùZ6™OU®KJ\̾DgëKäUPâûEèÈ…Zj{ô‹À´ôäöÕéÐQ{ïä°ƒŒ‰Pƒcù®ñ³ÙÉèZF¶“Ýì°‘HV'¯ÍÎaÿþ»çÓ¯õ—Ó'çÏ ý±úðñ¬³Y´>pºì+<Ô­Ç v#Ž2ßûsÑɶl)·…“”êsAïÙèë÷¦7ò‰²Ü‹ì´&ÖÓõý:öf$Bô òåÔñ:ÒEsªr­ ûÏQn2‡_ÀõØg’ÉkÏ뫽ƻCOÁá?&sÌ7ö|Õq®ó헿ɦ{+ÏêTa^G̾|™„iŸ‡³³ë=•<‰fÅ¢`Ñt”®«£ò:IÏI”¸Yß´ö¯Sò–º¨Ø_qI|È×ÞYLbàöñ>q˜¾‰î£=¬£ Ä9˜Ö%Çét­@TŒO ýIºK’ž:B_£{M$+™dtZMî°ýïf«Z, ñJnð’ì…¾%5C“|©€O ¶Û0ç(,‚,ZOF¢C¨âïŽðÿè_4u„_oÆÛ=¶DÚjZdÁ Â1³ž!Êtv(£Ðæq‚eN2"­ç¤ ÝèÈ3‚çð tíXn¯«jÑýZºâU#÷ܽ‡j“;WöYó»tˆQ#óR×çïfg'à^¤ƒµ÷Ù¹/#0–órpã'•0&üwï DˆÏE BûGP2D7†~TrÐö”pò4ã¾adüôý—2ªvIFˆ5‚•,EûnÏwvV e1 F22|/šë=3£åQéÿPh=)ÓbŒ½QÁδ~ªF¬MKÕúÉ„œ)=²n6ž~ëË>ÁêAµÝ*úÙ^6Áëè]Î %g»Ûz-?ÿÐsf¯ÆÚ~›À7æão©Æ¼ýk>èÁX­ c-m~ÖHrQkÓù6Œ4ËIǺÝ NߥآƒõΣlLξ&øÞÖ{ðè»xÐ& ú!š˜rÄø-^ì"Žÿøß##R-™åº™rßݽs×íú™ß<´uyµ&q¢;´ƒ~‹}ò à„`Üø|ö”ÆÐXã}> €2äøÎñÙàzãKºŒ}åpš÷H:jPöOثƭ o/(¹ÙÞyùÝÙ›o:³kao:ƒÍQÂIò­ï;ÿ­VToœ˜1û!²Ùž¬Fo¦«%¥8cëáfÃB –éàñðÁÛœ‹ª¾ÛÏqLZ?¿=y-¦õ÷îÜŠ  @lÈxxÿN¶ÄË ÖýcdÍ㺛+£sDtë¼àûw®>‹ˆ~9e¾x2tÀr¾ òÓœnòŒSD—>®µ«j6âC'ð*szúüùó#©îÞ½€Öøá“`ôƆ‘± R™©rájAgºJ†6°P7>9™í»=§âiãx¤X™ÃôªÒµáÝ[¶8`àz¯¶à¶ølg¬K çäœ:YKÙoÕúZ×@uv“ªAç² J à¬>~PEd•v­£@¡yݹ}«y VlÞo ´Ý @íï>— y7;NøôÓKØ»sëÖ}§Ï²ûû+WoŸT'#¿s.\hm¢)`: õ\×YïɈ/~òãäÚÎéÛï¿ äØ˜>¹x‘ˆêl·ëÉ÷7ÓŸ\ëÿÍåï’‡/²¿OÅçªÞ¿ÕZ¬O—.œk­ ž÷<I/^ÀÑ·ßqª·JøÅ@pàµ$‚Ï/]è,ÄÝÓï “ûªHÄ7úü}ûp¾ñî+ˆ~ùûï‡3têÔÉÜ»ûΗÀ ù*øål/ë·-çèAÁú]É𱯭ÛÝ®wœŒÛp£z€Ü´m}~G@nkìrÖ2[WЋݡ›]•3¹¹«}ùèbë¬üxi[(;ˆLá`RÛC3zóÁçÿËÝ›5Gš¥÷}‰=;ØwÔÚUÝÕÛL‡íaHTˆ²#Tо±Âá +BºÐ•’B Ê$aZ¢Æâœµ§»«kž'‰DbI @ú÷{Þ*[Š nÈ‹Át –Ì|ßóžó¬ÿgSþܸъðg“0”á<&ׯ–áºVžÊCÚQ~ï+b›µÐèÂeø}@´ÚîF‡Â7n~::: g_Ô±5hþOŸS…„(?¨;ÔŸò¤_VéhÔÑ%wœ½¶«6TøC¬#Ì]þ¦ÞÌì}ZÒièó/2€=M…£œ´q¤ÙqI–Š/ân摳&ÁÙHD^I·&ɸ¶_ÌasfHèhAÿ€Ž¬Å º•°Uº:h³ë_ñ±¨éb$”÷R7Ô°IÛSËj^4*‡½È†‘è€áMrFô€ªã:æ ~ïÃ[/^¼†–—"Æ ÜW³‚¿óìÍ6¼kb¢ÚvB0I]1Aâ… Gé¨Ä³8@Œ{˜h255–VèÒÀ½¶¶7ÓÊòýôé÷¾—6×Öƒwä›/~ðºãm2qe>þä³ÏX½¢ÚümúìÓOI¼yƺ^P9{ãÆ­ë|ÞD%“T•=&ìXY²C…ºh5*§çæg ‚RUÿ:µŠ¹´¸ÈëœÃææF$êÍÌÎã' ôÞ ùôûá³o¸¿AéE@Ë ‚]!&àKÎ/òÌEÞOy%×¾?"iÆÄBAV zth³(?#ÉßÏ(ÔûÈÚCÖ?;; ¨ ¦ŠãÈVö̀ʲtœjú9èÇN&k«‘¤5 ÿ:{ÙójǘD82d¿T Ðj)ý›S@ª3|[œ.Äì1 G{pþÜsn¸Aéi‚õ&ë”àw刴fÒÎ ÝnÀdŠÈ[s:ZI½ b |m/þ =¢omâœ²Ê |ƒy¶¨—çMk`FZÑc5fÍÜ[y4hE ‚MÛMüÀ¹Â&6]á£V l›,?9=4îó\ ‹ýiÖnfu|eç¼kƒŒ2"ª Nb…&•êç[1}Û²Í4öÅ­#O2Ôvqa•3A ÀQƒ” léYrÙÛ ¿‚.•wËvX#aÞM ‚ƒT7]ž°~*ú'»y_5åiƒßN›Ð3ºhï>zöCdš•3ŒÏ"¡ŸG„ä°Õc¸ÚáÊŽ¹Ç/_Þ ÞµíùãÇOÒ 6Or*n‡í­ì×^×ÇÖŸWÖ¹~ížKÆ“€"°H|q„±2 ¼õ‚¯@Ò“ÇÂïØ‡ß• vÒ‘.•&[ôÁG1¾š ½Ç9é3L SäEm}mñýYº+БÊÿ"y”kú›Å©¸¾‘¶”kÔßw£ðF¹,ø™ƒv£jšµIÓß±KÒÜÒ=LP|\p¾Å‡OÑ%V@B8SW™95AàƒuŠLhëI—cã3œ »Å^™ÀášÖ?^EE[̳wxúÙ÷Ã?÷éç=¡„ÙGõ•IÑ]…} ˜ƒW[¨Íy€­¤|P×í‘8ðàáC’Ù›éíë7؆÷C¾­È@âŽA–ulÂåÅîݑ֟³<‡8Úúê[èqÛ¡‡¿¿!˜?ŠÜèO[Ø:CtÞgOìø£>Uvk²“$ÇM‡M¨l™AæÁ3æææg•Ì=xü8°£=lÇåådJ›j9¹÷~ÃZg°mìf¡L×~5‘k}}3aåʲW»t]Ò$ n™Vgê€ÿ¶ØŽvãÈÀû`sÔu$œÍaÛÝDâ úÒÄíud¢ëç³èjÚJºÝô£m¸÷ ˜ÚŠÊïsFQÔÏWéH ª99“yt„ÉðUª”—ÑbÑÊç6t“çfÒa}ì—ÉEì í²Ã½ítM ×±ÇØëÒ =Ù£s‰ï#€ª­/SGËdl4ƒKó3±‡v£ikNŸü%`ªd·¦)xC¬mw{'Õ±C‡ÑUÎÃJn:’x|½·«ˆl£Ó&àù¬xul†3Émq[Fïv·gûVâœõµoMú¾kކÌ3©¥úh^÷Æè “·î¸ç k_’/+eoÑw·hÅ «<Ë~ƒò ‚ììƒçb%ôpGÛò ƒ$öðn›gBÀä–ÎÈG)ŠS çk$ ™Ü$æ«~LNŒÔÄî:.â×ÁûìI‰}oçÞŽ8§R³Óª8Þq mðSxÀ5ò¾ _Ü@{òP‹¤K fmIÇ(ßLTŸQîñgô2úZ¼÷à÷À~ZX^ l^;Ð$[±z}Ï"´ìÏ&ö‹Z•o¡ˆ…HÚ1é#Ç–B§\à™Ø¥ªMf×;?DP ›aZ3ÍûLsöêR?&Ðuê$þ >J›:“{vB_Þ ¼ÙdY“¤µ]M„šÖ&à>&™¡Ûûáyõ² †K+‹AÇâºÚJâüupGÀèY©êž8úÐûXõï³ýÖ—_¦Ù©¡t´ö5#I¾…>êQ™ª-¬.Ñžgq/9¢Ð]&†('1 Cîº÷YýnÒI‹ïÊl$^zúñ3æã'û‰ÿ ŧ?‚åOÂÀ@ðÃÿṂ +åž[L^üFìÆ™îcøIžƒ˜Dà[üì§þ³ÿƒ>ÁñXx</™lqÇóŒ GÄ Ü«kl 1y÷Õ˜IWg?tw¾N¥z‘žý:’ó‘ÊÔg “®Škƒóóš2:Ã>ÅgÁ¹àa9‹GZìk7(—‡W8– ‘™©b¬­¶µ‡ÍØGPÔÀgщ¾¬ÁGÊ]ãŽ?Ó'³ó–ÉøP,tÊõàh­¯±áª ³¿Á=Õ“®‹Ž3 æ‹Ãs=n” YvìU—(O/°%-ž”nΡ§~l#ߪ\’™ô¥´Éô㽘Éùcž7òçæŽD ð5«úªò5O<àçõÕ8’†°Ùðç§Šùôõ1-Û í©TÑÞƒî8«ê6 ú<gwcm¯òæ:÷?§«×1qdÔsÏsŒ<@¶Ú)Àä}\¤Ð¨ÌˆWz¬·µêij”ġӔþãOKàméGŸ©÷À Ñ·2žÖÎÓw‡ôÑ=Ò´U±­ƒzWÄïƒ}-ü;N1º§f±yIž<¿coH†ØÜXMñ—/ÒÏ_î¥/?{˜¾÷ƒßf/Óø³£ËÞMMOÁó´ÿ?Ú} Oá“"KÛH¦(S1_à9{ðý®ÐUãw$z1VcŠsézÄ„oä âýø%°F·ÓUh|”î6ì×ÎÞ Ïo‚u=è\GÛJš¸ 8ÅØÉJbCïèM€ã‘þåg[,½]cüÔXg`Øvü‘ßù»?søúôïÿç(­‹ ’V‘uú î£Øj^B3Ž»:³í†OŒ¿ëáÊiù§:pÎBSS¾ TØó,Y®\­G¼ª›}Pï<¾?Uîv£4éÊ5›ˆn"š{¥ß\$Qš…ß#Ží3™4 ,Õ1D!eW-Gýeq,d(?ûØ5´–Ovì>gäû@¹ãÙä'‹FÑ«Òæ(…ÓŽ+Wæ^ß°Bà[ šaŽmé1²þÁÃûéOÿýB&3΄3qŒ• A}’5xuÂõ i:…ÎQÐ8÷ ëU2mU<…rD¤g>b)AF³8Íö”:7ˆÜh•‡™ |aGe"÷R@(PšNº—X݈=ëqð:#ëµsb˜O )ðîgý[–™Ãµ¸Fö3k€9hB A€ëHë„9_Æ€µïuöÌ%sÿT1`^§…ŸÍî@ÒâE`¹€šYÖ‚J%²@'‹Bó^åHËÈÚ¸æ½fKÚjLGÃç1[^0|†¡¥cj ÕŠN} ÏKÅ31më¢3-0¡²¨j„c†tÿ=f?êLAðíš‹øªÈ¿uâ®*rèÁhÀs*QK+75Ô"(Ïk t³õÌ®öº·(~•¨üÑbýV¢ëÐaP|øh)Þ£‘ «ÕÝ]#¹Ã¶¡yžÚ„¶]; ’­â”Ÿ,Ê=R–*Ç5ZUˆÒ·É!>ƒÁ&ŸÍ{)/åùZþi诡íÞ˜H£ó@÷ éš÷ó&Ö† ŽRưðßBƘХqê[ÃXˆuð”±’ƒ¬û^r+ØE䕺Јs½¶ûê #VTØ0ç™4vB/¹ïþÌ…¤ŸGþ¶%NÌ[ã5åºk“g<é톄*[íŧØ7éO½›¿›­ý^_±~ç¹…LåóQ]âu ê3t­|s¯­ºÑÁ0`©!`eÅõ­`©«‚X¨tÁŠÝèÇ> ¹Õ’l˜g«qv‹Œ Y†ÒÓˆä3­˜q.p=ŸM}ÿÞépLª¸•¦87?³= r{~f †AÇç”Ò³ŠÙÆßœo|žÞnD•y´ã™¸wßjåêë0l¤!žKY+ݨï€ëdË_‘ª±ã“¿ü†ùÊ|þ7åË=6AQþÿá—ýÿÓýÑ¿À!ÛOOž}Ÿ m1ýøÏ´¹0¿•G·TŸ§íÍ}‚C ZÃçf–:–B>ÑpÀ¶¸¾¤½-º{ŒÚkd´2¨»—äl Šè°Ñ†<Œ‘œu–0“Ñée¬*5ª’ûзðñmƒn=ä7E+*þV© ¦ê'XÇØ–“g´}W_;ÖÊ«’œ‘tJF«¶Ã¹òCPÅ üúê€À`H²" {æ°Lë[nÖW黂 îíNúo¥¨mTM¢±ŠøpÐJØlIƒnVUÚÚxÜ€<¶Åêêkø”*ìYÀ5Îys“@5{«>µ6ÕV%[ág+o×1V&€N(×+Îi·:gT°Áà¶úÝ»ºÅ ,xË€®:moßà V1knnïP5ϺåçõÍ·ÐG`ñ :¯;*9å©?|ráýû— •*Ù×c6ð²~ȶ­-‚ã€VK÷:ÙZ§Ýy¬väÙ¸m¢§æff✎}Ž™ÀföÍ`¿-gYŸ•˜¶ö·ûÀ"ɯ8<&œð™Ee+ˆw Ò¼.ìNr ;˜„0xU¤šô„™U¾vÐ6°Òu‡kL°?ZËG^ÏÜžâ߀e…³3ÀŠWe®òM»Lýf‹P«3‹ÎR^X\Ð}ð¨I& Ò!ø%è­æ3šT»¨M)½éÀ˜Ó¹=àl9·²²Èݰ‡ë$ŒüE¥º¶´4­f%kPÓªÞþ!Ú"cO+ Õ êm A*~ô?N˜$xÀÀÚç«Ì×?óUA:+Ñ|;ˆÍ)À„nC×(oµa:[!ãÕƒpø¦]ݦ}m?K‹ß[N{?ÛHÇgéU®”6á•]ú_&¥È‚iþkãú¡“xŸkŽ×ÕÛ®Û8.DÙ—‡ö ¬™HïEßG"©z‰Eég™k@Ò–×Wœ‡Á½c\|°…¥EÕ%ª¢ß¦E~Fnînm?Î/.R¡ý ÙJÜ$ƒ Ož«{;8ˆo‚ÓV°ã‡z½š.òrÀŠÕnCÑ–À°ÁÚ™™G|ùòmzûv õ输ѯR Y´²²€€íÜ¥!ÂŽÞð»-ë €˜h°ºÊsò\#e'V ò<|øˆ„”û¥ŠýƒGÒ‹—¯ÒOþâ'éÞò½àQùw„•••¥´´¼Diƒ >A'øû)AÁ öí»/"|Ÿ€“òÇõ߃Ÿ>ùäãºÿô§?#9`%äœ2î”±•Þ ‹ÐÑîvúú›oC&.Ì/Eòˆg·>`ýÊÐ]Þ³Ì^LDY_Û 3‰Œœ†ßa5î" EÛW7bk8æ£z;@&Db:òÈ–ÄçÐäQd†„iµ¶úi þáã¶jÞ€Ž­>«èªíí=|Íž¨ˆ×®Ô×$-è“p +ç”ùV8Lð9ý«ÖD+LðдóUÎÍ´jжßkëÛáß»V+“ ªðYn°» ÊÉ·ôMH Bõ“]Uœ%«þØÚÛFpæÚåyÕZ@i”à×H€tž› ž>æUŽÚ£'•#ìM‘ÕÐû;<‰U&§P…ĽsØ&œ×hÏÉ5Úá‰axÄä"»9¨<4Èñò›_£/¨Ä„¯‘¥ìŠcì÷7\ JqMM’ÿ´eñ ©ÇM¹à;ɪ£\³Æ>ß‘ŸÓ‹^¹¥*ï¼ìÈÙ–›¤*ÒúH€Y¸ŸòÃ÷ÞãÈЉ©9øÎÊ-$Ï®Ï+hí>„íÍ:þ¦¾ia« ~NMÍ2C~1®¿ 9§Ñ… ·ˆdxü; >þÏà§äÁnÀ~ë5ÆõG ºè³hqÙÏxƒèWØûqYðܪ.ä03ƒ.&Èçù¬A1<Aå•XE`씉¿ý´ŒÖ×Q¶*÷ôC¤«ÏŸ‰ÝЗë®ë¯Á?¾Ç?î©÷4aÁ†ö—òšÃޱó4 ½©ÏbgƒüV T²û— š39Z›_©¹AumõÜP´[,´°}¯˜‰ÝóØ|vÐÐõÞ½Üó„$±ÕI»up¦|0Í#¯µ'ù/®ë]€Ó<×^q-ã$ÒI+&„±<ƒóŽdrÍÊ¢>žµ û„Jk1­ ’Kì˜!]Ý{ðˆä÷ŸÑHÈz»Ò¸‘Ê6ƒ;ÚaŽÎ‘Ž XsØ8p “gÝ }ëáwI³ŽìÑW¡‚£í­ )Öá9<×wÕÞ›FŸzÞÊÆ)°méÊ{ °v«| hc@¦ÈaF3!ë L³Wb7ã$óy?yI;RY'/Û‘DLD»oœ`»˜¢²Õ„ŠhQ½ÄÏ<“ ÅÚ¬Úßî¾þþ‡þ±ÉS“$y”Ö®òZ&½êþ ¯Ñþ7AÆÝUÔï÷Á¤¥ 7øæˆó—Wå9õ‰çl1cÚ´ÞÃı˜™Y¿ì§I^ÞOÜEÛÆâu™4*íŠ Ioââ@þlÀÒ$®`Fþÿ>×þsíòR?ó“¥yé‡ð<ˆí-ùYùGšÖ7ô=ñ>þ¦>’ÿ•9Sñ¬üXÌ£ÿ¢%k7'e|à{ðŸáð”ùâoÊ:»&© ,F »Y€.¨(Ãød|Î˹é\ÜUÛÓµ €‘@IDATyN¨•ØhÇj7³P®œa;ZÝî™{—v· çìžšÌâ‚^ô/ýÔ ‰dVœ?gLÛITv§m¶ËÈBé\Ø8 .®c^_ /ö—uˆ½ËŸlO“Z4ZÍ/¥1’•L≠ß]«Ïù_þ¿à=ŽlÑn3kjç^qñéì„-*óÙ¬5‰Î±q&<˜\*–å^¹Nå¬{Õ±È;XÔ9?±1×"íµØc±õ‹-ÂÃÈcb›TØ Ò§É{‡å´ùöu€ÿÅ+Hî”§å™Ða\Yù+-ªó£úýšÅƒ8'ôņâQÆcLp´XIÂ{¬oîN-ï‹oóÚ?:¥Ã£Ýöð5Á'´›)DE×uuUCæ6ÀX´¹N±äI1þÎîþÉrð‡;/EB¨üv­Ò¨2NöÑÞRç[ jËnB®Ï¸€v™-éýn‚º²ºsØ®˜KasU†)“”AV²sÙ°Å^Äw<û[äm!oKwxd÷ƒrù4Ý[,¦oß’„Ù]ƒ¯o•±ùé`„??€m±E¾‹s«“¸ˆÍé,i»ž #³†à‘l´¶Ûòâ\Ì4»UKOžÎã[•ÒÖ®ä ÈTdûT¦SÅòÊ|Z™é'ྕÖ6Q$kÒf|ay[ur•éûèÁ´¢ÜÆe4gÌ“ªØÔC”¡çi/¿Cнzð&U°ÿÿê›ý40RL¿ó[GpVûí·ß’˜¿† æÆ9þþï}BÜ‚Â;K›¬³ÇtM'—Ä›ÝÄ‘ðÕ%xAÏä É(ê¿›°Sšxÿþþ1øÂ I¯Œ j 0›¬´s˜ùƒèÉJŒdé¡KäSéÌDã Ú©ÚJ&BfAsž¥_â5 ΰkô‘¤C({]F2¾¾.ÛúæÑ³Ï9†ZÙˆÎò ö :]ÊdŠ10Åó»Ü4ºÁø¡J¬É`·öÛ XɧÏ>‹Sƒä`u¢ÏqÃZkçµh™^óRŸ¬¯ƒ‘ÝÇCŽ4-&_š¸!MºO é«™T5L*…ˆrFÜE;Õ}u]`rE|(qiÉ®ÚûÕ´slRƒ÷ÇwçìÂæ—Ée-öÍ¥&ÜwðK{\_íò’ïFæ­t/ŽÄæ«€\…J^aç?7ß6Û“&^ >je`À> 0¸p#)ÝÍ,õ(Û{ μh`–Ûd×FÈyøÎXQH<6ðsGÎMˆ}”] gOÇ2”£Dg•÷Q`Z5­r5ƒ¶Îu"È %]Qé~Fv…à« ÅBÎ 6×8ö3 ÷ŒL…Ù Ï/1¶Èf‘U fF„# ÑÊ´‰‚ÔΔ¶:î ãÀç°õ¹­ç4,T¶ÑBT±¸ö‡ê—÷S0ª\6îÍc8p² Êðº¨2“™¶+ñ_ªX³|T¥rL†2{Î@„ÑÄø;8¸d?ÏbŸ.u,M„°ÚL!®r €}¿®Sˆ ål÷€ëä¬s ØœU Š`ppþ†·dÕ¯@@(p”¤U˜5mŸ;£«Ý3ÃHgM:"’Äd÷g]7¬w‡ª2×Ç„—`5°Md°mœû©Òwÿ½–ƒÖK'í2TZG—U€Y80¿ë•ÁØ*Fri¬©@"oüa¤Ac^«rtH©³«áDà•Üwvˆûª0 ÎýR:y…ô×ÙXa°½e€% ³Jã®]Ã\ag‹?§ê¿ïï¸;ut¶Ûš/³¦*!ƒþó:§†u³söÚì%|1ðš5–Jë 2îŸ,³²?9Ó; 8ó[÷ëôíóçÇ<†ß Æ÷ # YȰà8M¦7Ì4¯Ÿ¥³£ýôŠ›È£¯ÝdäèX py N–«gŦy;¬‚‡ƒSQyrÇXÿ®³¤¦s¯ó,ðãêÒb?F@ ®#{ëÎVU9|ÀZ|Ö.èýa/³IhÀ“ëÕ€Å08Ük fW«Æ4Èt¯ˆ xz¦òŽÆ³`äñ‘{«¡Ÿè!G8»ÖyFË ‚ñðûd,³ìÍo6åaŒhhÌ{Æ\´wœæZœŸm…¿øì oƒÙ“4̨w>z‹ª ×áL8ÈuΘcäïÈÎõÃî‘å‡1‡2Sé@wàQÕ8P´³£²„!>ßö^VÓ– Gî‹ï}ײ‹ƒ-ãx\èIz0 –ª49—ûJ_Ãò…|'-ÌRmÉ3 ¨1 Hšn‘Hà}}Ÿg!MyÄÒ™™Ç]t&)—u0œ…MôwÃÏ*(È6öp½ÓB,—?4>=»;d¸|É”ì½çe¶Æ3Ífa„"úÐ?¡£Ø7ÛéXÚGé¤;¢siÐÙÌxE1Ƈ÷ Z°÷ò¹³ÞméŒÉ÷Ržóx>&ŸÃÑÄ-2*OërŽ0Ê”î‡cè›Øg  ”'­r´ [èJöÂìAƒwSÌÁÅ ÷ÙlŸVã@z˜Ëèu$Ÿµûé3˜¬49Šc @è~ÚõÀv—§7´|d¯œ‹èÌcÛéxuSU¹´²|l¦»sè8©[¶ 3ÛÔ–nLî A? ¾éÙ"€ÉLýÎÖQ¶#üY?™ê€`Y0Ž=|*ÝrÑÐ}>«3t´”™j³ìçÍûrè~ÆŒMbй¦ýÄF>T÷Â>PfÙ·™>iow^™†Qvê¯rŠï‚x×\G~X´è9ü¦|©ßtº‡˜]ûÃý}Z”ݤÿÛ’~úÿ>Í.>gßgÕi¼´}«Õ=8R¶ WïÔÈ‚m’œe’DÌÝ£Z½J¥x™Q­»2ôpLðû §˜¤¶NíaV¬£ÃŠZƒØØK=¶£¢Bµ &På™#ó³šQ¿¨ÿËÊ.AäCfÇBØíð Šä"Q}‡3tNðEË#cÒU-\•¸4ÃûëüÛ®íÇºÕ ÒÇÛ:U¾ð‡qÁ·+øþÀݪ8-KÁúc*Õœ¤{ÛéÕNû°¥NÂÙ´f:µÕçÑW•íÇ ´úÜι¾á™cn0ÄêNÁMm«þ5‹ÖÌÖsªÂ ÒÛ%À,Y“ÄJ|Æ hN¹Ýgi]¢ÒÌ`à$÷µmÔ¬˜me€‘çÞ ˜—TmðÃ뾈À¸-,™ÕKV¡ÙZxŒ{»¶J¹„½MË2žÆß¬¿`Ÿç¼Fá *è 6i{…]ÏkŽé†ÇŠa'—ËG8¤´²¼ˆ\¡Ò‘ Níä)À1³Û 4×3VØ+gv Néô ™Á‹­ºGàÉ ¾Ÿwf¦8I£ƒû°_÷î;Ë~šÊƒéÙ™¬²:›žKKKÓ8'T ÑŠ™,e+Š”›TRÙrÜ=®pïõuè8ãQöÎd5ï1‚,š@†JsVÈHŸ¶>dO666ž[ êZØØ$XˆÎ·z²ßw÷¶•$U ý|í”öÃÈW+.í€tA•Žûe¶·r»Š ³ÏNAGÑæµB÷ªpÇFÆ"`o7…ØOÒÈÜÂ"t@÷tïŸàß•[óó3jʣ݀áò–çbW m»w™˜dÇ/uŒÉ4ÍÑfÜ6pgÐ[úQG¶D²ó¯Ÿ¿ŽíDí,õ¢6¼º1< ˆZ€(ª ÑÏ&Ê‚ k3ÿ+ÀGdå¾¼[¯šˆ#ÀwÊŠ,osõ¿²H¡/@ÙO_— â?™H-ôæ ³ðN‘ÚMÔâ¾ÞŽÇC~a¿Cçü2;lw¹•ËÉË9h©Û±aïä¼t p}…¬8…Fç„¶ÐWÚ#1þŠ×µ“zLrÓ¶Pæ›1ȹ`¸¤"e`XpwdœÀ û9 ÍØÒØ3²óXåx—}¦51ö¡ü¹ ­¹ÈååeNõ²É€è'³³ìóøÙê\£È ’É ó³Ðï@œ]èa€“È(ŠD^ŸÙy Ú‡Èl+ÜGËå¶öUôh íëå¨x“Ÿ=zža#<ÿæyz»ºš>øà!»O«ÐÍ´¹± ¶éׯބ¯rïÞýÐýß>ÿ–- Áhn†çëèÛ‰g™œž »îõk’uØ+KLöûå¯~žž}ô1ðž÷¨„=Jù­í¨>_^^JŸP¡ä¼õ¯¿ù:=ùà)Éòb¿o¦_üò×W÷ÓÃاýèÖÑ9M•¼‰\œC‰„í°edC…@çÏâõ>ý”Ïoìðo3‚þŸ}úIÌM>'Hn`ѦŸýâ—ü<`þa‰$d“"?~DÀ½J úõè61ö0‚ëȆ,“`¦sS‘ˆ#¸h`Y¾”­¦AÚYÂ@U麌™€¿æ‘#$ò9Ë|kLFB—gj[±m‚ó!ÝC´›¬¶Üq·v¾¦|ïp£LØ6‘hšUÖjwÐ]̤á#àÛÐ\¡?:H?¯‚ޤÝ$ ý+mŽ¡«éÙé ½ ›¯Û²Øxr NG²køÍ€Êp‰"ì× |2V¤j¹«¼»½Q=½ :{^ƒPÀHÀDƒÒ5ø]»»Ìþm¯½¦m6ÉZȨ’j`$ÓÈMÁ*ס>µÅ¶ØƒíyMØd¶ãé ­ØÁ9´ëFi[\ƒÿLªÐ.3i®^£='×B4¥Òéaº¤%¦ Û¸¡×­ÞÉSIÖ_SÐæÄWAçàÀ[c}àH5m$RØ Õs|Àuýò¨ÞÁÎ<ÅOêd £ <ÍÒ‘€ÒÉF&¼,Ô:Õ—xïëim+?þ6¾”¡xžø9TãBëVË]T¶—_¹cŧ ¹r»D Fa¢='ЭÏɬ<Ëaà²}öLJÄ'°ÂR¹¯<0pçyKÃvÂÒî°ÓúÝÎAâ;Úi̼¦‰¿Heôˆ d… V˜ú^#Ý´rC’"iš5©wô§1¹Ïá&ú‚bq­‚Aú Oì¦ d=þ2÷²ŠZÞÒ¿¼¢M{\×µAk›­(rï·c€Ûd1Hü·ñyô3 XaÞÝcûl‚‚ð­zÑÏ© ¡]e2‡ö¤¾D¦ß°W¹¾þ“2[È×Å3#H¥²âƒF~áå¢3óÅ6³ÆNlaq éH}ç›y,t“Û!½°!0È{ÅÄÈ$9ϳI[± ƒ»á÷bO‹«z^Ó¢#LbFbf½=#áƒôñ}ásq¯&m[õáÕÛ9VVx.·ü®ì÷¹<‹èA?J[ïŽÂ ¢Ebœ{Àg2_‹ï\ÃYäb@\-+"Á\Œë©×½§»æÖ„´ E¼RLÐ-îçf¸~}û°8g j´Ír$€pÛБi˜8ÛÉg´)ÛyµôñÅÝÜ6¤BÈ$ïà~Aµ\Ó{³î«m•œ¬Ë=5ùÑdy«Á}~çMkKèKß²V±iý ÏËb!ƒ¾Ú;hóÖVÙž#g Ì%ãâmq6Lв7l#öqˆÂ&(ü½ŸÞµ¸oþn€CÌTÜH19\¿HzOnšÐ ¨çi…4‘ ž¿A‹´TׯP&ˆ+»¢ ƒÏê#K_wÐ’×âežSß÷ð{‹õeü‰ìNŨ´;ù5ŠÉz<ï»Zò¯ôâ›ä-_g°#hì¿síNžKžg7ãu?d¢GG—ç½³—Þó·å¿×b¹A3ÚyâŠâJò§ëQÎù ø}^ý-å—ç,Ýxm Ô«Ê&íxi»Žb@m }d«~ýEÏÝb/Ï:*(ÙcƒÌâVOºQ5g:ÓæÛquÚÉžr‚ C^”ãÚ,˜$¾g¬¡~p†uõ„¶Ðؘ&'d:GF)×<ùE9xç¾1÷ÎwЕŠ÷( ƒgx¯AµvÎË"CýAe"‡Î¾D½¹K%ó1ÀîÅ"A™õØ=éñÿñ¸¼¢ÿåzù³ö¹¡gÞ{ý±Ìèš"¿k»:Á­ÁSNñ}ÅXMZ¢5¾›ŒÔÙŽŸÉìñú/ÿŸtožä<ð Óz¡+m@“°m{/ß‹—*óõäåT¾ªúünè«‚‚ûˆýˆÇžÂgâ=,ù¤V£2<ÍãwwÓƒ{Ô þí3ÂÊoiIýa'_å#qò`ôÒ¬xƒÉIVôj·…lŦD{quâØØÅ1pá?q¶­]ÁÐYq„ît¶àhçà„â¼Éð=Û hµx¡ýz@AÅÏY~¸Ã7%pÞÃ:ÎåêåøwkG©8vÏï$¬ì'>hBûö;Ÿ=À^'©|¼Þ/ðU‰´Î™qýŸ·Ó¹dùxMKth­€ÍxyÞL§]$7YpGÑYýh'mÐõãW¿ø–3ª¥ƒ‹vŠñ&áì¤Í­½ôüÑŸ :W½ ýÃÿö3öùŽŠôoÒ+´§‚\Þ¢›î%÷Ù,7ÒÃ)ÀY/¥µ1p^bõN‚ìÌ(‡f,~}»IR-öJƒ¤ ­·ó&½]§8ƒ‡¶èvd€öìš%ΤϫM§N+V{N҂؃±»NËH|öˆ}|×Á-ÃáÕ9èjdX5ô¬w,²¤¥œTùYi#XÁ Læ÷<­èÛsEøÌÂ/ºLàûd4™é<ÏÒëôpý>ð\±<“ †°¯'ÁРSm¦v*èïà/í“|Û@ŒÖ‹Ž!Tìw4IÌÃNéé >P~µS°[Ç.ÍáGƒ…\ »BÑé K™È33¸Ä–[œ™O—tÈtÐí-ò\¨Lâþ9vŸ¾úQé„ê7!ÈL,Vî¸&×ëóM’Œ,¨?gž]^Äöœ¿n1Q-ý-DVF“ÚØÞ%S?îÉ›Î?Ю”¿Ä‰´#:tDU9Uœ ósÚm`ÔÊì*`GgËAO|Ï 3Ò¦&‡XØd8oJ$3Ró•“dB¨B/‚0(’hq¦ðƒ€ rª˜ î)œ²q¢¬GCA£€S©!‘csu¦ü52oXBƇêCè¶PÌÜEg¶,JŒ”¬¼’ÐùüH6©Ô X>Ï­ñÒúÍx7GCŠÄfªÀu˜­Ö¼$Hk SÆjÃJÎ4¶¨´B(Ø9 ”Lg€q>·†€„a†›û UW¶½Ò)‰ F `ï³}z6çà0Æ װĪz·Ù‹ßtj h 2 A‰<«´ÂÒ ™Gü-‡'xžPà 2 dBõtƒÏÚó?cÞ&Qz |i0*8½¯™)fƒ{îѾˆ¿eF3ßU:¥žmfxhòeg% ¨¡cýŸ×ˆRHèì¨Du¥¯6] ¢v3›ˆÑÏsñ^ ¸BüM£Nj%™÷0SOa¯²2Û.{8ær¹«°…†ÌnÆ[Ý>“™îÖ¶ 6çÐÊÞ@ª´f¶š»ëÙ„±‰ “ Ç *¤yÑs´fé8£ •À†DÙ9¼õ¬Ü0Öï¼ìÚ¾[Z%ÁB€MºQ…ÑÁjuR¤QéS±8¨ <Wùš…ÆQäÌ)° ¡/”¾Î¯ç¦Sé>Üõy4P£uFýÙkKfâH,.œjƒzM*Å<ÜcÏWÆGðsMÞÅéåÔ7L†påìÿ‰ðг@Û³1ˆ‰ylÑh§v Ð]ÚÛ`/¨`Œ}ö…ɦæáŒðºÆHí:Ôø¤#kg9ñ<d•;·ÒÿµOËÈ?ÛK¹Qøö“#yJkî›Jßj¼É‹Ñà0$ØG—.Ýh$iŒI—áxs®òº²é%á¾@à±o=Ž# x{Í ¸B>êwt.äÀíÕÏ.ãŒ5Ìp~0¬TB¼…7˜dÏÂiî°‚ Â}4¢}íú¥ kÈ–X†!ü®“eVËçA9/™YhvFpŒrÏaÊS·Õnõ¹EÃý»Ï«sá=u¢kLæpL§,Ôë°”ø»?¸¶à™0¼Xç»kÛ¸šà‰.S-Šb×Ñ0°«9€S&ï¨`bìÀ‚Ê>ÐâÜŸî6÷›{ðPUCy( ‚“*‘{¸‡@²Àìw“‹õj÷¨ ‡ ÀžŸÆ¾ö3š*ÚÞQíypL‹x_'“º‰>}ûÕ·!Gõ_´Gšdg¿Z{ Ÿ°G€&Ãz†á|òSg£ÿ&¸'OOÍѪ9!ï\Î]*¬0̶íò»€Ž:È_"ÀÄó¸ß!Óã)¸¯ H‘ÿ€’~‡ür†sÙð+»µóçœkSè7ùy~AnðÜ£É5ùK|8òÒ£AåÕÓ'ƒ^¥}çLÖvíPÁÙs=ßö?k÷°»A… ¢<]&ØwÎz¬B6yY0ÈÀ¡3(I´©°è¼yÎŒ3õz¶Oµ#Ö`j7AÒyd€'{Ø/ÚÆv{c =‡O‡ #pëL86 Z"…þj\e€%AôJ…y¿«o#x;Lp´DåÃúæVÈ4;%˜èb{ð}À¸7ø-Ê^Û¯Ú~ôúÁ"5vpú+ hÛ›Ð"ø8¿¸È*[io{'|MÛí²±ÑÁâüÂVÿ3!7µ}{®ºXû}º2ìpÿ5®5KPþö¤Âõ6͹º{Ö»ÈøøÍŠüõ5f}n?¼?d}´‡çÎ$ì˜P³³»í‘Fåzù¸ASí™……Eè”–ì¼®½aBˆg·Ãóh‹¹ÏÒÚÏ®ž˜„Ÿü¾Kí­Ÿ¦òmk{‹ÀùÉ3ðˆ¶£-í£rš3:âœóø7ã}îÉ>5ì>“"ì a'ˆõ5:…x¶C€‰-V¶˜àc`ݽµûEޤÁ"ƒ³'\ã™1 îî˜DP ÈÓÊ8k×0j×xÜ}™ "ÈÖÃ'yVŒ›T£ÞÝá9´EM¶œ yèŒ$Møvh‚_n’™ &íèÛ5é9 ÕÎÿýÕWáà €‹‘l³—â$}ì«•™®¡|¼AÅrÀÖñMt«c ¨W°­œÁxKEˆàÿÖ|öÎ`«ÃmÛë]‘$mÁ•þ® Ë݈}—íÈ‚e‡L¯q†V”Z•BÙ–'WÌ"Ô‰î%8_¤‚ŸŠ¤«³­T„gÄnT¤‚áú¹-ù,¬6Ã@k´ñÖå™.i7œ£ZÞºD…yHƒÇùÜðØ8‰&×ÛÎÔñ V:“œNq €<6{›ž¬<‚7‘/ÈËa+~£zGªÕ׿‰ CåÇßæ—zà}ò÷ÿõ¿J»$,hƒX1>Ìråš¶„´êÌÊ]èÞyÃVW•h›ØÇ¶”µÃ‰­µÇ©b*à0ºG@pž3hX­’Œ¯Õ×<³t±ö‹~E†½áGQœÁ™ˆ™´÷™ÀœU£I#V$ÕItäŒ'úHØ£m2ö“vrÛ$Xý8u¿í>Õ‰ú{t½ü‹¿œ§µlµBB¾ô:¾—®G•²­Ó‘·$ëÚÖ¤«õcŠW øÅ1mØ L²‹ ϠάcÇ¡ð 5_{GÅÓÁû¼·‚Ég6ež ©6œØ‹:¥ÕâyøÙ×´‘ ›x ã³C¡3õG¥±m‹,¹½×ÓÿßÒ'Ê¿3PYD‰$n8(Šì·ÉÃî—à5çdµ¡Å<Ú €ÙúÐúJê7í}ÇÔùÜ&hËk¶ƒ‡hß¿ÅÞr¬ËÏÛ…²{ÊÀÝ•{¨.V·Ëk0ÙÜ’ZzÓÚëlºÝwöôEZýn3-Òñrhl0}óIF£èÆYtãé%‰J$‘Í’21ˆLÐ' Æ>"‹gH®ßÙ†ÞùMÎjžªoöoo—à]™ì “y—gæGÑ…EìÙ 2“"dÁü‡tvÙ)ÑÒšêRèâ`ªÖjðxl’$WìøúY ß–ñ`¥¶¶‚7±ÇÖÞ2ïzq–n ‡\ÿ6=¢’ñŽgÝÞ,§ýmÚ©rÖ&ð »ÿÁdZX›Æß‚¾œs=;7þŠl^;N÷âp Ðåƒ'3è+O ²Bc\ÙÑÙ¬â'lÚ‰ñôûëÅŸa¿ö÷*¬‡® pNáCÆðÐzû žVÚ0] S绽Iw*æöÂW1f3ì%X0øAõlï 8$]3ôÚÛá}®¡-.ìCpa‹ó£êuª²À–×ê H Þ à1`7œ™ô«.SÇX-l!†•ºúógؘ`bg(>ÆuH¾&¥ç© Ÿ)9oyÿé)]ÆØ+¦´ÅÄpä “•wòƒx‡ übYâ å-e€þ‡6êÂÜ,÷%øoj_)[|.¯­}7ˆ­¦ïyÁ3Ú9£M÷£›[$ÃÎÐ-v:¬ ‡ÔW&}iëî¥Iè¬V;ÀgBWTÊŒ 9:Æv§Âß© y9È^ƒ(°F9íÒyÝ0À™Ö¯´eñoÙÃìñÒ>/ôU £Ž*:C&™Ä«­coyð/± ƒ¬ÆTœÏ¬¾‰$$ä_õXxCÿûî{øYåþ*;Å;ôqìáþ:ŽHz×ï3úó¢wÐn÷Ö° K¼ßЃ‰ï¾W~Ü! ôðÓï¥ÿþþ‡ôX‚…Žb0ʹ¿þKÙ‹ŒÅ'%QAì?øFzɰHlöC[Þ¹3M¿ÃWF‡ldɶA.3~*bЃ>‡Ái1}GËÙaó<úœ8³s eöÌkˆmKÿÚ%âÎØ!v9 Ѳ´¿IÇ Zš™ˆ³°Ã‚kq\“rÊ/‹J8¢ðúI~`Ë wgØŽŽ „é¡Gªæùi ;ßà·‰=òƒ|Ò‚nc»X³Ýöúñ¤Éåy±ìï™ydI9ƒÏb”\‰}S‡©WvÑÑâÃvuŒNœ1x¢zLùÙÎyEàŸÎnºÆ$“V¯Áú»v$Ó–vßå­HÊwm⃘h Æ ]£ÞRç4ñù£àHÝ­ˆ«)'ü¼|j¡LŒÊÁ´Óë¶¿|ÅõY'4ýñ£Ñôdž1ɨ1}È}:gÑzÝ„ý1S[ ‘¹£túâü®9?“»ÛØû~ªWGƉ£š ý³oÒçO&ÓgÒ?ÿw¿H¹²Wœó Ÿ ý&67ô0ÒË¡7[âÿä/¿#o!LH–!aD¬Ô¤·èˆ.‡oÀ#v¶Òú«íôì‹/S¡ø,}ÿ^¾È\œ÷|`ìaÎM^­S±¬íýåç‹Ñéçü"-õÁWú«œÀPð ‡í!øàƒÅâ›w'<ç'ŸrMg}Ûý„B_“bªõVzºÂ¨%b«¥ /«öMÐÕS†Ê›Ê+åƒúÜJi;PhjjIÇvf²Û‰#^¯ÂNÿ†é7~'Oõ§-þRæd1-ãs¡N2y€­#Ná8#sÎ/èD‚©Ý ,`2©w™±YVü+gÙzaÒƒ?kƒY,a‡?ÇÍœè/`ãµaËU‘±=Ø(&¬Õk({.gå¹Ymã ž$hÍ :É2ß ¾HĘËóYÏAåjû·1¬Óv±Ó‚œÒz´Bc=FÝT™—ÃÁÁì¢% ™Y]da *50LÖè@‘8ƒCC¹·s !ßð,yŒSyÿ »'(74‘Fû¡-äQWÏ üEpjF%1¸TCPæŸÀƒøÊ¢VFBûìÎz×À4ð P@4ËYˆåV>>Ïå\Œ^þáXB?7ÍZ[l[ЉـÊÁ4eƒmÌŠv–Œ×T©f²‰œWeïWÁkd«DÍìÊ!7ßËiÌ•‰Eì¿íwZïŽýpt6¤ÌNÓiV^H›>¯ˆ:| äœh ¿ œX®ŽºªLf†%A“ÔFI68 ^hAšt^¬¼£òDÔÆ³pò5é܌˾wArî¯~Œ@;ûÊÆ âʈ<e¢NƒûhB”ô+]ë\ú· ¨Èöô†À–Ï%¨#=ÉË®O:Pá+Ù›ï&xOŸÑ§ÎVvŸóé<Ú/rèÌÜàd¹7âìÔK:1&H¯Ò›U!ñ9d<õŸw’š`rúY]-˜Õï U=§òD©ÚêáÐÆü˜[zA¢ý£ï¢5l‰*èfãw®?‹»Û›qÎ?úÑßevã+ô¾-zɈF~š¨x†ák[í,‘HùÈY(9›wÈÀ0 ‘/Ê–Œ×\.›ýs×DÅV§œ`Ãär¶g¤Ú¶‰låu7A˜7¯©Ê-´¥Q[:b“„ÌãtµÓ´%üRß)#•×òœ´of³†® ƒ¶‘¶í•œU¤ØóÌãÜû¤A–ýÁȆl§`9ô¥ò’7À‡™ü€<ƒW|­{ y íH³Ê^çe´GCñìGŒ'i›ÊNdÔÕ6´ÚJCÐsbmÿçy…íd%ê™Íþè¸0â¡r‹Ïò¾sx¢R9„WM(À"@{\.DtÔõÎŽl2›Áˆão þà='¯IÎsuï²½9ÎBùä>˜M~NpÇg²ÊÞµÛ²+F‰ð(WÐ[ËÞÒõF‡LÙŠ£â‹}ÒéÒ&ì`6¬#V†I”5Vb°H²¬±{¯ \1—sÓ òC›…=<#¡'@ed‡]ZtÔ/qzoh«Þðês97ê”užho´ÉÇœñy톀{ä8“šÛëWY€ÔªÅ‚ï¶z½‚6ïš'Ò+óµwµË[È£³ ž )€-ٕȽRÿ"Í9*xN ¼¦gÞÅ3_œœªò¬9ø2PdèŠH4AVÖY )å&³ìy]_hx'Ùyx´<£B€µRðmcMyÎʹzQ›ÂÀ¬vûö¯I÷¡Ë´´4‡¸oÒªnG’ìõ‘©ŒêÇi˜Vq—ûâŒÀ?ÓÈWìäáá¾ôr£•þÍf‰Ùc™C«})†¿odº‹„+Þ9lõ­" ¨[ÝU¤ÓÀ­=9À§™]!oršðbf¿*}¿T‚ÛÙOBHï {B9` û¡¬aóÞN60‘Ù"l>ë“N è¨Ï¼”Îy,›ûÙOÕ2Mq$ôÇ Yc“ í쥞êö=ô3²ß³, Gèö$]>¾?™¾œ'@ZÚŠ€æþÝéÄrªŒ \nü»HŸüàûÃT lÐýy`ëjƒ,[L÷Žø¹Þ ³$¸ÞÁCÎyÎÌ ™S$àÀí#qBÛp`ߊG-†úZ­«ž/Q]ÑN ÏÊ Áçp,iÝŽG¢Ó:ÁT…Û ë9=)¥Íö4ÏdõÐI¦ëÌ€Ö–2ɶÆïÛ›ÚØÃT»•ËÈKø“½TÆVèÈ€¨ 9›#{J+­<òF{ç¬Fëç÷<¾ÃW$ôÃw‡w^s¦´'¨l`÷ÍÛ¬=ü Áa‚î[ëœÇ$û3ΚhÓ}¸ƒœ½‰ßûÎ YÒgkU¿•Pûå=@¶¯€Ád+̯`µxép—.*ù©~÷žoVW‘Mõ´¼°íôWWßRݾBÛ–{;{[ µ£„ 0/_¿HËKKü›#ç5³Ú¿öö&wékîïí ¬BEþ#ìîlà˜dµ³E¢¯Ù~Vðqg›ÙÜÈgç˗ήÊé4IÐÔ¦1ñÀùºÚ0È6Û›ë³êKÚ9ïpŸ=€ïQ-náÈ õï%`£×8(å¾V®óûíþ› ΙŽ#t•«¢7¬¸¿€¶±7WɼÝ'‘By7n¥üb÷•3èªP3QP_ÆÄN» TŽ™CÎý¾ (Ö½‡mó«•VÚGçEâ6ôtJÀªÆßÔ£$bÑ"D0`29¨X0ø§¿`EøŒú_º»IV›$è /DWB ÷Û¸_Å;á+u¶ºH^ž©ö¢Õ"AU“^µÍoèÀeeó5¿¬¨3~ÆB„©çð”&Lˆíëô»uŽ$ ðœ¡Éà33£$˜˜žA_A;3$-÷òùA»m®ß¥l³cò&ú·øÒ€ÝsÄž}ô{Ó•þê'?!°ù*] Ãÿî”Gþl€Õà‘ÉÈ>›•B¿&Kœ³ø–mžõ‹×-z°Sš÷8'á^¹ãÌGûì€ç{µ'mëŒÀVS¡†^ÇÎ6ÁëŒä,“*lóÚÃý,°ÈCôvoðZ;ÙÀÓ3$Tpr•cý´×/€ 4ðù÷à¡‚‚Òð¸•dÅaG"ø2A‚1/3ÃØ€éŒvÓ?sæfÚÀ Ú“€`¢‚SJè+ƒKò®¾ˆ¶¿þ©Å (²Î5h›„üŸöÐq€ò¶~ªû¥Ý8=5ôc`JšõL,$&$“ªÆä;“8ÖL>ƒTy&úIîmãÚ÷ÚQ€@7ï3U¿Ú‘AV¿K]¯ïf…½þšûîg ”YgKs×e¢‹öÀµòÞâ|bL&Î(׫ò:{’çç‚þ<ç¢Ô  gaa(-­øf¿ûæˆêZ-¹§‡*NÜ¡“ç7F º•ÎÐMC¬}yq,Ò}ìhk¹E¥×íÄ×ï\41¨52Ëý›“´Â8Àå‡Ó$*9: lybðiù&-’¶w@…¬g|liì½~îpžyѬo~²7½y±‹Ð“ž=AÎËi[©ÜÍÌ-Œ‡ípÝÝT*œ™ŸBæu¤Í×{Œ™&ÎþT‚ÞjœAæ¥éa‚ê{éò¨–ú 1‹ L:‡V† ~ãóˆ•ÌO™lC"&¶ìï¿:ìçÔ»À.•Ýy0ÐÅ™Á´8k7.’d ™ ¤®uŸ«Z…@ؾ©á\qæ3y/vçBò„¤Ë™q°èj ^`<.4#ßÒ̓¸k«;©B¢€MƒÕÑq³¼wBIìÆ£«ôæ%ÏÍ›P¢€ÊACÚ²Î8.N¬‰.×Î0a2E›ÉR<§£é´µ:Ñy£#sa{˜l£LÍü:ìYx 0f®Ÿã½MÎÇ""}¥Ñ±y:RÝ¢3ÊØb­ÜgÐÎáM¯è—÷ÑÉc€€°Ýü¢Ís?2QÏjéã#éó•GC±ÿêà÷8F`ÖÜÏ^¼/¶"vÐÁûËGTS³Æßùh_‰]Š% ‚È'&²‰hË:–¢ÙÔÃz+‡úAÈ‘"|år°Ý®á }š1ä>µÝ·rè!õúï•oLxè%ðmµÏì^}–vôÑwb^&cqCc¼.F]hÃjcËkJ¬Õâ>ñ÷;FØòYíÁn|¥ÙökhbÒgR6ˆŸ:º)dziSŒ'õIúú«_0.e+M›0²DÔaî—Ý$[È$eˆA¼ˆgh>¯™Õ‡^øä·~˜þÎïþÄÅûú–žU(þÿ¯û³ow,Uçòw“çlK®üö:Ú 1fÖD9íîÂ(û„TG—“lQ&°]¸·d~@b{ë—¸„‰A—$D\@ç}}t‚‡cÖ08œ|Šaïó9ípxH0~¿DòÌÖï#H‡Ì­£+MÚ(Ž-Ð)µpå±ã´-Z4ÙZ ÷_p;^å8ÃFR~w*çÑwÐt6>ÖÖîÙh שj‡[¼›ìf—¸¼nX·ë‹1«$eI´¹Sm/i`wq]ãíVä‚£Û¡J¹¯Ì—‡ä·èRmetº’PßEŒjiî¼Âô¾mùqè—3@Ì-L£ƒ.±­ñQyhe‚Éà%xÊNak¡S¼…ÏêsÀþq&kkëéñO‚_¿úê×1JɘÞãH 2Ihz¿{"~z¹zDÇ:bP`¾‹gÃç ]&ψþ¨U踽EÕ1ö ë½3“W6Ø9íè ÜïÏ&Òÿôû¿•^õzu7}òhžèNcÃáwÜš\Ú3¶Pñ1za|& ž?ÿ%Aé¬ï9w|Ž/Kç+¦ßýƒ”~ÿ)D»Ï~Šÿ2‚_253<ÄÿÞÞIÿöÿ0m’„5=Ö—žÜŸÀ–>Nÿì™ò£SéãéAâ#Y\ÎG;l«§"´c|¢š„uS7rp†÷Yظ ŸÖ‘~ö«ÍH•è ÄÎ»Òæú.Á]“ðíl –p„,#Áš–gÚÃþÀðàKܤ9;"š@glÒ®³Òd/Å*ˆ"Î9…ÑWÖF×Þ_Vi›[°hI<í–q‹âÞ°Šù[HV…ílÜÁ]“ÖεKÄfq9¢ÇâZã¯Æ/» `ëÿÿ󙌌Óçï¤;ÀuãØyއC êþêŸ;©-_„³Ø±:¸€Î+‘ÿòć&(|ØÚOmKà Øwv',€cU5×`6ÎNÚuôEyÜÖ(ÆV@— Q`Å`óì…¸|v÷µƒç0Xýqq…NaÜ‘E…&ÅÈ™>‚êøËÇ%º߆?Yè%±Y§c3ÝÝ=@Ö4½SMÈ3ã® å"bRe(Ø­÷/Y7d2;JZ Î ³Å˜•­NÚ\Ôì”Èjæs#õr2 Ž“À¬Á3>w¶‰â7¢¥­Aöv &•³!‡ÛŸÒêK0Ѭƒ³r¼Äa€ÿ˜*¸ ²&Æp œ˜iç:Ý$g;ÛÇÞÀ¾Êöà¡«l íe-øxfŸS"Ô ®“Ù¡ó*1ÙÆ@F3aÛ܆¼ß 7ÍuxOŸ¹‡Lë7Ûµ4¹ žCÇÙ,•”ŠÑÉ>dÅ Ò¸…¨‘¦¬б‡ %ÒËZm£«’T #kBØù 2 Ž5ûÞ’íØ™CfvÝ7Îì£ÙR¶²1ƒJ'S@Ï@‹Y">·í²"€æâ¹P|w=C(6ÕÕªÙÅ…`>ˆ Õy®¨àå½¶íð}Α¿ãï<ÿʰ& µƒR‘‚&0óS`ÔÖÕ‚€Aæ· ¶*æ9ƒ ƒ):±Où®UñÌ î;™ê|îΪn^ÑÏA‡'!X[(Í ÏICÎï:P¬ŠueÊ%ÚoóA©0ù¬´((l Æ šlfµ™ÎœÏb@Ï ´Éü#KÊ„ ß{ sîe ß*=’+4 ÃȾ òW‘ø½ƒï¬„É® /.讋Àô5†ŸY%å ©€5¾yÈøŒA» A%÷ÁóW°I§‚êf¬«˜ x,ôÔhÖð´âç–ó²Mµ´{-¯€6K¯»¸@x®I'ÂQèü)€ >#Øß™'ƒšç¬žÐ ÇܳìÁq‘¦"€ç©aü´Ñ¸£ÌÁ.fÒá ñ*™ÆeèØ=G¡³/¶¿nÃè»êþ–òŸNÀíÝ2ˆ 𬪣îÆw =éÌDœŒ_Ìž¶½Ù¬OxT:p]Ò”AÔ¡¼J9€â1ù¢Û¤¯ÏïQáÅç]¿teÄz8p'×ÂHeŸÌ¨õÕ÷‰>P\\+½¼‰À•¬¬Aíš¹dÐŒŸ÷ž:Y¥³Ùˆpk(õ>Ò©¿´)iš´ LØò=~ò™´´Éë®=è’óðÆ>ŸÚGy.ï¨|#+Ÿº6åk’æäýʳî]\“åë$©ÎÞ”kp\$´¸^g ‚ÊÓ"¼rÂsæ\ä{e–2,ž‡³“v=>t0?S9JVöeMÙM†¸òýð"2Ç*C`[¸ô²~yζU'dú«ƒL°5ËamMH-fL¦X5Û@™§ ‹ÉCî{©ñ¯²T^º§¶€vÞÑ0ÏHÖaRÐ9DyÇØ 4æLó7K«-ux\£m¯ÕÛ8ÒøC¶a˜™° î°êHtÿ‚žàSùбߞ¯Ý"£‘Ó¡8?4eq® `X}és†žk$&Ä™ã€ðY…àY^7˜fÆw—×ò^¶“o\;ä×r/Õ+ŽÜôá0ÓÆŠt´ÅŸ K“ˆ4’¤+åÚ0Ætd®³~Áxìí‹ ô2¨AÐß¿)[ÕÃfþ]¢„d((èzLS‡É:ÞÚ/væ7öK6Õ6<ï,‘7Ø7x¼ðÿ߃8„Îõ.aá̲akä§9**5¥ëµÕ7±SÚR'e²>¡g¿”7`@'çì«¶Ÿóôºº#ðáÞ:‹p~q‘ßw¡Î쬕ccÙ<Ù^ô‹€Æ“©’_[O¿úÕÏ T1ˆÀó-<ÖN÷Ï×Ä «M²³›ºÊ(¼F0Òq¤B–œ#ûOù¦-†C ?úušI5êÖÐy¾Ù¨Ž ~Ècƒbe<Þˆ8ÛJ»NGÏä8ežô†j §§"K QÞÊ÷™¼G<„,Õ8ïì"‘€÷› 溻¹F`9Kº”þå;h’¬e+ÿFµ¬µE€VZ¶2.gû@6V»AZæé©ùC§(DZ‘ÛÙ§^íOüŽýÐŒ@ŒÓ­"ÓE:Ñòžö’ühÐ ŸÎOК¨$!Ë»ü‚ÓIÒöQŸhcEwæ8$J|î€Z> 8IÅ`ƒ:â„ CßcÀ=±ºNüÏý•Å&"jBpžÁ½áynÑ™,ÄêKå¨ãCLHE²Ç: 0·’ðÚVøVÍ”XçÒùl›Z>Ž¥td{²ŒùuüH]g;¶¹:ù|`ÀìëŽhA¥À~ÉÙ&§pƬ@›¼ mH/\-öÙL÷öÉðš?€îAkÿtAûÃØrüpu¯éñYõ²2^PŸ§ [ÑuÙRºq.é ²Ã–‹Î(ç-ao9ïN‰Š#+•mÚBÒ²6€kdëÏrHMŽäÒ²ªu ÈÍ©·onÒÊ3ì_‚{çç_Qq—æ9MäP:! ¨ÚÁ¡ããõÔ\ü"]V;{N ªpzjfžî+Çé €»Vàƒø¥€@uQ?®’¥­<8£å´Ýœ‰ª}„+ Ûòï ?¬“ƒ³³ØØÕChYÇ›6v·Ð·tku;6¶ó¢;س=*³Û±E:á/»Ï¢yvEœå1Akå[¶«•X«¯žså@Äú›ð{À™NäØÍÁêÏÂJëÍUì}FÏÀ&´¼øî+侌gཻµ•2ÚIúÓç'é’¶yaÒõÁåužy…¬2Yè¸ìXè[~DÞ9Û¯ü}{д½AQ“:¯©È~ýú%gB·tT¾:#ð¯ì´[–Éöì§û,XísrTާ#HšO[ë¯ÓÏYf,„Êb¤'ÖÁY€rmó2U´¶íCHPu†6c0ø|ºb€6š¶£ufº2k”Šôë 1†ótb¢#I~‘lïÐ` VÄÕÏ+Ñ ý-ZÂrVÚ"êSùð„€¯…v¦mÅPZ|Vlǰú*F pMÁ¨ëKä4¿µµ½P‹ïor‚sfõ*Nñ«Ô]èÓjÇQÉÁ›xNp›Aä tQPŸ‘8Ÿ²wðDK¿ëÒ$ÆG kÛË® :œ³~_ì-Ôÿà#<§âÞîhvâJOüwô’ïõKPYBT? „êûës:ïS_@ÞÌf^£[=?Þ_è[Á~VŸ™èäû—ïÑݬ?í­¯CÛ&à£ÏnØ{lþ‹+ΜçR¦ðR$X9Šm¢ÿ¼¹¹‘?ýš­Ã+ûéÙ³gé{ŸÁ} d’påLvñއ=@7°'î§:Å=ð‹—âÿ´7½×ßô—¶¼{î|чMÒ~öyúÓ?ù@q«ßÄËôq&¡YþbÐJ_D0ÏÅ*‚•/&…ýB[?x‚ÊXÁl[…ú¤û6¥ ý@ϤÍI«#¾è#Û¥Cz›"1ÆÑ4vÎ1Ø«ï0ɹhw\"dP_i ³BâƒU¶Új¶×Õw©Á/ÚTvÇñ ô#ù¬ wÚ¬&n·w€ÝU¹Dõç̬ü ­ /¥ Ûu^“,f[oÕkt“Õ©54‰¤ZÇÔX{`/œ©ã®š‡Ø›M’‚Àôs³Ù°øpð•8ST¿‰©ãúÙ“2ôs¥5¿âØ]#:2’ÂðŸM<ã<®Ð ™~Ž#=ñ/|fž[¬ÇÄ(ùÇêK5©úVÍʨ =ÎSP×kD‚9ç׆l‘­F4AË.Úò—œÇE=/È/ÏÂ.%Ñeçj ¼”ûC¶ •nk¢ ™éYžŸ³Î† øX­Ìšu,ëPžY4•3jöõìÂâTàšî}ž—ÿMø–v&I2SQß2žd˜`Ð)¸ 3AµUÃFãÌM⽂g}αy¾nôÀ1•ÙÃTML»>ŽV¹"AÓs&× abßÛÅ yÞ‚ÜŽš¨ƒ1*K«ÕË4Kżþ©•å7ø:ú*ú£Åaô“£î0>9»\IWÃtNÀ\@&‡¿©j­ßõþé«BÜð»aT+ÏQ…ìÕ‡Ð@ï¥Ê9p• ]ÎÐãFÀkÑe&Šex¾C$‹ô8¢â΄©l$× î¼nÒÁ¶.x¸F;ÏÛL·U½¾ô•ɨØÛÊqåªëoƒ¢s$tÞ­zGgýòS’¼®øªˆWÛDkÝÕâÞvª*QL…¼°Ë3TlÐÌäëFO™lo`M¼ÎD‹“c$·×6V?éKjohŸhÿ58å®v¤Áa+9ÅVÄ…¥kGoÜRõ"žö÷W‹¹É#êGŸQÞÑ>°›ž³™ÅÞì¬éw;t•JGÈ q×cF?s4tŸûïkV7IÅ=ÇÎ.ƒ„íl8ЖNϱwÑ_½à19ŠóŒ¿!vÙW’Ê¥Œ¯aÁ€öÏ1cŒÜ“ ݯ¥aiúÑžëìv×膿Úhg5 R?þàÃô;ï°ÇŽëƒoØ¿"]Fšè‚>ü  ªØ±§\ŸíDΖÓ%ÉÊ(÷KÜEÁâYµ釿û÷Òßùûÿ€j{â1|ÀÄõÆýº‘k]Òî翨p¦Äe8S;?‰×H6@ª?áNDªuÇùwôh‚NÞ¾zÞ²7â[ê% ÛËöÒƒ:×q³G{;°þO”ãu: FÒßU†ècˆU;©b7ï½z‘ÆW"è¨ßÑg•xrV|Q\XûG|ÙB)ííì[xê¤Ä^àYÐBv…>Û¬{X©o'¸küŒÒ¡ô—O EGÚ_óòù*4Cb½*‚Ì`òú&«H㎞»‚~Ô ×Mõ'1&l{éÃØE$†ðŒÆ‹º™®…"mçge¿xG¸´6’×q ¯´¦¬h²?Êmmq÷´LªæçWœ·ñh–sW^™Èé ê©Ùy®I`r`[~/ƆIc{»Ûà˜`ô$œ½^-Óþ|8}ô°˜þü»i˜uY ·J—’‡f±ù«®Ç‰DªÌî¿ 9Výg¡z¥G÷ux„ÁÛøÒÐÿýÓýôÛM¥Ï?ù};”~úËWiënù€ýf¼'òöârÿ;ë°eÿøƒ0.œýf•Î8{«®èY¨`/ëT:@*h³oÍFÄxc{QŠÐÌQÔ¡1q`¢¼À—ÀÂØ”À\· VIËå ÕÁl<âÚUªŠÎ.ì$€3Ó¡a#³ Úkp(ȹa\/¤5̨¡/x³?ܳ˜E%&à&3]“Ië÷ÈÆ0  •ÈÌìdòýzm}×Î#Y@*‰62šš#§é3öÚlžSè×,k3ÿ5Œm«¢£Ô©âd¾-ø,ª¥#þžÆÍ¨;$¹Â¶a£ÐKœ3Æ¡ŽˆJµ—J ÷žC¸d3¦Ã¸SÂήۥ-©è(†ŽÙ~ÒMqF³YÜk1WAsS[’ ¾9³Z§Â,D ü;øí×j{÷JJ&È#¡«ÁÂýcæqFN…mŽìͺdNɽÅè×5ö$KjÒ ”.#Yº’—¬ 0W jÙæVÐEškrf‚èÒiçJ~ðó®ÑÙf“Õ!>»-“y\¸Uç—Ÿ‘W¥££¬ˆ’€•ÑdœñhÜÛ@*@JR¡å5ÍÐ5)Å@…kq/üœ T*õ+UN¥¡Ê\@Å3 ‘I**¯'¨=ü<§ë>ê, ¸*ÃÔ#Êd¯Ív…ÌÑ1ìÀ¨Õ—jð“g¦Ó-]-ÌOñÙˆ¾sMÏÊç£R]¯|1`ãÝã•í¾xÖ†@“]‚:D4|$D»T°L.itsMReˆÁœ&™*‚®vxa±/¶6²ÂÆNV@™e×ÁþøO'ÔŽVé(CÜB«|½‡#”ÑMB6Ò­gÖÐ qo”û&U¹—-΢GP—Ï\3oÝäþ^³=T/gF:{Âû­Ê€”y;Ïu”cž÷mbÚ1xxüÍï<·Æ¡2Z'HÚ¶“Ëfp~i¡é>Ø"ôŽ÷GÒF<— •‰*d¹4cuY5;­· kõMœ×ò|"»9äàû™p‚‰JÉÌïÞõ7ëËçòV½ý€,òž<,œ‡žýÓmð¿ÿ³šîML¥øÿ#ÙÎTëmoA3ÎÿÂY¾÷ ýð¿ûoȸÿðQTá­¾~ >/Ç—É:;5t¾r¥]|†±(¿»§­ê —Ï¿ :‘(å]×·¸¼DÕ!Õ£1Ϋ5ȰÄßLì(c#ˆÒ!³2P W>±í®tª>Ö™ÖyÍc¸+£ö™1牭CoÊ;ç¥5™£$ý([êu€ eüé¸ O^ÚLãí¬ ^æ»­Ø•õu‰4Ì£Žàdžyëÿ¸€Ïdër|ù0h•ëš1+ ¬ytRb |*K$4qÆ ƒ÷ã¶Þ^Æ‘á:·&¶¹Ä1ð$}›°é¬ú J®ÉB\‹g#œV«šìUð˜¯Ãsˆ%œ¬Ÿ´·“Ï>ÈÃÌ”•OydL¸áÈ(d=w»m ı§¼;…0‰À ¦³e½¿·°2߬çDÂß‘ˆÄßn›´;î”â~~Ê ÷RÙãuîò†ãáù=Âþ]÷Z!a2Žkö½¶E& ñl&×ÊçÚ?ˆa^·Õï#øbRCWv'’êre¥‹V»‡¾ã³‘|Êžkg¶dD· [®Aâ¨A!×ÝGøô÷È"KZ}xsCwÖyÝ µ,`YÌ´æº]½èôDžYiÃ"“Poèc·-GçœôöÜD2†¤|‹N ]a_^›0@S÷kt‰3äx‡JP¯DP}Üzê¢>r´€<¦:“÷û|¶’³[‰[KsãØ|—é53Ö&iý¨M¨£­.0a¶E2\yH¹hÅaÐ4(¨kŠI+V3öb˸× ü®Þõ|%:÷÷òTÞÓö Û‚3Ô×Bg²œ6Õy ñ^?´5pþŽéQi‹Èç^+h›ë©Lô»Â>òË rÍ/ïcž«§\M”d7ÂñŽ ½{_Ð*k?¦-m©  ÂÞ\œaåÐ >ÙÂó3ï £ÍÒ ÐÞo9¶ ]5i±]µ*xr¿JZ¤ònadKZH__|‹îÁî Ml-Ÿf˜³×$Ð×Ö~´¿0 ŽöªÒ*6ÙuÑsÄŒ7»ü©(i£=‰ß Õ[ý52!„yåŸ!çèç›É NaAYº¿÷ðÊ:#õž“Òø‹Î#·K89–}ød°Nß=™†ñö;ïL{Å+ÙÁf$øMv-›T6½Ï{¾š»ùŒÞéGsÁÇ ½ÿþ{Ë×¾öµôü·’ƒ% d>M¸éFÐm'`0̧§´ö¿}5×™ï¿ôáß~ûoä üSõL·…+WêÆ0-÷Ûg>û«jþP¾„<‚ñ=NõñQÉc?¶Ö4|²ª³}Áé­7߉Õö3[{WNÀÓUß+hÑÎzKø¢Õú‘ìÀ{uz”³þac©4Ú—“‹íSµ?‡¶*!ï&·žV5–{2œ]}3_T霤 wâPuYnF;ö/æ, ì>v-W«î™„§966r”VÜr³£w¶moÜÝùí¢±]/£õ:8·˜=Ɖº+úáW€›auþ™®ïi÷óMá¶Pg:öøêK.¤¯ê¶äJ¸’˜÷¢v”¥`I~5öâúBKá3¸Æçžà‚Üp~ÐíZ’´ÂÂÉz)ùüEeFOmÆø±Œíå(œ±o‚­ª3‰Dhàyvr3ÎgSRl68ø¡´¦«c.­øMahÍáÒõŽf¸VòUk¨Ü«î-ýJŸØŒgÜmOöË«ÍmMBfW;u`ÙZ‡ŸÛ7k7ûÓË­³£,’3]¿½Îh:~V¥Õáø-yn\ÊOsíûÚëJ­ýëÍ ŸÇÏÕÆ_|™ÌѶÚrOãùtàÛÙ·>¾½l¿˜ÎÀÿ?¾PŽt><Âïóµ+ˆx´ªÆUÞ»ûlù¼vðlo 7O>£û‡ ®•:Þþ0>záÚƒdGºLbpKIŒÏú<®4¼áf8¹=çøÓäâõ/*ôê|î‡íß–ðc˾tž”¶çó6:÷ör98¢©§Ïâð`Cg§xasRõkÌ×ÓÏjÏ»õ⬺wt¦ïÁC%,ôþþÓdꂼÝ{¯¹{wó‰\ýb|ÉÛòïÝ-åYÏ¢ãl‹—ß8ÿdùð3r!ìÞ¹?]AR÷ðÄd©¬ÞRkÝçÑø½[pQ•´ã¨:¶.\{^°ÑÂw!ño ¸â÷xËžŒüG=`x7?Š6ÁÖ&8¿-[þüù‹#ûá<¾‰™Czb<}gÉ9x<ÇÓÄOž<-9;8 ¼LÅüèœôçtÈ>㘎wtÃpÖ5äïÁ’üÀÃøÐWÍÅ^#³au6^OÞ¢3ÏÁëù;%”O£ÎÞ8² r -;uÎ@Xñv[]Û³•Jn}Ø^7­Š±·•ÄKlèsOÁíxom_n–Ðsáíý†Iñ/) ¶›`–_jlµÕ¿_Žt,d,vÈÙ×Nµ®µPŽŽº;ž¶¯±ØaºÇ°m$áño¾°î›^!Ó-{[ O2Ê·ûw–÷Þÿ•±—Ïœ9Ö^¹s‰ …áïþÝÉÔÖÎ$×jíÚÑ«nP ‰Eà?ƒËŸW…L*0¸[ð§yük½‚9Üúìü…ö þ÷ü\ϯSAÉôú‘¯érŠa$à\¿OiMÏ¢;] Vû.½$˜Iäâ{—€¼™nÜ—ã/žqƒãÓô¯/:Z¬Ë§p8{1~S‘ZÒšò[>åçugú¢vÊë ÷³ÿhÙ›ßC·ñ+ÉúââY/Ãw0gËÃ!2Á‹ŒÐNúQ~6©£Dີr [ _ù®|;¢þÒí%Ç;ÂI‚5ŸšXQ¨^“†Ù+Ñ/\ž„ý®#2’û ˜=Òé©ô±½ ù±”OžxI*G÷ÑZ|{™îŸ.:EѸñ½môøÁ>}¥ðò½À7²fеGìÁ±}ø{èŒbWÙºZlðÓ_.¯¿ñúò­o~sùø£ƒ…cïvôá©úþÚ×ÏTõ}#ÚÌFMüÍO/äk/q¨\—¯”äùôþrå£ÏÒ"ÀŽÜKž£nÏ6ÝÌ>p2üg×*¶ËfÙÿùÿ“—ßÿÖf窟™ñÝŸ/ÿ—ÿæ¯:·NV;~§x×±üKwïí\>ÿïÛëè\ G=¾Tl"]£}Ù_ ãîÍûQvid7ðý÷Þë¾»Ëw>ûÎò—ý½â5W–ýuNú÷ýöãåò'þÃ凿¸ºüÖ¯­8Ú±dpt²+Ÿmhò2„–ø{  °£6ÅÔ6ð¾CÇ—Ãٽײ]Ts¿,ÛéÃKÅCêp‚/ì÷¯¼} ã³nÌVÈæv¼¢#zÞ}ýõh¤ƒlº½óÀ&Óµ=Ï3èÙ’cñ§ÚÃ':CIÚhŒÅ¥rq„+ÅÒÒ1îc±ƒ'üWñÚö]¢žøÂÉ–§:rPwÁŽÇâJjÙýhogøõ2;ÿàPrjwI›Z±óç=|={K&—\ýìY6Q|Ulæ°ÄÁð–ŽW; ›CÇÕ¥Ÿóß<í~Rùj],ŸŽŽTZGÏëaáޚثs!]þY´õökg—óŸ^˜„áÃgòCÒål–L¯~U(zN‹M;²øÌñôÝöâvIu~Û¾CÒ¢hzYZA<ãÞÝì¨ìíiÉÞ¸b¸hÊ‘m÷Š_Må~üS—áñD³Jö§‹ œ?ªØƒO‚͵ÚSÁûf­LL’=3µ5Ápd ¿Œ Bßaʂү¼÷õu®ëƒqê^øìÊTkhñæ\ ÎJFå Œ0 ÿâŸÄÈSî»ç‹›ÏCôkxËòÎWšNºxùúŒ…‰Ýèì›Í»v[D]ë "ŸÞym†j¬/š'ѯ|£¬ô”ð‹µcoRtŸ-€g¨¾Û2¿(ˆ-Põ»€ëµ²`÷ÈŠËÖzA&:Eb1¶€z(ƒÂ¼Ÿõ ŽÅ{·U×fÔbà1éÉj¶{ï šp¸V0öV ºó÷dðQ®ÅXŽ–}¶=f}«V‘˜±Êmc(ºGkÏöE{Ù‰;1ín7Ãåìë§{®Ö)7jÃVP»ûáCV†æ§ç/- BkÈ^–»?¤ÛZuÏ­ÖöIí#T3©\‘5¾Û¡yËÖ £T¹‚UCFJ1ƒžC\{Rœ!'#I!u <•IÚŒpxÓÜ—’É1ü°1d6K8x–3Á»fN ºÄù@Q§ô¿þúÙ-÷ç|¶µtQM­½=£‰ Іb3ÄÍ*È}VP0!ð 1ˆìCÙ7ú\VàéÚ^©$ù_ïvö…­mj¬ˆ.ÅFf’€)Œ~mânl¤”hŒJ‚I6 œ”%*hNØÝψӊ™cìÐÑÎjŠݼýñüG ”rªŒ3µõs0h×VpƒGar¿ŠØÝ ƒË×rW¤ œ Û¶•ø9¶_”OÌK’¹’2ke‚ \£’JÅ9æIˆ²ÚÿÝÉ0“t±/¼—•w¿Žª!¶w¶FM<*› €dnªùîcï,OTŠÞº¼<p}¶vT€LÈÍ{æq´ì LþÖgÖÖ=E•;W>]ö}yí+_]^”·7á»Ùï›)qË’¾‘rt"ÁuoóX†ÛŽhûÍQ”µ.R 2Ár™@µu|²5QöÙ³çñ‚”nt"€#ugê OLw¿ –Rvàâù hpõP8Æ\gÒ`a˜ÓìMdÔþ. ¿ý|”A)£[Vø9Þìþ ræÌ™åxÎ-‚DV®LÆ-ºÏbýäHK2‚ÎZÔïòÕe€Ÿ¯Ê´äí·j`1Fû_$¬8’t½J!YØÎEs%íØñãe\…[ èm1xÎשЯBáÁ•;%½Üï~ Qû­ÂLµßÔ"n†ã<_EmÖ[Aø³: (Úpó‹èýÆk£+éAË0Ê©yo7uœ:Ïñö?·þ©âŒ6糡ZR6Ÿ¥€õ£èÕ¹T×['þã¬RYÿ ™•'õ»à$÷¢\ ×l+¡Õ¥vÓ'[ãþ n{ ?§bP0¿6™s_v4çéªÔ% pðÉD•¥gãäᩚëY ÁѼÆùÛšñH/² ¿&ñÌGÑó¡T|àiœ¯d‚„–C%€i³3äÎ@|pÎ ‹‡â-œ.h“³DB¿óRg—ùo„Óª§½“NÑ™ÎÚèÁÜ᩽p4§/¹?®_KéM);”óJ‹;Uâ âMæ· 6GåŽË]¬÷JJ¹–¢»3ʼn#z{ãIX_9¿rŠª2’2 ~Џ—eN>.¨ó ¾­w›N4è¯ÿÈ5Î.<™SÈ~nF¤ªXÀFfï:Òâ„Â{%«8;qcCEbN÷Æüwù—ÉÎ]íÑÛo¿Ìò s7¯~ºüâ§?X¾zæÍ@°¹|ï»ß-ø+ûoû· °ÿpœÓ\AÎ7ãµ{ÃÁSµÑýÓüße€jõ•󻳪9)öIÛZ–s;wÛšp"å½}Ò2õeôy/í³œR Ò—áɃôÙẑ<÷;ÿthï­·ß`3&DìO{xˆ ê½ ÐÚÂÒ¹­3âgηVãpjZ»VZóCŠ-9àúwå ùC91ú·52h²t˜Èvhx4ð•cÆ”£R{Bù*¡JÞâx˜6ªB‰Ž9(Ñü‘LÔÍGˆŽ)€©UÞàxÁßÐNÀ6ðU)·o·ã9=ÿq×ï–ºô´„ÖYdÏ9½«1ä,¦¸^Ðnœœ—]ÂÈfYÝÆ·»ªV‡÷S¡ÒuCý#»ù… Uë–” Û±Ýü+Ùä½ñBhrgëÀZîeÔIRäü¦‹MûÞæ²Yxœ9 U”hIˆ?‰&Ÿ–`A÷õúj^2✱Þ*âS)+~ ß@¾r(¯2@ë»"K]/Ž'ï­m¢v¿ô6²X#ºŽ,a¼Ë1œîÛ—ƒƒÃY°xk0 â¤Øo±OéÚþƒ7ǹ€äóŽkºŸí Aƒ£ÝZî– g uä6¯öÎ*®‡{öàË{“³€:Åëió½ åËöâùóÎ-|ñl‰tŒÚÚ'GÃìÛéÙÈE²ÅÏÖ‹?KÒÂÛ5øñ0gß‹œ+3;öFmV÷¶¿ü“‹U.Ë›µn S±~(»äÕQWøÿ²ÑœûYÕ²Ѥ§‹ÑîíŒéõ¸–Ó9¶£*À/r~½„›}¶³¤N:’sÛžåà˜wñÀëµfè‚£vwjŸº·.Qª:Ø™óªTÀÚÒåDŒ†¿(¸ ‹wç°W)M¿çX×­æ_}¡n³Ëz)Èœ“, ¿Ì°þ¢$F8¿7ýŒ3RõáË‚ô¿½É-ôâh€Ûxî?<Ÿœ¢«ׯÙ×FGëþ£G²±ÒÇu‚Ø—´Çè’“N ¾ÚIïˆF´GðC’ÏF]“àÈ–:rlKçðÜ}{k›O„«É’„0bOH ,@ߣ·§7sdGk˶%¼¹Y€tî ýèáõžÕ\9ç–«Ë“ƒtœ¾j/wô÷f]Ú^îçÐOÏ\KÏÚ‘rŒ<ïëÇ.„ÃÉôp ~¸ãPÇg'™¹!ÌgÉٯݥ ÑÎô’­Màeg¤˜d³Ò7ZŸ„¾ñƒ»Ÿ|º<(ùAréî&r¯ÄõûwÚþ¦‘A㉞àç >G?!Ã_f'=¨ ü©s*[7xß¾ôlGù8jæq0Ú‘3 Œ¶¥åYn×=ƒ^³¥ñž¶ç·ïäO_ÚýÛ~y±&á Ìšãt*iMí@/4‡õ®ÍGÿ†ÿyµëûìÒÅå‡?øÞtÀฟŽ᪎;KDÜŸî½µ=d«l¦ÇÑqÈù鼓`LA±ÉröÃŽ]äLÕPéu|ì%ºÅ¾’85ãÓ»Ûö^´×͇ì<š#z{¼–3rÚ˜‡ãðœÞŽp@ÒåF¶ü¡_Ði@Þvñìó¾èmôû†_;\õ}pÞètb>ãjâÅ;›|ÁOäÓ9R Ôþjÿ¿ÎýÄòòDç€÷ ¦n ºyVÉþÉ›ýáØt_Iî<*p¿¶Nçcj¯»†/KñÁ$q†·æD·à¯°Xצu{ºncKB•q4ç=ÿ¨b¶V oþéRá5Û©‹sØ×É%ûÚ±ùSò·ds“ŸtS2jïƒ7<9muÁ`ÿÁp>Ûž¼ô»RÐrøxûm¯Q«cÆöpyMг@[;pø½:Ýë,ȶ^æÉÊ·s/:VÕV ¸A:ЧŽD;KêÛ²·u6L1÷%wlc%kë>TsÙr0ŸX÷]íÚ-ãD¦¥:çÓ '¶¦ç~rÞ$k¡»-»¥õꇋá-~Óh$Ø«]Ul>kþ³wé§ØC•Áç+2Úµ«®»“eÙÛtë˜Î¸_áq·g>xÌÎwÔª–É6{»½ZÇâÆ#/{»¼t­ùÉ'³òîªâ¸µ>¼ÝÞõ߯¾Ã%§y&­éa{ª%¾¢!öÛòf²‘ïaGÇëÜoÜ›7êÐ<œ›mø_dË–v§¬àe_8–ntãsG†vÔÛÁ—H“ZÙF/µÅßÝ~¹èì§‚ÍÍO5´üv¡ôL¦ãÀ x´¯Ï_èo4=6Öup–¾Jfh¬2IÊñ¹Í\“|Ê—:›Áêü#QS o×¾ÚÙæÿÙ_RÜÉðct¢ø¹N)è>ñùm¤O:šfç.g±ç S}Ø:Ì«~ƒé ùÐ’Û7¯ßèw‰íÓ(‚3{‹?T"ÑhúÞQ0~ØÑáó‹¿à;Y­¨ÌÜÈ7;NïÛ;´aýüRàÚV2øñ4Æ ú*ïc&éóadEŽÕ…S¼Á…蓬,À;ŸõÜ0·5âyÉ’þ}0Y¦…wýFÆJ°c³ì﨑äÏ8¨‰ŸÅ˾8ßíîÝ[Âz„„cÄtÚx¸üüƒŸ.ßùïÿd¹|þÜ´ ççÚmMQU÷òÖ¦T;Ö1áÔ­º÷H@3¸3>âì§›W.§Kufõé³n¸àë¬zóÿïÕMøà›ÉïXBsD#ø‹²éébx1>{4:¤ë^¿ôÉòlo㇠‹G®v­ÜJcpؾª®U·éØ¢äÙÓ:Oò•†2á^ÊŠù8&”¿Â±fÏ*$|nûËŸÿl9ÿá/'°.¨‡fùù-6»6dIŸ'[­=$ûu:{Xò̽»W+Ö¼:¾ÅdÐÃô[sÅ^ážþ„î㻇k!ÈÈæâ8ÇêÆ!±žíÉž˜cˆ_” ÓŠQ%$Ò ›SwéÍdìÈéÙ„ŽHÎì/P)¹”,>ÍòÓ±ÛTùK6#pcS¢ 4‹æù"Ø8{JØ OKF]ºßâ*è€ï‘o”/û~‡Ÿœû´9„ñÃ)&Œ¦.çwä¼W üa¶Üå‹—–·ßzcùÍoýút_PݾuËÓ Ÿ:Q‹ô·,ÿäÇéÐó ½ÿgßýåò¿úO~³c€âÛü@_é­áqIH™Î3_:–—KþÎÀkÛ#õÝ.œ¿²üæ7N/ÿá¿ÿ«Ë¯}íìò‹_~¸üñ?þérõ–£Svo|k ‹Îž=Sg«ËÓîÒÅ‹ÙÆû–Ï;:æv‰¶»Ÿì)¦ðÚìÛòÓøÍæòƒ¿ùñtù9RUùðí7óùÖiîâÕå'^YξñÞòû?þŸZë½åÿþßüá²wó~ÝàeG¦k·'|ÑpžN²Y'¨Ï’ëû*–ý¬xVöýŠüÝ»S6½GÍNˆ?MXòúakÒqh_ÝzšU±Ò=â$oó/ÓÒ§`-f÷$;zoÉ–"áú¡b„ºÝ,Vz´øÈëgÇG“ébÙQ{*ÒL¾8nDwë}ÙáxÙUÈpòìk}~¿®™JÆ»2ñ`Étb@öÃþÐ 3·³î@µ3§ÉB²Z’×6Õä䔿‡Ì;*.± \Á)Á8F'á‹18Kî«ï¼1’º¿Þ98?ÿðB‚½–‘=ìHÕR*Ϥ°N@×>”w^‘ó³eC@’»9`/”mÀô0‡½ƒÌç1É@…laÂÕ›÷Æ9ÉÀá%ûME­35>½x%ÇÍ:?ˆ*£R¶Ž ÜKWoN»¬lÛ„uAæLeÆÍ2ݫ꘲ê›7Œ“Á)x 1poŠÄÖŒ†;DÃLµ$ëÍ >E0ÑrMr´åÜ å2ö,g#¶­]³cÇz^ÚÃ’z´àÍkUÃï-3xOŒÚËf Œ¶1È Zmƒ˜oseAHÄÆ¡K¡„· ªÛPÈÆ†ì‚‰ Üóž¥5¥¶ ›íG{>LC×®DeÙ‚[b²MzªÜÿèaë9+Q¦r¶? ìææžÂØ’-8&eÉ ÀɸJ‘ NÃÜc¦Ï+áS&£jvfŠŠÊð±u&T»ÜFXeŒppnô½Ê]Õ“2ù&K¦u=טּk9àoŽƒ‡«ô…E˜mÉÞ«mƒ ûé2«¤9;(/”‹ku@À˜çâ6k&ŸŽ>çÖ–)‡À %—p»z=| ÿ(<΀™ê«îçhð½¬10¸r¥sŠ¢‡9 }-ªœ]¤MŠàƒªoJóÖUS‹^v̺;­ùµÞ&e¼¢½e7Í¡=í‘–úSá¾MiÊȶ²ÜÛoÆÐ´¿•|"KUe·vÙÝ/j㱫ýÒšääñ#µw’ùV;’œ½ÇÊ6ÆXT,oÛª YmÞÞÝ(cKÙh8¥cW0¹fOîë~BüñR2„Qx8Ô€ßZÐW¯¾•TVÀb¾>ð7­Q·FßÖ@ic \ºRÒWx¨]÷Ñ‚3ιV••øœ=ÖQæ³Kµk N ÁUia7‡û%Í kt8Ciaèàö5³mà‰ÿ;ùB?ÓúZû8ƒó¾×Ï,Çþ³ÿ|9˜òþ_ý_ÿÏ•—¼µl+³òO¾û—ËãI|鼦³ðŸïüÓ2²‚SERŒshϾñörñÓÓÕN ¿ÚÊðé1Æç<ß’‘Ù.¥¼rÄ26SFù½³Oªf¢`ÃÊÿF×_»œôÁ‡ž¯?*ÀÏpxš,ß~íŒPŸ§,?ªJS‹Ü9›,<¢ke()Oõ€6p:† HÌ^'“¦}TÏã(#ËÈ©'U1éÆbßá‚ ÜfsfìHÃ8ÊV/þAW[/ðÌ.Çj÷’£Ó%#>$»·¯ÜËÈãh¥Žƒ5y#Ñi‚Ñÿ‹hMÅn7MköI¸kþ·1ÊÿItÇé ƒ}X.yÓ-íìyðàþ îÄGéSóìè1N.Œ*˜9ûy×q®«"³ZðxœOU" òH(òž^"ÙnogRš®±FN¯Õ‰Ü4{c%# lZ§`ÇÃŒô]U›ssKj]x ±%Ì0‡´ à ËI-øÂ‰%ˆØ3çI×Ç•LÅ&“%]ׇôgɵ.˜DÆÓ¬íŸg‡r­ èZ=Û3š”Š~sò·_äÃVìVÓrs½¶ ÓE³ÓÁ¨…³IˆÎé Ó^Ãwz:¹Iåd¡B¨ºœMq°5½y†ëÍþ¨Ôį,–ëÃçæ0<>ÐgÖ/gÇÏÜ{/@e.hhtÀàØ±ž³}%léH°¸£ó?¾Vœ8ž`­¢àä¯Õw†3Z|eâ‡5ç<Ü G{Èù‚~›éàÌ[ÐëANSNUÚyp° ÓÙäNîúµÈ<5ºFЋ؂w·.ß|÷[ËË7ÃÍþf3N ñÙ ŒsvÕtisÅÃÙèÚüÑ£Þ‹N7&‹$9MÄ8 =´¬¥&=«-]þ¢Q“è/÷èõ”,S½|.ÐVüè’sî9 zü *mÂ<ß&í«íÜàOÈædÇèÁVséyú{uú©m}!©Fš„ANö‰ûfÿ›ÿ ¬ëòbì yØ84/ÝØkµ=;£ãèu{-Ýá/gù‹ª‹·ðhÒÍ‹®—ÜÏ¥ojëoÈ{óÃñ-Î>Ž*|q:0ô¹%‚Lþ4w¾ §»NÐCò ø²Á%%?¯ÂK†¾ÏËúW½jÿÌ}Ú£?ül©êž=:uР³n䜧§ÀûÀØu•êzT³Z˜=±6ô/)OP—þ»lÔ ¨õâOºjÀi0ݲuS2¸vaãàñ©ðFî£ÇÑMÎ*Î3I€ø"xàAæK[¼ip©ß÷j?mMðïeϹÔCßí1› ~ YbÞlg6°ùûÎß Ñ¾Ä‡­­¹©2š„¢x·¤"ŸãÛ–1ëž6 úã;tÍ|­žýh­Ö88ÒgèÅ~ƒgƒ«§Á8áåZ¸­f#ëÞà9œÿì_í7oש¡™Æ'  t¯ï%yà+Ò1s{º;ýïXɼ_ŒígqvKøhìæå¿úk£3Kng}zî\LÇÈ?䙌*Ê資½\~ôÁÏJ˜=³üÆo|«œ—ÇŽÕAP§§ƒÙ) õÞóû¿}3ÿ@Ö?“¬Õªü=¯Yã—ïÿGø5p ^ªš­}ª>Õ­Ñqè3ÕÒªÛülHmOuéj½+é%ýнAÑEAê»U¤þìG?èˆí'«Ô.9ù@ûtB2B¼Ûƒǵ÷Þ÷Gñ[ñsÙg8±f{l裗‰€£ÃÏšy®3n2rÐ ü¬¹ ¬£'¸N¾â›hÔõt„@0{MÎÏüÂÅÁÕæ5í±GçÈ'Ó¾£;p›ùwÝì]´0xÛ€ƒ½=S…ÓÕ+Wó”8*³Ã{x< À_wìÎÏ4 ¿ÉžuuiL¢‡‹ÏdÛÈ"¤ƒmmÒ8úœ¼1_¼okúK+ù™_û¸7Y¼-ç®õ“ßc»‚±ý Näƒÿ{È\°ŸÏÐfÿÜg¯àè=^2 Äù{ðN²Ç¾9Êì@ü® ÒKÆ~(¦£Ø< q{þz”Zóé3ë!k¼ðwÜ…_Ìo4Jož€ä—<ÃÀÖ58Ó=ÖnhAþu}Ѽ½ŸýX_ùÉ<çËÏñœÕg±êÖöÿ…—àǼF_ŠÒƒÈ±ˆ½ÁÓq x(½_·ƒk¸Iš{çí·—w*ŒyÜQ8씇e_}P¥öoÿÖW–7N<ê±üЋ¯Þ~¼üåw~ºüý¿ÿ­ºþ䫎XxqörÛ¢uíª+ð¾ðEöî¨(´d¨ã¹ëãVá¾½I§,¡“:–ôÏÎU¡¾cy÷#Ëoûëc×MÇë‚Ò]ø,=»ªç£Ç –ž.îXÂlg[oT…öéŸU$vz¹pûrÁñKÅnOwໞ,_{ÿÌrô7Îæ‹¼S¡í¥å;]—Ž §ÃÎþ_ü—ËïÿÞï/þOþ|¹–?òó^íÛÓuuŒ[idµ“Fïö/Jºùâüå`Rw—ÇË‘ŽíøñÏ./œ»³«{‹ŽPï¾Q²èÙ½Ë?ý‹æˆ´m%ã8¨(ó@ô~uùÅÏ1ô+Ŧ_ì ÏéS’ß_ußÀûðU65z‘§(—|ÿI«Ìˆ7Ðè"âöx&Þã«ýª1˜±tOïÙ&_ts@;É·5.ž6÷‡Ëb†/‘Íb5ä&ùµFÇkrȽü{jÛ¾¯Ì5ìòaø·ýÆõüwÅ/Œÿ¡7=­ÈƒpG%óä‚.Üì4¿oW¼³ÿØë“ðE/‚¿â¡[Ñv$øÜ)¶±óÈîŠcÏ œÛ¤`åÌÉ£ùtJ-SC!ÌÃb\ºìÐiŽ9–¯|_|#>Þú¿HG6{ý·Ã¡³ñØ’Aâm:òñ}úÙÕåÞ±ö!z_}ùûÍ팇Ýs4¸Õe ½L†~· (×SÇC™ô=<%Á^¾Zusß/ÓŠò† fÌž)ð‹q"¨vÄôN?XðåÆÜGã<ÁP âí&O©yóTë:Œéycíʹ)nìÞSÐ]¦”ÖÖ‚[Ô9Fœ¤îˆC `è/Ç ÆCQ*Bšëa&¨šÐ‰‡Æäb*eÁ1Þ0Y>˜ø£.¸ÌÉbî΀‘±µfy¯Š/¤z”£V°æø¡ª0«L_Ïu(ಭimN÷bº`ñ$¥îAçóƒP2ZmžõSžun ES0 À9¨9¦äì¥2¢T„ÁýŽ/Æ÷¾ö^…ä‹2öï¬?Ñ>ßÍØ•a+1Ó–' {¸äÕÙ„Þý‚$œj«"³:nFQ î‡k)3Õ)pMwÞGÁô¿|IÖ<oUÕÓ¾—U}/B•¦HVѺð]‰%ÖLi§s¬m\¢©”¢ôÅ~ª”*ƒ®8ð,ÎgÅèðýí~JœŒM<Š‘´/ú6ö8uÀ/Äûu°<’8 !­ë7&+nhm‹fT“„SðNvùí c\@Þ0ü¯~Þ‘í9™bŽÇ öÞ®UŽ}Ü_›ÍËu†¸S"ÌÁ‚茹t€Qš÷¶ÿ ²<l2Ö9H8g„ šÉ´ŸÄ§è˜‚ŒÏËøø¶'›½Çœ!¥R¯!'ì5CÁfIâ,—QoýwÃÍ{µ‚;Ç×i#Кp2 ãóx敎ÅxZÀ[2Ñ5ºntÎܰú¬¹³&÷CŒÚ ÝOÁ;UbÍÎfW._êû«e~&ƒR”ð%2îxç¥S‚–V¬à¥cVµø—¤!­Ë.tïõ’Xð'<’rx¤Êƒ}ѳv¢ Š›Érø âS¢øOvs4²¶èscûÖ÷t‘Qšz«` ~¾¿³æ¼ÆàÌž?ÿù­ÎkÞ¼ú‹‚œ#ãð±å?úÿéd¶þõ_üÙò¢Ä¦mLÿòË©ZÂ\øeï𽿊ßJ’tgùß¾÷þòÛ¿ó»£Ï|ï/ÿb¹«,ZÉ2ñÕQZTøÇð9¸$`s/g·d­è2íÒ‚?ô3ÏЂÒ9ßäÂýô-¸#A_D‡ºþØ3è¶¿qdN§ÛN"äF8±%cW•ŠYòÍ*ñŸ­%“à£Ü£/´_x¿òžãBIÉeƒ ­•^`þ¯œ£ÿâ˜ô&0d,Hª„_²rU„ÐÛT¢¿ŒF¶–¼6™¦EZv·Œ²2c¢#£IΘõ¨–x"šoÌI j±x®Ê*Xx5žÌa`‘à1­Ìíâæ5Ž00®Û"‰Îu]²QEÎßò󮯙¦Yí»Ý_é}oƒ%c¨õ•5ßp ÓóÓ7VC©z£öŒN!(´¶R<÷îë®IÁ5|JÂÝawüÙܲ#“Á°¹jë;g“çÀÄ+À[òÑg6qú²¹Ñ‘vh½2¯U‡šýM70·í9ƒLÚs% ØÛâI«S®Oƒ£¿—þÙWý¿bäÎŒsóÁ÷½sÏë³t]pnÆái×wÙÐG6UûÌúû¤ŸèÄc– ;Ïñ‹SqpÆÝ?/_xß}i³«¾mÖákÏ7ˆ9eg»­1 šùbÀÒý-ø¥sŠ 0^Ïàuìζ­’t_Žþ r@"šsÀЄÀ ØáŸÛ‚»Œqð–lÙ àHá°…7’ï»wÎâ öv†$˜;K‹zv \'ïñà-Ñ££gàýþÓµƒíó¦=xúíT¥WŽÞZ^¯«—.'ú^Rœ¹'ôñèÎ\­màöL2*‡™ë´n&Wñ¡­ýþG®'ß×%ƒé #Ûš|B^Æ?]+«Ã¸Ú{ÊZñ.¬/NrÝÐà:§$9|·jFº>>#¸ÝË£Z±êÈ"±úq|æI:Žk½ìYbm^`'úpø`@> ÷аΠ*ÞéM¦{$h„Gp±kÑ.}cŽf YŒ?ÝÂé]݇|ç:ÌÄàžgóyY@Þ¬ëˆOwŠ{•ÏŽ¼“^Á9À>¥sH^z_^«Ë×ÄHº[ÚQ3Fnª3_<ÈÚ×D—ø[Ýà]ƒ?ò÷¯ñI þZU³G¯j4×Á+szÐéE°×4g‰…GÁ}ŽÁh±sÖr÷³•Ú’í¬÷| ´Eø'ÀNp¨‚¾ÃϰVbã5óጿòÉv¬ë ÇièJ2•MÑÀûýÇÖM6•„àÙÍŸ[ oîCr·ÌÁ+Îw¸Œ/˜Þdé§k"|ã4Wv 8Ã%÷Yƒ„¡uæè/8ÄkW•`ëzTºè+˜õäù,Lè:<»=™W<¢k£EŽ;•‡& V`Á‘çf{­âÓ)IÛ8ZQÃ9… Á;ë0Nn¶SkÒifgš½ 3Tc,CG ó³sñš÷Þ:ÁIâ¯O>úpù­¿÷;Ó¥î¿ökááîåÃ>^þïuùÕoþúòÎ{ï.ï~åý¦àÿˆvÄVlrí™T\í¼øb0ž¥¿¢±6zÃ\ùoöŸynÀïûùÖ;_Y΄\ŒÖÀhU†’Žø¨T¹o§¹Ú#É?œžbkJ&~¯|ZUSÒ€õ+Fyý7:ãôµq¶¢ñÑ%º>~ ‘ÙÓÙ%{ƒÖÛ`¼s’UHn °q½ð•IΗ…¾‡wà»òƒu| it ŸB{4¿eŸ£wø9>©æ5a Ï~ÌÕëº/ŸoŸÜÃvƒäÌ Ýuž,`¢ò¶¡/|L~M²Q?=ì1ö4™J?5|óUÂÜ@wZv âýÏlV~:´ÕÈ=0Å«½ÀÎkìôÞÓ{Áf{8Ìwd¾EÍ<Õ›þX4¸÷åXîÝ‘×8(y­°¯àí!’8ƒEŒ£xå-`Ù³Úüг—¿‰‡L’çñ$¼€¼ú¹Ø§ª¡ùoÍpÖ˜\sÖØ§áédúžÍö*à»ú ÚY“l}>_ÿh3ú{>î_]õàžhޚ؀ÓÑ¢½„öËóG·žýàHïîBOeÛã9àÊ‘>8L6³ó¯$—“ì ú¾I'eoÚÛQΘ“uÂÁ18´ÊQ0\õ§á»- .4Ô|O§;û ߌ©ä+Ý—_g`œ%dØÞ ¬í©ý[åôŠo`gljr”Tð-"žÈŽn¸ÙG{ WìÛÈO´Pb|Øîè{f/àÑÖ—_|÷>=mÜßý¬@Ü+O×¾ ú;ÏÍ–Ì6·¡….¢¿xi…;Iä@À-þ…ºÓÂUú)ìÙ?{&XTç¿‚®Î÷Üü’¿,´6û /á€u‚ßèÓ_Îqì"“è5ì6FPÒÜN®D÷S¤^íG›ä¢¾'ë÷xÂÐ3Þ–,çêèà;{Úø+ ˜Zß}ùñ û Vój¼áO`ßÊK¯ÉÇk ~GV)&ÙšlŠ¢Úç¿üó?YÞûú7–#ùž7²±6ëêú¸ùL¨-ù‚¥Öè#—GF®²Ìºì+ ·#ß éÖŠg^§Ï¼– >´Î1˜z™ûßé5pl®Áõä±£Év|“½úã·7”¤±7¿öíåçUrò=vÇZxþþê{o.'òýЭñDŒf&a—]ü‹sGc Æ?ÌŸt¬c>÷įñ°wßø@{/0ðrÔèfWGíœä‘ì˜|¸ŠÎ6ɨž4Iá Ç»êŽ,@y¬8$Þƒù`ûš+;¹©«“€\IÀ}?AÍÖŠgŒÏ||,Á]³'·MA¨®[wê¸Å·'þ%^r¤à¼5èºûªOb²4"Iíy¶ ˜Ñ£öâ*£Ú{A=~j6¿„JÿçÏÖt zhOŒÁæ{Qâ¬ãluâe£‹;ñÃñý+¨‘xdßÐï$©„÷Ž„TíÌ¿Ï΢£Áa¾?þ·ß|=¿ïK|¨ÒýÂÅ/òk^_~ýý£‘ä\wÉ;–ïp£¼—o~ë«%ç3%±~üñ±©*xGÅsûJÿâü…º(¼XNúÕäuþþ²³6î:ol•ˆ½d«]¹õ¸sÐ/´Kþî±åí³‡Št$M?lŸ{÷;v÷\¾Ëâ ß|G—´íuYø¸Ž›T½½¹œh^oýZþ§ùaÃÑ>=¿|PÅù»_ùÚò•o¼]R|ö}ÏBw~ôáò‡ÿ¸œÞýby»ãMè„·¥¨óŠ¢šÖò dˆ˜R?Ç^«Ò9 x~ðóKËŸþàfUõq€mŽÙµüÞ·Ï.Ÿ~òÙò£Ÿ_?*Lª‹ íчà9ýJ;ÂTGŸklIFŽ€>˜?.Ãkäûñ#'ÆþB3x´Î‚h‰Mfmø 9EJ°k† ·Žð·¯ñŽ4±†'Õ)aߎ:>ÆïF/(Î/+nH·“ÈÊþ–﹎ç’B‚?®;s™eã@4õµo~½äˆdë½/–ÃÍßZ†«ã·Öþ 8ÈÕ+hLÝž"¹Õ»’×+ˆÝ»ç@çØ[¶‡«:¹@j{"Z ‹\½uu9s¨nñº¦9{°)™Í¾ÛÍó‡˜ÿWòqž>s*öÅ:s~o|óû‹i(™H ÄOØ+ä÷$¢'{õ¼/‹Uµ?䙘Õá:M߬£VßhÛ729– FgÝ2©r~«ðU)îÜn’]s¤@Íé“Ì›¨–Àû´Ôˆà1@m wç´=ãÆœkN¸P&vÔ’çxÕn§N–\†‡Ån$ä_l£øo-X!Èt¸¬a™Ì‡k÷'`—$žMÜ#EÜ©2QŒ¸8'ò±¯ÏÍ•‘¿oωqäß-XÖÆ8›Šñ~¢ŠöTŸA0Œ¶F:Í/C1fîŒ_ Y5ÆIq06¤ÞQ­ëz°œ~ídÕàe457ÆÊ¦ˆC&82>¦ú æu¶ÓþagB`R–¢Ò˜óe”ÚîûQÚƒõùþ% OÝ0<™ƒÎòÞÜ|\V-çr­äbãHj s政uÓÿ2³>âÒ¦C°ù`ÁTMŠLS˜¹ z bÎY”MžÂö²Ê‡.µïº_åàÕÎ#ÈNÁ„%?0ÒF©lNÎŒgr«â„œ[³í)µ )‚œŠÎÓ»^ åƒ]£æÅª4)Àp¼öé/­=Íb"0 IeWmmoTó^¿^¥es Çk›î<+ hŽˆ˜âÞç‹®e|:“‘ê¼áh-Â<¿3!ÇxÒ’ý^íÌ1…U™]js| ók}–!ªÚ‡ð"÷4—m‰šæ#•Ü Á8F$= W‚a×=˨á\Á6¨¬Æ ¤àãÿ±ðñzجœ‚«µ@•JKÚœ¥²u?ÜéÜ´`~ЗjLÄËÞO%\{¯Êø³ZLsB2 ´?Y…¹ =§Ëœ÷PfÃ_ÌzëΘËÖËÃýݸ/s= ‡eÉ„˜×maK Û624™mR‚Ò8³¾¿,HmÙœßÈpÑúåœ__À~p†An¶7ðøpq× k—mß½–’Ÿ3jgð% (lªra.#åŠI,§@ëUYyîüÅ®_Ÿ–Cas<ØZà¨öʽhB$hÀ9å<NN JóÞh^ð`{’2ʹޣÃÁZ ¦QJ\z¢àå–‚“UÙÞS¨qîÂåh¯3Ã]N7Ï6}a)2¡Þ}ót÷›y¼ Bkój;/Ô¥àHíSujÐXëOø¡8eI5Òoœnžk¶äàFØ_þø“ó±¤)‚¹µ‚ŋۈp±ýè=¹/~¬%·V, 3ô±/™¢’úú­/¦RšA‹i­ 6-§:*¼”ÑM¸oŒ¨Ö¦¥ '¼Š<°…Ÿ›á,Gß¶7ÇpÙ}MßúcT lÒPì‡÷ö^L•{÷¬F?ƒ[Æ1êçzF×¹Ö=’'ð@÷ìOÁgž”í(Á ÿ”(s]Õ*Ø~ã S‰4xì3YÑÆS´…S^òâ¢Á™öÕêP¡rUEÃ8qÂ/k?täè8µÝ¿×˜`N!anFx"¾D‘ Lý½:·, ßf ]ºt¹ŒØ²})>­Ñ\(Û”.§9?‹®ž¿¸‘Œ]ùö¾J^ø"EâÙ“‚¢­è Þ8‡iG¿?íšcÉÝQ¬£ë<ß?‰/ ’9X•q„»S~(tœqۻϹòÕ¶*%ý¾ÏJxÃë9?8‡§$ë×rž²ØæM4Üd7‡èK}¥¤•Œû; vé,áKŸ;#Oåe›Ú3l½ »æ:ìx±è è=ª,\!ÃW/TöoéËÔ[ïßåE^¹”ƒBKÇ?øŸÿƒ Á£Ë}ÿ;%YÍ‘³ÇgÃï<»ÿÌuœ#}ï1`é3/…Ê~?GfZí8k„Ëéªý/µ6‰£ík2à•KxïÝÛ¨cÑwÌ‹ ¿Ù^ƒ“Çâ…ô{ª—@œ3—uýä¹ÀX ©b•c×ö<‚Á$}Ø{ói’èeÝ—æÚ öÒ™_^Æ'sïº^BåÀN0¶“mæO¼8ÃNðDuÃ$GEä,cÐÚÁø“èÑg›ö¢Èþ=%2,Ír/¹0Ú'›ï—íu¤³(!á–Öê¥I`KWc Ž™éZ•Nç˜`Öz9Úà£n9pšÌ¶0F)§ÓƒhÇþ€¥ûEÖŽC­÷®ÛI†‰ˆÎ—\/%QJäA*½æ˜žäú“tE÷«X§KÊì æ‚r“d•¾Bf9ïà¬3¯¦2{/û æHo_éNÆØPûG‡†{ïΕ~ðŠö<àø~o’!#›ºW v{I0ôBúðt˜gïø™{ötƒG®sûrœ>'ËѼä´B^“exÞàM|•#ŸsÑ~J¼çV¤ð°jòic¬ïåTûÖoý½åÛ¿õÛË'¼œ;÷ÉrñB‰Ýûé'ŸLÅ”öí§_{=ÞTÅÑÛï\ÿZ6ÎŽœc_Y®Ü¸¶¼völŽðc70[÷7øÇ‡@j…ßJS-e>óûl‡N_ÁQýôZw1þÏÖºþõ7ÞZ>þñ÷‚m~ ¼5Ù¤Ë >5ãn˜Ä­ðµíI×)5Þ™ýI¬îù’.Û£á…íëø¯^l&›Â'd3Éà•¯fdTïç9­|:ý)_p[;&Bb™·vDk_{¯Ýn›;û¾ÊÀp`ð5j>pý®f¥C67L_ák#’d–õšƒß“üÙ{< Ol2´û³G×}ìÚd 9><<¹1ÁËúЗA&à'©¢g±™éâùù($a¿ºü¡±_üß 4“]qD“$·ÕfòŒu.tô(p²¿€‰*Ëù?ùl­ói×<‹Fð0Þ6âMç”ï3ôÕDzk<ðHÏË—oÎx‹Ä…M{˜ï=f¾kà¯g›&ÓŠ»¢ùž¥Cú÷²ž.™¹D>]ó¥ŽÜØÝ5ðÇàÒú|7 µ'.0Íz£¢Åpü§æ5v²ndu”ø¯ÁàïòZ© Ø7.ßýâÕgº.é*ê[Ÿ­6\G%Ûé»aútŸä÷ä›ÛÖdÐ_²…ø-¼ÛH§æ}ZÀïàëù ¶ñQÒk%¨Ø<‰¿’.yþ³‹%Øý2ÿ^•ªÑ">%†Â;.ãöagöúð‹¾ƒ:xp÷òÉ'çê‚'É $©|X«¿Æø‡î ½ì¿å$I¤ÓJÇÃRsÇ?¢.ý&m¨xƒÊà*ÿ'i\Ѥâ(ôZ 6ßÄË瘯Nâ·³ÏweÛ86ahºq±yóÖõöõªV‚s+ÜT öô\Ï‘¤ŠW£3°GÃGòñÐÇœ] ΣsKI=ºJ˜éãõy-¯£§‘ÉSµŸ½£—Í‚ègÞ§uæìž»ùç%cþúo..ÿëS‡—ÿÙß;»ü¿þä£`Ó™Ú©ýñwÎå‹Ù¾¼÷Þë%“Vaþþ»ù¦:p’ƒƒmÏ’xS˜½üúë–Ÿ_éø× •ÀBÇÖw4ÿéo}û7ê,t¾öë?Yþê§7ëÐöty­òÇ ‚ AØ7+øÙÛû¸Rk)y³=Á»nÕAûö½Í峫7–ëwž.?;w}ùÿàï-ÿûÿÃÿnyÿ«_›ãhoV„öÿý£?*pþÿœ¦ç/_YþÁ¯,yYWUñ‡}ÇŽ!Šo¦+?ßZöhq’~Éÿøûº«ó’RŸ>Û±üÇ¿Ò1h×o,ÿð¿ýQ{v'Û¡p®#K«>üårûæç%¶ëV‚Ï`Z’6ƒ‘Ü{²åiöîÖâ [³._üld±B)rwGÕìüÝtî¡»ö oÇ;ñ…éHmñÑóü&—é;vƧƒPÜœäÌ`f<¼ª‰õ>ü6|zSÂ2RëÌ}+ïßÒÜÅ윕~á =ž8õzçÏǧJnðÜ›y}ãíåq݈/òˉílïØ3ÈÖNdùÝ£÷bù›~°üÊ»¿²œ¬’}{Çb¡'…À7+êÂwî‡ãyž›Ç‘âDuªˆÙ„ó+ëJ±3ã£lŒÛùEtŠ8ÖÞ8y¦ŸSù>Å’Ö„:ÄB/=÷É'Ë7¾ñ«u±z­ývîüýåbüäj~ë/¹5Ûy^<û~:$ánã›»—dgÈÓq¶i{µ·vŸ`„å¥ËWS jLiO1Ä…)OK Š6Ê‹2²#À HÌ–ªßÿÊ›Ã8‹v«J[Á u´ì”‹m-ª¬”&§¼`žVŒ{ë-·38ì©Eaxqù~{À™J„" ‡{UoÊ`ÞÓØµ§¶¼8xœÁ}ìXgöLF¥J‹Ï¯ÞŠ9n› öœO;—s}Ãà0þäbÌ+nA± xÀ¡7Ï+`$ó…/—s5¡ÖZ'ûG`´kNÂàNÄÿÙg×cÔµÁc”Á¢câ˜!@Á4èk„‚)n*ܵ:9Þ¹ÃÚ8Bì%üúïn›y®yÊÜT‘øæÑµÍu4ßøRˆ¥ýÁUìˆÀÀôé–h›1äˆÃs&Ó¼ïÔû„~§OnwÍZå½%zKs¤d€ù(z­/Um 2çIQîU f‚î…-xPª .egB–Q-À-½:3œR›µÚ,ô„!TÊ×ê˜)rNWDÄYÜÂÆ TÆ Ù÷ûŽÊÒ‚këÚžBÇ€¡”É¢ZÛ¶0œ¬µu'Ì(Jœâ[R"÷l–\`¯×ªŒ†B°˜v3j‰ÜÆà<ådQÁý,_4'-ñáÆ–ð^8£˜"åmÄÊ.lŒæÚþì=\u7&š1ªúo{çëmئ˜Y®ÌO÷ ¹Å´$>ÀBÝ8ÖD‘sŸ âÁKsßsèTç<¥Â´ç;öŸŽ‘Ö¾éúûÒÑÓüþ6Ó O¯ŸïªW–ÚP£}á@ê¶”€Öí}t¾Ì©îö„øoY<¢©qö™¹Èm9ñ´€æ:÷:‡Þç·Ê&ÌÀm²ËÑhöxëÏ®×ædîàbðÔ†Oça­¹0{ŸßÿÞ‡ã¬~åèý÷ç×j“H€‚ÉàR8—¤?U'%l”<Ñ1÷2 ãQ=½©ý%2Õ^¿ TÊÐ'£AX&^æ¥ðìoÝ÷ Žþôçê<ÒXÁwïþË7jçó¨ÌW°s&9‡¡îÌE‹Âp¿±!”èáwÑ“ßM(•𛑣 ]†çÀ>:9y¼€o÷Jl™= ·¤Tq\ˆð4âŒ9ŽÔØøŒ©9OgꙜñ2N(œŽYØ›§…•µ þŒý¡ÿ”8@‰$ãîw6²Ä™ý%<é¼# LdܳZš­3ÇÉc‡j)ôp¹u/yÒ<È@°-ÙˆÏSDñÓ&–2%Éb\Z=#§^<Ü9Öx‡D(¸ß^6¾9t×ði|R+,8 ›Ã«scð‘0~¥õž½'ŠCK ’œ@ 9tAéŸöuÁáIÁæýÍøàHÙ»ÿ„«ý-êÖý¢=œ*Ø6r£s°ñ«Ç ®¤<‘;›uV±èÇ) Vº@<.îœ%Jý€x+e’aµ+<~’d’qî–YižÇ;oÈR/ã½rbév£Ó€€%Ö,ÉOGhQÅHÅÿ%mpT1Çàó¿Õ¯ù¿º”>A÷`¼Ÿ|ã+ËŽŸþt¹uáúÈÈî?™Ž°oùî?û³p1<ï:´æ¸gŽ£õ“§8Ñ^ÔÖ=^ÞXçÏ}v%K‡2v[¿á§êŒ’ìÒ¥Ð!œD_èlt’ÆcÜRîWg#C4£»Y„¡s ^5 ÷«¬Ÿ·DªžvϺþxdzšî&þædᨑ䥛ŒdEÎTº9­uº€øÓžñqÞÉ“ißä¹@äQc½ âq‡Ó_BÿžÅÌïùx¦µ4N;¬Zw<Ñçœ%ž….À-Ê˜Š€q?õlIt'Ïdzú( Vè‡ ©=²/Ýí]Pg?“'é/»Îô2£D• ~§è†>¸ÐZŒÎe¬õ»žÙXë€w3éŽZ$OÕhðgSó˜=n[{üg=—5‰¡ź_äÝÒI„ƒ\âëál&72N9dœ ?Ælpµ75t‡"žt/Ì™ÃÄœEö¹"˜ûóèáY|lÍ›,ùY¦¶÷ã Š¿O ¢gìJIèãÑËÇFKf!‡¥su÷ÆO9,ìݾüÁïý^8úxùÅÔeàYž}“´j¯ÉÅIxËÑwãfz;˜·.A7ÇdmFû.¬œý{V÷¨ 2ô°éRUB.Û¾êu­¤.ÕëðFgU1’tïåÔ½zéÜÈ]\$Ï2îˆFW–-ÑÝ£Î?ä˜ô©$ÉÛ›Á!:Ž oBG»¢u×±`¥¨yÏ£Íy56ùñhg0¶×ì³½lOÐCÁåÍ®Å#´Þ’#/Å7Цkà'Å+Ú±#Ûmf{i\Š5¨B¾6^8h®¾óBsÆ5ž€)8׺|Ž®9$é\hgì —v¿ë\C¯¡søÐÜ|>‰®ëÇTè^£›°Yæ¾Fž÷}3èÕ ž )áx³ì¾~ùŒ-ui°·«ófFñ\ò² ¶A†÷ö óXƒVg‚ Ý­û‡EÏü»n…ºû4is𤮵¦äž1ìÄzß* ×çÄX‡ùú<Õ³v„®‘¾\‡[ƒmûE¹h(´ç ZLp«ûì-õXgJ„|3‘ø(|èöì¢Ç·„§|Õb ³ßÖa~ûö®èžq¬~‰Ä &œlxÜËšä¬ÜýÃ×›—ä v1g«³–Ÿ¥'9† n±u¬w…›ßøYœÚkò¬ÁXò0ü›N­Ú“º|ææ=þ¦˜b+pÅà]¼ËwÞ]6W{çFU¡Û+ã⤃G[ã;dø“ÞŒpýÚçË©ÚlîJÇãøé¹ZÕJæÎÿ¢êæâù uQ»´œ8ujl™[µ‰¿ßÙW·r–Üvd9T@}_ÉÉÇ:Šj*lãGpCGpZƒÚ»þòÞT÷þ&`Ïÿ/ûÀqH¢ÀéS1hÇ\­þú }Bõ$üµN|vè7º#»\ƒžåØ¿vM GËž`èÜÚ3ëÄÈçEg²N¤kåîm‡cx7þƒVñWô5÷ ] ¢Ä&6 ™}g{¬ Ø€Š¯z¼ ÉU&qpfoÅ«<1Y> =ØÒžùÆõéë¾$§:¯Ù Þ®kúwßé>Éô=é^Ö —ì}Šex{u|:‚.pq$ äϦÝã…þƒÃºÈ <'#¬ÝC‚ìØ.COѾ@Ô8~»†žÊ¾œŸ ÒÌaõ3]ŒW…ûœ°àGGž ¦j·g¬EÍ1—==|ª=y¥Oµ \Y+ïäo”žÜÚ{øN7·òîmýÝ2óƒÑ›Éó»U‰vé}àLg§´¯•6>]Z YGŸ £á‰t¸¼&Ñutßšé=/Œ~éŒæ¹ä1áÔØeÖØ|Ÿó55‘±ï{"ÝnìøÆ§·¾ œJ¡oà±¾·—Æ´Vþ,c ­¯0§g·©]Z[“-²g¡“~=YkÝ8ž‡~FO£·}©Íøü¯Ö׸ô@6¸5¥ìÌ3è,žËÎàcA;`¯°¨%e·:Ò1|ì/ò®7Ûá‡y­ö BhýÁ”/ƒÍ" ö"x‘Û‰¡c ÆQ.[KšH ë»Ö×¾´î¥+²¿ø„øZ_m©›è|oŽM1\ËI†;ËUëjó›ä§Æ·¯¯x‚qèªxX€de4áZòãyŽf´ˆV{ò¬cö*˜ðÚk0z" ÔýÖC/cãkËëڦОôœ>ã'àŸ&Ÿ'ÐÝ~ñÓ ¢L‡±æa½b ä=¼¡‡FæøÀt<ôK–¹/,l~:¥]<¬õ˜ Ø[ ~$¡tô²à1H¿ôi±ßæøØôÈÀp|qƒ»U€Š=Ä™=s ·à+Š ¬Ÿ} Î*[ã^8jÁo{¸´d‡Ê«/Ïv„žqåÂgͧb­| ¯ûöž(á¬.¿7hÅ/VBüóºu¶´á³x1à°-Ègt3þ¨®Óîzb]࿞ÔÏ@Òtþ_déÐvü‹¹&ÝAPÿƒû WIñ¯hý*Ä8x,lúÿTs.t±ûúaU7:xü¨ üAìµÛ×ë*šÞ°"½µk•)y¯Ëg¥È󳋒Kíó~] ×à¾óá%QéB®|ómûx’õ| ||—*¼[ï)0Zïp>M­ÐÁŒM WFwÇ6ÉÞàãpѸ #%;Þmu Ü¾ïÔè(|`û÷vβ½ŽGóÉíi“ì Ÿ¿Ä§­íev“ó˜Wû1šnOñ8öñ¾l m¢ïß_»œšÞcbCâ*/kßÏÏožæÌÎSàÆ_xãáµàs§˜–öú%“u.±ànïu¢dïëÐA¯š„”·ú…Ñ_]N³Õ£Ã^ÓMs÷î XÞ¬5úÿé–ÿä?úöò÷ëõå¯~r½gç=ÑÙèuahô­w_ï™ù9÷µW%˜<¬ýöå/,ÏK˜b§=Nè<ò¯^þð¯.æk½³ìŠðµ‘â|µdˆ"¤Ý»ŸŽ/Ö¾û‹ÏãŠJ;0Þ§•þðË” :¹˜ß78ëJ§üÆûo-¿ù›ßZ¾òկױ3Ý1œ´‡GóÿþÛ?*qhëòï½±{yÿt~*ÆÛ`—NSÑ …FñÀÅ,Ï,€«¸¯#Uù‹óËþãsËbƒü^Õ¯.ÿËß=XU÷óåÿáß,×ë0{üèþ*ó6¿¶ ÿ4?pIéíñËŽ¾ëãƒg3Q@_Ò·b:]ðIæô<íÃÉa…»u3¿òÙùdEÏŠFž¿p$Ô–pý'?úáò;uá«¥sçÜn)XNù¿ÿùõ娙·2FjR'FÀ –^G¸¸k£Œ<ãªý´žVâý$1´>ŽZ¼fø ASýÇãZNÜ”ˆ€ÏL'¿ã pKÃǺ‡p@gð± ƲOsa qoMkJÕúGt‡oPÞµõ–éAu ·kñŧÃ@¥p¡ƒ9&EÇb{÷¦ˆµªBvG+è}ÍØoß<;¾Ái1çöÀ1Õ£'òªÅŽ,(Mã#*k7æÞŲíW¥¿ûgô»/DÛgÔŽ²Øú)qãX&ð͸a ›Ö²Gžôåx¿x!ãVç86ðo®æ²Ò/š§Lsz ž`KýÌ«±G@ܣ⮓Ëá¶½ ^ÕmÑÖÉ’†^dôëÞÀy–èÌÏ8˜›+:™ìù~¯ó®â­GÏ·Ây ?æ¬xF°÷‚»sî Ší/(þɹKËGŸÝHáÔI¥Ä:ž¼yæøÀÿõ<Ï»wÔî©d#¼Æ8é-~¬ŸÒKVrt÷1Ð÷Û¼ƒkØ[ ŽÎå1ŸvgöJ&Ýóàò(ZbŒœÈA@ hÞœã#¾¨£Å^x–õÀ±×Μž9^ÏØ ;œ­uÐ!G~„?”(†á´· .‡ª”ßÖçÖÆ¬}Ž3ËL\`¬´©«.Õa€V*ð¦>aÏñáC}Žºhà—œ ÖÝ6ŽPpÐȵefHM’A{Š^fðíŠ×ŽÐ>ýìZ8 KYMc´žÃO{¾Cñ}ç„霂7f[ôÀô‚®?®Ö¿…/ÎtEÖ£TÖÿ:/°ã3{Óq/o¾ö^”×&0u4¥ÿ7û·—¿øó?~*I’ΤÓÏ8ƒzÞþœ·œ—o¼ùær­óØÈ”1ÈÚÉ@dä(]€(ò÷2þžfä2Ö%Nîн&^w3£¡Ô‘ÁUÇ?´²ø{r v‡_hq¼ß§K¡Õçy°Ï²HCõù­¥È ¯ãäÞ‘sˆSQ XQú9šñðI×xcè67Ðôe$‚ñ“æ€49@{Èj¨¥ÁÙgæÔ½ÛkÏ¥õ5c—e¿{5é(Á©}Ñœ%)ßýÛLJø_{ú$œ*ÙÖ‹§z~ÿÏ|^”hjß%x2d#’ÑÁb †ðKÇJ“µ6|ž=ºVÏzžî"ÀŒÐ?ÏsÑ1ÚwÎ6„ÄCŽ ¶_uX¾rÿûœœÇ¯Ý äãüëç5›#utäÆÍ1ÇÙƒ¯Ûyè ÓôºG¨5ËkÀæ ãÀÈakÍBzðtuÃ5öÇØáÚfóЭ‰äÀÇZHk´·ýݵ’/9òö‡‹&‚NÌï^pã.™à÷ÎæëFú«@í¬§uq€ÕÓ’üöJ¦mÿ:¸Äß»(ÌWB€Dé;PdÔ«Pç(,—(—Uû0‰t0{œÞ2Á×î{Ò9%¹«ûÈA}€&½£›Âçî@: î~:™ Æ»V®ùkMîxÅ/#ÜÖ¾ñàÀúgÎû¡³—VÀZÑÞÍp|X;Hòž 6~ÓÇ\èZ|rôÔF¶ðèN_>|h¶¯$µ¿ ÚÐëÖNÑxÏ™g÷ï¼ë` ÞZ¢S÷çQÒÿŒosG¼<ÙD}2íœGž6P÷I‚£‘ ó‘g®ki¨ÙÏ"?Žä¤ƒ÷œ'ŽŒÏ½Ñ ˜Ù'½Yãï?x¶q^.wn}®æè긓›éiwjÑ9¸,¶¥c²Ï{œ©L¤µ‘™èžI`¢O®NZ0_zìŸûÀz@ÙÆ¼éóüê‰Äǵ'|?Ç­åó+—ÖÍë{@ä ÊÑ{&‘s<ÆFdL³Q½ÿØGÇ‘sø×ºÝæC®ôûK¼™ãª‚Û³g}ÝãžÉ^…Ctãñ¯¤šÏÖ~ïï\Wrædb|`äos¼pþü$Õ^.hþ{¿ÿc›p$^¾|qtŠ]8¯º+ýf“¾8^÷6gz²/ÍDѼ…#+§lð^=þ_z­öÈPàÓßÿê5ÿÒ ÿÆþ08ÍߟÎÎ.¨ŽŠ­²¤ ÍólR›Ñ ’E#9ÜÕý íÞ¯³–*Ûs;FæEGk¼^5ŽÀ¬}c ~Xÿ£ðçÚµŠU²ÅOæôTH‚ ÕÑ/Â{º‹j@G2’ÃÚ{ÚS•Gl¡¦9€wú‘äh{::^ß­ q`½êpðæŒ'*ÖZ‘ OÇÖÓ|W N­cæ×¼ǼÖé¶gŸ ÔØQÝ;zZûu $G)ê(õ87z °(}çqö½š=ñ—}¯JàØ.îÈáyµÏŽÑó»ñKS‹ãô¬™[pÛ™ícØàbùœë¦ïÑ.|B?ðÃx‚ðô2xÇAÌÙWô™-áíá£'‚'àèJß “…°¹ÛÔ’ ïO•éþÖ¸oöSÁ*ɺì&Õ×›ž•~·Šº;ÑòØ3áÛ +¦3AÏû íÙ#ß“Öým¨™½Î:_Un“,-jðÀEº¸=à×âŒF‡ïÞ½“ȳg:ÚíÍ?ϧ‹½|NÌþæëCà -M&lOï:RŠ Dßl>:¹â $>úäÜL¦q2µhz»Â¾ù3™ çð/×âÃöÀ~£ã©FïûÚ×ý£®—Lì®cu|û­·²ø[‚O°ÀÄÌOÂÎÏ~þQ]úvtŒd~ÆÔ¶ïôlô-i ¦=/@ÇÖà»Aûlù£ñ‰ç›Úãká.[ {…ûáiÕ­£—“ÑÍâ%öf2¢|Ž­ú¼y©pµ—-rÖ1´o±‡Yj?¬oKë{%cÿÅ[6šê¨£{}ÑSþ­›u=Ð:bîÃÓ'vDÇjÞ`:ü¨½ÓOëv2E}¯£±kvUlM;«çfÁí˵ÿÉO>ˆâ©á?y5ö1?DþÄcÇŽF_kG_þEUØwîJä½¶Ü-)/!9¶ûµëU›VH§û‹g£o´†é›á®"¯ÙÿpïN(ø“¼FÖÊ>¢§>( ®‹ä$;„Ó ˜+¯"œŸDòyì?üH÷I—ä'rðºN‚æ`ÞŽÏsïÛ%BðÁ\Š.Ö¤æ ÆŸ:vb쪇&ðIÞïúIr‹')D;v´N=­Åwö ORä Æ÷S8œÅÍþàG¹­+&À×ÙPáÿ£å¯¿ÿÃå}º|ó_ïÌóßxߪ‹éý›Ë·ßȿ호öE çI-É/}ðár¯ç¼õõw3¤*ì‰_my?¼ûpù£¿ú¼³Ý‹9Ó»·-ÿÁot¬uGÑþ£?úÁréêíå­7NN÷¼î¬çÏŸ«SÂÏgß%ÙÒ÷ð;‰<ðVQÑÄ;ú›Ì1E®™‚àö±ôÆ8˜~ƒ—é.ê…_ñ-Ðȶ?_ù9´o"âxžˆ*JÚŸ¿/A·lzҞ伎ž“„áqðÑÏ%J¦¸Z¥~9ÝÛQAч®¯Ÿ:¾|øá/—ïôÃå»Ïð¢Êû“Éž:-G‡¯¿õV¾ˆpkº•K„b{m[èžps£óé?\~ñir¤øæf1©SèÎùµ÷é$›/šXbô³’6­ÅÏŽŽî{ZŒO7ë›u9?xüäòñ‡¿ˆ¾ãCñŸ»%ƒ®oF7àûÉÇŸ.?«àÈñOdŠ#éx·ðnô¸·½Ü âHŽ#v ð嫟ÀGÀÚ@IDAT%›WŸ7~®àr›à&gÅt„LƒP^q^ ¸U½±1 7«–ÜRCdLÿêÍ'ËÅkw–·Nr6P¢µZØ_`7'sÏí²ODH*uâZ;'{m &«€Ó¡ª©C [­3)YˆþE—ÍŠá.fœ51ÄOñ“ùŒ¨œq!cË\|N 0Æ9€U¤CÊ=7!çg‡m.×Ë’í£-gÿW¿þëUŸI°^·ó|ç(=*#no€åX1mQµtÞ^çKɬ¡¬½l“1H¯p#î»A²x9‚ ½AÏ sÎÆÈ‘µrö÷—=1Áͽµ¹=ýÚéŒö šˆþy ë#Ì;gÊŽ!²ÑŸ–Ñ¢]>2T ŒÀ eëYûÉÑ(ƒøõ·¿¶¼ýþo,Ÿ|ðƒæ•ðJÐP®œƒ™JF ßê$RU¶sû±”´*~[·ãþÎÆ¼Þú)fàÆT·ˆ^AÛcS¸Còh;C·ËòVáY©×Ýú¬P"ÁDŠ•‘ròD‰9!äÜ þmUÇŽÓ8/Çjoý$A±¥õO–hkš60á>qìxðö”Á,i ÑÞùûLF<|¿ðyç•4uø+KÞëlmë7ßzc˜Àpû`´õ0<ØÖÙÍÎJÛ¨µÉFsÜž:0‹=¯ùêúp4%ðnUL_<9ÓœËzVëö»pSâFL;gŠOŒ‹]ÁóÉÝÚslQ½ÔNg ¢‹e”ø“‡«.y!|g|­†ªÁà'2Îë¾aâ?x#ãúÍ7_›Ö9Û§½Ýh2wo+aåµcË¡÷ÞN«k< _4_Æÿ¡ÿuçùcéyÞçgδ33gÎôºSwf+wI.«H±ˆ¢DɱÇF>$A#@>ä[þÀA`ذØ@ ÅV£(™*¬»är¹}g§·3½·3¹®ûÝqœ €)¬#®fæœ÷¼ïSîú»Ëƒáxnt MÓâZS^ç…}Wf¶˜|ó. ¯C¨Œ<”>¤ ƒÐ+²&gJÑ Æyí2æ=”—mü›©t˜@©l?Á¡ ‘%2ËçW«y ZÏl´cûme¼/çé<|OG룶G’OY>ÐB–AT‚ôÒ”²ÉµÒð=1Tn°ç:8®q=F›³Œnk þ²êÉ »Ž‘k©ÓY«\ f˜Âócóá 4a<8çÖÛC te&ô®Î8qÚì—NcÀi ÈWmtùœ™àX»…tt ÃÃQ³Uï:Y{ð:¤‚3Bò™ÎzH#É`Ç&²ò$ù ËNdÖ@UÞØ.W¹i"…úÓìPë lð}WQÖü²ZѪzŠÙF–ɇV* žh仆¡cÜ8›ÊUž«\‘¾¥}þ §ÙµVžJ;¨üŸ«—ô%mhoo­Rb”ÂçÃ#£!Cœ«óþÏç,¶US%m:äePþpP>ò lÖ”{)𫹿²\^`&†½ s&¡/µ¾knËAîÄOÌË*7"Ëù¶å{2Ì/*3lYÈ3*ÑUUœ]RöÆÅÍBÖ+5¸·±M ÄùŠ€cŠ}W·Æ¬xt  ¬€—”kÜ­ÍÞã`!—•sŽ4ôr«žôúùõ0 %¨¦üul:ñ<>Ûd•uÚñ)ÜŽ¿¹7ö—"JúÛcNîoŒ’5Õölõ¹OÊ9åf$1(¶“—{*P‡ ví™Çׄæ™P1w[w{om(eò.×ù?ƒõ&ÉF+g£™Ø G¶ÇãZA‹2ú)*øéº/ð{èmžgMgª]á·s¼¿ÏuíøáoVî«4 °Ì+ZSrö 3Flk󲮌Aټɜ”1ÚeRbØþФ¯ÝS·8‹š÷”ß&(lÃ'‚ሳ£ŽÆ1;‘mÆ íFÇ®ø„99ªÿ3ºÖFÕÎ °¿EسpÕIVº9S­ô«’™€£6p G¿ –A.ÖãáÃ#žÁçñ ~»àyeÍÈè(NájÐԣΕ¹ ˜÷ Æ%woÝI“ãSŒǘ±Ï@¶­4s:gŒAºÕ¡ö™qÔÓ©>t_ðyuU7¡ó‘+Ï¿øE@íÐx$޾Ù*àL×P{ÓöpÚÝòöŒvÕŽö’úÒÍe~ÊOÿv\úpÑòÛϳKB†øß7€t0¸­\ªcZ'že~ HÏ&D-ø÷S¿éT«§³¨˜‡†¯ Z»ï®»¶Uôþhò ¶¶Ï|#‡:h˽SFIÇK;Ї÷0Á-c,Ú…ò„òJ¾’FCžð· ÑEŠýP¦(]i7³G¡kˆCþcfñ}aéHþ•”›'úµ†ùÉï®Mt­p¬×›l`0Û—zÞî R«€ŽÏ´ÓƒÁÿ©·õÓôö»À»2^»O™¸„,eLã¿âLy¿VhO°ò‚h‚(ÝëÌÏ` œ‡ïÆ  ç2ƒqÇRê‹)‡œ«éú2Ö,¸è|Ý Ý ÔsŒL`¯Œ£ß ]4pOMøu<$Ãå™YMù˜†Ú:…šÒ½[·Ð‰`ì©zÔî*> ”e>.?™Ÿ `¾\£8úËuAkïKÃ5¬P!ÒÖÛ¼h$цÊ1Æ¢¿bpqÛ®n®;sòÞVÿö÷¤¡‘³a Q‘3•>þèl’¡Hà»yãSìc:ï!³^~õ5t(ú{üÂ…‹i—ùz˜G ÖA‹‘hÅ|s$Ð0V÷ö/¿þÏ¿Üì¥,p]ù:¯ÿûªGý ÿPçDvh¢°._ »‰çê‚䯃Up`5à>ãV—˜¬“­jë(3s$Å™¤mƒäýì%}JÊuýÛNß¹s'µSTaçÃ]’¤<‹w]¿Ö*Zm´™¹™ôÑGW dé÷cŸÐ=;Î qÈþp/]aH“ïë°ó¥ Ÿ½˜o—¸clÅMucÉBÖ3Ù.°ùr•*¾qüοóõ7˜¸ƒ÷Çnñ¸(q,“â9w°µ&&æHZø] ¬òGÝãˆõym_Ç÷6õ„]Sƒý*ÍÌÎ.ŸèCON¥«W¯"wª¢¨ÃÄ/¦Á:ƒÑ)Gù=ü HDŸG¹*ßæé¨èzª$qe5Æ {Á‡ü®rnEtŸØP Œej®”ÞøÚWÒÅK¡ H*â»!÷øÜäÏá5@që³Ûitôt¬³÷—ÏÕ?ò• Ä À¯ßHo½õüùb:EÒµö¡‘-dÖ>o0PUÁ:ȵ(Åjqý,ék—Ji}:2“` É5Œâ <á “$Dàif¡”¾ôêKé}m±N K´?\õ»cz÷Ý÷Ó¹sgÁN;"¡HO\B½dTþÅ UjòG‰<=ëöIÿx‡Àï*öG.k³â̲õ=¤ÀyàÈsiÉD‚}ìv­ãÓÑ5a´´× [ÇN˜œšçȶrúå_ýåtnä4k»>„Ü.^¬íltàÑää42æ|øbkB"³´OÅQ‹$ÿ|zã³ô'o~'¦#i'`¼v¡6×à¼úNÂ0[ÅZGÂ2ÆDõ,XÀ‘€k%t7Á)Ö{Üb íÔæÙÂÞ<•‡¶¸çÃùeªû:Ó믿G«®ÃÒ¤²ÎÖÒú4Vzæ½ûcéñÇKó¬Õ*ø°6­úZüDûMMÝLïþà‡iˆî°<”'Ë+ð<<ˆcçÉCLJˆ•¯#¡¾´Jrg/ ̰Ø:™_Åö§:öF¼Êd1±‰yº MÌ.§sΦ‹ç΄o¦Ü÷øÔVä:–MèŠéÚÕH\%°ŸÍÏ„2ö],FêÑ=ûúPìá!òD;uG~€ÙìP¤]¦>ЛšNóSãH(ÖpݤÍe@ÎàtdRýec`ã|_[¼R߯}Ù&pãQ9&Ÿ˜ÐVG‹áæÐùâÞk±×«$üdl£:\úXd¾Òö„vftÄžmÑîæ>·lëokáð›Õ;ìúÜãHµÍõé”[[iÔG¿d~³]0˜˜Ìþ‡ü¤oáü™@èu¬r‡;‡}¸Nb‹|¥ EBÏàÉ|G}jâož ÒÏú {Òûû™(žÿz×ñÚ×$=ÿÕÖÓV¹­=ßqÜð‰rÚÆk½òÖ>ç ï±7ÈJ»aØÕ­\ û67VÁš(ÀâqMæ.[,óÉgŸ¦'Ï\$xÜ‚î Ð ]h³ëOø 1FŸmñŸ¾¯Áò#t¬6šÅ„Úò‡v´£Þ\Q¦á€wÉïÚö®¹Õ©VÊÊwß–a¦™¯ÃÂNf›è*Öt™ÊKÄgú‡†ø‰N$ÜHÓasb“ŠoŒ‡>«Eg»Æv*y†ØDÁà9kàô{¬nøŸI/vóLmÈŠèܰ%¹?t—¾Áf O±e½Ÿ‹Ëž¼2¬¬ÒÞt>ÎW?SìÐ@ íôµG!8èl ;na~šûÕ¦þÁÞ«J·ïÜM}½Ñ¥zÜðW~éyì·¦¢·q¶ù2­Óßy,Žr~öù'éè†ØyJÊIÛÈ1ƒôd‰‘¿_ÿ…‘T…ÍñßރǗXÎg|ú'Y±›¢µ"ÍÏ«·éÖcu>ÂÇÄðmt‘´˜ãom_i°‰{ öÅþ{^øüâRúø“ëéþ½qÏ¿w5½óÎÂæx8½FÛkÓÓýè1ì M‚ÊÈ$;°ÐØe$UAƒ`¾¼µ½¹BWØ›é{ï-;)†­ ðú©§%—¾ÿöµ4¹@«ù>âcM"ŸïÜü4ä»X•þ›<¬>·º^¹"3¹gÚã5ˆˆëʳ.L>Ii¥-1¯ÑÞ=ÁƵã•õÒK†}‰S [¤;Æ\ÃÞ•ã™ß¢ÎöÙ&Ä›õ‚¨ÐÛÝØ%×±ŽG<ÃßËÆñº¾F%6§ró`Ÿ"*ôͼÙIGÑÚêQŠÖÖ"f² /·µøG4žÛÑUÆ =vSüÙxp+vª‰Cú}êðbf£#iìá$t˜%ÚõÕvþ‘T€.;VO7²ðYœAÌn‡BZœ>Z½£g‘ŃgϧSýý±ŽµìgwoÇ@u¡Û…]‹éñ'®¤däôôT$š´`g2I®¼2d|z.½ÿÁ\÷D$ç„޼ÕÖ©¥T¼Nÿ3Ž>q/]|7OPHEíK EêÔ˜þßç4c²ÈÌ0œµcYF¼µRCÁÕ‚1S_°Ê³"ιêj3ÍEˆÃp9 šhÐ…q¸?*þ¨Žc"±Hü:ŠÂ œ EŽèkiiŒ¬67C㣆6’5”G”@O±c e’¯aŒ,B¾ÐB0¯›3 î¤•Ò Ï6c«¥\9s)=÷Ü éþý»,àÜÂ8‚ӳޭƒ¨!¾cæê˜PÑj S€bÞ‘‡¡ ³pE( .[M‰Û €srãœk>ÔhP™:÷î¾A„%gG# êðYA³ØNBãÔ QNc lk…J âDà«a#Ö‚¶ˆõ-iêÁ"Je#½òÔÓSϽ”¾ûæ7Ó÷¿ùÛi`h-ȉàd.JƒZämáÇD*3 nPõ„A  ZYË‚ìÕÕ8ÜÌT1 ëÈcL2c粉#½IFÒ顾Z~f†I%‘ëq[6šœàšñüªrv~¸JÇ£ ›ÅÅåŒ ×]ÁTE; —¸ú´]UPf°êlç ƒª»†ShV‘Á •‰UÒ”NT=çýJw-Z°@a rOǹù€Di‘ÏrÜ•ÞâLp¨íR4Ôlq©Á,X¥C£òSa™ueU}´ùfNbta ÈOû•[9£faz<®L‘'xŽé¶—ÿ‚VXí%y5²UYÏpе½YOÒ¹ÎQ™*+3Õmû¥’„Û†äO“›ó©ž‘or¼U¦f»‚Ž’½kåFÏÏcè݈¤@ÙFQšPP‹âiå;›È3¦ì…8—‚‘}Œrž™~H E{ã`!¿k$”+Œ¬ <º>Ž,á¼ ­´çýë(ˆOÒ„Žç™I«žEQÒ)1ø[úÈñœÊ Ï~Éh‰Çàð²8™&øá‘ÌO'1.³ñ}_Ò¹ïk +àÍôH*³loré‹­ýÜà†´hBAú}ç‚r*ŒB+ÂhõÌß³q”UÊÔ,ÄÅìÐ*ò%öüó%@ç%®Ê“VY'é¿G۳̱šØKª·ÉúÔÁn¡ãk\‰Ò×P­®Z†¦³õÜÔiÐñ2éÈi*àöÌc1²V«D6u>5\=gF¥wDÛ¨Èv…gV4ºs4‚¬¶,õ{˜£¼'‰Rd¡"°ìsxžç‘ùr˜l±uw|!ŒI Ë=Æa6^²Ì=6àÙÚ¬rо2§½uø] ]0‡©£oÈçóUn&OÙVe9gÐÆ¬GuÍ>r¨ÎQýÏúiˆ¹®©t`…]d¸áÌ8WÁR Y%[›‡ÅswÖ¸g:Ñî·FQÑëV’Ì7kV9ª\•¶âž†õŒ]ùf0Q] o»þnûr¯"À­YÛ;ðX3­†ÜåºF´ÙâQÀä ÙÆÍ`¹ m'„rJºa»Ée²äÙï-/•¿rJÝȃC–Ì—ÖqD2CM`Vý’%íS)>Çø9n* #€ß(²Y©Q´•îŒÍÒꨈaÝÏËüæ,ÇS'gB>v’ ¥Qì³5XZ0 ¥=«1t~ ˜¸Ïýs8?NY§ÓWFo°á†óæ Ë;¨~¯”¼~.^î½¼á\Õwï݉} CŠbõ|”Òªö›ç†uµÒEÚl%ᨪê_„óÞsåé§Óç7ÍL§Ÿ~BeC; ÝYÎdºŽŽ:êʳÏ!× éü…ÇÒGïý„÷×!Œœa€>ér›1ù<†Åê£; ŽÚ6ØG;`e® y+`lÑîù§-TE™$ î5˜kK-+ЬXQ÷U¢óóü‡†ÒéÑ30¼w `@EÓ䥩‡cŒ šÆÖÒ2ajÛ!V‹53!CÚê$à$òA›fãÃgØ#'t¢nV.t=â>‚ûèOßÓu6ÀTbHâþ«•?ômÖu”n•úƒÈ1€L*lEÞ3°ãÍK\θÇ}–º½žùÃn,wAg!ç‚­§m‰A–0vþw`ðò."ÜHçG™ ä:ËŽ!;GïºNÒ‰¿(ä1اq)˜®E%øãø¢ yÉS°Ê 5ªökYÛBºåŸö™ÚôüÇwù¿ø m…*öTùì•~_`Äï¸FÒLISŽru8À|Àž9FŸ¥]Í7¿[Á­C¯=é•nI‚° yƒ¡Ê4lOŠŸ”=›ïÍ*8«´Ê÷íÖÇï{¼—Ñžm8õƒ ®ð!÷¶R\pÚgùÝøÅ{ò™{àûžÓéq/ÊËt‡¶¦¢ú%öào«Êè ½»VÏf´ç˜ 0Q¦Z>¯c_ Ü«–Vê›ðIŒNF Þ.®“tQ hË™XÜÖîyiY2‘ÇÀ¬Ã»êF³¶ëp¼MVÐŽP¿X-ÖÑՓΜ%qgŽkšÒ“WžJW?xð{£¿íj"_{–l Õд¶«mýlŸê¾°qt#ÒÖÖÙ—Ö #Ô‰K×€ŽR×B1_÷M`xïŒ\w`±ýÖ±Ö^òeÀ/’y¤kƒiÇ8wA_èAÓçiÿú·ôl$å0¹ÁÿØÜXç"ó·SÒSö·k- È&Öø¢KqŒ[Z•¤cƒ–Vk›diF½sχÏ®(s¥öÂ{íÂwžï'M9Nù^’Ž|y–÷eÁx¢#öA`â‹Uo~î%{_Bà%h_p î˜ùX¶Ç“wôa¢S‹6*ë`¢Kæâ+ú|Þ3ÙDðÛn-†ì¡üu h5yi‘Ø"Ç0oþùó„OåA¼ÔxW¿ÏŽMaÀû<ÛÄ×ÓyT®pöÈçâ|%ºøÃS™_´Î¹xÊ }–FÅ3½6ã¹=d{™‰×ÅÑQð¤{÷dÍ"Íþè#Ió¨ƒ,ã3=/m AKïã1 ûøBÞŸ•Œ}qüÊ&e¹]nœÜ2À^&¿Øæ¹I‡÷§ˆl‰}ÜØÃn3 ©íkPÝuW÷JŸˆ¼ð}"Q—ç+S¤Z×=ö?ÆÇ<Ðu 9ÚD:?œz$פ5ç@Ã$Ou”óæý:äÊÔäxŒÅÄO}%Ú“n×Ñu&¼‹/¨ã®¶á³:t»`Ô1«÷÷3ýÄW8ꕸ?ûâC7¶§5aÕ#Ÿñíö!} >›Ä£ „ÌXWƇÝ.`¬ÛŠ­ªxOËô½ÁVÇÓÞÙö¹v¯º^½f€ÁÀgoooºA¥’ëÝЀߩü$Ù¦yMLŠÄѾ^:&­³•Ž"³Ocÿ]Wßú//“Ž\'m¿èßñâÃÏy›G_øëþ@°Î³©榲=/¡1ÏOù€/¿ËÜ·a"+†¬”•÷•=&—ËÏÊâèF¨xDðË–¢ÚÑ›[‹ás©‡¢*û¥ÝR}¶.oØl€þ `©Ã=Ã5ˆä0ù…µéëíÇŸi þ2PaG(e½ò|[E©!½ *Ö)DZ‹Ðò¾G# ê”o|ÔwGÞ™ào⿦| i¡ [=þ²žß±½²‰(ç¥N†ì±m¡?0›¾S=ØMS±+ Щ×ä~×LûÐÖõ&èÉ×ê“ýs•ÌK´Kˬ³>`WO/]vp;ðiÈÄ…6l¿j€FÓ¦¾Hr/¾¹:D:78d€Ùge-•ñçüßŸÚ ÷YÒ}‰Át}v¾E*¶ì{ÚàÏ4Á'îßÞ.ßgÜ€œëÒR‰}ó(d syÀû¶‚µ-s:Ô"Áu§À <  ûT`ÌÕÈ殃IÊH娦FûÀö²ëœ¯¹EðD›¶•51QZù½ÊäXo³#ã.8Õ.v½ºFÕ¬o1•úþÌÙ3ᛊEìleRÊ‘X‡VŽ.ÈIÁj9ý}õ‡¹Ñ2|]åqsè,“{ŠÈ2ƒ—®‘x’0ý[i® ¹íÚšðº{`(ôÄûl¯^ ´ç7”¦¤3¿+¶l®Õ±b¤ú›¶o×·Ð&keLê7+Õä+eŒxQ|[¬FÅ®ì<ÕÍzó¯ƒ$vƳÎs8{þ¤Å0¾¬OYrè ™+¦ˆ\ÖÇV¾‹ï‰1ø¼uøÙ¤õ%ûßÎ÷¤1}ëe‚¨•ØdÊDmÊÀX¤;ž%¯¹—¶ÄÖ¾“î£JOœj›*Uí2å]ôñ#Ã*` MºNѵ¤;,Mèj&¡a¶´Šá}‚¯ÁM{‘1&®8†¡®Ñ¾T—YÐiÁóÂl+»íD× ÿj_„Ÿ!Õ“|Ùß‹ý!ïi'‹9f¾ˆö…ïkgÙýAÄÖäžåmõ²²£1°œ¬e~tà³Å9•걑=®i›‘^#™ùr[e«Ž{«`¶Ð}ašÆžÐÓ2í¤ÕÿÚ¤Òž¸3X‹#Vo²7«bÐg¢‘=”¦Ô•̳ºa‡µçH hÛ‚Hʃ/­øu¾ÚÍÊï‚ÈìOÄVxOÙ[¦TtaEíïaß²†êȾ>ŠÓÊ÷xŸ÷­ªçW x´½‘õ!÷³¸ö¯ö~±¾šþ©…BÑŒ|¼Ç5ú’5$|úáGifr6]|ò©4Ø?˜jùNÈJVügy)ƒ´_ÔÑ<ž¶!ëä½ÿ‚®,ij+@¾¾-è× —¸¤í¨Å¤,0ÔžuüÊbýú’hMvØ]^ Ñ „}Ñ ?ÄfÎÞ;:eøþ®Ç6ØD0߸q“$Ç2KÀ=™»F#±ŒûŠSeIGà•ØTÚü;éÈNÄü-m«KÛ96RùºFìæwö C°aè¦\^ÜSvÐ=Uš1fÅ&Ã{ÆÀ2ºð§¦(Vÿ&´-]èŸ9&ãUúéb{œí MÌOßJÿÁý,âI—ëØ;û±ï­%ª®³ vóÉFÎqŸZ­°G†ù}âEvL6RMµþG_"?ÅÇ]óˆ×i‹²æe’¾°‹ƒþXceV¶, ß6âì4_UYŸJóSq»’)“ÇN~7;;G@ù³ôÊ+Wb ¯Þ$± ¹Ñˆ,|çmäך^|ñqÆFA#ë!í“ q€ì«&µ¥„6çO 7¥Ý'ÛÓ÷n‘€çéü)ÆS‹ý‰®ìK úSº[#!Кäu“:¥!eƒz°HÁNy&¦º>#½j³ÃS>šdöÉ­;ÿ¹Ü]›~ñ•Q :+ÓêÔ4ö:ö ×䨫* D›_¹Ð‘(æÝ`_ÿüÇwÒ­©}lÑV|“ÙÓ/¼4~µ›þôÍ÷éjJÛvèÉ@«±ˆ{w¤¯¾²Z[>dï+ÓÅtMšYÃÖ¨„–ÄÄÜåyùLú„,ƒ®ÅNL¥Þ.‹X³äíi±t -ÕU5hKsâ¾ú1\繓bã(/Žè¸gù*°l媉Bò§ãݧbÛ[ý5u·I9Ž·U.ë3Fl•5¶Ã„6Ö3øžRà¹"6T 2Òø 6ÊD|Ó¢?±·MâP­àÆRßΞϭ…XÇö,²ÚÄŽ3£#aÈkÊüHf,AóbKÈײ{XyÒŒÝv\Q ]¢¿¾£#F¼§·¯?ôÀõ럄Ͳ¹¶¶8L"HWjF?,ÂûëèžÀßáAq;ã€ÆtíXóðþƒ° »pÓgÝüêà­Cdªëfb“ò *@lvKî \†áÉÂV{ ­Q«€wce†j‚‹íÝéü_Lîж`z ë‘MCé˜I^Aæ¡á‘t奯BPõéÖµñ½Ûüc¼†EhîhI½ý#, Ò­b–€bWÚžXµº­c¥7 ž¹Ì†­§"mkj3æS™´`,ÅyÁm©帊ÅÆJ{ žAóöºÁ´¾‡¢¤ò®Cbuš*¸Õ4rñ¹tù™—Óȹ'Ó"Õ±7?þ ms дúÑl97iVï|ç>0U£TFÍsNŸ@« Q´E¡U˜:‡¶¤„ñtnBòSÂËÀ | Y ¢ºÊ ¬,€tˆÀrÃôDUæ* [ßÌÒ&ÁìŒþþÄÓS“€Ã{·ÇX›ÝÃ¥`° ì Æ i`Ú=³’•/òY•µ(˜Ï†Š€ì«Í wuŸJçž ãb›ù *™•%xaUð!”ÁIA5NƒÜ¶V_Yß(Gñy~™Aj«;¤ +fe]*‹Ž¤n9c»w¦È~;>h å4IK÷.fh…nÕÿÍš9È0+jeU£¶’LFlIUry%MNijU‚-§:Xá(Up&:¶3ÛO–ý“ÎY{Ï^YÀn@ø¨p!›ÔŽÒª¥ ‚Þ£glLLϧÅeèOe†A§2\7Ź›•hàÜ6 Vðt󬸈c“ó™vÓ¥óÃŒI!nÅ’ÕéþøCãf< doVÓ–¢mhF©ÜvØ»=[µ2¶‚MC5!÷ÞC Aˆaœ[Þw]ý?A ï¹E&NœÁɹjTà*Î8¨Ž]'Aè¥5ζܢ²G´ƒg“`DÁQÂ{ ’k(çÆw-Ù¨žI”G`à«"èlÅ ¯£Ø=_twŸöK¶àÄ´ª]ž6IAánµ—ì4«œwBèÒ®G~‘fŽ˜‹àj§žüHÆqœÚÞyèX~2ªð—G°k¢êDãœyì³¾+dKW[(O´.@‰ ït`àþ˜¸pHÕ¿ŠØ #ËF‰LÌfR»~:³* # [=Þ¿åmùTçÞ—¼¶Ž£ië¯A*®|y;/ ¨L´²©e” ôÞî%î™mè œrÖLþø˜3µ¸Ž»ümF‡#_Oæ4 ±2Ñϳ¬G«í‘ S’r]Ý£ìTÖϱX `†‹ ¡¯`⊀€|.õkè„ØGgƒ/q¹]XHZètp×÷àé\;kÀfZ1“À6P#hg¥ETz2d=ªDyÌ.:àÊ_¾çKj‚B7÷w,®çÏË˱:õµ™õ##£±'VW(O4‚?ÏË{(äã.:sL/Ì‘Á½Žx-¦/¼ðÅØ¯µTÿð:Æw1=óì“Øi3ÐÂJèîoüÊßß±—tö¿øò«A?“÷ïFÇŠmõpJlGf²[®2 Êùü8[ ßì]÷²úÕž4©o÷x+å¡÷Þ¡¡4;E+¤•6‹Qp°h;éÕ™ð%ض\Z Ãû©gŸ ´•ïL·n~–&ÜKè3÷ͦ÷¬+,õtæxUÐRöÓªv+–ÔÃQå{Hß3H úÐs˜¸E’΄b•Ü.Æç÷Êðw\ÃóÙLª 9ÆgÈ&þèÙÏSŒx†ò0hTðŽ}{ç°×’w¶¡ºÁë´C*HŒ”W•9ê‘H$e^Ò†€´?wH†qÞV ªG½V›Óu°B^^㣠Nf  °Ø>ž¯07u¡kÇú@!}Ïùcs_*s¸ÔËÕ>™ÔS¾ú,ËÚ8å­kãÕIÈõ;nò®ëâžéZiz{cVŸËÚʹdˆüЉ=tšC†Í2|îåØâ¾¬×!vvì1ï™ø«¾Ì3w+cCo1oRxPeò `kx„Ìs-´ßL„t¢ê(ÇàúðóDñ]ŸSI׫n¤!W# {ÿÐ|A1®Hp•Þâªøô}þ‹9)/NxÁ=uô¥Bqö¯<çšD¢6c6¬/g@–ô[Nj úS§)Ǭ”“î xû »n¸ŽlEÿÙMÁJAÛ&zÔ‚UÓÜø*sà®|OPßäÍhï ¿iE²3cp "™Ç1³f.¿Æxå—cèP¡ª¢=mrŽÕçÜ6ìù3ä ãõüÁ5ø*9ƒˆN[Ü'€GtXG MypÒ¡Ã)K;îYðoD{ÂzSvólŸËãz </Ï~|0ITÚÄô#¡Â÷Xîi œÛÇ^  °_´?\e:@SÊb“¹3s„¡5í~¯ ?º±å¦É#µÕY"×.´[6 {È úìì¹GÁkP}þ…ÓÒâb€W_|íËa7˜ØPOÞ@ÿí!WñoñKÐgÚ”0AªTV?¹nòØÏòòj¦cþþÙîâ7ÿú/±›{n§÷~ðÝ”cïd*e¦¾ˆ$ƒw‚î«ØtQu íÖR¢nÒ&®$À‡ˆÃç+Ñ’OàzÞÔ_! ª/§O§.¶¬rAyfÀ4*Ûy†ò4|bhÉ÷Ž|и 7ê¥Yõ»¾ˆãT¿éyàb3É&ð[¡·…\qÌ~ =Þ' ®Ü×;Â?Ÿ'A|ƒ@»ÉeҧשçVIØÈƒÉ7Ò¤ÁiyØ€»{½Îð8«·¬ÜŽù16¯ÝÏÜÓ.ó”—ëZƒFôá* áàÆô…„Ì1फ़ñx XÖä7+§Å|3b4[¬ÿ”Fä7·”5&›WAÓ+t­óø*D1RfxDÙÔ¡¶â=©JÍü0ð&8ŽädNøýŒ[P]:W'ëóxg$È„.K‚&€¨#A¿g$2èÔým†õ<ÒÒÊQi–Ã_wß´[ (´0à±ønKk»sŒ²ØH`¢¾¤É¯âÒ”:À¤%qOIAýeð>… YÎÅk|é3óžíOKÈeD0Yw1C-G“ 7ªèÃvRaÕ»”VÁÿÖÖ³n>Ú!Ò‡G é„©Â*JkøÿÇˬ:A ˆ}Ô÷Ücýô{=úÅõŒVÎÜÃñ)³¥Ÿ&§(+Ý|;ý¨‹ÔµL‘kÿ‘í&ýT#wòÚal®>—Éy‘XÅz ’ÈPÇ^ÓéjLƒfõùå m9õ³Gȵ¶±¦¬ÝÒ*-¯á/mS+ÑÅ=fHÙ©%iPH=l[h{‹µ4Ø%&ãØµç Dk¨-°R—áy1lq;=°+ºÛu:§`=´ëL¾lmd}À=W™¯?ÂOX%þs¿¥ ¸´í:ÀƒW9²q‘@4n{è°‘EРkÁDôÿ½*V¬¯r€ÿ⾌۵U®hg¬Q?9þ*‚˜Ê¡©™)¾ˆÍˆîp½ŸÇwX@ û¿•ÝÕÜ=íH…™­ëõ—z©Œí"±_V›g;:v"_“¼ï?Âü2¢‰$ ‚¯Ø‹5ëЉ ò¯ÇEê‚‚‘åw6ÀX•›Ú²Êgùü¤¸G;`q¡¹£Ãd'×!O¥ítå¯ãÈcŸk‹‰…E{a軵½3èÍ.U®zT?©‚B#éS=¨m/íjC¯ K«ò»T½ÓÙÏ‚d4®þ(—Y—[©mp8õE¾HÿY·E·âó¾\{“+쀂 ºÔ·R.(ÏœÛ ôÈ’†’ÕC'`×øl>“ÄâMêð5GqÜ ûçƒb {Äz:Û™ŸUÚ™âkèÖ]ÚÒ瘜œàÜåwéÔ9„]ÑépÞµ…Ž“^›Z U6T=ªíÂ2"c-.©ïF@Ê{i¤ _e %]· 3¥Oã‡Æ'‹«ð•ã01wޱˆáÛeÕÄ$ì õ¼‰Ø¬mÑ‚_6’ÍXs“ÂLÔ®¦HQ\Îb:ç2õð.c&ñ©€½©çè Iž•K?üéÍÐ'_ùòÓðP.ýä:­ÍÙËžžötsr5ÍÿáOÒK_áhYbN]ñTs\I{íT× ž¼;–¶¨röÇwÁ{o]_H³dHȳ& ç¨VwÞ‡ü; IP¹_l à݉O+è[cTv6Ý¢Jþv|®ìߟwÐŽQð‚‰v4åÒEºpžëDí`[õ2§³#iæöˆ\oâ˜ÌZ uݹ;Ÿ>þ¬ÄqÁ5ø¯<ƒêk»i½ñb?h+ýþï}+ݟÇ-pd-׈©Êû_{yÂ?ù[¹a‡e¸Çw²)ì¹r ûºv_µË•)v RohX´±@ŒÀDóS´KWUé·Éß•9»“we­ P> ,-ÃëᾓƒvóX iwý´x1O°[lVK“ƒ¶íD:GÞc‘¨#ŸC'Ö‹+Ÿµ7àY»ŽÖ£;*)¼TþEP»Í°BÁ¿Eþ6>¡½»Ì1Yú…vÒ‚5±5Ęé Ë>62žb‹²Ð=ÅÖA¶…/WŸ&^xªúÏ1˜t¥^U·G,…÷Ú:zÒ³/¾ß—gúIê=ÕÿsúʧK½§mƾ¼´€}¼@gƒ9ŽNí ;G*Ò1¡5—^f''I8žÃo9›†‡Ð$°—‹ÄÍTpÆŒA£WÍv@©01efJÙsœ=' Âv¼€\'曆W¾’ž}íï‘U|Šòø>¾OFÞ÷¿Ë=Ç¡!=õÅ×Òs/¿Á7‡bS‘α²»Ÿ…ÈÕÁ‘‹´ä ›” 4ÁÛb3Jk–³È<1èÕÜÑŸ[->==žŽnÞa¢daˆÔÓ*»¹s0µu ‘%YL+œq{†¶ä×~ò=†œºûÏsîVgªÈ·qÍXw˜@%UZ—®¼€"îÅxèNç#hÊæþ§ßýWTe]H/¿òeŒáb_ž:¥»o˜k;ØÐÕTBY•ad³@ul.V+JhG*«PX¨ ë¥Vy¤pV›9#ãë|ï§8ÈË(¾Äø5T<Þ¢Ì^Wc! Œ£”T–f¹ ò*¼dPMÖpNø©0³QÃ4ŒŒ4ž[¶ÈºÕTcHØô¼Š}œ>Ežà’Žà‰Æ¸kâ3ÐÄ>Ú´¢FOUáÄx¶·ÊÁöNÒ£JÝ6÷< ¸ëTªØXœ '«â ®ÃçF5ÑVBÞ PÙ«>gu¬5ðƒ%¸g•gE(7Öp…h;3J­P•¾ej+³‰÷ {8#Úd@³LŠuæ3ÉòÔ¸Õ¡lF¹ú|Al3I‘n>ª! ÿ-襖$‰JñçCC@aj…O(7dôû[F" ÊQÞµBÛ`™u”Em­\RÙÙzå`c†¥eÍp¨áÛÏsFÉ!Áï2A–Ýïã²K·8¡y:5ŒÑj‹îµ¤£‡œ`Ý­Š¨­fžÜoÅÕÓ߼Èüä>ìØk–˜Ì 2ºö—+HZXÛKEªitÆd#¨moåW߈¡„ž[ÙMƒd,¢Ií )4 ôÈ[®©s–F¤ƒ*ì(ã–Jp¦¡“ ÀEϧï&ëkž€ŒÎ¡N‰2F»y£pP+¼+QD!ï<샽0({Èø¥-ÛЩ…ŠÌ4¶"YÃPÞ\ÇHjFöù™ü½ @µ@làÒd+¨5ªÂ¸dìøàwË(Ñ*Æ*Á¨à€–þŸïG&#†šF–|ê>IêGÝ` x€ég~¾‚ƒ³FÅ6÷* ¥e¸ÊWEÅë>QRS·®7Àì9Mfœg f¬ºfYð\¾dìÌÇñI{ʪȠ–&ySPÃWΰM°2Á£>VP<ŽM-€K¾ï=¥¯šGót®Ê3|#)_U¦´Œ6«MCе¯Ìq^*üxôKF´mUÉòt/Õ)ì¦I Ä\W–ÄVOgqwŒ9 ò›s;€¾¥+AŠè祜ô•¿Ëœãk2M‹Á(/ &þ… fÜÒÿ—z<ËghF BJyÊ3ÝSeíÙýÕÐÎÅÂ-¬o{xªïU¹géŠr]i'‚”¾ðÊé™_gÈ0~‡ä®ûi“ñòXýĺ´vö¥žînL8C‰r>ÇJ¥<2A^Êófu† „¸&‡$³ØÍB`\VU-¯°(¼gÕ‰&dÈ/-c ˪qDì*ã~ ºùýÕŽ= A‚ U€‡jލÐ)Þß3Y ù ?ÉG¶ò6йBU†zÁq°*üŽ,c]F$S¨ï"!‚õöcÙÇ‚8ç nþ¼X+Ç¿¸€³€<bÐcÛ$™p¾ç#Þú¼3a‰‚ŽûØëíF[Žqä]S;-ö·×Siz0}òÁ¿Ouýé‰Ç/Óab.}û;ßNÿèÿ“4zö,xDËæøfvz2=Kž2zpüíüwÕt‹iØÒH^†Þ ÈãѪgalg»GD%cŸ')Q‡Ì¬iíI“sL¶;±ÔH­Ê•0® 8ÈOÒ¿ºœ„6ç­ a…êr‰3f¡µ×¾úOÓôäD´‹m•–ÚH$L«YUVTVBŸ:"¿|ªc¬eÈ.º ’:jTfÊû*ȃ8>…÷_ AÌ /t“ò †ÖáÖa¢Ïp€X[ !ÄìAöm®ô9|#ªŸ—²WÙ¤£\1  à³4¿˜û¨Ì0¯Gg¨VHHTÇ™h’'ؤ-``‚µ™UqÎ9*bL‚§(ŸLãŠsT+Ë¿|G{ 5r<¥œ{úγ*ý–Ž»vœÙH ô}ä•IÚN-Tldá ~³!‘ƒÊG®QÆI)­*Šxú£gp{oà!Gˆ P±]|‡óº±›«²Wk´ú8OQV›ÈÃ#Ð8ŠÅÂAk+taá–Ê Øg>s~>Y»·CærI\ÃÍùÀ@#8€Îu=oqAÝï¸6ÞÈvaÜ!þI÷YE wç-;Œ„íÊ=ÃægŒqŒ *¿Š}³ÅŠÀ—ôàËe–þ} fÚâÚ@«cåÜTF&7HçÜ'ŽX/Ÿ­žužòŠô$¸âf×…^åS«"á•qÛFÓ$?“ À ìQžêKÜ»—uzTÿ;é4‡©ëne©í­@·ª_Û]ûTýÎ:ã”îëëãÙYµ@öŸö™•]ÑRžtŒv-yë{ß ]®ÌXE~˜¢^T¶`¤~vÿ]vž!˜ã³L¼Èö‚uÃWª  JÀ§É=úvBr}Ü×ËÀ½mÞìj¢¯Æ Xü>÷ï,qÁgHýÚ3ø0ü´»[î¹g™Ý¦Ñ‰£~GyâþóH¾½ÁkxaÐhöžÏX5ëW"‘’¹IÓî¡/ï-msC.€/X‡°µX|,€ 3y•Gf‰vÑí} û_™ ¢<ðlhéÂq(CÔå‚Hõt¿d3º“÷bìΙyfïBúÜËƸ4 ´þHV0,ÆÇÞ2^î{g³k)H"=Tð¬¸'˾±´Äsµ%"`ÿˆ?”ýY¥"rÄù1~0.~w#ø\`NV `˜g{¼åK`´ ;Æ ¸-^¥«=gØÒ¨‰§Ê‡jîi×3ˆgò;÷ÊéoñLŸRýæ%á0C“u¤Ö/ª÷¹N¿Q›SP;¥ÙG©G[ÄýqÏCr1éÖ½ðy®›E®»4׃ãgXCçv«Š{ð=éKÙ…ì£Avç[È-óŒŸ(4x­g¸«_Ü+UökvL¸½ô‰t$oW2írƒ‘»ø¾‚ÎÛ3E;ÚÚñÅhˆM±T*¥ÏlµÞßÏ=Y;ƺ‚ùom/SÌ@…¼orêIŽG{Íó‡Çéò÷ø•gÃußµo´y7Á6¬ÈU×kZ)iÛEgJàÏ<ûlðÈ8ã•·±¸3³#lñØÈ:º¦!ó]ÛÏñòZ_~/Ö>û‹¿ƒä⯿ùÿs ê>d/ ê[ØA&@9å¢à¶XÙQ-º¹^Á †hƒ)ÀìZÿ@êbƒP½è±!kAí€@+>C }òÁþC/[èÉ›¨®Íóàçn‚RWgÆ‚o¬.ÎÎc›±îjÔ&¼( ,ÀÐFÕF«d¿Û(h¨Ã?/Ô2' èž±ƒÏ¸ f²ÀùÌú[îŒúT?¢ ÒߨÇ©ªjN³|ºŽì?+°«•c™x6€`àÀãHŠýv À:Ow‚¥þ ó60¢ÎP®™ ¦ß‡Á-zgçæãyFB»×<'õàço74mKm#´LÈ{íýíŒ.*¨8³rµ•jþ<i»´m0'×xJiÖ÷(&[ÛYÐÄíFÖ©=T>©LÓÿ±³ƒâ «Šª`|óÈGd~Ÿá*›ìиL@a;ô"ÁNÖ_,.¢®õù;QPa¥”ÁO õuk‘}&:züe'í^;8†«Šî~ê^ÍYGÎÌ¯ÄøÕ ÚÆm€Ú‚×]`bw&Á{ƒqtoƒ´¢E®Aõ·6¸ó2ȯ?*ÏÎÌ o-ôØKÓSSøâ†Ü°0Åc8›°vfJ‰t@IDATgi÷;‹ãp=x†D&Õíè 3™hUÞNñL "eè™ ­Eóàgv»ÜÅ&lm­#¸ÖL!)¨Úö¸$“Ê †Y°‰îÙ":1PapßD‡ÐãàAÊ3Ÿ³Ó3!ûLX±ÚÊD+¹«‘Á§z{Ù§rz8>Á¾S˜ÁZ[$dò›Aì%+•üm—ÁйZ8ÊÓ$³]ÚÙlD1¿¸J»]üat…ÉkÛåߦ&|s|a+ õ]JÚ®Töoqv«’töǤ0\Ýk>3`snx˜–Ê‹´ÂŸWä\VÞ·=¸{tö»›–þê’)®Y"J‹E\Ê9ØÞö¾û©‚fOÄ2ÑBþ…5óG!mòäh] ë8;Zéö /Öó{}=saêCyA™âÑ\jåu°0ux³¼i{ti¹5hXoJ›ŽCÇ(æ§§‘÷Ê™ã+å¬ÓFŒ=œ€'kKøô5ð¬]iÄLÂYA¶x$Mòhm‰cU§é~ý´0•õÚZù:»`€sRÔxû³ké£k7Ò7¾ú ð”­Ýípöƒ]'¤—Ø#ä’ɴЂö¤v‡vä6Œ !Y‘ëˆ>4fdPõ¢¸¨ èú ’–£¶½xµtGƒDÕ=ÏRþïí`ûÂÿúõEæg2‘ÿì ¢Ìsä|bL@ÿÓ÷KGª0®°ÿØ’°$¼'…aü-Nk§“{jØ÷K^‡x®¡Í ýgvq³þ²8ƒvDÎ38”Sl°þf– Õë– Xøžø²Ïòe¦kk‡Çðñì´aÇ”*’$÷)U0åŒѧbVÊë8oíS¤Ÿü¤ï›YŠø ð I}®Y}=˜ sÒ6ö#>SÅ-&н[H`GiR l±¹•N}ð¦v´I~¯Z_iH%ŽÊü>vd :»Lrž6Ü*2g—¤m…æ¶ÎT‰óèý” äûƒ{wÒÍO>&>íÑ ½a?Q¨Ùj7&lÙà7 s*X9…Ž0sã ƒ Ú sA\¯‡©oè<æŒn´³—LáÓÓÊ7ÿ+÷ÎÔÁZ ¬˜Ú^Ç8d’Íí=µÏ¤;÷Çhí(ˆv²“Ïrí`Š×V[Ó€ÝmŒ¯©jP"]ÃLª'ˆp`ärj¼ú>o÷œ%hßÕŽM¯Jƒ#ça0¿ à·ÝC¡ØÊ4¤¡á3i`ø\é÷KTC]ˆs6UŠ Á»ÓçÓãϽ°<Ë&ïE6¥+a{î®>˜¢)Œ> ¿˜ \Í&TÀ „^ Ae,Á»©,!ÿ$^a]z•UÙ©`°Ó Í€3ãøžÙÙëëkð-?Öë <¯Dõfí´-¡PÑê@—e<ÆŠ”ï*ôU¤§’Yz ‰s‡ˆ&$ŒqE€‰ ÇǾËßéÞíÏBà ’ç1bÈP ƂѪ |ÔL1·cîa`]SY0;ÀYr ãzæâ<*ŠrJ Ã3v¬@©:ÜDÀ`ðò¿L2O?t5Õdà‘cP-À˜uØÈŒY7™“{ØL6TìßsݤZ¿:E¶­„8¶ºZA₯bðºþÝ´8Z | ã~g(ò¬r“Ÿ(W(Îofî‚ùÛÛ03×Ûzê€D5 ë.-.d •0i&¼ ÐcÄÖ0æJéñ*³·Ú(ËKk÷óä ÖBSÁg•ʺF‚Ð ’ºj³’hu¤…þ*8g)«f!HãPEv“Û/¿ëK'vhÐ`bk!e¾WK²›ŽÙY\Ý£@¤/Æì3•Ðgí­‚tš¨˜0Ù¤žçG+ÖÏŠ÷-ŒÏT­ã¹fð†QÆg'íàTŠ•8ìfØ[MYjËDV˜|fÛ;å‹«.Ø"(>å"ÛåÅgAtvŽé6 òà­%0/8_Q£ƒ•ŠB‰COФÌh`…^½W¬??MQžÖ˜]àøX–ˆ¹À$iþÊ£Ô^J8‘ýËçÒ(Yd M¾4Xh€Èóø|®.¦ÈÚÙª YYÀ3åãc[nb¼œ«\û.8¸I‹úfÏX3]_ïm0Þz¾ª²Š^Åì³4üN€`÷ÎŒcå2F˯ó<¯—ÎÑÂÕÎW€ɇµðóèÀ¬óvÏ0¸¬¬wMÕî­Š[ã6sÊöÝ5q¾lü'¸ª´ažüÔ°6 C™§8~_à³ í Žž!èy‰Ö3Ã\ÝìÒŠ±öÊW¾A•‹´µ$û¿4ƒœ óÀNMžµà¾­Tú¿ô4Áøê€§¹Õ¹CwÏ©8ws™#=0äͲ¬¢3JEy=^øío8»ç3äÞÈN?øn2õÎ_zg§°­5}ð£·3§>Ršyöü“ð{wšxx?MŽÝ#óï~è8‡ˆl}x[ïyBòhm--÷¡ƒ&•œ#(mD0Ÿ÷³LæØ¢ k¥—/×RÌ…5`¿øãoùK‡TzôçÛ· 3õ Ž/ó––uht‚•GLís½ÜqužZÉÀ™rR;åÙß‚‘÷Ô¾ÌzU¥»Ÿ¾õêáÁôþûï¦ßþ­›þÁ?üµ…“‹ÒÁ6“ël;ÞÔÚ™J €_зƒQ÷ØQ¢×Ð-ôEg1ž;i©·‹¼ŠŠé}nõÌöÄXZ˜zÆ5„€ÜXcÝ7¹Åàýüf°/O†¨¶g3úko|{ÌM„£Ö¸þgÇSó¥s @‰€ЬÀŠ“@¶: zš· Ç„ Û˜™¶v2ÐD;;="ê)V¬DÄœmáe+mAâ†0õš`™ºûŠØ}Ú¥rQ¶?‰1”å_æËÿecx´^Ž $ëäøÜO²°ŠÈ„Ëó/Ôð“Ï=#¸¥ì9Ì‚Ùýëà¦kØê{úÜß1Ä8$ ÇEEç"çp[IÀÈ*²uÊö ãÃtÔ¡UYŒÝWø·¬ë뫵w°ÿ¤[u½’DÛV¼E \]¡Ù¹®-ÜÂ7&NŽÕÊSDjÂOoCïJÒ³]¾ô»M„Qv‡J f µKm{þôsÏÞrýêGPvÒå'žH]~<]ÿðƒðyL¶v\÷°G´©¿ðÊ«tf›}hiãÌqxïôÅKØr£bÎvðV šXòø%zÕØÚ¶NÜ ]âà• Ì,~Z^$Qpz2]ºü$¾˜]'ÖÓK¯~‰ IO¬Ñ+¯}9è¶37 xžgubnP}½ÄÑB&=ÔøÙÌÑVÚžA˜½¤—“ß½õWüˆ]bƒe¹òùÿüB61ˆ°;ºE ú“¦$h7¨›Õ‰búu«tî2*KAÆÄ€±3`îFl»eìúIÚóÖ€‘Èñ¹º šî3:è¦òZLmbßÛW ^®á+´…ô©^€CHß`Áü:|:X}TVlƒ–Èð#ŽÝ\Ôû&,!›Á6ò‡–޶Ó6rwÿì΃iŠ5V±ƒ¨¥úÓ.R+kÛibž£xžëf¸;µ7aëaon0ƒ3bZMÌsc~i„î[סåE ÇZ²bÅ)÷sN€˜Ð ÇYé»Û’^ÒP®Y½™ç«…žEC+v¢ÿº¶ˆv‡>¶ã´ÓG Ñ@2G#èÜ1ɨè.~çÑ‹ÆÂ6¡…k‘îÔüjš]ZG[—Ò0k×X¤úŽ oÜj›`Uþžw[0)k–NšÕÐs ¥vˆ½A>?˜–éú\cãØíì•zKô€VºÜO»€X­_™g¯±J™+G9ÐS?»¡ ÉwJÜC_߸3½§¾.æÄÞÍ/®§›có´NßIWÒ`8o® à ¿ºGrKp•XÅ5®¡­u=PÛÂýRŸ[•?Ï®ˆA ¯Ä+6ÁZºi5+½•à{¸­a··KЦg=g-¿ Я Õãðd;àó\*Ë(†òX°OîM&5·Ó‰ö‘AØmæmbˆrR|Û«,»‚sÞÁ ¸‡GȉñŠääÈ1|¤mRhkƒ0ÙZ}NáÅcï_»Ob.Ç‘000Àž!{¦¦K`ŒÛ$1Œƒ¿í¤‹£W|ëѹºŽ!­Ž¶%¼m´Äý³à! Aø™Ú4[ðÌ!s”žõåçfÐÙEá«yT„>²²Ù@²kEFXüÔÍO“ÿÄõå»·V£·×V—Ò:s™iûðVzĔċ´Í²JP0Æ"ö¶‹Œr%Me¡þ´ Û¨Žb$“ 3¼!¶]û«_Êk×ÛN oÿÙ›ÄUXó^àXì¯q&æ;Ìß ÅýŸÅVˆÎ|×=Ѩ«õÌo1A;äó¥$æ»-Ù9Õu1‘%júÆ@lh‘ޱ÷îݦlŸ8“ǃÚEøìÙø·3’~Ä5£3úÔŽ7bkŸ£Ý2¾‚ºÓ¤!‹²€È¤@™êùb% <½©ï¢m4?÷±¥ãh3öP-еØ‹ŽÄÝmËÍhÞԇÀzB^ê7XÌ'f¡’/´ô«ýÞ*ÅŒÞÃDïgG±Œùù<ì˜ALA{{°©œR:ŠçY çÞ#YyžÅsv“>ô§ löÁ„Ÿm\Ȥ׸–÷í’«ªßc¶š<:GÓ8†þ6¨ û:þT]û†¸?Ýrš–Ó=äõÆ;½ñ•§ÓW_HŸÝ_¡¥¹É`Ç©wðTZf-ßþá5ºôÓJ»;óÙcóãÄ„HØ©„:ò‡©m´†[ÐÕÒAsmúΉ¬ÃºI=Ø5ÒðéÞæôòsÇú–Y K›éþÇ–¢E<ât‡ºíÈÄg/u£Ó‘—¥4- ÷ ëY›ˆ‰ÿ ûêMÊž*Z ƒ&’x6ÒÇ×ï¦Û³Ð5×4Xô›VÚ3Ó“gšÓÍŸ¤ßúío§Zºévwp„ru=6JKš™ž¥“Û-d{m$åcÏ™°¦Œ „½Ñ¯”ö÷I¶RŸ‹mîì)þ,{ãþè©ÃOp J@TYüÍwÙ × ðbä|=É%aŸù}ødŸ,i¯ó Ɔìêº!Œ¹?‰­ÈÜ-âwb7Ñ@ò q"ÉÒ»c”— |ëOúÒÚGNúSA£ÿ×Fœ·™xn%q0±C ·´²8Çs×@Cò vµ± L;Ïã·Mj06’V§t¸ÃzÈSMMüñãšV¼³ç<ÿYœ¯×¶“Ÿõ)±Ö‰8'FI]ø÷îÞ7(’ˆ5ƒLâXœ¶ÎtãêÚSc³Z´*®$Ÿ˜Ôgú6±~ººÑªÿÇ¿’žñ¥ôÉÕ(ºœŽ’¶h-:eèô2yŠþ‹®a.5se fi43\Þ4k (&2]Pp³R½nvò P‰Ñf@CEaày ‚.«‹“éÞ§ôÞTÑW³(žmdëÖmAg'ï2([“×ЦA'@@K%¤€Sðí#«0*TrîÑÞ€g4± žÑÌú†Ò­…#ÖÙÓÍwÉÀƱk¥ÊÜíüßßh#ð¡hË2mg'Ò8 ìù cõ¤ŸþôÇéáØÝ(û¿õéÇœ3ö€lYÏð2£‹–¡(ý}Œ2[ïnÛ²:ó¾2TÁ !4ÉÄ€  jfzË µF»´mY -cè<%Xð“ •­6;hgßÐÒžf—‚1køÞ­C;Ü✠úRc\˜e¾nv @`÷!ÎFŽgT(chÈ\"ëÑlÚ:#Û-ZqkVœ­žé+±zN1d„ågYëÌ£¨nÇÚ㙀£ìƒ¾¹ž!ó«üg–¥çœ»ŸV*e 5†rç2æì~£p"*Üc˜U0Ñ÷3p“³3 9–:˜]¨a QôzR9ŠŠq ªzoÿO 8ìyàŽÇ _²&¸ÑÒœ¿eÆŒ†á&R‰“£bUð˜qV ó2ˬA¸(ظ>~ìX˜ÿ¬¸®=´Š•㬠oÊþ‚äŽ[Í/ø øjf¤Ùü|ÀCYæí˜ŒŠKîe&ï!AÃR) ây?•¢‚Æ,Ñy²62D›ùŽÀ¡°:×Vñ<‚,J2gÉÒµJÙÌ4ùlû˜6ûyök ƒ,G¦­³æ09=ç¾ÚÄHDî“|L;Y3u» ±í}ÆK¶‘êÖ½2óày‡;«œ­{GǾ´á|ú,»4šF;\*gÈV«…fZ»Òø"{MÆVUìµ8|yhñ€jIìªÇ7ô4¥™ÕŒT³vàlºÖf Vl†èØ\ FÀ#ˆBz[úE™‹ˆ¹‚ùþ3¤<‰c¬‹`§F†Äª!l–lYcYÛ2€;žç]rùÊ=„xB¡ÉÈ$?3{VƒÏV%՜ۢ’‘AÊ8ùеƾ`c<¥ÑnðØ{‘á-ÐkfsU®®¥`®kUqg Fi1Û ƒF°è[¹&ŽÛ}ç\' uŸ›=0b®ÅÑt?Õʯ‰±ðùTÃ>‡AÅ|ìîa!O¦§Á< ¬ž­6pÎwÍ4•¬Ú³;ƒÁ‰#ÎãÝÜÆ@Å9*W›àäuÊ6hнÓß`U^›¤¤,SÆß¼¯¼96‘')hC}²ŽM8€î cf¬yL(ÖÙýјµ­^œÁξ ŒTVqlrÓL83Î]“'žx~&s ÇS00i¹ÄÑ#=Mñ<œ8HµƒV6‚µ&¸ÍLO§šÉß d†v§sŸãaiùÓ´Dölgczù«¿íÀÇ9Ze°á½½æÑÕ»ièìåÔ?|>hñ:€äæg§ž.ºÁÐã̹ËéÊ _ 'iðì>3éƒÿ -ÿø§€,¶çme6Ixû2ú§ÓÍO¯¥÷~ø&²E¾PN²¾Ð†Wäøi"RÐ<´èþñë%èϾó?ƒŠˆÂŸ›—sȲ¶åo@Bèvxä ûav¼z‘yò3ˆåsÌÌëýвEfQVšh# ¨Så&/~éëûáŸþçÔyj$=ÿÜ3T•Ò‡ü4½ðâ+èpd!ƱºÅµ· Õ fƒ~výZ*ºP†¦È•k$²@û&Âô…â»Ê*@¢Å'2Ãd–- Vç¶€q­m°þ€ãE”òŒvßÖÆZZ$Ktü×ÿezã‰*ÒÛßúS’/;Óè¹óé ÷—Óõ·ßD¶éøHƒvý…{D’Œj]yo†îIpVI? ‚÷³€‹•‰Ê½xD¬›Î‹¼#8i5}È>¸Ö¾à0H 3äþíð· …:O»×=0øázhÇ)µÿ¼º>t ÷s¯ýWZÌZÕÛRwyq%í„7p;•´`§Ù•C©°Ÿ{V®çSI’ŠŠ d¦ =;ê!–ƒã`-xbЃ6–¶•»æüƒ¦O¨²L–FB™¤“Œ ûÞû¹ÈÇÈu†Áý ö!¸ï¤¸ç‘‘DZÜÑ&[×ÎOü| ]ršç‡=Æ{ŽC:5ø[¡áÀß>73HªƒŽÊØ]È nd—Ìuõ+z£’)ú €IEÀ…ûµ#Àí” 0qsål™MõX_‘Èä|YéOår–C=oØÙÎïûtùÉìyAt$‹¤>7áNÙëË}]/'©žþ9©®ÕJö% ä}¤ù%À9;1¨+ÍòªZ…ké:Ľø©^•F]VI»i›è(gnÎ&Ê—>z_Æ ö±AZ*› ØB¶ß3fç,ƒv&MØÆÎ7Ïk»Ÿ¨ §_@H?È–¨áìBç&jk`Ñg0É3ª] K׸ SàÎÝ;ÌÁðª‡uJ‡IÊiÄF·*Ê$6Ïö>D§èwì Pq£Þ¿…™ pص9¬×g¼1Xg¿rD“ÞHMT3"_ì„åÙkÒÏ*¶Ġûm­aCbGÆ=t€ÙW龞5ñ@$ÓöVÙÓ¬« œÍîÉ+ OÒD¼Ø‡ðC V~´^^ízD†?û!Ȭ=垬ƒS—¾ÕØ!;œÿãúDÒ‹óŠ`6kb'!×QÈ _Î7#óò3ü/7E¹m€2LÿοÕ%òg=¸>öDc= öøt+cù“äå ìa|øº3~kKÜt,÷±ºQÿvõSÖ*›œ‡óSþÀO_;€î³ãQH Çì¯ÎI›Ôý5 ¤>Û º`fÌÝñJ#ÎK:ó9^‹ âZe„vŸ6°‰-ê÷ÚñÈoþ ] ~Ü8X?E¹/]¨#  Û‘ÄDÏ]d˜¶¯~ƒ>Ý‚¬´êàºÖ4F5‹Ÿ6¨rò\æf ò¿Ç‘ÙÆ}ÜAÞªŸî}v#]&ø²¾öYŽÏÎ+YwmWl[À2+=OŽ¦Ñ‘ÓiìîÝ´ e6ƒ—¹N1Doúà½÷Ò‹ç_ýòWÒðð>˜ϵ"º´X☖›éì¥+œ9Ø{¯}þÂ2@¿u°÷TФ6µëv#ú¼¯¸ž½sÏÜg_ú÷Ú?ë½>ï3ÿ_×9vÁ‹  Žr4ÍÃtï“«…Ì±Ï 4×~Ð?ílC^+ï¡™(º€òÄ Ô)Ò—š#‚Øy|cƒê]à3iÇ ;\AÆ’l•G‚õê ÛÁÛ Ã#¦ ­ˆT÷I ¶åÔ/¬ãžÕ\o`UÐ{³ÿ¶Lø®'ùmg›*KäÉý‰Ù4»@—Cõ÷º8Ü vR™nÜì…—<Ÿûƒk÷°aêÓé¡.ô7ô}ÖP©,_ìƒÑìÓÞ]ß¹mìüÅèåe®s3Èd°ö¸l+kºaỈPFéGż:ÅDRmPe6S„? þÌ"@je˜¶î:AW/yHB:0aL ØNNvµ˜§Íðíû³ÈWýòrºxº‹ãZÓ»“¼·Bõ0s¿vß‹g\ ƒX7R6×T4Ç^VÀ#5¨ýmèÜ `»Òxæ¬úÕD÷úú&d ñÈ,ùÚÄTƒAv}qþšYv%³x™Ê~+¯=ÁçգìÞ3ðæ:4Pq[EÅþ*vï½ñéôÕV/÷´Òc£]ÐP.Ýfžf¨¼#9åÏß¿›^‡¯Ï ×( ÷^ ž?¬q­Yüòëĉù…c\&0zžj…8²ÓÄPkí¬˜‹lñ2ƒvS¬Å±³Á„ñ@ä”4ÚFUÁzlÒÛüSç\öD[S]zù™Alß|z'G2Îú~ËzúÁέô5öi¨ŸÖÀøÿeð®céA;(¶9‰ÜÈ1mƒÙúŽ|œZ ”¨•V¨ü–Àä «ˆÁÑÔc€ùÊ= ÚNvRó»3!u" 0X»ßÇfI|à¸Æ¾w“ùxænmzò\Gj£sè>Oó«v.ØNoýäVúêK¥s§»Ãư[Qy,¡L'M±öbDý©Å][ÞËš> –'6ÑÑÞraúUÖ[õh€\<^úÕ?°Z[y­ m›pÙ•$2ä l°Ouð-͘K%Ǹo¤w¯Þ‡fàðáBOžïàP6hÛN§–àÑãôÌã§Á€àMžÅ*s¶.:X[Ó¿¥»е*ÀùÄ–ÕÚ î³x–ƒºÔ@Evf/2 úŽ‚Æe7ƒOÊ$.c9H&…?Õ7ê{÷ */ám+ƒ %ŠmFÒ´èÃ`?¡'åÐÃñÙôÑõû$¢²&ç8× ¿“ŸŒegƒ ­EÐÍ͇¥xÎå3ÑÖMr±H(Z 3»Jž±Év“ʺ!ÿ  yÎõ6V¢îwŸ ø(qlŠʬ¨Þ—®QÕÞ=Îq×vÜÚé&t8oǰfæfjk.PÕâq?LÊÒþ5ؼ¾Hk•žW¡[V±û b_‰„®è’ˆ~{õ¸ž=t—~õ*ÁÖIºGôpÌa؉`àU¢î´rsD-®Y¦bT~ÒŸ2>PŸj×hérÊNì£|±…ÌÑŸâÿ%Œ:Tß©O´Ñ°ØÂfÕÞÒ1þ¤B#tïõz-˜0h¢ï4|þPO1}±»`°öh,Ùñx;uØeA[À$0»Ôc,“$"OkŸ™ä³º¼”zºS,&ã7!Ô€¥/éÁÀ·te’ûoœL¿ZlºŒC߆èxm÷Ï´‚n¤5G9E¬L¬d‰±[ qò¾E,5$ißíå‘ËŒM¹¡ÿ½CLàÎÍŒwˆÎB]à‰vQ¯§üÍßývúÊ—.¥Ë—Ï¤Žæê4Æ‘ÌkTç׿ÑWýwíÎjºúédêë-¤K†À) p·tQéŽ^_'9;³:耆G‰‘4öµó´–/צYºA¬QQ=·AЛìÖÑþÆHŽé$ž`'Ÿ»óÛégjC£í$„5s¤e]åˆeäè¿-öÊs&;Ë‘j‰MÖ [k¨.·ì 5Æ–Òõ{vo¥èËîøËk(àSŒw¸ˆ]²þìÍï§÷ÞO¯¼|[ýC‹Ý=&ޱWTÔó·ë¨žV6¹è&>Xôv‚“ë÷i÷‹è¹¶ú6Q¸!Ó@Sá7ñÞ‰ýãÞ{œJ-2ZÚƒROV™TB«¶U×ßÐ?·Ó·váö´á[ãod«¾’~­XZ×!+›¾¬ü¬ì( Bx¡83ÑO2ƨì1S]»±ÿEÜÅãðŸð·6Ñuv79H»u÷@>cÀ/¶è棾0ùED|Eš¬$V·OòƒUÞAð«>¯ëcà<â@°ªEwÎ]>r}jXC*}À=+éx3“ênÝŠäËÙ©‡ã[¨m ¯©™æÜÇ®©[Êíà—øŠŒ¹©®MȧŅEžW›Ú‰7UŸñØK†2Í¿Mb;D×µ¶uëoêf‚,šl¼‹é{îΪÂ"ËÍ2;TÐT—ÏÏŒA0($×bÖ,ŒÀ©Á¬¹©{©Ä¢ÊÜõ|¶ ‘×côF;óÔÄ=’þøm]´'Ø ¸Š²Ìc”×X¥ºÎê\',°xDÛÞZva±ê"辺ÈYÚn`¸1.¯EôÀXa1öî;Ü‹M‰UB{.À×ÞIŸ\ûB6@Î9ÈToÀÄK%²~ææÒý[×£BÈK‚ifCغE BahÀ+œ~6Ò3ºmŸef‚ÆË€@SàA¤¬‡Z•… W0Np…޲ÂZ ߊ©„lÛáÙÓÕ*ßv²±uXŽÉt5¸¦A’ý yŒ Î‚”8=×ÑñØÎNfUhÏÒC…¿TZ$Û«Ä~¡p60V0ø"lw^ R¼¥¤ðÔˆ8f­U ž­2ý°uR´¤â9Í( sdŰ>Ç€]NO&®E™« #¨ÓÅûÜÏÌüNŒç¦dà= zŒá«R0ƒJ9¢±áù y Ž› 1³†k¶a2«@…ßwU²žé µ‡RgÞûojZù&dÿÃBà)p¶I=²M—{Še}à^²§œ«¡0LØâš¾¾ÞlO¹ÖÌ"•æ.cÄÒ³þI!¢Ql‹sÉ{÷ÇÈPê ÑÊœdÙ;¿gµÀò km möiãi@Ö0@öe{£ˆÈTb¶‚q¸>Y–ÐáÊ4—ï46üjÌåyHܯ¦÷ŒÈΨ?Y«Úž¾7ÙÔhé±»A†*gÐâ?7ÀGX—Ð gy™ ÍzN˜Àz)-í'ë©€!AÎ+ü€ãf" ‚ͨ܆LOÛ"Â’vY´U«#КƒöŽ©.Öíož¦­JôžÎ)´ê8‡VCã'­úà …6* ;‘)m¡ã”t>Ò4Ô ’]T³, ±85œ“bÛŸ#À[…ýX›ðP F«ûòÎ}äV(™µèÙZ5öƒ$ˆ-xIE%MøËþø=ù·’u1ËÚL=#S({Øããc²™ƒ´§bp/åƒøÇs nš @›}2€:39"&{œŒd«°÷à 3(=³GÙ À*Z“UøÇÜͦ4hdõ^ÖÎ 'µñe“¼ÃÄYæÈxsfäq?^ØT Z›5®òD ñSùâÚð'?#ဠk4g€Y&%b@ONM2gÖº5“›¡ñX&÷Šà†˜•„SãÓ¿ëix¨~Í2/#£™ Ï‚ä<Õi@ß„:ׂEVèà‘î•ÊYå°r¼ˆSnÅ›€¨€£FCÎÌ@ÇXÅjÖG°)Œe·®ï!Ž‘`Ê‹uÅX@f°øÌ…u`¢›2ѳÒyDÌK¹à34ª5Êø§àx¥[š¡o[8 å‹:Mg1 œ ´êÃŒÔföQÝ ÝJ*)-$£Ü½ùi8d%+þO<“ÌWÄwuZ9’D'Å ¤Ue›±»×™ïÙnûa `3#SH`ÝÜ×Zô±so h'+}‡Lê1®wOV–K!?ú9ßmaö>ÆccŽê«ƒtÔz{{£ªho«”1„Õç½âÌø`àôÙÔ71Éñ-²dW“h0rþ©tå ¯‡M°D…óÝûSÅ-ßå&úBÇŸ}Ó°³U“•!Ò°ôåçêVå’]LîÏûžÏ$ÿ¿·´çXÇÇÇ"AÁN<žùpì{¥l&½…MRÎ|Þ×É•ñó/}Ï{Dàš³RïÉg^”½ÓŸÿÉïÓ¯9]yârúìÖÝôGøÍôÚë_å¬ ZB«>H]8VÓӓؤõ|ï‹ici.€ i¦„>µ«ÇÒÄcx}ÄWYË=ÀBhZYTCÀÓ³´¢•üd‡Û<9.X€ùJ‡vQ¤3÷ì>jLÒoüë_OϼðFm;@ƒ]\Ôñœ•GâÖ<ì^›/°ášVcð äˆZ§:SÕ w$á)Øà/×Ûñà!ÄúEP½áKù+MúÝmí“Ô9Þ {Ä} ;ƒky‹ÿcqX;eƒ«¥³žÃV2Xã:¸Ö:Pa÷r…çÂñ e÷c¬Ê×2+ËÝu¤IzʉåÊ%*Of±Ã¿Êô1mmaï©‹úqFéÿ¢î΂,M󻾿™µfíû^]U]]½LOÏhVF B›ÁHC`;l6a_øÂ7Âa\ùÛ7„# ^/lC„,Bh4–fí™^«»ºö}_2«2+«üý<§c;$9äS]]™ç¼ç}Ÿå¿üþ볡9Ñ‘wr6ÌÚÛsŽªžÙ-Æ›´h>ƆÆ÷ñ\a+xÆ~Šñ®µ÷:™óœ9tI@{㼤óœI†w3ˆ¶áíñØŸeêýµ6öè ýhcìçø¬÷\Å(D»Ö µžˆÍ=TÌ_+›ýHÙû«Š¸Öhì59¬ûbë‘‘¯nK'm¨³ÑJx@;ÄÅZÒÇéºSãVx$°!ìë£óJ{¶¥„fžÙÞ„Wà„&þÎÛï'Ôþ’œÑö+îeجñö0Ì{ : Îó™¿û8nn§?Î}7çà’f®êåà“œ­£1Ás­iXÕÑ?ï½ý~íËlï¾öL Ù¥*ø–³¶f¸ Pw:ýV°3£®tÆÚ’öÄ7Ý·_©«^Äà0—IXgôn½tØ©g¡Q²Á¡+/k6tO?# Þ>zÍPhÛÖï>ôÐ/QüìçÞÇOp ûLµÿ +ßïgëïnð|7¼Ô¯½gÍãÁî‚9ʺÁ?Û\;d¶ÏýôíFŸõŒa?µ.lñ½ÄI@0ÉHŒä`ég,±°_<‡ÓLj †#Ñ5C>÷Yï6yÏnÝšÃHâŒvƘòšhaì혵±Ç¯ÍÏþΪZjšƒl&?¬WŸõùèR÷¸Ö¬­· èY¡3‚ªº¾ñ’ gè3>ÆÅñz°¤ÅôèÆhÃ_dš$stgÚ™û¬VÀ0àƒš÷æôîQãïO­ñ3´:0`sì¹coúw ®í¿£¶è?[_‹3ìïhŸ• Òë1hÌz»5îú Ãô½¡".áÍg’v€ÙYBŠ=À 웳l—{dUa¤êÌ‚<ÎVI3ÍÃK×¼Ž•Ô’OC²¥}YH·^½xqúú/ÿÒEŽVCËm0§µdà‹».†Í¶ yòÎÛo„îû9Pùy¶W½²9åMgJJÈ[.áæð‘ÃáÕ|g©†°.Éö8<:N°Ù¿ð富êw6õæœÞ‚m¢ß+ù[V²1t|“Ø* d®ëqÜPNºø~tsbÛ#Â^dþü\?ü3}ièk¢è‡¼Çó¼ÿ§kÉ  ÉßS§^Ɇ)÷Î0VÇÕ%ÿèî5ñßJúX'”íÑ­$\{8ôDTâ8¤ËW¯D`,~Év‚RÚ„/}<'…’–%´þ˜ˆ²¾Ž¹Ø˜/G yGÁP¶Îz¶kz3_àCÖwud˜ 7lÍ6,äûz.Ù;»O¥üÍ‘ç.ß™ö ™½§3vnn\[$G+ÉÅmÏv·vÜç/]¯ê¹€_t¿Td9”6ð€¤+IÌ®d´-:Æ÷÷²kÖ4.:~ABI²ŽŸÏ‡žh¡aR&<ö Þp·³>Í—Ÿ…ü”l„6?u?Nä 9”Ñõ,Ù¯kè£^‚”ªëœ‡»5ŸÜ“àôé¦]“͹»#Ÿ£ýxÿÌÅ{²åsX·ŽäéúpK 0öhm À š;š…´¹%ÔºGMRƒ}ô|¯=tœ.…[÷àx\{õëw¦Ôß™ÃX‡>/˜{a=¥¤‹ÕÑ6]Kû·O_(hµ':k¯›Ó\÷f“³KÙÀ|/sMÈSwüŸª[øív‰8ü’ð`NtÆ·&Áœ,1A2žÀ+¼¼¾Ï×$¯F«Ï¶VD(è¢1ï«ÿëß|·ŠÛ­Í³î’ö>ü0ÇG›Ï‚ü?zäPzÿþôæ»çKöÞ]‘I Í£-¸äY6ϺüKëó99ž_bs ?öý:Ž€ê{0¾ë­ìã™ïYW¾g½?_g´&œŠC§á¡èV° &ÚP"(¬qáâõéëß|§„$ò¶n{wj¿uì\ÌÒØW§£‡|=©Šþ\Aðíù³Ã0ÍÛs%Aò.¶Þh”üôl:”°Þä¢$ɇµ|G[{âYIixÏŒ`Hó”|©+À| xܘÉþ™?6]}@Tü4 "ÍìYÉoüíOJì#0‡ù|Æð¹M†n*iCçC cKñª{Ó¡†Ö¬}Ì?¹kÏþŽ®Ý_àôJÏÝ#€¯úríp6Çï76`G*ˆÓ±þ´füÅdàåËÎ Úó*¹õG¾ø¥x¥Ä´Ænÿy_Ÿ\I¿ðòK}íããÚf7b)øH¢=ÄãŒu~>|IJæNÓb´‹'*ì˜ùOù[ëNÝâÝ0W*2»zíz¸_|é|´ÓÞæ¿ÞSG“;ùû齃G^hò­ŸzÒz;Æq µÚšÊ›#¿Ú×Ù—÷ûÙÙæðÆÞªþ%Â{5Ý@gZãAsm?Ü㎵ÄÿpΠ;׿–>NØPšŽiäö£:O<ÔªgÐ>o®h«/ >ޤ²­$Gð+ð‘±û6ç»\|¦b¾ä–ÖtVq›M™NÁ‹7o:’Oµ­:‚²ƒî-aq2Œ<ÒéÙXa¨[ramz’Qeu»vìcû‚¾ÍqØ Ý359ÖƒnU,¦˜s1¹ù,8Ü0ì ä“Ÿß}ûÍ:’í˜N¼øbÝRTY~¼BÔ‡Ó¯ÿæ‡Ówpnú‰…¾ô©ÃÓ­‡Ï§o½}k$J‹Ýy¼nzóý‚ð~szý•%£¨ ¶„Ã/$âɰžáÆðäö{sÓ½q`Ø Aú°Æ”.,Ñ $zI@k–³}æ¦Ï¿”ÿ|ñN´–Ï7~ÐÙb]ý¿÷„?žv®u6ýÜg²gÂúí¹NÁlƒû‰|çÜ­é÷¯Mï¼w©øbMŠeò7Þ®+ïþ§ßÿZÉl[êØ|úÌô?ÿâ7*4\žþØýòè”(.vñÂ…b™gÇ^V³ùuþÀß¶íQ¾cö_¢8]ª3b2£=E÷ÃÆHΰ³ÐÃðµÏ§¶°Ì'6Yê>¯•ðŸÙš9wg~›a_gWðÐÝøk)ž›«8–†^ë[“LÆÀhq%zVqnOš–ºç³'³9ôá c¼‡vö‡[¢÷èœ!V…”ÁïäfáºùS‡…þö84ÃÒºÿˆÏ„Ni-6cnîž±P& Dç=m_צw7òí7f1Zvʽ;u#.É6†‘­ÓBUáŠÙˆy’û ¿®K>óá‡ÓõË}¿xâPøS‚›E'¦­ÓíÓrîì8úáØ±OÕê}ïô÷~éò»\ì¸ÔâÚEB+Ì È>ó„…Û›Í鉽uP(¬Õ¢ÄŒÁf=Ì‘¾~}² 0mÄ’pCXË,ñ¨;ÿAû42¤š¤3_0=à 46E}t¦–$—(3eoŠ´õ¹zùÂôÍoþfà:Gy÷Ve ø½Ð$ÝÃx.Ÿ/à—€64 »½ìœQu˜¸pöÝé½·~s:ù©Ë ¤u‚åÈyû{¿1}÷·¾ÞYá;ª+n·)Ö;¿@KáÇW§·Þünß:wë÷͵O‘½ä\wŠ\mšüî\i§Õ6ÑûÀ,ॠ‡R ¡R°RÄp25.gún*PŽ‘Ÿ)ˆ«g™«àŠL mè瀂°»k{°pFã³. @I›k\ <`QÀãv]ˆ×¸d¶Êܵv2“öì;Øžî-XYÅF1¡l9´BP¹y Ìš»·®79s ÀlîÏ0ޖº£ýPL·Ü:hó+Cû×çeö&Œÿç)”Æ–iݪkÏ Z­>ÏIšP_ÉØ@o*>ŸfTÏe?‡;…Ô‚æ|XŘ9,Z{®†å¸ÒwUì·*ÆŸ®Ö¢Ñ‹Ëñ<¨jÔâxPI–áV†é=ŽöîáüsÎ2ôXzHŠK}ˆ?óUm„0ÛZ º¤à[ ½Yæ ­[ïM  ÊØ—!~ðÇGç#Á0m —úùÆõè, ‘9z=éñ;§õ§¨ G&L[1•Ÿ´}ßM©¯ =Áñ‘ñ[:¢€ø¬sÀjAZÊnP`B•°{/ÞÊøÝ<ßYkÝ_y]Ùe<0±½ äÛ)òo+U(m( ¼\Eƒ*ƒ:…T¬ä+×nM/×Êl]­ÖŸ=×#uTö¸ª'ç~oÏɉ†pü,ûʾH†¹—ÒB‹€Ëûª”sfz¹ö.wk©µ?£ðwŽáãèþR-°¶×6Ê:Q £- MÉòšË'•n¼ˆ=¬ñµ‹ÝX×ÍædµÆ‚¶U~nè>Î(_ÍPög®ÀÏ|™]Àí0„“‹ƒ“=óÑÔpz'»êCÖ>$ø›PÆqLH[ONFÈ|ra[âo@|]ßã<Z0]à%4õIu¶}”5Œ×‡üÁdJã•õDŽ4Ó>ÏèLvs:ßÄ=%óê¢a@¸?‡%ð7*–Z“‘霡¬2§vèÆß3ú‰vZdóû$à ¼-ÔÕ‰aø2üÌÏ€EgŽÈ:F§móÐÖ€#ÐÙ×›è‚èmÚ3c4¬½13ð9_vyæ9}÷ü½è~÷´«,PŽ_ù»¿0ýÍ¿ó Ó«Öä ¡SžÆ‡-V]Mún8áiF^&Þt/£,pžø|òy>™< ÑG ý?k!±æñÝmÌ ítGRT¤$Cä EjŸ$Ç`á}çJ£×XÈ ë³2zEt$Eì«=u$ÊŽŒý!;ÓË›s¢é•ÖrûôµŸúC#‘ôÿæ_Žî-ÏZŸa€'è.^« fÏRÑãù‚¦öUâÉß}†¯TcpT@#ÃÑÕ¼ï*Re ¹šo´ØoV¥û¦/’‰‰²îLÊ´´}>ã>o®Ì73sîß>˜g¼7ÔOÿë9ð/ÙÉ/»‡@ã}ÖÝ£ÏZrRGr©¤"Rq¹½R9EVx¦Ä<Î;ÁÓ탈÷NŸNãÀ期aÂdi{ÛÛá¯ô } “‘õ-õ ‘!÷ýãYÚ[A@Ç ãµïnŒ>ͱ·[ÏŸ¿ý¢¬co÷\@Æ©2±¦îÃ0æˆ×´?èe¾1$¦ÂÄÆoÍg÷ø¿ÏÖo¦‡ñÛºÚ‹« ÆoŸiNÉÀdðÖ#Ç^8RvíIò!9Ÿxù…9˜t¥Èn@É3ö°]Õ¾Íø.w|Z5n•“^è oâE÷"çÇœ`»¸ÉØ<×wØwìÞn4æ×Wº´{Fè©{·÷»Ö½K[Õ÷Âûã.næx‹UR÷½gœî.yšEôcÉk{Ñc]ÕýëgkÛ}Э„‡‘´ÔóØýR %A÷Áëhf[ÉÖn$S5—1´”.bËÎ÷\ömË-Ó=ü 6(ýÒ³Feˆ=iž­Àì³ÆÉÏÖcLu&×훵#«ÇÛÍmL©÷3õš«Ï“ÙcqX ~_˜7›¢±xæXçäÅàÖäškÓaðÏHÊî;^º3Áý*Ìu¹•Í6‚vѹç ôÍÙçxçûßûîtýzç‹æPåwɹñî\ßs±àýq/~ÕíåøË¯ÖòòüЯZßýðÃ|ÛâÛ(³Å—°°ÚwŸä;q ™ºì©ÕhìV×ûUŸÓãÖ¢ràsµüüñ¯ýþÅqÂéÆaþxm9Þ GT‰¨Þ‰vL}´xGhF ŸPÄŒ¾\öÃ¼ì“ {Ô­Õ ‹ñÀæN¿ýkÉpI  *yŸWÄA¦r²«,eï { Ü.dOó¯õ•è$?ÍãÛÓÍ[U&væl‡ÓƒYU % Óÿ°Çúxh¾NðÞÙ/oi­öç%¡eC¨. f×&ûݼK ’ï[w •9ùÿª âCëKÑ4;ZÒZ~¡äáõZ™o¯«"{@··å| Î*®=ØŠ“©‚`{ ._¿Ñ‘p}gwIõKUÿI4Sng¢ÒZW°|4“ì+‘¼x–(cıQ†ÝÚçô®ó—9Ù;|™tÚà­øN` ÃŽä5þ´Îdäj¾L‰}º;$4Z“ø¾çè²v» ?lnñsRwï¨[Jü7S¤ ÉófçSÓ7h–‰Gèd2«åÏiÉFò }ƒòƒÒ’¶GzÁØÛÇÜ`”½¼rkЇ5°"Ûæ“媋ùßð ¼wÿQíÉoÔÁ ßáþ={G{Òµ¹½Wfw#Ù!¡ådÿÌéò½êô§c…„qxMÐsæëc÷¯F›ð߬d 9=‹,IùÑ ›tŒ«±ÒµŠ¨`á-#ž=wÌ\>š®ñ×b LØ>3¤Óù×.Ö}wU·º­}øÁéqôÌÝÖ€»t½FçѾäÁù+/^8;2ötTnOm]üLòuH˜U¾Ÿ×!ߌJím­U4Ó3‡¬M. ¬±¶nôY8ªÚ’Iøp©²Q²‘õ…ÑÇú0ƒ£%¶ç3’˜w¡êÅùÖó¥S/eûÞš ìñ`ÚZ=<”ðoí6—httúÞwÐÑœ¦—je¼©½^Ÿ|_Íw“8{—Ž'?çÚgÇ3mj®Û’ç/7ŽÈ8ú¼;†Ñ·uç 8Û˜/ÆüS)™žæ¡OÌ[ËP›í ýÑó™sWkw\Eok'hö`1]Óœæ½r»êÂdËVºdM×ÖFÿdÁÇ%̇5‰>¦épòÀZ@jœìÙƒÉÝzþ£è–ïO@Лp¯Ä:R›sAAAš5ñ!? «BTAgœ„|³œ¿'Mº–TiÎKµ”;pë»ß™žûÛÔÏ+1*:\ª âOþôW¦§ñù[ßùþôó?÷‡‡ _’LÒ5ŠCTG›³*þg«%æå×Uµ/²Å`ò š„í(²YÒU~ƒ|×}x€_ñq6¼kÈIvƒ õ†d#Q´±Tàžn¸_•l[ZªÚ¹ ¿ë3ÚÚ[ÉÔ‘$'qz^ºÌ,þãHÑ…*ŸÇ¾EïŽCnˆÜÚYÎýŽáwÜ\¬jÑVñ½ä>öí{ÇŸùíóÁIØÞqŠ @o°Ðõ’!)_€Ä8zÑ:´ ùqóÕ¶ÖKa7ݦ+D =ÞƟ`k•:ž«‚ÕüͰæPúÍÂYà+ÉI ;û—}öÞ;o†?ovÌdBòUžzyKÏ»7ýí_üÞtò½ Ó—>ÿÒô¹—·UxQRxºc÷ô&¿æñô÷ÞŸŽè8ŒýÛÆÑ:›è„sñBIaË[F‡éyAհ›n€R5ó0‡¸$»Â¹ê§êê!d$Q‘pÉšëá˜ddq€Å´‰w,éGgoÔ¢£-ëº{s1;2ÞùKÛ b˜_~cÇtt6oÞš~íWÞ©“ŹZ–ïŸN¼âHÓ Ù¼÷ Òž‰7?êû’2ÒÁñßÚöh–DÝ'÷`ºEnmÄ%Ð%’Øøêh-ø,¼†OÞ!«³UÄì`rËþГ|Pã;ͽV{‘=pC¼‰ÿûÎ8ò¨ûè6˜ÉºÄƒø‘MóH¨ÆÃ?@Ž,Ð/ãxäâçå*ó% ³ð?ìÏx<™Å¥úÅŠi6vä ?ΰëâË¥Žˆ1€=7w|Á'0ŸŽƒÍÐÈjò ~qï§uÕ] ‡Iè$¡ÍÛëž–Þˆ>û¬Ýiaˆžõ¼dðØiÌǺ8j×ÞßüÉÊ»É 24"þ´gÏ%´Tí‰á‡E€?têã« {‰"Þq½Î}«×o y@öãÁ=am¶´D(¿;{-¡Ç=ÆAò¬¶EÎ]^S ? 8`Ö²˜M "¡\f0L!¶ 7¯_nšx1‚¥­øüšZðe ]ºxnz¿Lf|8ÎBn³eOiÅù¤J‚qøB“Yj3Ÿd„aT úÞ­k ´ j¯$àÖ—Õ%CȦZJ€Y X0ðÃÓo—¸núƒôß@èΕÎk¸ssl€³zTLœùðýÚ¡!´¯^:Ó\;öv0"©´Îê¸uózU¡µY­G„¦ŒßÊS@Æ¥êu¹@„ÊÄ íà å&À áá˜iÁFå~×Pœ†ãLÁ6l÷î„}÷ä“Î+Ä>râ…”æ€ÝwàHNé‡UÉ»ŠÞW zw>CDñ¨ïH@(,F‘ ;w ÖÈŠ*Ó?bt4äö(c¥À¬#cöO}ú¥M—sä_™ôoÞ¹6Óx“çÚ肇÷®Wu¯juªGtöjÑžÖ%î2›Æ¼µ³znG„—k¥âì={:¼ë8ÄÃæ€®™}ÑÊBßµ/ˆ:Bß ¤]1Rκ¡p+ç­Þëœb‚žpÊ,48Ê}msÜÔ³›Ëù+7U dH­Zu^¾qwŒÓüÓcÖ÷þ=}wdn7ÆÃå«e‚'̆BÀ©¦. ç/ßë.h@aíeåÌZ…¥lÇ•€§šö”¥ve-®—Y+ üÅùãü¢æžðà|8¸{G×gøä: „Åè=1ÛX™.‚æ9\2H¶ˆÖu¶ø•2ñ¯õyáè¡ö݈úNûöl¹s›Z?¯$´6­Ù>]Oq˜,Ì—ÀQưkö’*Ö6V£OæûkŸ%»¯&œUìå”ðx'ðpwåaÝ®—Ñøíy«ñS4¦ÚxuͶ ÿ k1q/!˜iÓÚTæäƒ T :'¿ÐZ¬›NÞ7€&eÃiކWúÞ’k´„"ügçQ“/^Û;•¾ràzͯͽ,ס‚p­GÛËÖÔÚ3 [BáqÙ‹d£#»´½ê¶=çù´¿}q­ÿÖÕZžQ|Ï™gí¿@Òí¯qã¹ûµlePKÞqo4-㜢Råá{ãvm]kíÄð”!x=C¨1Žk‚!ƒÔá»{kåkŽÑÈæÕxî•Y=·)ַ˸o>ž¥M›'q²8ž€}œÓÒLËÊßóâ¿9Ê»ª®ÀŸd•ìó<|}÷þ4Úèm×HbìTÆYƒÚHT0oò»öÂ9 2gÎ=ì½oÉÚÞm‹f@ u)ãJ°]溳"W['Šy6­;šÅ‡ö_ELl‘ÓAÁ±̓,A¿)µ-OUý€ùýö‚â°Ñƒ0°?xL‘ ¬C^õýn=Öœ\y½¼Ò{ º/ h?“-]¯Ò–<´®>ˆ†akÅÙ!“ÍŠ ^κž;œÃ œ!5s,sÈöê:²s$îD›ZË0æ€gô±c×Þ@túéîùêtâä©Ö²3ãg­xÎZvæ<8~ò•|ñUï6Ï…dû“Î6Ǫ7rn>©m[CˆvJ´=Ž¢*-&ç€ýf2T.]¼4?ñâ´çØ«9]/T•ê¬gðºi¼ûæ tÙ0þrÞxž5uÔ‰û}xú3+«ûPÏŽz9ÇV€EâÐö»J²ÓÒÈø;rüåé»ßý^2Ú¢›Ö½ñª¼k= …‡9ý*øÃ`<þrãoÿ|ú{õõ .0þï¿ù½ÑqMÙot¢5w¬ ¼äÚrýïÌŒ¼3œ27}æ³_Ê80ýÍÿé/M®›>ûúËÓ¯ýÃ_›þüþ禿øÿRíÝ¿Ò~ݾô£?Úú,æwçhsŽ2g‚Ššk—.Œ}‘ÀA¾ŒÌÞ†Š.צÏ9ï’„ä½ÉUØxÿárº/™Kîqò3bùá±Ï»_zetuxûß/AïÞtàÈь柆$_Íœ2Z}ž}ÿýŒ xB†x<,QUI·É•§’ÎЖ[`=‘œ¼©ú=CSÒ ”–‹œPäB’{È/<º>Þ ç¼T)É~or|¯INà÷ÇÑ5<མëþÖ[ÒGíÆž;Z÷6nmÕïß»Õuá…Öbÿ¡#”§NœlüdfWjë~¯ÎKζr†§89±Ò}ÜÁÄ2àÉrï1¦ThA¹¦J>I[k3çù“;`ÌÜ2&Á/x¼Ù¥‘»G{ü<]£²›nU¡íë†î‡fUó{ 9lÍ„>Gi²Ð³èzØ#&m ÝFLÖv?-¹}èo¼×Xü<—!ÅI™°Êø¯M}ÁIX@I˜°3ƒfg•£:,}5ªÄK˜pš„M˜ˆãØTý0* 2¤8UèÒ U÷Ë‘ÝLûµ7£8?´n0–?³dƒh§}­§s”2ôzòg¬qcvDŠ—ý…=|÷’]tçúì™ÖsT“iýŸGK†iÚhb¤ëÅSjƒ¬f¿ut›éÉ7¹’5©ÇäÙûd" ;º#t±ç§§ý´Ô£ûÞ­*«$FëÈ…_·$Üsù¨}c_?zöhºV¸Ý÷tqYi># ÆØ}Ìöb8\š{ÉìZ´2è!y>l×(5¢÷­ùh¶fÞCÃxJÒ‡ux§gZ»:Ö‘C’M<[cž­×ÀéãAµCfõ=ÏA3öÂ?­ÄÖ¯ÃÉDpàk3ÿQö•îuÖx±µÃ Û³§ÇYäÉÀ{Ém¦µŒ|\;ôååh$Ìž-­q<MtËö¥!IVíy[4ÁSÂCÖÇ@$ÌZ'¯.™É3é=³öU×ΜÖi–¾³¾q {3™Mÿ;rkæ¤wK´:Wg¶ª ÆqìI8óò\;àž:8—øö[áúŽ^êiäy¿)ÜÇQÿƒÚ‚st^½X…LNÀC%ìãG¶©ã!¬k,hU%¶Ì§>ÿ…ðÚ¶Ú©?AsŽÄ½»¦cÙ ŽN`óÌÚ5“WÚ%9zxt _°Ú°¥@Ñ‘éÜÙ³Ó®üü'júÜç¿8í,Ñ[µ9û_UþÍ[7¦ÏžÉæÛTðéµø8½sÎ8ÁÄ/ãw4×ÚZèÑWßapkM¡sÏð4ÿÿÿæe ³ç&Ûv°ŽSd%üaŒ0²c¬•ó’ÑÏ‘Èo ¸%›ˆÎ‘$²&ûÔ=a8Œ-˜ˆœò¡{Mwz6|lÄî]ÕΆt¬]m=Ñ7ûÿiíº¥ã­}PqGör8UÅóóp…dXë¶>ü!©i]ôÁ¿U5ò®Ý‡Æ¹´ü/ž<ԙܻk“»¾jóèùÁÅlÀ’˜£Ñku Uz‹îGËVž?áIŒ' 6W¢ÿBØÀ1…GÕssеyHÚÞZ÷>‰A/ôxë °µRÕ/¼I>ÌdX>‡ä¼¤ƒ³JX+NoÕÑ©]­,Æ®–ô¹!Úüâ—¾œ¿ìú8O{ã–Ù¬µÅ®£ßî]9˜›Ç©—N Yu§upT_èJ-êÐÖ$ëØ^me2ª¹¥bG÷–ø] pI2…õTL Ž hÿJ^¦Óé>bRw*X‹~C°Îs]Ml‚M³‰¬),}=¹p`ÿÁŽ£<2~ü^¸0Œ[2âæªÌwîê,ö[—†­qüø‰éÃ3ç³9µû=0Ó·Ýš |ö{Ì'Ð6mÊ÷ª“½ÿ,}8Ž¡ŒnB”Ù1-ºJí*"e€’#§9˜^ˆ¯ûƒFÑ%\â<ñ Íemsº›Mµq;‹|ÇÎ]Æ>ñb(ü3Ùñ‡wT×µ½÷ì+±öìé²YsÈ_cÞl`›ö­LZÀËQؒ赟·Žx‹ühÌú6im삜|"$c‚ì;”“¿¯¦Ÿr˜=KGGXNºtérmðOäW}cú­o|£Ä…üt%ŠoÞVŽ}k¦—gÕÚ|Ü×ójùû˜?»[mlßVj<ëZ’<êw¾øF¾½Šª¢SAx²‰\¢»ƒÁÉâdJc¸^·w÷ YzK€LáæˆZàþq¾Žåø|0Kæè*@jL3.Ì/ǶcýÈ5r„$ð Ñ¥p†ã(Ô*[ÁŸu]ßZ?8ËúYŸ•gÉÆË#?_[]ªÒç#Y·y|‚q‘ð?÷«²_î_ÿ(^¬“jr‹ °¾xIbQðnzx»@z:Ja£¤ÆQŒùT™dj´Õ°Æ>;Ò)¤>ügOšßð•ù°»À=%@ñ)Ü«hÙä_ûÚ(À¹wúæ÷Þœvg’â;rޝS"Yä’OŸ+Ú¨*7Z£#¯‹˜vï|_º†ð]Ð>Ž"³äœ3Ñ%9>Dpöq£Ø·'ùá©›ÑÔõ°7ŒùgJÜòNÇÞµ?Ã'o|{íq׭ͳ¡NÑýN~Þ¼›n+¡ìÓ/˜ì+Ð[§›Ù‡÷–šsíÂ7§ ®?ë8ÁåÖš™·§Ž&Ÿ{ý@rþñô+ÿð{Ó[oŸôóòk§ÚL1¢¦.œ›>xÿÝÞß8â‰øM?ò;ÌWX¨+šÎ(ŽMuõö]u€hL‡/6™ÁÞ×QÇJ°q ¾µWt»Žœ'wÆYôé[þ2Ï’.z˜%Ú¡vÚˆ§â·¶røfÚàüÃñ@¾zw/‰lZö}“dÊ(ì}ҽѹ×>£-¾iä+X,~Â/Vºs߃ Ñ¿åjUãðò2Û¶1)žã`ÅzÒ’î7¸Sü]¯ Qñ_üIÎJf{R̂ϼק‹%ßK¨"-½X"DsÐÍî©äŽÆ§ñÁq-m|בBuO²M‡GGÖä0›PÒ›¹öô1èK€Ãq1³1C“ÕÌmGÝ €7_PçîÍx)ìu2vö^º==¡eG@£ë¸eÛ P§q¬3Åœ‚H–>ü(å”)[’’]o<‹ïܨJ< ·yû¾Ñ~ˆH¶F< •&äLlÏX»A;ù@yÙe›ö6ùíãùK ¤"HŒ"ëck žCM؃µ¤Ê1¥:õîÍ‹ãΓÍñ/°lü²D£¾pªF@&†åD‘YË!;ĹB‚ãÉ·fƒ¬âcÂp—ñ"³©`+ÁG9l)Àç|+Õ"‡:ßb´4‰N´P™gž·ÊdÞ8»gÏê^S^¯¥ZªçWw%ܪè•%»¦*A H+U:`=oמÝenNp\ë,!ŠpêüëöFåÛCU‚ \Î'â âG†lûá¬Ý½ZW~Z€¨¿ÀëpäöóR€íQA>L‹vÿˆ90B`ˆEê VUº„’53fKù ÖßíÞ€‚y=ì¬ÏѦ¹o¾”G @FÍÇ„Aö’ï2ÆÇ},ãóèg´¯hh2ýŒÑ( šgmmŒ¯™£’ó²Ú’’Ђr´cêÂÕÕ”E릵šöuöÓX9[¨ÖR‹Âh%‡§ÚJŠçöJêX»¦€`Ï"\øV&PÛš´ÞŒ Qc2ß–f(BcÕúÉYH›2‚"¬ÖŠa=« Ô._¾Ö˜ ìú^B»‡´n™„cz1ó´jܘ/wN™ÊSŸáÚú™á¾®‘µ Õ {÷ÌN¹K*Lûy~ýæÎ©ÅLÏ»}Oæô†éS§Žöìö°uy 9xìD¼\à.Ey(ð'd^KÑãÝ 9x¶U•º¹yÌÏÕ~'þÜÖ_‚X dÍ23ê{yeaúà|FaÏã$£Z?ŽÁ‡ÎÛÏhM`æ.kë̡Ǩµþ‚,@KsÎyÎÍ„â£ÖL¢`O)ÎiÎ|¾v^¶Õ½\vUqލ è£à¡7g³íÌ0Þút!þO)Ä_œï|pžî²EKœsõÒåZ)’aEûjSŠŒiÜ?#wÌ%Ú‰K}o‘×·Ê0|ð}C ¶¾#¨ŽÓ¢ÃÖâà %µ¾ VU8#ˆŒ3®Ûi8oÓðò®Ñµs7p}ùèü sì§ò¨>(“Wjó¦¬vG7ŒNz™³×¯ßh=¶ ?®]S¥uóÞ³{Ö’ _ ›«WKœ‰'É"<ÂLæ«Ä¤ÿÄØžaF™Y/2HbG=&Á„œ¾}«3­8~Úsüñ˜Òl9 ˆrËž ~‰ÞžÊÖÕå`oUæèäaÇ“ þ.O€kÚ¸XkÆH7jÛÈ^î;@Hôoe´ ƒ­qd=átç,Nþæ6’ÈÐDkÈÚÔ ¥Ÿè~ÁKˆ];jé°C—×jy @êû›Ò×Z×ÿã7~}úý?ùÓƒw9ltn9àKîØóëWÎw½V×:“òö{Z‹ójVôݰz~H½ïàù»erälhï'»Y´Ààes«=V¦)ú_)ë–¨ÍÚ–n{Ãÿ9|N{‰öÐŽ Cq[5º³gŒNÉ‹­ò±{oÙ4kž¿—_Æ×R·+sÓ¾øå±6ßùηr¼È?>Îö"û|®âƒ œi§ß¹Y1´^dâ GŽOòßû¦_ú…¿6}øÖ7jßwx$bü×ÿå1=þ÷ÿÃé'êg“=^?ý3ÿRUi§¿õ7þZ‰}‡ªNߟq{·÷Î¥:÷mÒÓÃÍRƲŠ9a< Ì«xÚšÌwVÑjîíð~4b®³äº†³°DÌ Š_ü[}ÈW´úòkoL¯~êáØ@´§^~µÊºïL×SÄQÂàƒ‘[G%ñöÜÀ©:S‹K²ÄªŽ ÖÇò˜8Q28`¹¯É„küQ@¦=ç}%BÅÓóÎôòÌ­éʨ¶R[X%gÐ0ŒaÓx£{8/lÝ.ôÖpVà3v CvàÀyÝG©…<¯å¥*ª»UúÈ4v¼FJGXNIXuEʸâ„[é~QÒÀëÂ>ð¬y¡‰áth8Û$Hu›†¢âŠ!Æs¦\2(ÃQU Þ}ü†t= A¬ÅkfÏñ}ôÀØ*tçÆã|ìÆG3„Geo×’yT¼¬å“Únþã`¬µêþÛs&ïÝ»wºrætU ÙÙœNkWh/£?’Ó­ãn¢û’|ã?Î!›Ã@¤»ÕÔÔ"aî£Ðßì´}€ІÍk«úJé±7èÂÚŒÁ÷¾ÿfß›a6€¬í.ú]Ù#[*1•žQüÛž8ð^ÆœU±Ì©9³=º¸ 4Ǥê‚Â@ä >èíäÀÌ‘ÑSÚÌæoÏZ;Ž(-—=¾tÿ¸éK+ÙJ*6í›{K˜°ÇŸt$Å'Í^’<ò¸÷Ѭ„DÁ–ëU#ß•ÄÔ:IpŽhF—¤}ÅLéYcö­nÿûÿøx`¿ô]ëÔý¬ù÷™±sš›Âò¼™3}Õ›ÝKEËÚdº¶“šcŸšçLÖw±px§ï†îáž®›’[Õ®Ákø¤õißï:4†/%¼Ìh»Ö¶áñ}ë’o£u#çt|8Xp½.ÚÞ?ÖöÄûü¾4xñűß莃 –¾]ÒÔ£þ=zäXíˆwŽ1Ñ%ðµmìEãý˜Tš®ß~_ãvß³gއýÞþÿíVf&`®Å*lt¯îMô}¬ÝnÂù®Âæük÷«n&‹´–<·± Å#G@¹„¦U»Ž{ôÈöåss6¬V¾p¶=ç[›~¸Ó™¹Ë_·¥÷wØÞÆž=ÜÚ?ˆ6n\¹œ PÛW{€6aý0?¹²L ¿÷ù×–²“öä¿Ûµ«®Dég|+\в}´ÞŽÎ† '=¯óž{\ðƒ\7Ï8¬ñWÁHÿÆì»óSíÝ×±býîÙV„ƒù~Øåd¾N–;²ãöÀ½ðÆB„²iK~ªaË“ÍÖ$°cö¥Gù²æªà]­àe1Fo¹ºKLÈOòÀ ‚´”G‡’‘o–„â|óªPݵ£àJ•Õóóu,0†>Év:~à€t$E’#8˜U£‘{ lì ÏŒ„—ÖhTÑ÷ï½äŽÇÎ@?xHÐ2é’Ìeïðwë°Â/B6ÃW@Ûv̱ñz¶Î`Ʋ5>$ï$çðˆ´ò#1f~îüpÜÓUÆÙ#†/„ݽ™£<"ЉIu¶Ä+x@¡Á¾‡$ü1º&/nݸ=Íç›öZ{ràÐáôSÕoï—JÂxÜõ7+  MÖÍ'±Úò‹Ü—ô$JPìÙÚ'ãˆIø _lØ~°'fKoÌ—–¿2ã:ÞTáO¾Ÿ|uròßËFl%l ±¤´º‚övG~GÔ |Þ³ð ßÍÃüB÷*þ Gt:zäHÉš»ZCÝB"ñÂÂÂíäTZ§5ù|7õÞ|ç½Ó‡¤›ÏÚWóP(Äÿº-_ï¾:ì*Úwðqüºö\ûÞE}ÞdzÌ=K¢X[‚Ã\|0W %'ÜÀmˆaÏî=­gúaàŒÙsâ·›/Õ±‚=ÜÜz:tpø?`\{*ØG^.XÔಠª,ø¤jœî$'T£-¯±/ÉïÅŠŽÞÕQ;ó¯?™×½î­ÓgGb+Ù ¯°Ñ7ð¿èÜâœuó ÈÍÿª¢Ý»§„öo´K&×ú¬¥¼‚æwoÙ3Ú’“p=Ï·êìf~w<«ŠTwø—¬œùMWu¬OtÅ'ɸ6›çQßõl$ù«íÜ»s¤ÁÆ¡Ãú#ã¶×=T5(lðwßy'vj,Fe“˜ïHüˆ’¥Ÿ´oÏ›_·–ÆUb¹ª™“-çä[ç“Á§:î?x°íLÇ&»¢ø¡g$§ÃšýÆM4ä—Ž‰[jãþ‰­÷,ûMÁÇá#¦m÷K¨ÎW/h´”ì|’O•Ìæág•è(q‚ BÖX§ ô ¨Ån„I|þC½|'>}ñä íqÁ×öA*ÉT°ÿ2¹cî^ŠÍó#Ó…Ëç¦o”œò¹/•°í¾ðeG¤svEô;Â1Pd¹=Ö™Æf‹£]þ}1ªV¨½‹ÇóñKøÇ÷×o¤g<ßáròE¬ÂjÝ)±âÜ•ûÓÕëa–dzâ{¼|^|‹-ód|ovÈðnеdV}¼¼–oß¶tŒISm•Öaw> Ÿ£_òaÿšC–HN6fÝ£¶tl®ãf†eægކݕ|@°6ä€ÿ†/&‹ŸŒê‡M4ŽýHÉ ›¯gð)x±µ`·YwÖt]sº]‹ãTVKxp·}®hÇ‘«ÉŽ'% þo­Ù+îß n` ýÔÚããÍÛJæ k¬é˜‹wO_®­{?Ûò9Š¥Åó SÉD… blïûé`Áx…|&©æ_¹œî’}xôØñ‘¸ö "£ëwf>ûN6\½äØÉ[é·ÿíß8§*}GÒþ£×ããæßýù ØJósÉöpŽç áácŒŽîîø¢dþ{çêð>]È–Ù=>(À,>4Úy¯Û>zã+}Gì‰ý7ð\tI_Ù-ò,B…”øŽÌ¶Oò.3.¹ÒÞÙÛèèÙ\2¬µÙÁœhÄI ¾¢¹ ÓRüÛ]ÂG»Š/fg¥W%óïˆ.¤9ÒS@Z¡¯üÖö|o˜@ìH•¤Ú[‹–†íܺ˜;aæwHÖèhÔ½|Có4SI/ªŽ'>œ©Îæ€Ù`±d[ı±O’™ëëˆ-)Ëõ0Â\“}˜¬Àkt =ƒ$a,>¨(aé^û#Î^ˆV퇵XDxºu¼WŒ!Ö%s€µ!˜—’äüí8Ú€•£ ‘´¬ÝX`² ¢úÀ‘}‰sƯŽÌ­Ÿ‡¦Ì‡AiS£ª¡¬Z›†S"ÚzpÌ>i¢3Ç@Â7P½'_<1€lÿkpY}Îa@±¼öº ªC-F›Ã?~òîÔìØ±¿³1޵ ·Ëú;Üæm¯º¼³bŠUTnŒi,òݲìΞyŒùà¡€l›çìM•r˜Yã~™Œ ÎÎ[·nDp÷ú, ºfc„’M° Oª_Ÿ#Mff¤9z;<ãTÅ/b™ží™öÎÇT­­L;ŠT[ ógï”¶;ð³­jùÛ1Ñ£iÿáãÓ¹3ïN§O¿[ÕWç•× ay©6‰´¤››N;0[ßÖDvÚPÔí‘ ­ÍÆ,2'àòEpë7j_õzàä©— =è\âsCÜ2W)Xgq ä )•…ˆ¸éE,Z%¨ü¯ê'Bzø°–qµnçœ6WN"D(Ûk ½?®ÑË•Îd$à·7g`Ãi–“‚€ʳïs>?Џe÷ÌîÑ{ÍCðÁýÓÊ@ªºqB'šÌ°Yn| ƒn–jë,x Äsri-BÑnÍ ã`U$hºùöß )JRÕ5G¦,2-Öwgr: Ò:ÍÒ¾råj¡ÀR¾”?¾évCÈ­6VÀndÛ4A?ÆÆç$|M¶dƒ†‚lt H?ŒÇ(`ß”“D¢…Öy[kÇv÷Æå±§“—yG†2x÷f|¦À›ã­ÚØT¢Š¶OÞ˜®·’ (™‘¨Àˆ|d uöÌjŠr톇)ëd<êlô'`ÑøãŒÁÈ ú¸>Ý.˜¼­¶³’`æŸÕ&£@Ÿ`«ùï*YcnùظÏy›"Ym.‹u‘€/»y×¾”^Nùœ °•5eº=8—qH6âÅüU´;*B»ùÝe ož>8{¥ 1.8¹KrhŽ®·Ç ©ÖLÆÒr{E‰«T~”2¼;ŒÆ§OË ”IhY²UâŠìÂ*z‰®Rd–óUž×Vð~™†Î×â‰pÂAÂU|–8׺jÙ5ŸñO)ÅÓP1ΠWtŽ/—›'þä{_LþÉP»¬' *–õ³n?v²ˆA…a°9_)e ™‡qd}Œ‰±DƑӪ‰85îwë‚|¶à07YrŒÈ'”d`À÷é“~‹0 ëtÉñA zVr´yàg<1ø¿g/öùµŒé;ü0ŠyÎ…âÔ! §£*®y¢S¯1ä0CÊz&ô2*òkðAßÀ°¶+:Nh#à °;e,™ð¾—R%Tç-_¼QbJóNvqÞ>ˆ¿vU¹>ýÁ¦2ÕuD‘Q¶]ê¤"&;ân¥Ÿ‘’9#íeÈj‰‚~›3€%g¤`+þå\´ß#sºßÍ{8É{&ø?œËý¼{Ïî®YH¯\/¡DµKsDÄ‚‚ë3@˜w<œwöìGésáÜ™¾3—épk’ƒ§ÏUo~û[ߊ~[§v]tË©±'ƒÌ¹.wK(“1úhh&œ }µ=TŒ’%™h}µÄÒf‘•·Zï®ô¸µà¨{Èlm ÍÆ™··ºæA­‘ÎÐKTÙ¹/ù}wæøIßìÙ±oº|éòô½¿üßLÿΟù ˆ~zðÀºdƒ zë3äi ;“†lD›œØv\eiñþ,HE~ãÉßû/c„58µtŒAß뾂Ñ}²¨%÷U+1ÖówcFhņÑþå?ò¯O¿}üàÛ¿:½püpŽŠ=ÓòçÿìôŸ=þ ÓÏþìšÉ¼ä@ûâÉ“ÉûíÌÚC¶çá¯}ú³ý[fwíñ´ øÔ:²ë{OûÈ ï$7–äõqöæŒîÃ2Üš7ùGÈ]‰#ªF6FÏ3Y¹:ýòßÿ»Ó§?óÆtèÈÑžUûÒ0ØÝþÝf[¹§ÖÓɤƸ>^Hÿ‘¥d» ‡ªÄ{É}tAÊU'Òç1L«’ ˆg%‹ À*£JbȤYëÏMñ¨sìz À×wšK×sNÇzÝ^Ài‹~Nß'›Ì™áè|o:pcIžð« œ/|áËUt®c€œ|¹~å|­nOtfâÑ©ær2u ›Ÿ± zY·‘ØÖ7ãÚt÷Ñà‰€äÏštØŽÖg67ò‹3wæ ˜£ë$áúnÓýMIk®‚îC¤[›†<ô£‹È}“cÏ`ºMñ0¼ m¬D0|ÖU$Y­#Ù4y.î6…#Ö¥9'SÁÃîà ýcL™êHf3šfûBæ6ý柞0î&@OÒUø¨­sÚ•ÊÂÆ˜ 9”`­Âˆ£j7…{#9øÂuÝ懹ÔAà‘(ÌA±}ë¬Ý°*R×Ó·ÕhÝ?iaý±?gíútY?®í}Aó! ýÖ›c¿Œ/ÜzÎÉZKZgúÓYƯ]+A2ûãÐÆœŽ9o§ç`Nfúý`XŒ-¤%¡daç΃%ç¼0öm8–Óûo½ó^¸ÿb8½èMX< üLúŒùEãÑZ è’ñ®õg‡çâ]ôn“áã&ÇÑžò¼ïÌîÅQ5KZøèðÊ«§ÒsŽÙi->žÇå‹W» pX7šÒ‚õRgAÚ º“®}6 Ѐ$; 6¦sñÑL—?Kaÿ=ç6]Æ9Ñ”»¦ä±ÞŸ/Úé¾è« ½}9ZŒM$À¹R5Á#†¼À‹³„€t#‡\4ÇA¤3ÅÖ‚ùèÀº°=†®ìÙ°&ŒäYäÎLv$ ›¿e”ØiŒÖˆþ»Ÿìò⨅yﵜc *éu A§;ÙëñE¶”T¸· ‡vº9¬Ë9Âo䛟Cd&ýÜghUpÇZ h˜íÌ® S%ŠÂŸ£Ízc"×F«Âèk$¨t½ù›œ®AýÚ[¿d©q{ÆXß>Ïïw8(Ð Þƒç¶äÐxÜ:*¼*ãûœÒn ·ú]"¤±Ól¬­í7|u³¤D{m…?1}ùÇ¿Êö.Py½Îkü üp©ý‰F’EË>ž)xÉQêåÿ‚X’#óÁ÷í…öéöHÕ zC»=n´iF’iž7ï.ôz#{þåO¿1G·’÷Fç:|Ö$JNh¾Ý×: \dÖÝþ8+Y€ ½sXV áùAWd™ûxfï9‘¼‡/ØòÛÚ?²I-ÏädÞT¥;L"Çë0ÞkWM¶=V¢òÖ¹ò*.Ö…ý×´Æ;°¥DØ÷Ïœ™^õõégÿÐÏ›ùANKöÎ˯¾šb{­ <ÇNÚ]ðŽ¿×øøŠŽæü>|èðÇ‚QhÚ˲›Î'/{âïo÷ån‹nÜÿº/2ù¸ÿ?ïøŒ5_ŸY–ÃUl‘ÅœºxØHq$'ß.°’‹vTé`zÕ”cÌÑ^’È2º¯àxš“xt{‰¾Ù<#Ñ>¬ÅYÔNWÑgù8C†¿o®ÏÛî†ënâ^ë²q[üû,~p<ÝóèAr¡µÓp$®uÜॠÂfùÛÒ[ßî<èc?ûR÷z6}·³¸7T¯h=îÜÕñŒÑ$ÝÝNºŽ¸¯gÐUìxôá/>ôX³6{¸qŒ‚Žx‘ε^ ÉXûÉAJ/lèN2ÓÜ8Ö‹‘¼ÖwÙÆŽœ_ëø€lXŸø89¿˜ý#i­Á®ôóí»ëùlÃÆŸ?ÔÑYÕÖÖyïV2æ~Õ±»j7þ~ Ä×§Ïž:<ä€ñ*pçB·¶Ý*[ÞlLc/â;¸&1Oþ4G‘å 768#¼ÏèL8…Ï€NÕ‰nØ–]Ç'{ÛÑ­ ¢t {Ñ• È$GŸœëLî]› ®l^?}tæZz&]Ø;{6é½ø´€KsV4²K¤žÏ~ÜX ÄY¥´Dê:&8¦u’còé Æè@Cn lææ¼ºÚ·:fy5ÏMvì(ø‰±á4ûÍ—ö¬9¯O ÚszSPáüµÛãzgÌŸ»PuôëǦëj¯ºzeúWK>ÞÙ÷×dSüáÏ|vúì#ÓÁM;§íµg–¸u5ê“|³|&ãxGrÝý«¥ƒÎáÔ4Dtžfø–~ CF Ãnn\Öý(jà›” ÿQ!oW÷BWø‡ŽýÞ‡ç¢I‰!‹ùŒoO¾ÿá4ÿB´ñáµÎ8í9T"üÕ+ùª¯M¯œxM¿å§"ÛÞ—ZÃmÛö´f©c‹eÉ)ø³„ømó‹êB Ñv1zü„Æa°‘ÚþÑGsó¶N MjЖN}KéîNqló†]íWôèò‰ïðÌÙËÓ-é—:Õ½súÒàoACº£*ÏÖDWA´;ßz¬äWnS§%i,£ßÆ,i×xÐÌ|4kíáØ…ÌcëŠ18ªP2 œ!¸¤èLù–þ*r!!ø°$ðëÜÂXÍ/o( ’ìQA9üRÍñqÍhÕÍ4†µm~ç£+ÝeV-ÿí7ÏOû¶­N‡wmîèÖ3½?ÅõÕ :‹™ÞÓµHÀj%ÞÓ5 —xô(Þ€“Ñý'1†ÿa`xº=í}[›8ë†Öš5þ5íëÓø.´×:4Öîφ3?û,I½íÌf“0-·4çpvÅpÖÐ×u#!WTCÂKdúý$y|G8ýÞ \9X‡Myßë?ìËwÐ(?~%!¬V]nÞ’9·î-y½µ¢Sù×ñ]b¬-_'ß<, # ÏÏ^án˜>Ûqsë¬ÂÎ|3_àj }'[›©–Þù°®^›^þÜçG¡žb³3ï½3ýñí߈æóÝ忦â7Ò•%µ®Ëa«.và±ìNxÒÁ:—…À ú#/ûì~í›ï†a%òJ£ uÛy˜_zü[3†ÓBUƒ®Gg¹ö‹<üßàMº BÂþÖäù½ä~4 [³õØ¥#©ªõ´~KùS†1_Íàb Û·¤¿Z7vÛæötSúI\fù~ÈPE²ÖÏ­ôw«+³®6°ì£°ïÚŽ'eKÁÁô;]0ÞÌÜ–.¸tÿêtûfAçÙ Á`˜œ¯Î\$I4«Æ®§†d€Ä:ô«B¸¥n>|vÍ´çn¨ëÓÖÉòmÕafSë“4ÈÏ2v¦õ £–éZ‘~YD­¬„XÞQ¬‹ë‚p§XÜÕŽ3kÞÓ» °³·ù%·¦ÌÖ—³kïí2}o]oñ'jkÃM$J6cüÍ 8­?ï ùådä…Ëu6¼p½c¶–ï°ì£³ºª•ÀÞ¸Ì[rü‹èÙžÿ}ìýº‚Ýígd2OùíW ¦£ýÑ]¤ŸF›ÄóHjÏ%K±OöåƒÓm÷öÆ ÿZgI»âxööqŒ}¶)ÞV4FHD#ðâ'šéô͆™«»¹ølm-øw Íâÿù*ÿá@˜æ~tƒt™àßO*6Þ–´5f篯û€˜‹ŽYÏóÃ,K” ï˜ë\¾£¥ä¿8ø!º— BF-Ïu¿¥ZÀ?^œu­ãƒ$ƒ`–¦9d•gómð½}ôáG­+~Å×âñmk6ÕéõÜð*zE âESG®õãГÁ$sqÞ~màY–×[#ÖJ<´”F/ó1¥9NYJ{]k×Îõ§µV´À¨6R{ÚƒŸþt›Y†IHèr [ h©×hi¤mÏpâ´)ës2"Š;`GOìš^~ý ÚÎzÈy/ êQgµoÞº³ «Z1¡¸{ïÁ|ó0‚ !Ú‘{çÖµˆ‚“,¦IÀÊút]íAŽ A( ÂÞœöÚÙŸ/Ð 4ªlt|ؼe¦ÜÏÁÆy2c@IDATHÊRi ·vëÛüM ‡gÍѵÎ:Z™ªŠeåªÄ»|ùÌ jD¼qËŒ!UE€=¸­9$|b*@™Ó`´>IŸöj¦ÄÁ6b8/U|E£ƒH\ð.fŒ-Õ^A6ì ó;ÙyÓöú{ßú!¨OBH›9ijûÓ€wó²/*Ëå PõKëQ©À!¹>Ù“ËH¿³£5éýÙy¢ ¾{Ëc¤ÄFC¹rÄy›¢ø)>I#ˆ´(ÌÈŽI)mmT}¯í¹ 7m(õç´Ìc> Bi\ÑçPt”ˆÌŸö3Æ^)(ö|UbH† Âè^+Ñ>'(E õ :vþÕõÀ.` ®^F'2ÂÛß®ãNéhcäþ”?~hs«„  œóͺ͂‚ÔçO¯À”—ýšé*Ì Í’#t´¼/+p<“}ýIöÏrÛ5¼èRñ#g^ôæhçY…=ƒݾ÷¥áhyºx«‡”õS;Õ•ñÒã ®œi™Å ¢µÓK燳o}km½7¤\çjÏ\  ExëòÅŒÍF4D@:úb# ¶7³N©{7›ìØYBŲ,Êî³¾d›ç ¼»µ^z|úÝ®6îh"§ä¶ÝZÓÎV!õ:_fm-ÜŠÔP®Z¾¶/ [§;äŒÚ<]®WU:AÊx¬öÌ¢>«Ú °Ö‚¯Ê@ƒœáxöÈ.)œg)¹ zØàÌRç¬Ý1œÖ‹IÉôd<̪Þe=>/[ëÉCãØÀ÷Û×”v{O!Išx=<«½zÕN’3ËX"Íž— ©¬½Œ ÷Ÿ+€ßÇ­}NçÆHA –0Þtbx’#àQßQa‰_Œðç)ˆ;½gý·äèè’žß¼8F¢›å:9PÈ÷kËM ÚswãF°“•À1þdXk_ìF¸÷Ÿ2ŸÄ#`監Xöàhó×X9 7%õ(G(þg`t“æÑ½úŒ,dð4ð§~[H>SØäžLâá¤ï:íW3,µ~^ª^båhûùt¾nœ»ãy ŒÊn$·¤Ã8}´?tøàø®Šcò¹Ž@>:ßWåY³˜‘Îi>sŽ3€G 9’šÏ­[÷ZC´–a0$÷%‹qv9Ÿg$=ô» ¿ŒH:ŒÈA#ª €HsßWÅ„r|Œ©¹¶NnŒß}cãA޳ï}óë„ãu&í‰Æ.œû`:ûÑevɅ‚'ÇF5ÀÞG§=W¯Ž#!–®\+¨Vç—î·ÿÐ eûÍßh^ׇƒT5 @΀a (h”snÛët¿#.®O¯§ÃWÓkOâU†Àá’8æå—ÿAŽÒ}Ó‰“uY‰ÍZ¢‡f5tÿ»o}èTrúÎÍkcMÇÙ_ø¸Ÿ’¶!~’’€FºÄÛdxt4îw™ýÿâ…OЋ¬ÏǪÚÓ̘³¤Bú_üî¾ÈAËÎmÓOýìg ÿoÿí_˜>óÕ?6ý§öÏMý¯ý/Ó׿þõéOý»zúÔëo´Ö+Óç¿ô£uè<¾Œœ»¯¿ñõ4}ã×u:þâ©ðÖÂtöì¹a´w¼UzM"eÒ¶O»wîŽÄ–¡ëºÓ\m΢1Ž„µ9<ÐÐpz&cdïûyÈÍdXÿ•ìx{zëo–dy#šè<ð@í• Z²)S~8:Vâý­‰{9jÈ—d"'‘u}”̃}$‹ÂdséúJ6)L€W#82Qð"&˜ÉÓÞç/Å„5’#Ï ”=(¹Š±Moн@}Ü[—–dÆÇ²zˆ ×%¤6„‡´î|n=øÂ‰É»§ÏùÇGÅ ½Ø]ý¯ÿÚßí¾W3ŒwNëà@Î<ȑřKÖ $a¦†f´Ò¬éš°e°/9–͈|"p9è´G¤‹Ð+¼-Ð9ð"ÔÜÑ!,¥UàÖ2¶Qø€Êl»–ãÆfx l¡1¼°Fôµ‡óU§.Ç ›Zg Çrȼ^×ßÛu²fŽCÚ•ó3ûsº<ŒÐÖ¤ï &Ó¿øF;?c8õòÉqÎäJº]òÝwùÊ•éô;9´KÒjʃ$ÇPÕô-5 ø’AݯµÃoèYR Ǽ;ÞC3í›÷Ì ï·A°`o L?®µ*Öך4ó‘< /½têd:£6õñG—–²t~j.œK›ë¶'y¥dë(A½ŽDÂÖW· rçZç^ÒùœHŽÞØc.Bã›çrÌö>§¥q¹‡9Ìœœ¦ÑyócõKX¦ç4ªŒë‘À–ùÆô· $Ç’Vsœ€;·üö çÇËãõ%ʤ´æ”lµ®uȨêÜDx¶€Só5F”çHÛK–´vpÚ¦ö{Tæú¼wm ëÉ Go”ŠAk¼˜½µ9úÂtìø‰éýw߉[¬>‹¿†¯Í0€½I =ÇQHøZ%YË-' ÂHÆð,ÞVÔÐB:¡#9„TXàsm[7Ek»f±g©”×­K0Y·^ÂK¸º„دÿ£_KÆ?žN|q=ïÝ-)§ÎgøÞç{Qi¬ºã«_û‰é‹ùò·ÿÎßžŽ?><ùÒà§Ņ̃ L°DåŽïª"¦—}®òÙÞÓÍÖ -þ3¯™úgÞþí½á¦í” ëïÀ[ã9³þ.ÿÏ“ØäfÆ_²oÇ IœJ¯Ç{+éÝ'ËñI²Ô^Œ„‰öLËUÅh„O@×®ûµ*‘}û–t\ïGA3‹7Ò•3|}ö<žd{ÀsäÍ,‘Hp°Nû™öýx2Ù1tvßà¤É¹¹‚;¦ì¢p }‡¨>vhßô›¿õn/Î×ÓïûÒÓW¿zjºß}~p©ãŸN‡iªî¼WaÊç^>&tΩŽÎvÎé=poÛûá # ‹E$ƒ ycЧxuå9»Ž^e³fwu=šKéÚWë0tTs±Ä§ô~šlðñæ¹É42Ÿí]Ì1ЧÂ]ëÇŽÄ#ÏŸ´2WÕ׋GL¯¾v¸³Xß¾ñ ÓJ×ðq:>gO…H .´öd¨ï?dkþä?™$¸ûp\LÒx%çPNdµ÷Ží^äCÈ 5jÎÖâcšµf ùL–;³Ú3€3ûþáZ§¾õþ7‹™Ö•°÷æ£ù™Ÿøô´oÏæðä7§3ç³ã.œºé@Á/m×ÑœhèzIáÎQm•ªüІß#;8¹Úɞɠö$]áØå|1³ày˜«ï ,]JÇÚij]›É³èh9ô0[Ö\¬ù.tšs¾óÞ»8]]qZºöp:ôå½Ó+_ê€g¦G%\»QËY~”¾ûÒÑÃÓKu XȽ h£Ý•Dq%]¯ÐìÓí¸#~0ê5KXˆžÚüA÷ضi± ÌöŽTâÿ¥¿Öd'ŒÊ¾x”ܧ[%:¬;Üo|ç‚N³£Þž¬n˜Nœ8<}úÕýÑÍ·J09[ùÅæ;×zwœA϶lx’ÞUÝ+I)ö4 gñ D˜‹}ha³‹U!Ægí³½ÆÞWlæè‹çñ¶—€™ÊÍ8Ö£kÚ‚Yæ[>PX'ížÎ»Puhø çÜíhÆ#U¡?ÿâGùåÖOΞÉg´Ô¸«*Wc*ø…_‹ŽOÒ K%À¢qUë5£þE 3äÍr 50y·áý¦Hb1ãv_Û=Ò…Í¡ßá+ò –2 Á öÎçÏ“…ˆiȱÞC¿ðÿ‘»¦7ߋ΋IÀQ‡ï™^{íå饃Us¯l™n}ý£éÊÅsÓG§ß¯ðóôÙOŸþ·ùƒžF¿«ý’ÚìÃèÁ VëñdzÑ{|Þº–‡Ð:Ï0¤}ã 29¾óoEòñÀddq¼Î;‚±É_ Å|g¾3E¢¿Šª¾UXx :ç·±†ü}·oÝɦ^ŠÿÈÄ’¢ÛµëgÇ"²Ÿ$ÚX;ÇHnÆ»#ùþèGòô3_ø±à9‰¨KÅNT`»£5P©¬0€?U`+a5äÿ‘¤0þ5ãÂ{0ºjZv…±;Ö…ÏgO€÷<Ú~W‘[@\|Èz?Éoùá‡N{Ã!ÞS3|“É{އµ-×zý^ ý3Ü\P61oG¯­UbÁc-Ø»jxm›Å'f1"²¯À[BÐ_þ5Âõ^kî ïØLI© Å‚à3ÏAä¹ùÑ`’u–µ×ÁX?c„¡fº‡Í4“ŸÖÖCð3ûNB3Ú¯ó³Ós³¶ÒW/•3üiu»½í¨žMï!¶6¬§¤kÀgM‡óKôرO#œ>¿ÑßÇíy1lºþ}ÖúŒ#tÚS1þÒ½»ë²Ûó9*™bÏ+[ßg>­æ­˜ó¨”Ž?è kÏ ›¯IVó¯óÑ‹¸Þ˜ÅKÐ ýÈžY‡Mï¹F ÅuHx9Ž8*’˜` ø¦£=Ïå«F»£U~¾rö™õà«uþüÀ}-ŒµwIpŽ}ÜÐøG—Zvjçr¯Éäx`2Tƒª•b Z#vìrülO­¹uF/Æ¿øp5ìÙË·äXzä(%K=þgÿ˜/‚ãøÐØ‹# ]$©%È¡åè0»kÛöº½d<Æ£›¬1‹Ùd± ¼÷p)_MòÄZKô~¤°¾˜„0Ëêà!±ÙdwÏ&—Äm¯{ˆÀ¼ 6ÚãWàÏSØ ¸tÐR²í^ ‡=(¿V‰SÙÉôáìèÝOÚãøg•­+*_ ›ZÜÐØ$‰»YC ÑË)=û;üüÝ[ç ±\k0Í^)®ÆØ˜|ÉNÞ–DÚ¼Ø=â‡>è~°½L§:ªñÙVºª˜SúL‹ÿ…hdf!SNŸîHƒ#Xû=qjz5<{úý÷[ëÔºŸä­×:¦8}ÌFÝQš¹…!ÖZ4„^„ ¬«娹w‡=…ÜÆ`tçÄ ‚1÷b xéâ…‘ña²Ó³KEÎí-‹1Éy^Ü›R´®c»ŸÀA¨âÛ‚ ^î.ãdïÁ£=§ªÙ®ÛRfëÍ«ª®½³,ŽŠ;€×¹˜ËUÇ>* ï t ‚óê£ÞžŽxyŒóNÕºÖ¸¶y4&sÓ–}Ó–í)§²Ô µOøèƒ÷úžJŽ®ïç~oßµ5à#¯Ù‘aà §;cŽÏž l^‘jY…JN¾¹J”ûÍ‹cÄ&›¬öD·ËôlïÇûª·€›¹øwþ2¢Rmÿ° îG­-n6¯…²0îW1u¿jlÕûœd#ˆÕú¬[SÕ~‚\{M‹m½ DUžƒŠÐ,î×n[€Ë¹d9ÂΖ<PÇøàFEx G0ªS¸†’¡ wå ‹÷P‘âËâAd}»÷çÆxŸ ‹[‡îÙ|d;ÙÛa€%T¹%0Ý~fHdLø>|ÆLý.3i–ÙԚŠ!;Î[i“׵£õµcÐ9` )¥;3’õ&µéá¨-0{†@¢ñâloíŸvלƵ¡ïËÖŽ€©•®·V”ƒ ´u}ǽ AP-·t ®ÑãµÂh¢³@½}Ÿ9Ç( F âgÀvTx5®L¾æZ†]÷Û¾}óÈŠ(Ûת ¢!·5ç¡ÈS§ù®C¯ÔÖ¾L­Z+?[~4€ËúZiƒdor`šÎéÞ¶íÁÈêÒz Ýßèú-›:oïáØvG㊶rüË<k‚¬ñÞ¹~~ÚO¯ÔVd5aýèÆ­Ž^¸Ú3JŠÈÎ@'ü÷=5-lïÜ ²ˆ8Óƒ›WsàÌœOC1?»:íVKNXo¨Ä³&Ÿ뛣5µ‹•T0œh*»‚,‘©5èÝ[Æ`ÅŒ¾>ÚÖÖ>z¥4LlŸgdö|™’ö¯âÿq®UsÆ…qQ߳̔uœÉ Þ õoßÃî9²Ì mÚ\ેKXÁ'OƒŒc÷èŒu$Å“†Õº³}äà¯=KUöÀî‚­­µæ”‚cœ|·ié§[8Çpì/+ah8SÙ_Î, KÖoךh;Z±ÈãÜ÷¶]¶+/Æ0ÅÇ@Äj (ä»ñRô‚ªÎé Õ;ªt^zùõéýNOï~ÿ\:øÆè¸r⥗ꊲ¯¶Õgǘ9·Ï~øþtêÔË£Z[BšUEü z¥ ¼ Szg2_g™ñŒZ( œ¬Ýµk×0Ré º[›núèæÍÓƒ‡6g”sTÓζVá$ÑÆ⇇µ¼Ñ‰FF8P4ŽChŸtÔ´(äë¶ÚüIhùàô»#Ûx´•nU"¸g”u¤SѶ zü'ziæ|jõZã€m#0›ý{þ…ñð,ó±dÌûÀþHãQ4þ»ýòŒO ºOÿÈW¦Çò?ž~éoü•éäk?2ýÌOÿé—ÿÁߟþ»ÿþ¯LêOÿ™éøñÎ\´vjðÎ+Ÿz}úÜ¿8ÎIÿËé¿êýäK/O·Ãi á- UØÍÎõL†4ß0In¿œ0 Y¨ò x‡kèeý‡GûxÈ¿%vp–0>°;“w‡7»Ù´!ú<‚yÈ"‡Éa}{gФBsP¹~àÐá!‹g`x¦³µu"?n¥—“}saVX  ôœ#ù®ù¨ˆ4vó‘}íÙ^«¢´ƒX²@m‰Ž9E×Äç >ndÌ?=rl8$$=I.¬¦g&w6©Â ‡nOv<í9=.£¶¬ÿäÌ‚Ÿ÷wnŒi†G8M8&È'ç°?̉ÓŽµ™.šOã…W†C½Ÿý>œŠé±'ÑdON2‰ÏÊ|H ùÖG£JßZiYÏ8f@’Ÿ¯îãÌÎ$m'Ì;¬²ÃùôÖz¬tËÚô³k­3ùÄþðiUwñ¶cS®žËÀjÎ`è¹É…§9¬3ý2h£áñÑE†³­õF³óâ­3ù-xn¾,u-é‡c¾q¨þø¡k#¼æó‚ñÛÃ.r(‚@A72 ;çÆ{Ýbû2tÕ  ϶*ÖÛÇmfû§bD2 |ÁX4äÎ “´¬ÒpðGת2»}<=Ûþ7s¶€v{œ+ZV?­½2G€öw§¾X‡‰UÏÀ"ôi‰fÍ{tè?£ ºz‰=möA?§—Жµ%¯Ì§ †“ft ál úñhs'ß[ÍqTä^øb5ÃÚžÁhÅýœ'©Ãv±¨¬úÝž„Ú•6§^99½óý·GÕå“Öáòù‹r ô,]TÈ`élÌ'ÝØ;º–s_B† p8*ÑøBŸIdiBc½ñÇ5º4X÷˜”›_:I2‚Ždœè°ØRrŠ=©Ó…ï’ÖðKûÙT´ísnîæð!}¯*@¢1,2s†XÛÖ³ëÙÀÖOâ`—fÏ‘‚š%;?Ê¡6°dßl>*¬9®E“’6Ð=ÚYŸœòœa×d{n9±cÚ‘¼ûà½ï—Üv?|ŸÃ(' Ûí[;{šìއ$Í…¼˜œèAÂQM'ÕçvçÛ©è:=+ATׄ•°êód¾às;2öаð4½íµ~Ðï‚vè«9´–­úx.ûÒ:à£Í0ðÓ-­¹d!Ô3È£+g÷ÂFgfÕßî s–LÚZ›^Ú£l{‰K©_¼_m+ÁPõ§:ç™6¡wÙ¨ñ)™YQÝ#ôD‚DHr˪'çÚƒå®1’ud]û„¦ÙBöcèÔª#žöüùGwhzÅ{Ý-Á’CêÀ‘£ÓóoþVü’ ¾ñÐãÈ%¾$#'ýIúǽÙhœ_#Y¨grÜ¡å§fφc¯\¼8h~àìÖÅðU¥6©vÌ™\ð9'q 7ÖÝ5œZl‡mª —äºúVýpzãÏŒãf.^ºÐzH Kî³?q<ÈבƒRÅ̃‡F5Íxnû}µäÍmWöÕîölž»={âE‡V×ÿÔëÿæ­êóÁ_Ðý´‰>ýÒëÿúøÙ»¿{ÿ2 §Â°0ºnW;j‰ÃWòY†v¶ÃÌ>vTÜâ8–Tô ™ÿ$Û˜Œ“ÜÏv›Ñ Ÿ@rÎîþ, r*"†È¹h(ú‚!øfÈ‚aKGcvbèÒ¾ëgø©®±©éžÙÓ5ëÃôÃ)ý±=ºk×öéÓ¯½2½ùöG‰´’¡ŽI"w—jÃ[èp‰¾·nfóg3mÍYû…Ͻ‘caºZ•Ÿ.1m<ÐØšþú(g³ó‡ù$>#É føžÃXñþ fIñÉ¡ÖÔ>Ïô—gûK¾‘á3 °$|ta¤*›è;… ¥ùê.Sûí£Ç 0^¾V»~ÿÒ¯Wls,ýS‹î=G¦WOÞž®ÆKòùÚ—?=ÎjÖ:VPØ13:Uªå$gõà™œ3—ö ŒÎýNk/Ž6á2?ÛI’,ÙÂæÅÖt½µÜB&ÌçG`³9{öµ—_œ¾õ­· žÎç«Û:}õËŸ«#Ø1‹†ÿÔtéz….uû{ÎøÚ—>OnȺ…’mŒ}t[êBÀž §çur’@ k›YB·Áç }¬»@É\r}†³Ì']×óíÓðc”5WR(-ÐB ~êÄÑé»o¾?dÝ–õ[§/¾qjZÚµfúèjÉa¾¿÷ýw§sï½ßõÓô¯üÜONrˆß»yíc@¸‰=ÞÞKLŠ楞ùhÙî Pè+ÚSµ†ïù|U¢—™ÜŒ?x2û]òø“ÁKuBJ†œº~Ãt§ç›KÝTµâ¨SÇŽLW ̱3þÀ¾ž_<\?mŽ¿¸iú©[7}çíw³ûO¯¾t¤ÀîÞx/ÑÚ r®®K?··‚xÎŒ¥[è |+Z?ï¡SºIW´g¯´M n\ÉúöÁ«©GS0kÁ£dùüú|TáÝÎT¸rgU–¯¿vbúèïÿVIV%Œ$÷¿üÅÏVs(ý67ýÜÏo)1à­‘x ²¼ôÙWF"æãôߌÎ_λ}XpâÙÀmMr­ÂQcÉž†?ÆŒ†Lƒoêi44ª¤{C@jµÏ–¢©D±+{ø¾š 9@F"ŒÄÈÀ\0YfïID>+ â[ö|>½ãÇM¯¾xc:wõfAþ…éÅãG£çÓ¥[Šæ^œ~ôs Ó·ß|kº°xmúìk/N¯½òÊ´1ú¼±Ô9õéÉÇùL<[‘ÛÆíasvië?—>oGíÜö‚/u^åc†«‡ÛžJ2Á¯£Â¼µ\ëa>i4÷4yŽRGRMתœ|t¯.ùSö—P +À6‚`EIfþøþ…›ézk=ó †ßÂ`üÓ#q¸õD/+ùžGã"¡òÅVÑ…ñȱ“Ù÷Š›(¬Ê?®¿ëÚÉ ½§%3¹ú¨Žp›K̇7øÛtvuN2Á0Zš'Sà0øb2­c4¢ ïæâ/â,dÄâ×xáâŋӷ¿ó­éd…"ºœ)øt¼ßÎ=F÷~JëFð1lANÒyŽ ÁäÖ O6Ÿä²íÿ'u«ëšvÏž‡µçy8ó©yœJ•ÇŠÇ!à’ ò¡E[ø€Ä„ju#øÆG@t#ZjE M»Ø`;vÊ5שò™÷>{žçµ§µùÿ®÷5‚´Ô©œ¼û¬³Öû¾Ïs?÷}Ý×<ÝÏꨠ“ë“ôë ²²¯3¢Óƒtj]%ƒŒß0Ü?x°îu†<šnï¾W–·Þ|½êÔ³É+•Àû¦+þØE˵G&V`ÓºÍex=ÙƒÿD”{âËlý9j Ä”óùëøíì]i{èâˆgÅ3+½W{z±µ–ö½’¡ÅptíhØñ[J`täÁ•«-bσãÕËW&!!LíÝìòô`χKd\˧®¸”/üݳ鎭+òîèjOú3ýÕ10’-ÉKv_¼Å=|ÕŠƒ¥0>ìó§ÉTGÈní3ÛüΧÆcÇ/œ$€<PpGö|¬ÁG" ŸŠëŽÍOi¯[·P>ã'ëŠÕØñ™æêÈ<1Ç3n|(üs2¸KFæ ,KX¡{Ããï‡?žO=ùJ¦Åó½'›èRíâèpK|ƒ½§¬¥Ä'$BÙÏõ`-7O{Ûmãûæÿ¿Yoði&«ð,3X zwùÜÇbmax ˆ¶î©É¼èðûèô¥öE"ãæÆDGMuÆ ¿ÿ‚ÏÊÎ>LEkÏšs°e?8b[¡ðŽ:+*þŠÿÞ+a„b•Ôàù&²}ÂÇUÏTéîñ_1D°³ØÒáƒìÓÏœÀz<áÊÅw³#Ð×êˆZ¾ ÄÐÓ‡=£uéÔñ0zdKá7¸¦xRL´Çáw:“\¿çø3’çÙ—aØò$=¸§çY+6›ï(¸=\÷ü‚çUžH8M.'+ß¾µOÿà7ÿ‡åŸúT÷uG úßÿÞwæôëÛuãR¸³/££:f·ƒýgk“¹›2ж"b7 ¸•‚¶kˆt{Œ)Ìœs›á&È.ëàê•ZwVÕÊù%ÓX íf1LÎÊäJàp@,[ '\¨ä¨Z‹˜­›Sf,¼ì£{¾Ë)¢L) ]¿©j×C`ÙWV†c¤Ë=ÿF™‰xKc]®m‘ì¦Û·›c›qàð©åêÍw'{në¦*Fy¢]˘u 07CUËmÊ#8È”"nWá~§Š ;¦ëLÙ= RD){0'îñÎ1‡¼Sf¶¶ßÜû«%LpDf²TÚ`™ìÎc’Å´-ÃI µ÷«¸~çÍ!$ÕÍwkÑ#“†¢ ½`Åêö-Á£fÚ$ŽûY¢œõˆðqNG ÆH$ P ù¥ Ú‡÷VÕóm Î s†‚ö7¢†Ù¯˜DJåöö³VZ CTLÎZ '£Ö„ßTS£’æ|'B hPF‰7DM˜ δÛÒÃàË´áj]iwA4Yê DÉî眣”Ë8i”yÖ*  ÏR\ÆF‘®}„ö—[–ð–`U•0â5FxÓ¥üvc¢^p¡}†OžÂk2U9\R‚ ¥ˆ‚ê^†ƒïv8—µ¦6\ LS¢"ÆÁ´}é7eÁçÖ€QKgqšûÐpð3ïQò<#|$¬ñ“™kó¶GÛ«Œÿ²Çì1Gœö6îäL>èlU÷Kˆ@‹«óc&UoÞÅ*6zÈ(ÇæÁaHp¤ 0 óQäRhZèTéLö3%¶÷ž­’Fk'ûO¹¢ „PÃÇ/§´R¼í¥ üãÇ+ã{g]t”0/Ïá³v°›· HS´>?vâùåÕè2¾ÏŸy+ÚÖÖêd´¬¹Þ[Þ}ëèS‡‡ö=Z’ØÎŒ|’ò}ûfUºgÏA¶³¹>R–]2Y`ÿ´îkWk‡T—[nß+k8ÙvårçYfÄ<‚Ùu'N>?ÐuTÐbWÕ™sâ)ÖkáúFFÅ“ððö«ãèÅó6SQäÜÙw§2ÛË»³?’°‚O8¶¶GbËêlåëUÛ=WA#U$b`ù(‘s+z ë(Óñóö±­nãÙ93\ÿOÄ+tÁ§ñ³+ÁŸó=ú­U§EÉbþa­ýÁeÉg_þñŸMFìX~ñ¯ý'9åŽ,?óÓ?³ü_ü{Ë¿ÿïýß–_ø…eùòW~4'DÉRá“ÀÞþ™Ï~ùÿÏyùû÷o/'ÓÌoÔçÝïw”ÞáÅ­ud]<]åºd™ÅmÜò(ÐG0‡öz²Tã*®ÕÌM€UEÖÍ‚XŒUKÎÃ"«Ð‡Õ¾>¸ÕæöLmžñߌ@uþv®pÿôŸþ3ËóÏ?¿üõÿò¯OÙ>K\|†Ç/èv]:n™°’7Éaμqüà)'cî†XÐ <ÏG;x̦ãSZbI²3ðÁ8ZÞû[__>ýù/ ¿Õ3ð‘ã§_X~ï·«æ{ñ µÞ+¡"£àf†âk?xs*S´–xqcÎ>¼§ý –>¥Tá¹®Ù]Ðk4Ñ ‡Ûãtüݨ²B-º˜Ð—Ș1|“KÎÜÝ«Páœl¼—Á¦5ã<§=¢‡¥€2‡\û¶Ÿ:ÉÔæ|o†]ÆóÉ$¸å^AÄñpz£D­…×Óyz¯ê;mí·Ä³9VðÊg®ýR:Ç¡x ùp1y{îŠÖ…=­õ’uø žFfÑÝÚŽ’Vú+:ÂSÉØ±mZø´zLhàÅs_óе¯èÈ • p¯ã›È3˜V2=YÚ>ƒ›SÛë°Cyã5:n*m%}h唟 Æ>&]€?ŽNcÜhD+i¼®¥dzæìËðɆ©š?õÜ á`ÝtØ tKü¬ÃÁ ô‘T^ðm=›FðCÂàø‰æñMÁ[|ÕZÁÍwž¹‘cbäpßÏ盬gÇ'‘µ“œÚõs¬RóTò¯Fîcî¤7£èþ“Ùþ4ýœ3NŠI¸ÃÉØ§3~—µ†’–Ó+ÀS‡¦MÉÏ3W|ó–,±–œÙ—l:ޝ7惻Æb¤Og°Î˜–Üh48Òcè#ÖƒOïÒžŽ>g³qz܉µHwëÎÆ^éoé0ÁžY§àу®haßÁUÇѧgC H¿[ñ üH.òx/íß푱8Ñ£ x &ëÏû¯ï‡¶ÿ®÷· B뺞“êIºÎÙ÷Þ™ ÈAmñÛkkUmsGV}t=˜ž)¨c^hÄ‹³pW°Eïöq{ÑÔ½9è.^Éoœô§UxwÇØý&¯‘ägÝ·^å¼#ŸV4—Ž2¶©@!Ü'oà«ÛúÁ†3Ž^+‹½SUð´„sÉû£›§‚ͱW9ÚÚž îìÜWŽäš­1;z®oÿøÚöôÆÑ_ï£ë®„Ký3ú£ 'x‚n’ìÁC—ˆ{C“Æp¤–EÎ:çQ׃Np,W«mFü)Q[„ÇfOö<¼xoº){„SL÷…=U«MÕR0ÐÉG¾ƒ÷mXGt…þÀL`sdt8p y³/{›¬¸øÖ;Ëñç_^>ÿÜóËoÿÖÿ4É›ŸªmòÿÔO¦³Ö}©DÓ €4ÞŸùù?Wbþ±C'LJ`ìÌÞËKW sžŠ—ïìo>§®Ï%Öü£_ÿ¿>ÿG_ýÿ4ª]Ý´Ú²?øÿïàEûø¨¤¾%ŸB8öòDÒQé´¥Núý7ßêú§µúíµìþ¸ãÐ>ˆ¯ßN†põÅB€9<Ž®ÚÕIô"¼Îçø*< åÚ¯øorÞÃòrªñÁ>wdÖz~2þ m‚=LÆågøð«¯.æOýôò_ÿµÿ~øÝ¹Ë7–·Î^J—ZKÖg'Ù9é/ýóÓìzÁ[víµsB¶è¡3S žìIÜoFÇì|^[»}dþ‘ïÏDÉ‚á½]g]+9fN샞Ù"í²õ4…Õýý1ºÀð ïôêH’ ’ÀŒ“ãÑýÁàûñ×-âí߈vé>Uã^(qúÎçm鈗ï¿ùvõO,_øÌÇÇÖ×IìQºäí*y=s{ŠÄ–ÚÒzáeàl]ø+Ÿâãø>NÈUÖ=Íœo2¸£Co —6 ±n÷’M·¢]ÇJ({³%ðÝÃ9µ¿úStùÿ­_®cFEé)¯¿•}Ú¸‚ŒkJ¯½ñÆòs_ýÊòå/}.xã¥üVê2DîÌWºÃZL,^#ø`Ûê2f<›Ìú¼è¢ð‰bm3O8Ô{|€µ;¾ò¾ô½ŠÎªÎâ¦éƒ9Ík»»Š¶Ã~æ«_^þÚßüÅe×ÓZÆg\¸ÔY¼ß½<[žËî|7ÿc?úÅå‹Á›ÝD–= fFl2áô®|V-5ºAl8•wŠ| ¯øDuèàÅ7Ư ¶t~º>|êæ~²[“׳ýSÖÒUóÙfkoê,ûMsà8ßø±/.û—~µœuTûì' À¢™ ñ¸µhûF6îéεÿñ/±uî_%ÏöŒõp…/ìaºÃý’.öÕ$=4¾ °:2¦ýƒÉ[K¾'´×}Ô¸t][Þ£Ÿƒu8#ÑËgèH€ž.ù0\9T @€»¡’O—•´ðåÏß©›Âw–O”t©íwß mÿò-JBûA~/”øòÉO|¤$Ä,%·ÉÝü„Úð^O*3íÚ›ÑäaûïÖ¹}è¿tëis _ƒ1tö…5µ þšMekø#¡dŽ­‹œKkþ3‰<ɰÑ÷Ò‘nå_á£G/:£ `ÝÏ–Á¾ü#Ÿ\îýêï,7³;%>^»¾¾œ-IT³)n]¿½8ßûgú«Ë‰‡çÞygä,½êfëöüÛ%¼•°°3„7KÕðöF²ñØÏÁ~­û¾¹ò‰òŠ%Ò1»má÷ð¡ã[ø:¶$èöM(ÔØ#=O¢Êô!~$þÄéu|0w²\ã|e:ƒ}Ö€ïݸº”Ò=FYjðÆÿ ¯æ N޵O[·*’HGÌf¶Vþ"íÄéˆw¬ïzñ²l½uê&º¥½V­oÍø£N¼t…ŒŠ‘ÀÅot©{)ZôLså/=ûÞÙv²ÑÒaéº ?~¸#ƒOõ¾Äî|YŠàt ÏJÄdËN <2Þ/a»Š‡L .¸íɾe«ú7&¡äê›é7bø&ߣâ~:ºªñ·ÕAï…|Š:ï—hqðP•­eólÎöÂÌAq¨5ó­[3Yï%ôð}°ËTæ+h9}úÔòncóYwSq®«áoÙŸ³_ÑBŸ9¦q4ìuôØÊ~rü1Þ¤’¯‘t¡¨‘>騔ýÉŸ{î¹phåw^_¯›WÇα"ÅØVÇ4Öú¿q {íÚÿ·ëåݺßM`;ÞÊoƒ.É‚–èFBWä{ç‡oŽñ€% ¸wÅ1ÑÇãüܾÛëXÉw9‰õí½ýÿ­»Ð[rã;nÍ«M=°_‚„´'á:Á{vçG\Ùä_v: 'uîé¡S]ÌOmþ;‚5ß]I+ïÛÑ5;]¼’ýþdãVñ¯ lÃ3óf׊Â%ûð´nÕÙûJH†ìx4Ú;Úû‡ÖþˆW9fM1€â:ú==„ͨs*€ün´†¿Y%âò«Ým~‘ú²÷©ãš³Oâ¡à|0û@!àã'unM#½ï¬XTK}h—փ›X“›gD?é1£Sô==J|…]³+¶­ø*ßk[mD6 Þu'íîûÇ uëð˱oÛ1‡l»sýB4ß{Ì“çO;Ê÷QrlKr‘:%Žg·f ÓåèŒ[ú}óyŒ¯<|qt Ÿ ÝìÇ>k]ô:òîáy|–GÇGñpÕ™áƒ*þ{ݧcœ{¿äªIB/¿òãèè“ï5¾|þÜÙ奯w¤8ûéð±£óp:DÒ§ÎÅ{ÄèdD¼«É ‰| bZ-:%»Ãä9 pÇIß÷s ­ÅÐÕ•`!!»m[Ù¯e¿{æìµC)¿R((_Z–lms¨Ù1 ŽÖ¦C+Áù«µ&|öΙQXïµÙÉÈ;šÓþÝåh†œl? – 6k-¥ø|ÀHã (çrŒ^Ž °€m!ýÍ*ý´˜x–ÌèTö!6m¡Ê%Ü3~<Öcè1&*:#»BÕ¡–áS­2p¸`!%™CÀY*œ‡›3—dsplÞíLÕ)‡oþ Ör)–²ÿe”0€Ú²ç¦e@ÈX”e"hHç. PL‚ ;ÐY›Z"QÊWÁkÛz[Û'Lüv™b[ú›ßb¯[OÏÜÒ9*wî»{­+(¤à¤@ÇP0~w/ìÞU5è0ÏÖµ£¾ž³›ãÈùˆL²†Åi„Y"‚Í•H­i·ª¥æ6ãQq¥õ G"E–2#K…!Ç8up$pW™£”V#œ;‚ñ+?'!MÚ/­ÀF k?JFÄIØbÖ~k$zÎ`mކ°Pey«ÄŽ/N{¡Q†ÂínŸµp°s¾”¬Ii>ý1ñ£‚j`8Œ®ûv!ÀöïABÛš(ìÖHooEÁ‘¡åIÙqí‡äJ"ã¦'6Zµ6ÂuÚã4V².úÈáþk¡zª–›ž&èS ­}OJ Á·wŸÌfgš9#òéræÜ¥hæÎ¶a·kÁÂú ¶$èÕY8‡öSjÏÜ:µ£Þ çŒÜUo¯‡í‚®ÆAÓžz^L@ï« Gíæv%è7…ëï^J®±ÛÖX†z™Œ”ÍzµçΤOÉŠ—tûÌ7ê ®9ŒöwÞuÙKGö¶?ÁnÎhl|Njð1˜F‰MAÎù)p¸rÆ6·ö3ÔŠjÚÛÆÐŸÚc19¥Ñ;a' ½ªô(J=b²¾2øœÎ„¥}ÚWUнšìºæ¯¥’6Áëe×Ò” ùgá·¬`ãœG½;?N0W’‹êtŒß³´ÔäHe˜ ô‘ùüµ×¾;‰NÔÓ§Ÿ_^xᥠ…ç¦Òü|Ib¯}û›ÍìI銆(iÇNOþÕ2®³±t߸}~¨õ¨(ÐÎóz!ÚðòÙråJ23˜ÙëÃGO ¿zû­[íK†yü@¶èc.Ú'Y¹ÎîÄk)ž”Gg[Ræ‘ B’ƒ›ã(ÿð#cpТ)|1®Ùþ ”RBÛç>·/*‰É׎²SBÞ äLÿ&ãÞ=@/ñ ”mo?+¼ìϬ_My—pèzU+—:K‘q²çµ병‹9Kw¦"‡¸5þ0^`m?Ȥ/}ù'â‹»–ßø•¿¾¼öÿqù¹?þÕÑqþÊ_ù7—¿òWþ/ËŸøÿT8°Ö~®~?ú©Ï,¿ùµß]ÎÿàûƒûòA9ÈñÙH,ú¥¨¶GɦË׫¾kQ“ì>mD_£¸†Æ]7 /ˆN%‰9Ú"©?ßm ‡ETuãç7›ƒVrô–GuiÑŽ„%Û¶1ŒsJÓoÓE$š“Ó‘*ÀÒ¢ç»á5íå¾tÁ;ÁX;²M’&yf¿¯8ÿ~ùï,_ù‰?ž Þ7FúÞZ¯“5—®\^ö?Ü“~›1Ÿ‘N'aìrº?n¿©ÑŒ¤¬gœ»KNc,N’üiNÝÝhÑ›P{yôº­^Ë{s2Ñyì»®)ªŽ¶eäεðoË!†C:rsu.³Ž=‹D€º=OŸ¸[W!òþ™=nd-Ùõꇵn/A±u{.œ³U[Á•S]áœôjzs@ v4Ÿh§=FwÀ†žƒnµ[s'ùÇvð¼ÙopƒžÃ1àozg\ó ã¾$>0„‡uËøàwèHg&¿w~tkú$'1xã-:½K…øžBGk÷Ý‘'U5®§y6}À:À‹LšµµvšµNòÝè +Ç’k·B2@RÆ›—àÈžׯÜgê‡#<´x·2|æ1:+)q3»‘ ûM×û§kB÷>Îégô2¼ϱ~ða7šïÞœ*ïIO™gô<ðs ¤ZÆ¢n%«Ÿ¯²½©õûÍœÄ+çOs ~ð;ë¢1Ø­¡¹ÍW\š\òØžu[¶“c³´:~”ìÆ­ –˜-xP[ >³?ÿ@ýpy Б5¥·’w¾³Vº üô·j%Aé’ñ§c R¯·þ.o=+XâupÉØ`ôŸ¦>°”–6G¯Êp›q1IÂà·¿ùµåõïþfUš/…GÑ\É¿c§àÍxZ ®h•£Î~0Ç Ö€MŸï@„[߃Äì¬ÇBŠ…/Ù>ÍCP«¯Wô\€Ž”Üøƒo£Î-_^£/_º¼üÚßû¥ „‚­î`#ËÒ=í7Ýï:ïbßÞ£cŸ;ºãÆÝU…N@ªŸ6„âtjFÑžôù6gæ¦c;Æía8Å‘&!¢Bô¡}<?G2 ;>ÎÑ{{s ÓñÆÙ¬¯dû(ðƒß¿Ò:Ž0×ùÞíÏc—qX­7“¿épœM–|¹V²§¹ºö‡ý(¯?”‡¿ÿèžÍ–ÒMk#ØsÞKÔÛ­èªhrdýæ’Òœ©M‡¿Z;P Ìû§·†+ÙŒsiöö®Î«–|²^ †Ñ#gâîÑúÊÖ±^ò‰Ü˜.íÿ‰þ§Ãý .²]žæ×’,…ŸãÉ|?C¿®m0QOr†¾\eçå+é[{—7¾ÿæìµŠ$|­~ùG¾X ú•åôÉ’ów]KT}èøþ ¶E 9ÜÇ›èF=³ñ9^9ÌwÆè¨|[æ>ÌŠlŒG›«ð“„ y§}¶²;Ñ/\O_Ц8¼'˜ÕØèË ­ÐMÖöå/ÃlâuhH¢3ÿݧ>þáåG–¯}ãµù±ÃûWznrDbðÏ~õÇ–Ï}ªÊÜìÞë&:=KÅÙÆv”ä+¹l->ƒ_ó=áeží…çàæÄ®ÇSÛÄ[ë¾{'[:XàÖ&Œ¶÷Ûñjº†.«}÷µŽ¿8vâÄò•?òÅ|.‡–ßù‡ßêùxbU£Ááò­ªº²ñþì?ó'–?Zµq[¹\‹¶'À´;¾ü Ç¹ÄÆ]Áïþ aôz]ñp0n>ðϾàp²ÛÃÕqƒ¹ýÝ^òt?b>¿|H||¼kù±è6ö᳟þø´jýÝßøÆÀc~ßµ}[–Kµ¦ÿ©ŸýÉå+¡ùY®^>ßøé=Ùá›*"8š=‚ŸmIw„Ú“$Ú~_°Ñ<ÞÍÉÙ©Û›/šâ×Þ¯"›Ú1oüò ð òw<{–¿¬}Üž "!)ÃaìµO|"È7öÛ¿óõáw[ßîöæVmÍé_þÂg–ýÊ—N¯ VGl¤Ó ¤m© ¼Uá|òã\=wô·ù,#@ú‡~ù¬.ÛŸÑÏÞŸ/˜¢÷•¾µâ1]9°¾žM!yfw=ù7 * _ë~ê'¾Tw×#sdÜFvÕžüÍhåjö\ù™¯þÄò…æ¿;½öæÕˈ¦¹ç'©2ôø±:Å3tí#3WÇ*¦ƒ´8ꇾ€ø}>­áUk  ã)O«&ïŒ æ£au ÙB7&ÏV~EôåšžÑzé<Š¡à„„~ûdÓ‹/ž^þìÏÿìr潋gE7{J¾#‡®Åg?þ™.?óÓ?QÛúS‰¯dkÕÕ)úÜ~Kj˜N7É_z©ux \ý'8Ÿ5ûhMxcä:£=?CÛí÷A¬<Ÿ‹¯âs’Ïçzã7‡§¿wîâèðü4ëè<Å”Š#ÅP( ižñ¼jcÆW#A$~?à¥+?Ü¿f¦°ÿ™§5òù8FÒ³vÙl =uÕÉ Jòá›8E;þ t£*aöÄ–º+ Gºñ3öf?Û;~aôaûˆtÍÎ@IDATÙZ¶w$Âý 2Ö«V&é«·“}Û‚l=Yɇé|ûOòS£÷\¯«œ5¦Éh»âÏJÌž#’˜X à‘5ì^ö.Oâp ½¶¡ëIfè(†ÍíGÆÕ+*ÔQ(y«Â¨öâhrá̽÷†Fl¶¢³•Ï¿3­óÅ)háÏÐUíh6™#Lç;6uχ)î;îìÐ5\€ÏW£Ó“uòAK7K@‹ Æ^h äÿû½øœ@´Öß§OŸœ¢Œsï½›OòÀøBtqúèÇ?^Öy/¿þÁ[ÄŪk/\¸0Íï>ù@èè¾§O>|¢ýÚ2É? L%äŠë¡ÉíÉždÞŽ]Çît¡½…+ì;º©$ Iüü´:ªá«Îx7ÿÇs’±ðg=Üzô¸$@˜’ì!3àž{·m¬b)Æí£¡¶®$BÇ ¯K^âÓÂËâ5ëâ=m&_®6ì:~Љ}àøxÀ›évÐÀxüŽéñQ+ˆœ™ÆžßlÉuh]Â!6ÀÆOYw¨‘7€‹KYÇèäøWø½ŒŸ{hx¸¿ÉEþYzN͸Ї£¨ö†6W|§¹%jÇwLhˆð'¾¬=Ënjnž®S<§[ñK?©Øþ ‹ÛÅFœ÷M>Øç.jžùa -:öŒ^4+KO à>SÙ³w¸ù};Jrg±¼­§ÒgÒjÒó’©Åf˰§&ª½i=ÍG—ú„As°×ƶ/x¦Â;eùÎàÐ ¿ ãéwgpÈè[9ŸRøúìFYw*´vc°¯¬X„{/æ!ø,eâdÎ% ·ÖÜÆÙÔuû ÊÜkîæÖQ¡¶ÔÙãQhÌAågÈí”­;ÍïúÍwC¨Ñ<9ªÀP_-ЬŒûkU-1Ä´ÕÃÜ%P^!»j^ÑŽ¥\ª@JyØvh”güa*íoä@¼zS–[ë¢XàÐÙž²m­ó´ŸmÜ­­Ê§1#Iõ·pÍí›ëU\Dˆ'[{#F¤ÍèÎíÖ¼ÑZÊ¢h Î#LùI4ÀIqëvŽñ«w»3œÏ׺ïQŽ tóš€éÝÉ ¤)3‚‰*B–²2 HÖÒK5ªýÄ8­‚[¦›ó.(bÛÖoæwdpÞJƒîòa4WËŠ&ÐmÍý5Ž—œBÐP€1Äóks¾­ ÉÆ"ØìoÌøË“Ú©ï®Âþbè—ký`æîÌVÆC¸­:öq™GѶ,À‹áõµsom1ÞÆÀÎè;ЙY2µ¿Õ|/VÁ÷,†Â ½‘2 Óhϳ Ö;çªê{aÙq°jwÎ/ï½ó½ii#Kw[ëì{ayS:ѵ uzcq–¾zò¥Iȹï¸yk°­âða´ÊžpvKI3Ù$á@°ºU•íåÚ™ìoÏÂNnÂk„l{+ab çpÂD镽‰¶8¼fü¢@vøJ9ïÖpiU§bêIJ8¡ó0º"$œ÷F8bÔ`-þ0ã4{LÈIÐH1 TÂxÍõ2«ïñYqÚ—á{÷rH®?*8RK'<ÓYÑŸyà™„(&Í%"WçiŸ3¥¢äê²nÃï b£ÉÖ‹lÝ̈"`òÚ÷'˜)ì=BŸ{ÔúŸâÁí¥¶üõè/«LÒ š"IÁ ߪ©›‚m÷÷^p6Ÿá²3SŠn”)@¬=ãê•2žÞëœuŠ4%‚ðÔŠ=@L ¢mÚàèd¥ÜàßÓZ°¹”Q°e<:£ÇosÓæ "‘‡sG<’b›ÓËsA ßd²¶•ŽÏ3ûô¬ÕÙTéÍ_âh©ûžÜ|$¾ Ë®N5[¼^åïÝ*ÊZeû,8æÓ5ÞŸ‹¤4!F¤„R¼YE,Þõ,˜ÓM…8W.¼—üYµs|ó½K“аwOúwv€$œ½éZ*eï¥'¢#òT’ngTU„ÒZ–’­8²À«ç6_°ÙÚ½Ã_Z“>à>0s»Ì=éªå7oËÁ^h< íãØ{rbYƒñÈ1vt>Ö`”L)IÓ÷Y°o_²#†ÞzgO· mŠ×Œ JÙú㙼ºžŸ/øÂ©#ݳ}y÷ܵ©eŒ;rêtÆž ‡ë“ÏHn5³W â z÷,/Xj¾ô ŽË12‰Á쇓œ¬% 9 Ë’WÆy\ÊO˜I„‚Ÿð¼Gô æ]ÏÈe;Î.O•q¸†X¿„ Ïß™ž9•vÝ ½‡O+^鳜Áj¸3œÖÆ_…ÜÌzJÿšSLcæÀiÿp¤ë˜?9+·Õãdû5ç,§›~ï%Ý©ãÙÒu$>Îǹgž9Á’êWç^Fk³Þ^ òá¢y7ŒýYòÑž‚š«àŠÀ^{máJ7ÍZgíÁÕ9¯ëôÚ† ¶Z%î³p?õÙÏ-ô+_YþþßûÅÖÑsZš$[°b·p˜K•Üt¨¯£‡·ïh4Û¶ÎaöRÇ8üOeSáû*’8…{KB¿oÈùŽÓO²§ý™3h[}!o¥ã¦—}øcµ®¢RÁg?÷ùü;‡†w¾þƒ×rðçðÌßs´dO-@÷¦îÓ·QÒØ]ñ…¬}ògk¶&rÇçÞãayóé᯦0/¿©çó€«xöç8F¦sÈ™×ȉèŽN¼³nr7²ïÖ}Å‘57$kKb$‡]Ë–`[ ßn\GÖáMð3ÞÕXÃ{f[¢¥žÅöß½Ž?¡±È<Û&õ_€ÛÓ«gÀ.vÅ,÷?9óßOrûÊ•«µ¬>¼|úSŸœÎŠ·›ë$‡x»“I'O`_º~eΧ•X†æÓF È]ËG” MŸ»®KÖ[=n5æa^ªåù±èm#ƒf±Œp<Šú3Ú÷ýxñwk†'E ¨Ùã=à&x*‰rst£U©#fœÏ®p‡“}_þª—³mö†ìÒ w7ϵ*³7Zó'?ôJ6×ÑxHAè*×óéqãíhÝo ÎOâY‚²T¦ˆ¯¹¦§Ä»8´á øª=4ßÔÛø[ïÉÁwy0‘˜Zrw2Œ”ïiÛÎ|Zï')©vvD×Ç>òáItDØV`jK7ï*ôÇ úŸ(áZRá…sïÕ6:OQóÕb{Ïu&ðÄ럄bƒ[$]µ óDwêSuäz¾äŸ‹Ë¶à%ØÇØ•„º̲éVÝÊŠ9 •#øÄ_j<òônñé`lÑàð¾—Oþ1â’«ÉE>Ü‚~«N uyäË‘'a÷£z ÜÊ7¦ûðÑàÖžŠO) "›¯Ð¬èYøÙèIìªþ™ãžŠÝž<6ó1¶¥íéÚ…ßxóRþ´«c'°aøV$¢¢½áŠ6Ô’ó$(Š}ù•—'aêN:0¹Ò½*ÕÖµD5õ³ôFôé³Ëí—Ö“0~ZûÖìƒ ô“Ÿ­ik?æCÁ—Éð Âç=¿woëÝ¡E:¼¾s‹l«x¡=“D¤û&ÌQ¸í ½ça¾çù,Ý݈ÓL¬²½³9æ@FÓs··f1¼K§c¢¿²ÉŠÁñ7´ƒÇA^µo©lñq ´&™BŒ@¬sè*> &΃·¦õu® ‘nÎo àÈšÈnêôº|Ä«V·wå(ô6p|¬5‚9þhÐ(ðÏV>’ŽOà¯uÔ±ÎTOž®ì'ÝÖÀIL`­ùJ¬6.Ü…ƒhrü=â|}LJÎ I“ü“\þôCÝøðFVDW“\ØÉøáÙí­£‚wgÛnÚÔ¾vÝìq¢ÒkW E‡õ%6~rL—.pü4|øÕñ=*ß›ë™Å†§6Gp¡ÓêL„oòƒÙýìÁÅ÷쉧Æ3æ,öèsb”ñ ´l‚ۻ괌¿©Xüh¬á¹ø`8D?cßòQ…bãöV¸ú‹/-Ÿÿâ—–¿úÿüÏ–Ÿú™?±|öó_¬ˆíÂòƒï}gü#ŽUºqõÒìüÚ,7Š´_+! IÖ x`^xèóL¹n^e”·ãM6Dmr^2=´xÙ±mÙ¸³|ñþÄò‘OþHAÙký”¥Ö 7çùÔ6Nà‚Bu4Å{ÿ¡ÝÍ©C©KP`ÛÚùƧ_84Jå¹£C‡BŒ¡{'c‘±w½²ù|ÿ,çChç~þ½÷&£k#DœL¢˜úÖk—¾˜äö9¯¢ J`–6Æ9ß6‡â†°$ ܸ®ê£XesIî©í’àšÀÃÅõÚÄðÖ(£Aõ¥wZz|úh ñù­±-&DÎhΗŸì'Øc'Ÿø;/úþÕŒã(!"Ü[ö>dˆ´–#ÍÑîÉC껪„`P¨bŽ{ÖF^‹‘mUºjB!Zkî2gn`®/þdÑ]ª’éIsüsñ–SÀ¿òKÿ]™ô”“+Uï/—л|MЯÀ\ÏÛ›…Ròço„„#v­:`œ"”g¨ª©¸mþœMòqØB£¥M0Í)²3ÇçôõIlÎݳç¯WMëlÞZ¶h-´RZ÷á‚ÓˆBB€Œ_Žîç.\)#»,µˆ^ë¹SÇÊÈ;XõRÏGO:oƒPn±2à9Ñ0>í8W ÐjYŒä$'ê9ÇÊã„Äšœ5A ¬q¾ãÁ™Aæ(ðûΙó“]jï®\I€ö|­÷¶>Dy»D>L‹#†`Ùß<1HØ—¯Ü ·‚QH²§yªd&lvíL-ŽÂ]ÌU¥+îÅQCR( 'A|Y‰{öìx« ®xôòìþ•ÖvqÙ¾v4Ú*`¾>r¨u§Ü䀿¼_l ëØs/ż|{jíÒZḊP•o¿ýÎòèîõåãŸ(+¸ ×+ýÄré½3p}’¡ø4 Þ¿qe”†Ã×Ê{vöìTF¬U º{#çË·÷ËWî¥ôœ^ž” £j]fÓŽ`¿ië ¦ Æ”Ê3ÉO§ä24ñ”àM;öŒ²qûê¹åißç^xu¹”Âÿ¬s°œ‘î htJýœÚþI‚eÛë«Ã·$A¬%ܦ¥wÊ0CqZå…¿¡{°ìª"ô=ÿ`ñëu·ìíS˜rL’I]%úûñ}Ž©è!ãGvØæø¦}œ t|áNI—JZ!½9½á5A7ù&ñÏp6<𔵄±kMúz´{³ù ìë^™ï‡Jva,<ŽßÊlÞHRútØ×ç*È7å`àÙ¿{2³"ƒeïç, –[¶–]i{y3Gǘ;v¤Ä‹žÏ`¹ÿ’4ƒÆ D„oˋק#(Ñ8e‘"¢ÊþùF‚“s“rG80f †È‘ðŽÝoí M%NP뢸€^A°n*;ûþzÁ9²¢ñgÎUÂOTi¤2œóuŒ©ž.æÎ«2®ó@R\)ò^ XJͳ²¿>LáiãÍqŒ¡øEœŠ`=‚ø“9 ·ùεto¯[NUÍÍ6¹'xiÝÜ?ÌuÇá¤jÍá\˜ Þ`„Çÿ0^ çĽì»?ù§ÿ…plÇò+÷ÿµœ|åG–æOýÜò+¿ükËüþ‡Ëÿé_ú…9]%´cVN¤×}ï×#Íu×ЀîAOJ¼j œü‹çqäKNô°ÍÚkÄŒ8èŒ_4q4yÁ™qáÜ{}ww‚ÞèO‹6®®9NÍ}ærR¥ð‡ÃãG Dç·«°§Fø!»–ƒ>1oß>>åŒL7d¤f§dp?Y.—øñ4ºèŒEŽ\ZwtŽIÇɸ‘=¸×LÈ´qfr…›tÙ DÆ;ðÌáU¢µíìeJ|ÿð-óaDôg2ê­åk¿ýóÜãÏ}$e~Õ:îÐÁŽ=ÈáÈ@ПT½ƒvd>‡#àƒ¿â}{ây{3âé—®Ôf±ý™ç£§äºÑ?mf¹„ÿw¬e]8îõ½šŽÈàX·ŒO0¸lYØÛ½ Û1jÃS­ÀÐÝ“’bVÎ÷ŽŸ8R[×`qï¶ÏÈ‚’¯ðáiãXMnÒ'󼧾ÂQÀ°álfw¸gñAr„CRÆý»Öîôž’£‚mSmº¼Ðƒ^nßè/œûx=^Ž×øÏÄïÁ}Ö <ñy|d„2¶8ùýÍY­AóþÞ£ÆHÈaûðYí ›Ã“‘ááE8`í=xæE¦ ƒm~‚½œa0µ®ùÀÀÃÂ\ŠçÃslkgnà¬jœ£À^àÖáÚmUu)(ŒÞI·5x®:8UQþ¸F«¿icýÙÛéÂ{Öp›~[»Ÿá?í›uúîÀyóäüðûd/­‘ƒbèÝµÍ ž2äéŒV¶çv Ê\g„»œÙ*φSñ±£îΟGüå@ã£ß·ßzwœ.ª^é<Ö']ðvŽ“§év[9Oš³ a0Öºx¬í%ï¼î1}µzÿ¤¬°\ÙqÙ~èÙ·sÈrd[ž!²ÖÏÓèsk6‹õíîl¿ÉåµúæÌìÆ·o)pjdZsÑÕð$D¢0uy¥Þo-­z`ÊŽ›ä?ðh9Û |x¦ó áu ‰6V2EÝÑalBÕAW;æ`Wö;MâîÐqÏ 4Ñ¡§Ú$í£DI4úLµy¶‹ï&øÚÄú&>¼rtÑÉnf‡?Nn¡ ú};¸¿dr‰$á6^Ç$°GއynkûP«W÷¦‹ ¢ù;Ù81ïáAl•tá¹ã¼K÷+I—]ý¸ýú ØaÍãÄlL2.!ááa¼äbúŒäøÕ£íߎÎѼÐûxom;%I¡ëmÁï\ _Û ñ4´~Û»à! ª}ƒÎQ•LÊ@–á)© íûÊánñ9ôõ'÷²]vædìÞáÑÍYpýoþÍ¿¾¼wæL•ð9ö+4PGï¤ÓØctóÂ:ër¶ê­ôy/ÉûüèѼ؛sPßÛ;óó[d£cLüž¡2U€Ç1y\Cñô’ÝË[o½­÷‰*2>¿üæoüúòá~tùܾ8zßó‡Þ9vù©O~l:ý±+85ŸE£“@ƒ¶á˜M~ÿåOÉ­ã(ìzÈzÿÁ?üßóhÿk:ƒ þ47oHûà9öN«›—ìr|ô}I2‘p´¾Ó‘v†_ÇkÑʽà»båÄ•LŸ½TÂbÆ‘™vNãÝ‚ˆ¨ø~÷ð­àÍöƒƒ”/¾} ßé2ô÷n_âWÝw,{mAzƾüytxN ¢›üê̓œ®Û[Bþ½„%÷ Ø:ö‡n$ ²£gŸ,0:¼%<Ð^ÿà«S8BŽÛþI!]8k¢Sp¬’å®Ù^2´‘RSõ¹¤“(Žn ц3•Ÿ•,ó¬gOÀÞ„ƒ‡u}×û=ÙtøÉöIt*PÖüÁžŽî˜;É™ºµ%Á²¯£á\|7³iÈ ~Ç“M÷#UÉ–+TJ–uß(uí>CÙ†iwy¢K4I>¬øIŸ÷ÏÜV>Wóni8¸HðøÝá ·#ø<©Õ9ùN&6äø)6ìýï¤ÊÇ‚!ô×À5:С|°‚*·ë„µ»=Üs¢„ˆ¾$ëÖ³iÁ}_¾¨M«8 SUòÈÉ`_ì˜sÜ{ßôfÞ½ìŸõL‡“Љ&غùþ¶u$Üßš­x[þ× ú™’öOä{ì»­›Ó“ó9AK,[–cùלO?Y'°±«$nû5U¥Í?mxl–-ÍÙ‹üÁÛÂ%ÊKž#ÃìÜU7Iõ}Ïf¶óó,ȲeoòhÕ‡ž|ʯB¯Ýž¼çÛéª7¯.òå;™-’½}½ŠL>)~Ë—_<5{È7RÅó©SÁOó±)`´VõÿöhkG2{#_ͽ[æìi.ø|ÖæÜÒüúnRÚ+ɆhÙZ›ö¬‰†$[>M(x”ï›ɨÓM¨k¶6®.¨üð'NKIGL¦‡³ Že§9ºåÊÕË…•ÌíÃmº1;ÿ¶ Ùü‡{ýüfÿƒáÈÀè}Na _ ®¹ôuó?È wèÞÒIñE¸:ÈÚ׺WºÚŠ^È^GJWŸ-ѧó¶Ù¬4LDÐҹ̑@hA×]5Èá}ùŠÙ ·Û—'é¢óSÊwãáJÌ„/ÏÖ±;|Ó},·ôl´>èôÄW<º»›Ïø,>Ø;tüþ,Ľx5]n®|Œÿ国Ÿ”nµoß’r®•À혎VÓõ7¢¿¡jmü×ëí‘ â$ñ*ü]€güTMÞ?p5Ypÿƒ¾ÜCŽLÒH4£»Ÿý¼óA <Ãt)1h#^¾¥õ­IŒM_¦Ã%m·D˼ì¨Hò}±›çÞƒ•»ÿÖuùâ÷áàžôyßüú×—SŸv¢Áo¼±œü ÿììŸtSôbÝñlÁPEŽà„ïoSq|ù\§CK{ï² ïÞƒ›×å³Þ¶£. Þ.9öN¶ÇtÍIœlΆ°wGòK\ Þ+™º¹ÄÝ Ã›¦³RóöûÊÅs­/}§;ÐX¯ [‘Öxòä éNG,;þpuöo~Ó‡øÕñª_É;q§+ùõØVp]U:™¬£²9 xï+žð±~8Ýö\É/u]Ùhd!žœ¬k7 ðó·’uŽm´~¾üÈ~U|=loŸøt¼{ECx,ÝTæ#iÀž[±8\ql©”ºJœZïÜvrtÞ`Þ-‰Ón‡ Í·òwJDZïYwßßSñ9|š~«í;ßP cM‚³öy4‚?>ž©SŽ„«ñ¬»wVÅh@ð™Ã?€/Ð)ö_ëÛÔ{óÏ[¯€/ÆŠý€ {Ýó?"sëê”Ãÿí»'ŒÙXæbð¼a“ƒ†…¿­t/ü,=ž,jÝà±Q`X"‰’0èywò³~:=\²ðitrBåÿýh†ÝÈÖšB†d¿²¸È7Ø´kê|õ‰íÄ+èþÎŽÇwÖ³%Ÿ&—ø„u»뺗ÿÌ^†¸³ÖØðè(¥øoÙepËz%ÊH4‹ÕÕEÀ¦|·piãÑlñý E"ç3ïîi%ï»àA6±mÅ-u`eËK|Ø›|· fÕÍ!»>6~“oÎ’Ú£xŽãT[N|&ý*ܧ׮—œ£ÐboGˆYíÏÞsæýÈ¿öÖ¾í–Ò1/‘tT¬æ˜ÔòÓËOþôÏo}uùŸ~ýW‡F¿ùµ0ëhcžTU«…/cŸ Ó>ƒÐªho®sw0N·•½p5tˆyB–6b²[cXB²&0Om²…””ª|þ,w¯LïHjÅ ÂíÁ„„1¬™ 3ÖJIæø¬ê»kdÆmÕ²$â$è•Í¢E€Ö×bÞˆ ïbxêˆã¢v»Áíc~e9•ƒâÜon2\ÅDUB_)¨ò¸ ,=U>$={ñrLfG™]eØfàj—ð™ÏKr¸º<¼ñîròÄ #”÷$tœûq9Çí’(®Uù 3Ÿ]î¹åòòê G—S'¨ÎñÃ{¾V\2àU #BH»Ê~q6#HGÙÊ/¿xz¹Ñw÷Þ£”¬µ»× ~×¾;çðýÎÞ]Uñ•…+ ODá´¶HD›kt`‚ÈÚ49ïæìÙsU§×N¦˜˜ÀØk¿ß™?þ¡ç—OâCÓ»ùpNU¡ÎÅàTš_-x}½Ÿ›1ý-kGÏÿõß_ö^)‹?!óò §'`DRÚ)‘×sÊk;ãœUAѳ9c®Tý«…?Ó™Ç7–·kþÑ—O—…zr©÷ïi¥_RCð¸Óu‚k[2@ß{ïüòîùkUîE„áFZ-×n,}åôœ¦š~gLYµ»¹ òjÆ«íÅÃ~?zh9}¢¶m)#ì·ìÉ Ú´|ÿ;ßXÞíX„eí:ryùT™4÷£ëÍ›3n7‡ÌîÚŸ¯­žõ>¸}±ó•.;:Ïëéã Š”´v®…÷Î_ñu¦ã¾sµ8J!-Cöù—^Y¤ès~þÞ7´ÙÚ˜3U^ÿþ÷—ÿÏ/ýjÙ;?µ|æG¾TíL‚:?ÚÛì^ùØË˽öyO™ÏÎZ/p猱Í[Þ9saùµ_ùïk‰³wù©/¦€qŠPç¦?ÛÔ5‡ORÈöè»6Å8ûÑbp×–'Siòõo|g9ÿî›ÃxU\mßznyµŒß—_:=†åÅ”žcÇ^c‚¾ê„ÀèJø_g2Éà‘6/”Ú;>Woe4܉ý‡ÚŒ¤4ˆöüÎòÊóÎú+Ñ¢9nÊÀÛ_ %úz™ôÛµÀ­Ã…‹7—·K ¹¡‹A›»ífŽáøÍécû—¢¸àm º PÒvFó*ŽÞ={y9Wrˆ³ ½®—Dt3úy¥=жý(C2|Neh‰ŽàÊÖöîJÂðüõZº§äâ„ܽþ¾/;\[1U-ÁûÈqp³yi¥|h_A¬øÊ;ç®T кããZ¬ D¶Ÿ?œ;¼3žÊyÏ>ù`eä 03ä)8£v´‡'OJœÉ Ka#øµŽ9I¶sílþ ª;Q7 ™|Ú£9ÿ[+½kÑÚ(çËä†uòuÖøHÕhψe¤” جŒ†giÍœ7Ymã´Ê’‘:¦hûJ)%̽gTé¨Â1¨;ˆcÉȦÄI@ø@a¡ä¯çTâPrvôÃû)9É ¯8iì©ø[ @ÕÊIšdÁ‚ó’' ø;¿ùkƒ+*îìj›Ãùð¬qNœz1càüò0ƒà|¼_ÊÍ¥Ú7m{¾½ lzЬ)§Zp¦äq&Næk4i"ã˜V.uçãbÇ. yÿd¼Àn¨:}1>ÊHa¨t¡\²mÞšõ˜3: b|‹'~õçþlê}Ëßø«ÿÁò¹¯üÜòóîç—_þå_]þÿ÷ÿhù×ÿõcÚ*iê«?ý³ã̸ðÖË_ÿ¯þ‹øÍ³åGìÇÇ@S 7•1Rü—óog•ìó{9:¶wo·”¦®ö¬q޵ÿÎMe3ލA=·a¡ìì•6îèÀs8#S'goFÈÓY‚Ï*a ñM:‚énF¯Â,–^Óƒæ¤0xöƪíüåU5¡K’Êü]Ýr >·§Î_÷ã1³Ï]ÿAи:›¾óëo‰G÷Òa9£¦sIsXx)\ÉIÝu‚ì៤Ÿr‚c«ñFïè~üúa2ŒË ‹7Ã[Ý7Іµ¡ÛÕ~†ŸÙÖlk@çðHí»­ Ì«Z,ße’|ϼ[âÔ׿¶œ(il—=S…;=qLù›]ê“gékæGí>†Ó·ÍÝsá |pŸÑoÑ8þohÎL.ð1÷ø¦—ÄÍýñU²Úëg~îO.Ï=ÿb0Û¾|þ _Í÷·ÿæò±O~fùìÇ>Óþ%K:Ö‚íäeÞžø¿~±MÍ÷«Éý¯¯øÃyoºp}5ãÕì~h3é¹mÒÀ_ó ÕŽÇ>ç$g_OáGp¶ßt¼lZTÆ‹%ÏâhÉ=ÃãˆnEö†_¢!{F8ŒïÀu}Æ7ç›G/VNï"ß¼±SD¹Ìª2$‡høœƒzwöN>Æ£÷.Žœh÷èÈ—'P?¼6»›lš.Wáëîì]ãKúSR¨cô tÄ)¾'¾í¸06'½‚ܯpHÁ½ùX‰Î@_+˜¥³Í-Á—°¨ëš=µú„ÇÛãÏ}€¯âgÖ5‰IÑÊšÀÀOöÛÎìN•d`ÁiO‡â4_ÙZÁ‘üÉ)%ÑSòÂÎèÙù«ðè~hP"T3ˆÚÇø~Ïgú4]v³}pö™ýš þ‚ü‚JªÂØVòPèHôˆˆnñžIUj­ù+ú\=z_óؾ]²è½ëñÇžVͼs·*Ü•žIÿ’,cNŠ9$†Ñ öW*àÃ9mè|ËcñÞø5Žml nü“Œ?lk–Íñ&<°ãuôˆÆ¶ö |ÛàöYrM÷Ú¼U¤28Õ{öóTÍÑa‚‰Dlöm_„³ù+Jî´¹uôžiÍ~(8”tkrN°^`®›úo%K÷ìÍ6n:µ‚“#6ÉÆÍ;óõÔ© …ÍÁD€ÝñåÁmAWE.’û'ÈOÚ? `¡ÎCŽ@òBGK<àœäKðT5(9ŽÏƒüˆjƒox›iºõìmCûYü>ÖÐü\KÏò" ¨ Ú¼esþ§ƒ]2 “åâÇ_|U][ãíü¢‚kd`O똀|Ü­ÿxvÞàn0„`£‚q- Ú´ùñòOBgø>¼„W«ùŒ=Z!Cm ¡ÒݸsMŸÑ·Ùžè¾ á=¤®ŽT]–ýñ-åÍ\Û©ã­ëû;ÝMš ¿º‘€ÊžìƒUç`¼nàEkkû–]pÅ è|ðû°$ÇÍÁ§Ï¶5Uì’ýÞ_ů»Ÿ –¤ ^èøÑ2-‹»v`v@œÖe¾‘ÿÐÝŒ¢_°:zôdþ˜|pùOѸBAûº)ýV€ ¬ž´og Œ*üÓ©€Íö¸vÉÓdöÈóø™ãIî¿?à+yÐéÚ»kÙŽ>tŽÓ öþ]ÝÓûúlö»y"á-œ‚9 \Ñ3¬-MhöžÔÝ‘ÿYÅöƒ»ãïÆÙ$é§ *ô0­êBêë—Ï,áÏýùå彺|êÓŸ]~ðÝׯÿƒç’?x‡ ·”´¥Îs·d¿ ¦Ÿ½‘è>Ýv‚Kÿfw(JÜÕù;Oó^½UQLvõØÓïìØí›Äoñˆî»™Ÿu•`Až=ž€.8¡C1>¡Û% `Ÿ:]êÊ`ð]0Ýq!ìÓ_x!¿ùáÙ{pÅ¡™ w„ë‚ìô‹Ñýz®ØŸƒ5)†™}éZ>Z6S÷ÝJø=û~£|®©¸Rögé;“ibelÞ'6ŽŽÒΟ;;| œ·\½qµ„€¡•xOþoÇì¬\b7Ú}’?È/ÏÅ»ºut¬oÞÄ3ÉÏôZxõþˆÉgü¤àF/ؘãz“¹]ƒ?+ð¨]Û3ÿxœb$¶óκöîˆÇé¢ü8½ÿQq8áxESü™xótP¢%#‡¿¤ëKðP¡>øÛ$MD3lñ§í³×Š'±qÙè2]¨kà‚n¼ì‡Ãù\$œÍöùzðÃPGIJý"_’åK¼€¾‚ÇZã³9À!Ž4‰,ö}‰C’_ø‘ <ÐÒqKEŒŽØÛ¼5~ïn{‡-d]ñ,v6çíüŸùÄ^S†ÆŸà»ÑðþƒÛ¯b4ÙƒÎoæ3:_\Lb‡uIª&Û%H®Õmbmÿ‰åÄóБ¦—ÎWÐ)Ù$Ü”gb¤[ë¦xÛC íùYZ³äÒW?ôrp}T÷…ëË­ð϶H–Ù]âøí3¥S×o]‹ñ¯±‘c€ö}ËÖCc_óˆïÞ,ÞªM0ýsŸÿÂðôoëÑØ¥å¥W^näø™¯À‡^¾·d,÷g'(Ï ÄXåQÓB1'Û® ñ0쑘ýÜ¿ÿƒï.ýÄç‹ðXžÖsëöµåûßþ‡\{þ¥åìOĵºÛUÐíz’Ë—ªâN°8\Uj[lñgÞ~=Ä”%—3Êö‡”Úk_:÷fÀÌBñ¨H ›\ÂÍ›`RºeêƒÃþV‰÷dDµI20FÑ ñß½‚2J™Àg«€6Ʊµ¶Ž6blÔ–š Ä”·e`«¨žŠïˆ)Ô8œ@Ò6P_úvÈŠ·a›»OàÊí¨õÆŽŒ½)0žÆ–áw¿JÀÎ|(UùY Õ!òæy£J °†ÀáM«‹¤*U&GCVí­]ƬL-œÀÒºÁH†Ñþæ)(µu#…nûáNÛ±*b0­ ŽqÌnz°¼}áÖÛælÚеÈ8Ë#e6F¸2š?ap‘ÉAˆ´yhð ã邜!ßõ‚†˜ÎÍ‚¨‡c ›¶Ä¼nHªOKˆ‚£I0©z笒µ«å¥–z»s¬RödvîËxzÔ™W7Î^ZžáååÏþsqùµ¿ÿw'Ès5ÅÛ™ˆ„DSFEIVÙkͤý8©>ûG~¬ä‡½Ë·¾õµ—CÃXU‡¨Ú³$¦©æ´”=mÌe3>ìùÛ«.`Ä>j’(¦š­êSs´k—µåÌk9±8Ún•é)h  χµ—O@S¨(>Ã…ì%ñí'E>>È ¢@a²ÎqÖ'…öÙÓ-Ë©‚“±« ‹ñ¿ùÚ×—¯ó{áq-–Èizgyûõï//}è#Ë{µr?y¬ h8±QÿMÕb»w$Ì!­EàÝ*í.®§ 66%àhA9 ò­7^O wŽ!PæÇÃéZÅHáU¼® Æ½åíw/,úçÿ¹åÏÿÅa9óî›1³Áõ;Ëí+çË’{5¦˜,\?–‚p»uÜi‚3·âßþÆ·2FÊ\sN¢Ê¨uFë³Ç’]bR)÷kÛcÞO 8ÕB±èu•ßÑz™`§j+}»>ª×ðwJLøÖ/ýÎòÏþÜY>÷ÉE/µ½i¿œ%~#Êò³Óê.dhl¬­ üƒå4ã?¥=ÐŒyèþXÎ_.±#Yg^w;^Äqy´àŵœï„ór´oÚ[À7öÖšðh‚s2!?ÊòûÕ bž>Õ|Õºýv<ýoœ ßV "ˆ“¼g­VP BÙ·#¢Yî;uÙˆŒF€á7›¢aü¯¡d©V• …sr2*(¨ çë°á|\U÷âùw['ZÂgT^Þh_íR=â]íyÿ8WU004e¨›×Þ®¡ €µCœQד4PÛñs§0›ózxw¨çíÝ[.Å…s¹!&0¥Í%^±9Z'7îw¿=ßÕœUkp¼üZAòÁ’…À^…[ðýέ#Ë)Æž-ôÝîyãõß¤å …ÏžUpìAë—Àp3ÚùfŽ\´“'O7·ÓZJñwî½wÊÄã’y¢U-ë?öé/,GOœzÑJoùžì£h*ÈóÒ˵7¬Úd2r›³V[ë¿ý/Ç8Ú>É´F·ð…ÃâɺWI_گδ»u&]Žr(d€•‡—hYF~3tfÁ£&ýãÿ?8Ÿë¦b}ä#'µ—½[½ÚÀÒË“(¬h˜ á}zèìÙû¥û麞3Ñϼþ{ËOþäŽÂûïþ»ÿ×å_þ—ÿò¢íËÏ¿¼¼ú ÿêT¯ü™?ÿ—–ï}÷ÛuCg¢Ÿí;nÞÀßýÑ·ÔÓ‚K£ûõ,‰a½G 73°dЀÂך #Eµ 猪 |@6³`Ǿö½wÎ.»KΘd>ò#ÍmD;±€†æJ—Îd¹ úT²ÆK軪Hº|¾c4ë®#ʉº_Ù=âŪiµGœMïIaXpR2Be [ç8äšÏƒèŽ$`G¯“‰ÿL¡÷Ê–¼åùý7s–¡m<:!ýpOº¾C‡¢sà?*lW•œŸÁ‡ÓîÙ5ü+'þ Á2ÊѽV%¬½03ùN-ßß>ó^`ì;<­OwÓgGof´<˜¹J¶BwÌ4|‡¼Bã|ï>ï¯>¾–.QBH{&X«JçI o ZÕ3·ï²(ÚÿtuNU0VÌ™nÝúö ð­Ê‚ÆòÎÙ*“;»>Å±Èøä,G{kcøíK·‚1Á‹“.Ùsxe6u¸JÖ>î{ÁÁÑëƒùJ±NÏN)Ù±iÎøôTkcxÞ Wu=Ñõfœ±é‰‚„ôú!dãŽ^Ðwgs¹W3˜SÙ¾7ùt<Ì›½gmL'ôžãc<æß:[¯{‚ë3úQ÷h÷ŠŸ­`1Л÷8&eÆ2.œ3ŠkCÆ^G;pa#>>³ô–¾G`¸íøžëÉœî=wÕÆ.‡f´_9ñtÕ´/?”zwy'=€#zÓ–µª©Ê@O¿\¥Gž`Œä7Éd_Ó~Ò=ÂasóÌܳŸðFÐAÒî㜠³Ý´Jç®tÁÔŒ`ÅùÀùÓû;ÑŸÀÈ®¸xüÝtOë…_ðŒ-ˆñ£ËÌþ÷uÇÛí§ûÀwÌ(‘;G»ê(]ˆ6Ï¿X9µé x ç->4NàèåC%5nÎÜt?[=}wÎ%ö7Û¯0öøÞÉH”$‰ ´½=xs‰¾Áo‚Ü(q@ðÛY¸·ƒÏý>›äƒn+\µ?ð–Û9A½àHe§d†­ .·»ÌUìç Üí¹'àUñL>½¼ìRú#šF/tí½¹™þv5ý.æ?%<ƒñNûŒçKœ¦?]¹v«£®®ØþÑ¥í7Î,|X§#;Îñ‰ö¼ÛR76:#ú˜„®®ß\À¯;‚CèÇî•x°Ú7º¾@Í*i½¹âÀëíJö4Ïb‡ïÍŽêã‘ hDÂÐW#£?ôù0¾)H-Õ^\LG7?6 ±r˜®h¦Îó$k˜çŒÕl=:$¨†Wu>n¶pšómåÜz¸Ìy¯ ëí·ßîìÌSéb‰Žßúæ×{V2­k÷Fo+W8¶°ÂÙÙ¼ÖÐæÿÿËÿYkÓ:Àóÿ—ü//ÿ¡ýÝ´fNxæ8ñÃzø<º køM&Ù*üô#ÛÌͱN²¿ÈþGëñ‡w(]0q·†w'ëùíÈ\òg3ïÓšÇÊ;,úG_ÿÔ$ßC¦hFw€®aÆS"ÍMr»P"Ñãñi­hA"ðíËÙI®ml£hd’»¾ãwlWørñJÉÀ%èòîÒYŒ æý9|ý ÷“½t<­AzŸìnÏ„${è~»³?Ñ ¾…Φ•hãòÕáµø íéØsaL×òZ³:•Ü`ð0ÞóàVötƒ-Ògø¦8†ñ¤ ¬Ç‹ö´’¼|)=ïbÉàÆŸ@Äà7¾°¢c<€kgrZRÁ i£‡dÏ[s”;pÇqcâÿ+žŒf}ìcN¤JžŽ@IDATàøòQl~V%wcá×rüšûð¤6äÚM<¨=Iž­52ÛÞÜñ[gVo­cß™÷îÌ=t>úy: êÝÛ’?\Áqº~µÇO€©ù=NvÀ6¸£+6:F“µ§ô†hºÁ!XÎOtè¤cÀ3w{Ž„UV#ɈdUsÊ…Z±Eò,Û÷áæäh8˯ñFº>]6ìèÞp#uS²X7Üzú{WÉè‡ßf0µ{ÐÙ–má>›^´£ý$Ãu½ì†K&Güf¨d|ò¤â¦®!ËðT‰¯ÉŸº{fi÷œtäVOF ÿ{ð$¬ÁÍ|lëÁ"²~¹d§|̓Þö5úÌnFè4 Ùd;ó‘y¡kvîÀ§÷ºoÏ XÓÑÌwt³àÿ¤ÖøpmžüÞo­ÚÏúšK"¯êv áRóç§'œGm†Ã ú6áÄüo“¨<Ð?jAx~/áÊM’ô«(øšâ»è||&#G ¦+}‰ìž×óWà6÷½úmð‡ÕMÃdæºI¼ëY†ÞÑ^KøçÛí£ü×+›ÁÅŒÑü’sÁ.8¢Á>À½`|ÝCƒáÓ§ulj÷‰ÌËlÇ^í+€‹¿ùìZzèÕø~äy»’ßsmï[ACÄÇýÕÏþ­üHƒë}†§Âuûð(½’žohlÿ¢ƒH¦·Nö!ýàF"n”˜è£}ùüø°Ù2Ï6‚ëƒv¡Çx:ûâPø§c)|ßâI«n’{ìÔÿ¾W«‹Wv6qöü–øhË ®u*¨Ä§~ôYkÈ„A÷§áŸï§»C|H¼¡åÅC†·U—_:›í/*iWI xä|Ò|ô‡k#öw–þ/üKË—ô+éøuÑ-huüÄÑüBÈJ‚joí—ä[ô#È+ÙäqòQ‘žÂóòà'á÷®èJ"Uî| ì£Á»vl9ܾ‹#8šN“£öØ.Ï®ÆCçYú`iäÔ*™5Ù2¾¨‚Ìlx¼uñð½n°ŠbÆ?á¡^ðaìðð%°F{ñµà¥ãÛùqǃŸ(V¢UºqÝC÷ßÔ¼˜L<"ÿƒ˜ˆ ¶±ÉIŠÀvšZÙ9ÆG-~µkW]‘{¿ÄIz¦ptã9ûJTBÇ™Ö-Þ§øzý€õ•pTrÛG>ò±dã®å{¬x2Y /ßzçáöÇYã _|‡-‹9ž=waæ'áB?çwbï£ …äD:±"¼OBƒ_Ë$½ë~÷íáf|¾qèGæˆW혣WŠ“U´" ȇþÁü@\…œÆ›WNÑUôÅ.Ôu›½ ÏÍC Î茸QÆó­Eð˜$»qíÊr³õ‘»ŠÁoJôžŒÁrrý‹)éžÇ·.'ª8— 'Îw5ؾwö½)xÚ“ êè‚é˜ÎҙƦd3Elü®Öü(šØ(¶èÙä ¯±¯äåt] ÙFÆÆóÕ!úDñ2Ià>üªn„ªø§@ëZðÅw%)>f¯ö|òA¢’Dq¶Q jõ]·-Üj}Š£ÁEò09èâ}ɧ8Ñ¡SÛ\ñ¬…}ñÉ—οÝu20†cqWö²¹Ú/?`$Ö†?N²z8º§=Ü­9ŽL­¸È“ü/—ß{«yŸžOö“‹§Ÿ;Þ:NÍXŽÝÙýü “ŒßÑê7¿^ûÅ—Ó§Og«n[^ÿþ÷âÅsâ_Žá¯šh=Úºoµ›BlAr™ˆì⥫m¢l§’ãÁljtìØ¡6(EªÖº×.i“MöèýåλrérY¦weÎ&í>EàáPAËí2¾û­Å¶È6ˆ‘(‹Þ¦aÀ˜ýÂ¥àá•©ZÖ®"fÐÂ!„VIWª`Võ©eÅ¡œZ²F!›@8¥awN;mÞeøGl× 8oOA7ÚB#6€i_4m›¸Öª5ÇäØR…+¦Á!#3F5EF²!V‹âS&)ã€B9DûPûï²–2ø}6Ê Òj~’¦å|D$ãbÕúbœAœ”GAi-ÃOéÜç6_p^¦ÄåÈ—.u>»Ší‡‡YlOY8ܵ{k©ZÕ¦ Ë;O«v=2×b‡÷óúÈîÂhÚÒkå¹Ó™ÝÎx—²I )x˜?†î<‡Ãkç\ð=ܾL\š`£Š%c^Ó6&À“v–àI9V}™ ©1j-ØÚö7έpFÛ‰-OcQÒTÅjG¶:Ë¢qmgóoŽÁ»µ š=‰Yl­}çªå^Êpó…ÆÅŒöÄ\u=gæÀ­ŒËQˆœè;r‚=*€Møbk¿bûrR«<Õ‘£ŸÏÜ0Ká!³«5Ê€ÚTµc~˜#‡Fe¿‰@(Rv–Éh~{º؇[Ad¥Œ­i«h&H&¨¿-¼aðÔcTÄô1ÁºœvP}K¿7•¼²ñÄ94——wª<·v­†Ù7ÊÆ¹·jû?•Þ²£ö×öiYÏ^a#b[€òϰ8tøpY¨—–GU„¿òü–§uô½ž—€oïUçìè^ÁÁâï½öúò‘~lù·þí'uzùo.žçЧ>õÉå¿þ/¾ž/èWÆn4æÌ:¶gÐ;\6Ñç?÷ñ©x?~ÄžÚØ3TR€«š˜’,è·Ññ›ÊÀ]µ+Ú¹¼õö{Óyà‹_ù±åÕtÿ™w ¾\˜}èå“˯ÿÖ7–çžsöÔÉI¦a ¤ÖGI¥PLfU{7íÙÛŸ]?oÖUaKs{~­`Î8Â8‚›¯5lªìzg½?~|ÙÝgag ªØÃª2Ê$ þ·ïwÞRcbVãÇÂÏð;~¥ÚÃù²›Ú—»3¨:cú…?¦|âøÁåÜ••ó³f¸èx‡½»ëßWÅ2žº£3H´Ú¹n„íéÑ££u)¥ ŒUÁe-މÒ)ç{´¶ï IKˆ?e á›í‚Ç«ô¿ËP€w#Ø3’wí,i$^¿¯.;Û—­e™âµ•LcE§2ð¾ˆ' 12ñð‡ëUj1LwTmncVó#SÒùT“áŸÒ‡OAÕ=ó;^@'ˆ®ÅÊÁðMS¯× ~Ž½ÑŸ WÓºº?§AÁJ6eª˜É8c}.íÍ8~ã‘éïÃût¡$]¯:›RHp’s‚õþhœ¢MÑw¶æ8 6e¨6À8Y‡_¯²±9WƸ¿yž p¯I° >'3·%w‡eø5Þ+@<]š;Ç“hã«Ì‰áÕº®0FÙiŽèû€–¤ÁäVÎ~ý´'nÁegs&sn^[ñÛóçÎèªAü®M¥êj q2ñzÿ»·|í·~½ŽïDá|Aæ »¯}¯÷G»öâò¯ÿF²éN0zVâMò*žz䨩åÄéçShߌØè^è¡#'ÛÞR–9;(©1õÙcÊm::Á©Úï<Ðú‰RÚ\fŒpaªQ¦Ú³!ƒ%»¯ÿ {%+ÛN*kƒÚˆfèáa%´ICèk‡¥æÄ)À˜cI4;~êôò«¿øß.o½þõåÅ}vùÔ'?¾ü­¿õ7FøXƒ®%Ï/v^õt»ý±Ÿê,Õ-¿û[ÿãò»ÿà7Kò8Tu`ò9ÜV‘é9:)Oû8r¶ç“ZyF"eš æiñe­·ãÆ´¯teÕ¹s$BóŒWIWZ´>Koí«ºóø?¢C 3\Ÿx®öi[6IX‹gõ9|¼ZÏøŒ¹%}É’šSr¦Ð]îõtÅÆØYPð~óû×Þ(± ý$g»Æhº$DÀ«q¶Æ!wÀuÀÄÛ ©rPq=»¬š‹9}`g+ü¾OÓù¢Žþ…%X‚¶T`·=³§öÙZçEO·žé%;|Dé èꈚm›;æ+š¿·~»äpÜh_sž4¿©Ì뙽鮜Ñ%kš;|,e¢õÕÞ°î?‚лÓSЇãlœú°ùîh¦oz §ºx”Ñn.7á0oxÜá|'›¿9B#:;{îvs…£ª½|fm«u†Ã‰º&³1çó®ÕéÇl™ÕÙ¡Ö€ûķ­m[uäIÏOÆIôLIï‡>ÊQ´jEº;gŠäèÿ™º?m¶,;ûÎó<ÏYsa(@€¢$Rm‹òjÉáT‡ßôp„£¿PGôËÛ «Ý2[–DŠG‰A@¡ 5feå<ßœnNþÿžI)`BA…e’:•·î¹çì½×ZÏzæi©Ò {ûÜìÍè7=kG{wϬš¶¦éÔG³Û¶°SÒÑñðÍÃãt¼™ ÞhÿáߎöPb_>órÆp‡™–0YuW<æZÓÃ=ãpëÞlÐ:½Ð ­óføjUÏðëþ>þój4·»gt9ço:ËtÐÍ`EŸ›óþæ³Wžâiwƒï½Ö¥âóGÇÛÿ3'õrÃ7ÂÃhÜ#£æ³wy9^|E[s{Ó3ï}×Y—ýý¢u:~Œ ¸ûU¥:Sg<×ņK”m½çhdûræÁsº)þ‚Ïá-øÓƒö Zs¸° 9,鵜9øˆªÓ;]ËY ú»ÝûÑÅŽØ’ȃÃH'üS’ q/»a[ ëÈö·ï÷ò=°ÙÀaªëÉçà„v7 Œq4Ñ<ÿÀ‹9ív%O|# Ä[ìͤyœŸF‚"Üø6wc7кW}¾Vð߯Òú×c¯V?E7?~‹ð­jM>< ­ìÑ‘ÿFìápÎÜUâáƒ`<¶Zß>ÞÌA¥a˜$,¾Š €¤»¤ðômsþж—NÞ€5OŸPÕe2xiîŒÑ½æ,Õg õËWMFÝ|Ôš‚Ó³xäŽx²¹? Ø U…¬{dT°³s/løÛó-Œ?¦fÒÑî6‡rpó<Çw­ÃÒ'ù"¼@÷³ÅTF“À­‰öVp¡…ôO7$<‚Ã)+šáõ¯lþ´GÙòè-7u4˜ü dôI/IÍó¾ILP;øåÈ‘pñQ‰ˆ_~Ÿ{?‡ü[;w¬€ÀTÁ“dð“ís÷Ñ¥Áà~¾*S¼°-ÀëÁ¶±®ñý ð¦.ÚPÛ¼Â|UZNP²Åj›.8~ªôlû —†Á‘~FÞø9´ïà5×4¸ iÀ|Ñ£vƺTm¿¡/ð; °þ Dö[и¯zî #~,üÜ][·Âéô÷ FéL€Î=;£`¸drïQ¼SPŒ%9óW¥µŽö u±ðL~y"‰ÖøÓÅ*øŒMÑ>Šw²•×4®®Ö»=Ï/ä t$à…P}f6ýïßçõ›?¼ú…KÑÌÓÖ§s¥ùÀiÅ{Þk³>´” ¦òu÷ÞŠáúÑúØÞí;xtt®û·+€ì(?û·§}à3¸Åcà§bºûÁÿÜGï-¿ò+ôÔÿÿ¯þ/í]tùNoÕçå‚_üI¨f/Á&m]øn8ë({~,?ÕÁlÇOë~¸¥¸ÎtÊ .|{¾ßÞÑŠÎÎ}v9¸êlz*Û¿8Jß¡iÛ§R œùœ&ù>ŠGÂc1šáÇŠ'#d6êÕb5’­m7Úêq£OÚ8q¥Ýùê._ª#l{lð Itd¿í’ât) •%¦‚ÖœC–ÍÜ=KaM(2ržKà🸋>RבOâ{è˜têĉppÇr¡âƒ+Ùäß°}Žg½ô 8ÍWÌ‹|§s‚‹X ½˜ÝŠVžÜ]÷N½ÒCð%ð°·üÀŽa›²Ñl;”}¼VãëÁ·r°Î(’Ko\½\s]k;Î@"€®Õd.z?Þ¾ƒ—nìõíu óQ$¥µ÷pO×¹ýÅ#Åôð@²7ƒ—í½xÓ–-ì«:`W͆z‘pWÑúÎæ®ûÏ¥`$Ø»³ãdU 7ðIþ‹ü6ø9"©uμožöܼÉq»í»ÐÕGq®DŸFüõ{Ãã> n~À ¬.ç“E_|9ßÃE…|Åì&: ùÄq ýáêiAÏ'-§J¦ ÷ UöÀØ]‹¯8¦XÌöfôk}Oóï?Ëî·÷Ûw˜ï%î+xx*#³nÇäŽÄx°#»ÿ¼QÇ3Áê={‹Ó„»óQ¼òò+Ã?F~Dçì]É|wêN~µäÜé†Ó³¶¦‹œ}éìð¬kÏvå3àÓ£iê ;ì[nÞܨ°ëòðzE`øòîºøª¶?¾Ÿ,Þ´QR7¸?~̾ÎgW¬)v58#F(N*n ®÷7Þ|ý¹o|«äÈ}ËïýÎo-ï|ÿšw´U7æ›ÕÆÙG¶ÙkôÌ®âïïH”ZW_ø,¡Î‰²=‚“ £ªéîeîÊ`½[‹•i'€šÀ£'Už—i°¥–êÒ#'–?ü—¿ž3ýŸó\õ̧çrÐçÈÐfp»ÒŽi­…+§­à· 3ãNvÅåç‰Ùpoο–™àa~xLÁ9Úƒ#ôÛUõN¥ FAÚßÃë!ŠàÜ”õ·Á•ŽQB Aj™|Îð57A0@€bt—jgNÿ:‚m¯B£ÙHé}üäö89LÝ×þe_­FªÄª²´1•K—®¤´$¼&§}¤ŽŒ«J-³*$›¶Í¤,{ÕP‡œ?Ñ3(Õy’ 4]«5Çá*Í­×ÜÚŸ¾ûqL¾³b¸ï|ïýå—zâk¯ÕÒŠc8%ža2Ù+­ ‘ª²×úc¤ íÜÛùÐíÅí €mUtS’1aïžÕ¹dïíÇ´›(³ðVåл`–²[EFŽaÌöM»+^ˆÁÒn}Üß ‘Ù“˜[óO €& )KÆ ‡ø|†éîˆl”ÓˆÂ<¾­GÆ£VR*o¯µæ=U0?Lÿ/ÿèî¾À¶‹’JÉ„à ›U1Ýš†ÃlÑõ€aäþýŽ× åÚ0]Ùnpi{x¼'Ç9¥I—– L8Ix©¶\`„RZ$apN>\e€Iq.6¡³-µ¤Q¶ ¢U¯p“CoÎãð-H2¦`o†‰ZDkG›£¨OÛþ7­¸Ù)[ë. Õ ‚Ç úµÎ'ó9“ýÁ êJX¸¹Q%lzœí-…—¢E‘8œâ¶£À裞q¦ îk/ŸÁ¨êÝaâ[ÃÙn›ÖÓªDÏ_þ¨Q–å¿ýïþË[_z{ù䓆ñ~îsolywùO~ùo/Ÿ¾ÿýpY»ùÁrœ§=ëpë}òpëòvíÝ¿úVŒ¶½vž{³ª2nûœëx¬„ƒiÃÒ¦‚< ¼#}šÁ­Í¾ø¥ðiY.žÏRðìËÆÆ®å|t(™€±D1·FJ|—¥hŸÐ<üÚZ2ĪìwOÂé^Z4g˜ Ç8Yâx×Þxáxâ5:‰z&.p"kÐHŽ–xZ#ô`Mø±Dã­ñK¼OòRLi‚×=Íëq{X{^œ´ûãûv×Í#òÞýe[14«L‚xV|£y˜§–¾²­Ú¸p+¥=ðâ &ã›âÌ“þj?o%>)!±‰ŽQOV ó6S<ص{8Áâ[€Â/=µœyéôªc"2PE:|~Ò>£rñOät?‡Jž˜5´ÎÝé”»ŽìëªÚÚE»¸;nª¶Œ÷b¬xÄáZÛ;Qâyû/;7 a*+9UÈÑG³¦Ýns—@CoÀáçØ]íÂûÝ‹fŽÅ€ž…?ëSϪjhŸÂùÂGËýà°µ½8 'ÚÛ½ñ¸;²¨9Ñ]’à×w÷«ž€CËp—®ÜúéfŽ; € 9Û„ŸÔíËörNz†ÀÐ.]ϦsÚKnS¸·%ð$þ—4fnùð¾£’ÚÿÍt$sÞÝ}]<xþ¿?Á«®CÖˆ?Œlþ·/ü?á½9ïO¾f®Ï¿˜ù@À^/æúoßóÓÖµÞ±þÿ§óo_ó“ïeŸÁp³n‹:‘/³§$Þ8gY±ÈÝì²6ÍäÆ9Ì—²VÍ ê°ñª,ëáp‰þ/Á›_…îÒö}êÙäšUuuîãs%Ûàϯ\âtw(?àƒèê^´þüù©pÄ#Ñ™ÖÈ[†ïÀ‰WÑ(àšµúö´žg¬uÊ >¯ë™ýn‚¿®™‚¼fG9Vï£Ó¿úÊ+Ãsè•ðË´Oyÿƒº§ŽtÙ¯¼Mç—yÜž°q4°£7Ûµ¼î;Û;zKÏif³Nö»Ù&:2°È£6v”õ‚°¿ø íôqÏ_ÁXºBók,=ÒxÁ„ŒÑÚ¼œ:’ÍúìËÃo|i?øÙðƒ‹ù1Îòivúåì˯ÐÈdÇ:ð²Ç+ÞÿÑ;C¿î‘ŒIÀ#¼I°õ \à’šðë¡Ñæ æôk0a÷Ù·¶{Öÿø1úhð‰­;|>›ûÄÉ3Í—Ï® ©Å¶×ã\æ?|¿gmÍøJóYýf“ˆÚó¼Ìû½÷ÞmÜ5H +‚ãÕË—{T>Çà0\¡=t>ëÓ¾{Nud@m£o\¸Xõ¨®~têÐ Œg–ѾôžÏkZËèÆ/Bƒå+?»v*fkGî%'ß}ïà Â+AèÕ7Þìþh§|7›× /t Ú•æ(8¨…7]Sb =CàÌØôA}ÝÿþFwFôv»ë¯_ütàÅ·:cGK sÆ»KG<žÎ§B÷L½Ð·ûžø/v&¿$Û}øÁÇÛW>‡Ç·ÏÏy::yÜ3­áÓO>ÎßÑQ•ÍI‡BøÖ¥3ØKÚ" V>ô¥­5±þ¡'x(HÕó9ýÅE.1§nº¸Õç‰î¾y–ÿÿdŸG“Öÿ| Ïü—pw¥1rvõe>ñü®±þO«J•x¸¿ â˯~ݯžïÙ|^tv|ñƒ?™uóƒ(’r?]‰B¾ÁÑšáÎZ,~ù=¸ÓÛ[‹n.>÷YtdiÉß5ÐÄßÍ•­Ð5’œ6wh©9¿òú«Ã“¬k@Ö­€Â¦¹!¡³`öøw×ýž‡»j`ã(Û@´òòîƒ'‰øá8>’]2üÄYÙöÑb$»ÌQÍYå³$¡ã7J ÐrDðlxQûÇæô ÏúÉןÌý'¿xñw€åÖâ;³YÈ­s3?ë³’¦/[‹ÊÑÇã‰á­ägø·½â ‰¶à½¯Äzø%så\<¬£l·Å{Uí®¯uv–(0ºw·NÄŸ-_/yÿpÁÒþàÛ»ÇÓÖ}’éã7 WöVüvøhí±'ùÕ_$qÁs³j¾u¨­êÚQ‘ø³yHz$¹¹äãGÑÿ¸@ç“ì øn.l/r–=´êÖ³KÈÿøR¿Û¯ï¶vò ÄFvC€çx­}Ð5Eu=ù?V;ùØröì™b\W*Ì»<¶‘ó×Lñ*× ôò ¢Õ‘_ÉH:±à=¸Nf#Â@üÓ" AHRO•ö×Òç¹=c_í÷ÏžyiãÖ¦ˆpâjḮȺP®>í¨íí$ÏõlòŽ|'k…žÐͽ’¯Ì¯.r¨qÙ'PEdìü!óºGåïSY}öÌÙUoÞ[³qvíjÑ-[’½t±˜¤$öu°¦Óî•«¨W%³ä¼óDø¡ƒ¿¢ ·ÊçÑ‚ÂøyÚ“£„¬:ÿõ~wg‘=j_€LBšÂ#2i³ ߪ}?q,{sd~óÁSȾ‡ù]ð4c³ö4otèúkWkÞÈñþô]%3º~ŽÇ³_½PG0±^á%y—ï[Á–Ž‹ƒ ýƒU <fáe÷ Î?Ä|›×ø-í(ŽaN|HxJ>z Ÿc?¶ þ“9O[<~ƒé=êTݾ%ôìÛ·í¯\¹2>ýãá®hìIq ûì4M{PžL5|sásš$„øܽuýrß»>„ÿ¹ïÅÚæ˜ðlª´‘Ù·é-ëä­kÌ£K×Ú«Ž' ÏmÖ1¤â2òÔ^ù˜vÆoàá[ÙGÅ­ÄrŽWÅ¿·ã–éÏ’F\·go¸:±qàU.ó5¢÷3ñ[46ø¸ÿõ7_Å£¯'gFÖˆÑ5øÜSDÿ$êeS|öéùXªf_ЍeÄ fqnÜ)˃ÑVäò…ëmnŠDŠïµkW >"ÄÈ xКóg.”éÃïxïA Éw¿÷<ÙHqÕr×¹ó©lmQy÷Ïb”Z§» ³* hWJ± 뎂%DSPØ&9’³ SÁ( 2ã0\ÊÌÓþÞÁÙíllÁ× A}°û9Z^dkqâ1´¹HàFhתàTÅ)ûþs‚`WÊq¹¥ qᇟ]I¹Ü˜ŠÐ5Ö™¾1§š[¶¹1˜ÆÖzÚmþq_ËPnóìKóß½`h'sî lí)ñEûÅ-) ösZ”5¹N°äåzLkמÃËnÌUà߉ ›×f™CÇêñ„cöpmà÷\lBKLèlŒ;¦YBË/ÿ­ÿb`õ[¿ùO—Ë1â»£ÌøÝ]BÜÞHXHDÀKV•µåqÁû}D~늡ÝíïË7ku^àªg„Öš]F˳*w8´¹?øöÏOûõÛ7®,o½õ…øTŠõ-³l´h7¾u­Šò=µÌfîQ6 E/ŠýN<>Ë,Ž ~ÊÚŃy8= EŽ_‰+ÚÖß©ófkÆ;cè ¶-pKœ¨³FCpèn¯%&AÕ³ý„š5†Ï²µF©o>Ç” Éå  §ó ’Qƒ;µ%‚/ Ý7ž–Ð’…=Ɖ <ϤðìVž®Ùy VA+~ùø ñú» fm [ßoßþ$Å?јø†6—+œÀJPº ¹v4­IÛ{0»~3xϺkµžÁË ßäŠLÏ›`×i…s'Aç…îFA¨1ðcªtßY…ÿÁ?`LGPœã5:a<~\zëÄGÐj¨Ç­‹[°à•d^Ÿ|vmyÿBÇ<èy÷“KµûÜ»|å­7k¯¾*z—«¿Zè/$0©–†hœ‚hü ¯R.ù€ö­àÚ(™á74ƒ3Sê%Œcµßާð=e4ŸKöÊš<ÙX{£k…ßx›çRxîu´‚ÏžpÂÚMÞ88†¶<)Qj[ËrJNᨅs¿Íœd*g¯)Á’+´²"ë(‡“apÕÙo—/œ_Î;7Êž7-’[»¤þà{ÍkÇrî£lÙdˆ¾þæWZûõá%Wráço¾ùùÙ[üïŸÿ³ÿ5~^×€£g¢ÿÇË˧_ê ›Ï5ιÑ#T7ãi×R¬áIBdæ¨ðšD"QÂÙB2ÛWç<'™Óg–»Ï<àÓ,/Uº ‰ƒÛÁ~Ð>«>TVúGp½Æ0ì;ºCñûÿìïþÊò…/uùõü§»Ç‘ôW.,ÇÃç/½õÅô•}ËǸüÁ¿úýžG—¿òí_˜=ýཎ ¸M×b˜È€å´e¨:ãog4ŠrGæÆè‚•/šù|6s‹mà§Î÷ý49£³‡@^ elá“ ´^p‡#Â{"Þ “Ьÿ²5®ºØŽh^PÅóðxsbÄn ÷= tPZia}†×á[膱pÇ$ã9öã¥3 }ºTºR²`Îúã4ëgdp2ñèÑ*ÎK¤Ïf˜ö§Gí_^õåeï™Su“)a,ç·u᳂رéIp”°ÄÙñçhŠ£u [ÉA2qœUÝÃ豞'eñ·Š¦ÓC”vö›¼õL¼ÇYO›­Uk,†–öɉ—àÜz׌!ˆWµ¦—^9ÛÑGãg’µÂsâñ±àö„s‰!*{=ç]g¨“o‚aŒr{¬%¨±èìÁd¥jrm_sÒ"Ù1RÚsC´ˆŸÛÕ@xÏo§Ï‚u{Ç™¿Â·9Xé®M}`‘”˜äièáïŒìݳ³ÈOÕú»ŽLxrR0¼Öö‰ß‡tSíïK¯VyŸ,ÁÛC”‘?ŒvF6Yîs[/k~š<çèVE±ï^ÉJ«¬©J¨±½’µ6“_‡ÚÿO/—pÔs´Ú^[o7zMP¬ypvžÊ|óÍWg{Òè˜t`ºØ÷«.=Üуìx5õGy;ë|fŸÏöL"aº68C0:šà‚À±}ÄËü5‡ýÍiôˆ¡™^<4Ñ÷ðß5öéi È»ÓqßþêW—·¾øùöºÊÿd ¾VþøÃù½ô¶§OÔ©"åÓÏÂ¥m%n$K 7έ’NO— y/ãûrm»uû!o3£Mëd=Z$›ÍcÂá‚ßövhµ¿­S’Öèˆ]/íÂùË%®­Ù…¤ðmjÐ $g/a6lÉYô}ëÚÄɦ7O߮آÑ}»KÂgôWÒn02ßI’a#KØ9wî|2³6~w£A8¾µ ½S"Û ½tž‘Îz$žeŸíÞt­Î\¾1úÎÑ’"Ÿ„ªTw¿tf9Ù÷Æ¢_Ò1ÌÑÏ8p‚ ¸Ãesތϟ¯:¥¿uÄB×lÛ­Ñ&'¿Ý†'Äè¹ ŸKºµuíç¿õÕæ™¾Ô5îǾŸÒöÁz𕨮Œ¦ñúðôvɱê¬p.ß\Àÿu¼@ûg[ú\|tÏF÷ü¿ï<ºûд½‡g†ÜÓüœÕÊšÀ8Ýð„»éL—.^‰/ XÒÕ£‡èÊþs)¼EWèA–uH&:Í_@¿šnYM ©œfàÆjXÏŽ]´®dÙÎîjŽº(DÆË¶ði|Í}Â#ö¯dZvá¾ì+úß¹s–C%¤êH¾k9½~ñ—ÿ³º‚}3älÒŽÚ)™Ë1$œZǞȢ+æ¼ooF¦´4ý“/c‚%ûh`0;âÚ?íꟼûÿøß?m”Ÿüþi¯ŸþÍ¿¹ãÏrÍ¿¹úù»ÆD;èJBôFþ-°Z;dqü+›CŘ̓ùæ$õ˜2Ù¾·7ì×óÃÁ_z%=<¹ë¦½gò™\-cx0ûþzÑ?Çù&øeèÞÙÉ£WòHŸ#¯ È÷´&AIXÒ9m_:Æj‡‘Qæ0¼Í¾ã--eô2®¿ÍLuôáѪUßÑõÐøÓxUè?d@¿AUüà²Vð%ÛÉÖç6®õác;úÝㇳñW\#ÃØ¨…|[±{àü=Ë’ ^tŽxx UKRÁR:Dk~Ží+×#ãñ®Ák{¸&­ üƒçãx¥‚Ç£á³äËÃGâ1xŽ=0o{G9–Ço¯Ù7©i®> òmxP<šœ8²}wè73ïÚÆÞÐõ2~' ½{'hc3ZÓØ¤`¦=œ_užÛbè®`b?tu‘$#ÐI¸nMÌÎÑ»Ç n<žàUŽÿ|µ‡ *HåYæM×ÒÞ¯²Vº»w:5O¸f_­ÿ~þ >GóßF×Ov<ŽÈ¯ø¨#ùnƒøÌ^áñãglá{=3ÝUu;×þZÝ{–}1ŸÑÛlÍ_fÆã÷»·Ï'¹ µŒýlFWl݇;YÀŒFg™9®-]–½½3üå§±.?¬D¢o¬cïÙ×v7xG/“[º ¢ß6aÙ‘ÿ$”d ÐwáÚ,Ú{¸d¾ä(¼B)VÌ|nSÁ¥ñ¿ºÈšá ¤@Šëñªßû$çw ûè€|­÷{/Ù˾D…#«ÁbW…IðÅ‹ |Òø‚Ø£‡ð#5û8Gz4WrÝûl”Ö†F¬ck8?Ä ´çs¢»ÒÓ·?—i|ÝPéežŽÇnëZ:;BQ—&IÍpGW&/|âVzÓà}¾]ð"1COˆ=ÂýUî‚'ÝDZ¯=ŽZú²„K‡ušå“U¸@7LŽöÓåÁ¸ûzz‹š{а¹Zûté­ªd0=+ûÖØ`‡¿°1éQ|*l•×^ÿ\ÝÛÞœ¼\ÒÅþ’†%Àò«²!Ø'Mex¯" ÅY|¦K…uìÌǾ¶p|ñÑ0xñû€ÉO¾þ÷Ÿüäëßt>JëØ^@íAÊðˆ§ù÷ž=‰–û~ÙÖ‘ž-pg<étc݆ÇoÖc¦ËFÁfþ½Ý™uNÔÁÂs2uƒ ?á5ž‡¾¿ý×þzD«ˆëP¾®-%HêqóNò¦ûžä£d'þãÕ` ÿÛÞ±‰á.[VŒçYz° Xðx¸Y'ÅtüÀ3zŽø€ èæÖŽK, 8vy8c·É©Ç›Å7úïX[Ýéî¡£TB>BFây+/à_§d[{‡È>OIEÎ.··§ócñkß*.v3TnølÏÕå=¨gÝJÛßß ðgcù§À£–Ù|`=òRGhÝgou\"Zëâw…«Žd/ ]^,È1ñÄô;±0s0gÉ#ˆgÐ×üÌIôýìMk;žzöÍÖ€®-oæ«ü&¡¨µKÞ§aïâCÎǦS„£ï<+PR(›FwS~>…{§%¦—}¥ä”ëùù…NŸ:Qwä#3<§½âáKјáf-Øjþû Æ;_~£õ°_GÚ32E[xk;q¢„­ì'c€ƒ˜Î‘Æxp?â×^Ô7Û‡áÉ£1Þ$ u¿ýåÿuŒ š°w:Ò˜7›’/C¢|†3ÃAãJæÆËðž-Ée16ëôó¨$Pq#{ȇ£[J¨76ŠEn;â…û$5·þÅ£ð‹'uñË!3p¿ßÌk#ûלC£9…ÝhjK…Spj³ þZ¬’ ŠV÷»¿XØÎŽq£³Ý*~ –è¬æî÷ÒñÈC ‡ë`!éâüÝå÷~ï7&vôÖ[ŸÏ÷¾y)ncoên¾Y‡OJÞº~õÒ²]K$íˆßï½ Ä¹Zpì'yåóC¬Œ´ÓµCDzÒCTÔlI“¿¢í?|2ĸ;™7"TÈt¼–â{ &aP˜…Ê9JÝã‡JÙÆ$ïZxŠBætNèÌÎÏi qc¦ÚhØ™&˜ŽQyrîß^@iOÂ*¤@¤)ˆó³2eÅ«àÓö[ËÌQö¤31dÁ’{;¦ -âõÖs­VÍ'HO}­n¡s@(?Á2œc¬dW ¬mŒu=åM+bÙ#ÒÁ„Éà@c6{—³§´zT‘™rÓü(…v˜âÀxB\ÑA?Œ¤6»5iy7Šm¿e˜ËFÚÒ®™Žœ”X3„ŒÉF`Ÿ`˜³rò™³¯ôP„ˆ6L×S§¥Sc©„`@ƒ§À¹sꇴ_æ>?ÍÍoD¹=熽 S‰È!Þ³æ—::ŠóŽZÌ=ì2 .Ç =xDÉÙ&ʹ²™ yò4çW0§ˆÌYEÁôÍgk„Ù,†hFI á™ótç °ª"‹BÏŠâîpêPê9˜56ÎéC˜*ÓVõéPš ¼-œ…ÁèIíÑ­“cÌxÿûh#¦Ùƒ£=º {ÉÍCµîGUâïìY/G0Ú¦®u2GÁî¹^Þc}4íÆñœÉ|l®¯¹AÃâa%bÔ ¶,›Ce }zþJ4W%æŽÎ&¯2àq¸&˜/?ŸÀ×µZ\ìÏ‘*;ìj¸­å?§©¬»Qnƒ»$…];kÞ~o< 72^œ…H©ÐÖƒsñnB‰SŠÐ¼Ò^>íý[_Z3¿óÕÉ'kQ{ @–¯ýì·–ßÿýßiýeî”T0ikßÇ›µ[z8šNЂë=UÛ= O/\Ž-âé£Ínõ´:@<+S.¸8·hks¸Ï™P{Þæ?)êhØ8Ö?ônq Ã­;({ÑDôÌпQæð;p§¸p„àa²|ñ_´¾/åÍ0~U¶Ó8ö"гÈYÿ×Á— Ÿåö¡ M“˜¹N…º#È<W<‰rÌ1Ť ƒžÌ¿yNȱ¿àšµ¨3>}·6QGªã°á覄Ü1È7ªîØÖ~H{1­ÎO;ßÕÉBÀ·ò* eØ­"®U WS ð¢C) ÚHM€4›·3=ÁTâ“=ÀÏ)£Ð&û¶‡ÓÎk%'öHœè·Š8°½•FœŒL-dïÕšóŠÃ¨Kf”5•û·r^O€hfºhØ´ÎW»w'…;~éL,Æ‚™Ž®uþÑÿôßÿ?“»‡–/¼õµÁ±wßùþÀÿ¥—ߥ…bEoøö«Í£ xxBhJö«WÊŽ…Ï7‚ÍÉ'²K»Êœµo®;X0Žið`=èþeÁ[ ^¸¿èÞ vxÍÙëXÅŸ2)ü÷;1Ž *ÃZû7øÏêÄ!}ñ;¼/íÞWp¤yhñ$±îJYþû L–_•`àfð‡ÿtÜÉŽBFÉ|ÙxéDáß¾öŒ>Û¡xÁ*Ÿ<|ßàx‡[p$[pmNÙÍhöè“®Êï‘y&z$vr–N7 ™±=‹C®jLk½[@ŸyÔ<D·Ë¾§Ë¡'Fÿ°£'ã?‚'„ x ?Éeœã‚u#¸PS0x?ÃÓéœ(þ ]–Õy¬Úcmë-›‡Çï”±?D1^4%<¨¿9Ïä 9œNÁ ´&Y®<ð ²ÇãœmQèŒì7Ü›jøÆRñIþÐýbÊGžº°53¸ž8²\¸p¹¹Ä½mO“åHT¾ÚÓ=]ÔÏ–ž£ŠÜ&[Àì™–š HsáІ+Úµ‘#Úã™ÿú #>Ý u¯ù©ï±éœk[/hÍ|ˆ?8c6C8š#8¡Ý /r ±¬Ult`ÁèàÍW§‹mÙ:ðz‚P­É:-Ã/™ÚüèXöHû8ð=_E›Ø3ú`÷´ Êìa0ûѲ“¯;Ë`¯ed0_χ Æá-Ü Ç¼T=€‘ypl®)Ø/Á®÷ÄïÖ®Ÿ^4I}†®à•_tÐz,\L§¿-é\ZTçÀÆwr:qþ¼éî£ï7îðóYûZQ‡/XÓ®¹¾#© ²q˜z.ü fßì¡›dºØ®t39Àp]N¼rf²øÁcxaDàÏ}ýBk^=¥/bô]—Øñ·Ó®4æýxµMªþµÎW€ f¾AkP¾ŸaÏ\K¯Ù-)ª$˜ƒu“¯l8ªéŠ„&ötÝå’R._¹njÃsè£OŸ¦›äçàÜ–= Öª¦B©¹:.hð£çÁoÏ–tå踭[ÏæØo u“pdÓtºKœN&­Û>>¬ g?"<öJüUU#ÞLÁ6þgßöØP|xþèž`lÞö¼çÒ‘U˜ØG¸Ñn¯×öŽ­%¹ŠMuÛ²¯J›û”Ÿ*èoÆÓ~ø½ï/½óáò zº|åb:g<>ûæ—ÿî¾ü'ým[pŠ/'Ãþ]/8?x×@èýù–ÿ»nùsýÎúÏGМWØýŸ=•Fo¬ÄUøQ§¬ì±»ukä»»¯ý” çmÂÇäpz{_L÷öï@IDAT—ÄÞFÛí|÷µçá ûB .0ǯ¬§ñ†x@Ÿív•d5¢kºLy”¿c¨²ÏTëízå¥õؾ£[íëX%4xårUs:Û<ßýì _·WA¶&u*‹zVƒš ÇJNÏy¡mêF>·»Ñè©ô°ƒŽ Ôkm³cµƒW¡C¿ÉdÏñË·Ö7c Ápë¢ß g-y.`‰—Lo+PC¾¹ÎþN0©§‹·ñk4µá)Ž€AS*Ó¯^¹YõhgkfnÄSžÜXõbIÿäÅÈóæ$þ¢åöè[t„gE vœ~édò~µiŽÐO§_Ozã›ÇÐoïíãÉdÆ‘œäü–ì×›´šƒ¤¡ gö믿Þ5ŸÏ .7'É:>«åÏ0ÛHøÀŸA/ÃÖ-dzÀ+yÕµÆu–-œ»~5]&ûòõºG À9Vl*+ƒ‰Îr|…+ÝGìôkž£[“ŽSðOq=À(ö!µwk—…W^9›A7½>Ù$ n_.Ôm芤·Ž?²ëwóƒàë;ӇⓟÔiŒ/÷T í+~(Ð+ع?Y»µ*88^~étÅ,ù‹.²'uâ§„\›ß„á0§ú¼Â…³eè [뺈O µ]Cªj üO——ÏžLNœ˜½ ŸOQN2I‡Ô'ŸvŒhçÈù{Ž[hvW=ƦYMûÝZù!èõM'þ»Žu§‚žxíÉ]OŸ9Ú~å.ÈgÝ·½ðaŽÖúfÃ9K—gÏœŽ&ÒW’ôUzFð"aX€êT²ß1ºFè,K*Bºx!ß[@8~¼äûhŸÌïuLÝ›-V感¨z´§¹Dó1uÜëÍÎLÞÈN9Z€¥ý§ßÜÎg“³¶çø%Vt¹ê_6ŽÄmIPä$†·ÕÉ3uus†»‚=áÛçH8w«À>[]ŒLᬸʵôÐ2¨Ö½ï™ìä±oó]ëbÂ?´»yOG„æ¶-|ÝS—ƒ[ŸIf½½ ¾Ð³íƒžx éét ŸÉv4ÿ¾#e‰~¹Äe¾I– 7ÅÞÜz3ëߟÜÖIúZ~®«ùx'~ízºU ûСVùøø ð~_ºÃ$"”hàïm%§€;ЯŽôá_F x³X(?ƒÄ/$ÒHHÙ‘ÂwÏ׋‡zï¥#.^Eö’ðÒ$´€‘`9Z‘ìv°@ôá’Ntp}Rܳ·ÄÓð>÷ѱëyM ‹ÂMº"žëÁ“ÜÖü6ÞlïøºJÂíYˆÈ8ÝÅ$óJ¢Aßâ9hesóVÏ ÙÀ–*—¯#ú¾nx>ÿÕ£: ì|¶úuQx²ån< ÞU€•lÃCèÖà*þ×ôòï™Å@Â9ÅiS]_BÉÕŽæka›*þ6·{·%¦s‡P§ƒÑõYoý'·úÍG yã–­ÎN”áÃX•‰Û‰1ÝÛ(s%c˦ĺû¼ ]•¬­ìÚÕËŒ¾[[«´H0ß;û…ûAcOÎH¯`×rì·a 43 îµ›P˜ÞÚœ‚‹ ¤´É›Œ"Õ{%Œ@bÎ3 IöD+ôefíjCdh=ªåï•Í2 bÊ n(8ZùrÔR¤)ëO(a„(;gýöÆ9{*õ‡©†€Î   Qžî”%F1ŸŒ¸®Ù׿NIqvž*ßç>©USŸÏP5&´Å«œÃøˆüLy©xÀ¡y$ç†7ÎÜÖI0‚1§#„âìX«ñ eh5æ²?Fµ7Ø?ó¹sc©Îã±7 õ…,(4`HȯJÏñ‚£w6 „¶.̲Š:|is‰Ûî•ØÀåùÑÃ_Xffz¤Ð„ÁœF娛ŠÚj-áдJY+c#ôœ“M#DÏÐ8¶nÓz?œJYq6ï­ªÍUëòáÓ(%Áagø†_»²fÕj%—<åPfXq(Èt3ˆ$ˆï$—ØÛƒ”ƒnÉK4™6æêF”FqRÂ9bOfœ¢œ:½¿N ÒÆÜíLíàCA°îqŠ7¶VöâÁ³“ÇO´ímŒËš)j̤œ÷÷Ý2`ö–$°/<`©Òïòõ6ׯ½àõÓ}£Ø ~èQK·‹¿[‹åo4¯íË'—U;H»p©ì¼Ýå\¾"£¬õu$ÀæÃc9(ו g€_\vTÙÀØ8P0lk]!6:—‰rñ 8Œy=¸^[Ì‚ 9·•5¼«ùji²3¥iïÁŒÝk—cj×Ûÿ3Ëþð_.ßþ¥¿µ|þóo,ßûîí¶L»gÓÙA§BÏ{^”-ë[ü‡1Ívf&Þ³*›«ñmo8<Ï=Zíž8q4X”Xóemžì—”Ã"Üì3Ã!ºŒ.FX =o.ÇJ 8}úä¬Ëó8y%8¯]¹Úµ)#ºÒÇÚ¶ob( èrN•_ø(p4e ý; öoæ%Ù"ôê;»ä‹>?’ÃB‹™ hôÝÊ_£ÅöSÐuh1$ ùØÎ`âHˆ©fß­KPœ#—ñ1Ήøë8ÌáéŒÍ~Í$-É&ÄeöÅCoä@yá£3@Ÿã/Ü.Âkt¦-ñ‹gàc2¸ÜC9°w“ÑØsÐ*e–’ÖvÏžÛ ¼f…KÏš=_• 4(ˆõÂÁGðMFˆýö·ÀÒ>Ñ^“‰ªBÐðÝàñ ƒt{¼UEƒ¹í~¬ ¤D§>WE©‚ŒUòìkïžvÈÚ>]¸” ÎÍu{Æá‘Û:ó÷aJ3!>Aôð& /=³®#9G(è,žm‡6v&{c d1”'“5£D%U™u29„ÀÀY7Û¶‘÷Í)¾–öóε­Pdïè\ ð.fü£}-šçód0Àç§ïá7ß*q`cùþþnfrDvžÔçFEŠTûúÁûï,ÚNPXµy§°Rví)H5 !%gÄ Pm€ö„’êÐF ëþÎQM‘ÜñLwÚëù¯õ¿œÿG‹Öý „ á/£ö?𲞦\ÚK°~.Þˆ: Íøþ‹o}eZgýÓ_ý‡ËïþÖ?[^ãKÖUßû0ºÙ¶|ñ‹_J¼±|øÞ;Ë»ïüpœa_ÿú7ªø»°üñ}7ºÅ+Âáý v½K \Ümóxg…—§K4q"¼¸qƒaŸœèÆ V…?*ˆè7‚ñÃ?ãxÉfûÃÇŽçTÊ Ïáøy Z‡œŽ‡^¹zcZoOôG ô¿©Ú‰E¡:§¶Ç£[sl>ÍÙ‹¯ÈhÁ/856ãÃO[ãäú;™N¥¢¹Ódß+‘)°n¦M˜ÝM·âX܉f¶o»Zi]]îŒA)¨7•Ké_÷:¢„ófk í1¦ð+íãÔhœÝñ rˆ @÷Ö3¬5ÈÒÑü$G6¼ OÜ™c%÷t¬yšcwuj¬ÆIÌsÖŽQ%†ûê`Afy>‡¬qé„Ò/BÆ+r#É2ú2¾Žÿk‰æ{ø0 ·ñÝö,{äYs' ðJmf÷rn<팸ä-žÀÈ'£E…?d#y¤«yÂJ#K‚…Äú™£5ÈpràÉ3ºG¼¬êPÆÚ§Jì\»JÅ›ÛK c«ƒªõ4-A'IY9/ð08ËŽoÂ-N 4>XÕ³œÝc´ƒE× L\ßz_8˜YJ?æ´ÆƒÉ‚gSµ‡¿ênb ?°[mÕ©D—A[SiαÜ/k[0¶ßdFÓ™—œ»“p·Îtžö†0‰Ýá_+hÏzÛºÖ—‡ô4|¹=wÅð‰&ïø[ÇÚr²+ƒ':ÉÝÏ+ñÚ'`Se wÐ’öƒô2m u'aÜs6ƒ^BWë¢h±1 6í祹ƒÙZéäzά&Ý}OzÆÐacp†‚dhsvÍ8,uÞ)éoäZ{J_’l½»Í#ÓÑ»#žÐÊšð¯ŠîÉÈÉ(øžísð·^N‰V9ûkL<‰£†Ó¯hùÁ0¾.šÇ‰ìÈ÷»¿å y`ÿy]ˆÎ²érL¡Y2U%žÖ’œZ+Olþ€Úµƒ’àÞÞѬ¶fKÀyß©ÐXíùÖ½ƒ“ÄYŒ—.® [[ØÍóÚv¸<»ßój~x$¸Ã·€9pL˜=ï3k›öý£;²Á±p¡gHdØU¢îÞ…v¥kl¢µÓþ/êYýÌÌ¢ªZÀÜ?Á¾¿\Õ7>Â>vÜØ±ttkÀvE’­ìíÚýI¦…û ”×ÝêHŽ©ŽÈÎp$Ò$ä„ãÓ¹$ eOÛóZOk|tƒoøúÓœ¨ÆS©p¢ÀH›½˜®Öþìh|Õèýͱ|¤b‰«œ¡»vÔÞ4H¦úõI·*ÉÿuxÂûÁ¯åÒ¯èÎ¥ó6x¼-¹Ý}ðÎÁßé™ä¤ò÷8-ƒ-½’0þ´dHë;(˜pX=JÆ]ª{à¿úç¿•nµùò׿.§;ù+í¯,ßøêצ[È8à’-/ªÏ×ÍÿSþoüÖo{;óyqÕ ”yñ÷Ÿ÷oó1/¼×¿™ ¿ÿÜ&FK ”ð=𓘙<ÁÓçóò·½¦Á3òa‰½ß̯Fg¢³µæóÇkÒ#ȸ(zð‰ŽŒÓZw’#^3 œá~‹ÎVÙ³C@ã~xâ‹= ræäåVzÂÓŽ¹›ƒ·`ó•Æ0÷'‰zV<&GjÏ^ƒÚÖŠG{9j9,OdßÛ'¨N6&›žÜ-Iöa¶áZ)‹F8Mñq{‚O˜÷Tùµ6ü“Nbñ$F莡Q¾.׬òä¹×µè==¬âëfÎÜÙ‡K²‘ì-IgË“xNóë—ê$¸½£¿š8ÙMF>v›B#¬xºÞ7‰Ú©Cpíúƒ:\Ì/ÖlâðË¥l݇eªªCXÏ}]‡«Ñ‹¼| '¸1É^æú\‡]y®}_õEò\'ÎùTZb?ùÓÛ¯]¾·\Éßjé:ûx¼°›1zîØµý†GÃ_û¾Ë—S‡ Tï BŶ(fzCû¶¥ÀÝèníÃhôL~r ƒŸ„‰Þ“-ÛK‚¥ß‘ÙOª®–,ëˆÄ#Ç:ëõ@6t×ÝîüáKñÍtÞ•O´Ïáò£xų| Yû#l< ®(tÑ!t3™L:uúàròˆ ôüYu®»~#Þ&ž_FW"/2\$»pÖg`ÄïèÙx: kn~³Oæ“yVç†ãu =|m5÷ðd©°ãâGkE¼ 7Ã#ZÞ fž+±D¢—êçéÈÔºÐ@ZBð®ST6ø£|rŽrp4T&z2T»øà@7è~xO7€kãûyRàº÷ä?˜ögûI¿&Gê|•ãó¨3ÿ^:աާª­è£;Ëòñg·–óÑö@'  ôd¾|¨M=½<}XQÙvA 6Q‰Š}/qsWöÏa ju3yp/L¯k¤ã!ù w÷½f{ð=Ñó¶Üj=‚í“|…g4º‹µi ·èкKúîIóZ}P+¨¢„gÖæš$Vã”´¼?XÇ~Ö½u¼v¾VÑñ®ŠÐšçÞ©*}ø½}µèpM÷x©xÁìL0”8·m_Ç,F {V•½ì.¡¿¤ûe•t xä†ÕFló›¯õ86rtìðdxPĵVöÉ®{~IKj<Ò¸üwŸ~òÁr»sŸùAûzb ‚¦hMbЬp‹@æ8Äôzp÷X¿ù0ÙØ*séŠ3ø÷yõÈ@4/6¥jóÍxc ÌnO×IW×]sß]•‡Â_$þèÔ°«Ä0úÇÆ†äĤÝ8Б ûÕr»õ ¥·°©Ø²py^éµ›ñ±ÃÇ; »€¤³Ì¯´žI ‹†d“¢ßçìsp^k{sCgì ¸<ú|¾÷ILnž«mBO•²&Œ€;œÆgw6vžu÷nÕê²Éû›\Ù“¯âÌéøLûu%»ØÂáÖ£G·—·ßzk‘$ôɹsc³à…ìKú£d¡ãüÃ=ÿFñ cÝéXUIJ8é{ i­Kç¹ôX>/> IYsf{ëS`ô"AÊÍU ]";ž!é—`ÚM÷Œ¶`ÖKNHÆ·l%x«€ŠON)¨³bip‡{€Êï+áO¸Û>ÓWThÓÙ­g³îX^œ$ŠßëžéD½íí½NP'N(hÔ9bI¶§V~ÑçºÑÐmñ8ÕÌwnUeÞø'Nžœµ:ùÞ}…t«Îf_ÕÜ«Žªí¶½Ãx–õäK¯/¯¿ùùŽCyy*žõ{¯gß,NÞôªá%ÙwÖ}¾ã:oéJ#»¦ ú…Â'6£Ÿvþ`Þ`ƒ[ îã]‡²'Ž;Z ¨ÎÞçÏg[¥O×mÃÉMô.ùÎókI*!Ø™{š“DÉõ:´y©âÂp9?ß¹ŽkÇOëïøëø ñ¢Un)LÅÃñ1ð= ~’ð‰Ð|¦pÐà5™µ& @;þ\~$ñNÅQa}p6wÇÖêXú¢ˆëQ<îÓÓàˆçŒÏEŒ û8…IÒ9½M—%>kûƒW€¥Ó ö$Œ\KëÛíŸ"=Uíº¦8ªãîƒè,˜Ð•tað«RŸ}޾¬yg>¿À9~…Óô\tµ{×áøA<ú±äûdG×Âw²Ù\Äwä(xÀ§?|¿nÈÅ žÖ%÷~‰<ÁÞ{VnUè7>«0<|±¥m[LÌ|û‰±8Zª>Wqã|Áo¥ ìlCk•PÀêa„=&€¡m1íÚ´kyqñÂùœÎ0ÊÉݳvH­¾öîr‚˜ gƒ`í®²n´'x!RôÓ:Ÿ#¢*<™ânè€+ëÒëñã²R"‡¿ÛJ ˜»J?’Â5Yv²„§ŠÂk)$2yÆH´÷½°ùËö~,##„æÈ…”û"t­[®\)0—ó€ »X¶íß|¹Ïk“w–óž /+>©–M÷NN4J3 ÑB>e¸Ÿ£=˹ÏB„û%<îžS§{Hp¤Èb>¤”õxy–µ3–)sX9ç !Ž ŽTN-÷C ¿ –qHÇ„KŸ• K1¡Ä`b{cˆûrΗS/½"•½™ »^ ÖÍœ·=a•7ƼtþÓåø¾7ŸHh Îi/$pÌÁ¬S/(‚ßo,N”¼=ƒàœÖ=›1(¹€Øâdâthâ­1ê lGS¥Ó=˜;$â¢ØfN5I«’Gȉ‰ ü©Ø%ÔóôQlšWÃw¯½‹°Á!©£À$5”)82 oÿL cŒ»gPL$1BcÀõŽ¢"yÃù *—½}&xLE[[ç(í˜v!¸¶­–7ôŠHyeF­í˜7Í`U(µ·ï¨1煮OñÞV…Äö’WàÏÁðRȧ ïm[ËÞÜ™×?HX4C®$8*ûq8É!fM‚›hš³¯ä ÷Q*ŒÅ"8t¼àô ø$åÜK ÜkßÖ¬) àŠ Òê8áÔ$h ²õhÑ®)Y/zèþhHøüˆ‚iÝhŽÛÚÛ;šCÒÌJÝ.îwDK  Œï ÷CÁU‘èÍ8€ƒ…$§ ´÷T20âYœ¢x!Ü„¿2ÄO„ž'´&ÀÑ4ZƒïpØo¸ã3{ „9m ÚæÏÈzÌqÚ¸ž©’WvªÕ2Ž&a¥ßxbê²5|KrO¤@ÊX–ÍLa #`{ŠÆŸ„†4Yʨ³O)¶Ž0 ­H¸o­‡óôÅ~¬ò˜eé,tEG‰ue:ß ;68¨÷Ôðóc½ùwÏîðSUÈ8Ú{gMöoø\ÁÓðËуÎ=ÕðÍÏix—w_öÏ<ÙžÍ!¾2Ò#wüoGŽç ¼ ìÂküµ5£GC;‚#06^´Ü>9™¦gìZÊIÏÖì3td/ÙolÉÑ·ãçÇŽìͶà{DÃßÛ~vW=åa[r¤9Ù^ŒÝÜ:è™AÛãåœã`[gü [ :lãÀ1~zÎãtjóY÷Ÿ}¾î¼ãä|À„~ ÿí?Á tó³§%{ýöoÿÖè6ãäîžÕéýá1ѨÊ#{ëh†q.f²p°íÍ®¡§ÞÌÙî9øÙ¡>Sõƒ~US´‚ž­ºŽÌ+q¯ç±Ý·µFv¿^Çr‰cÇðÍwÞùÁòö׿6çÏþü_ý…å+_ýùZ»\¸ñ%¼ÏÞ?º¶7Ú ¬fßÚ"m ¼«~ñ§]ýçÿ™iz>4øÐ{ÊrþÃN°ÁÆoÓS¶wðXUØý»¡Ñ~ÞK××å„/d p?ªºéæ$Á{²íh/ßþü[ÃÛz·=D;«M5ÕI=+®6<ž ¯îgKjž ªEã—=Ë>sìàÇŸ^X~ø£——÷Ü_þÓ_zkùôƒ[ËŠ 8+¯^ÞX¾ó‡2[&™>°ƒÙ ÙSzé2c ½ž$óÐê³nï—ßl÷–Þ¹”nÍüüµßÿpyã//ÿéßNïhNºº±b IÏÕéòÑΕ—‘kûÆw^à†ïþõ{™,ðÒ}d[Œ†î“tÑï~÷åí7Žv„×é J8ݽÿÑ峫ñ¯ÖdCðt9I2=ŠœF“pzd[Kö7þóÒÑÝËçsï}öþ¥é`I—ýñ'×–õ½ Ëÿå—~±Â•W›I<£ëÍuwäx„³“z¸œ}õÍá¯Ö0:c<’>¡‹çýë½|ôѹ嗾õæ²ãñáü”W£û’mbò?zïÃÎÓ-É1ˆ?ƒ!š['ë¾€ÅØÇÑä0[ÿàÉæýæ™§Ëg?¾¸\Ê÷IÏùàÓ›ËoþÁùå«_ýjÕÆ§ã?ôïxGx«š—Àå‹—£¥m%,UÝ0¤/œ¥ ¬¶ú÷ðãå‡uµøæÏœ\ön=³œ¿é+‚h.|º|øIÕbéQh¤“”΀htü‰¶ptŒUgñ\þŇ~ñåʧ­[‰b°*Šÿño¼[ îØò7þæß Ö«|ôlwˆØ'0‡WïK‡š}NGž¤¶äöàLc\½vgùƒïüQCß[þö_{syxuµIØðŸÆÙ– 2‰Áýðyö¼ü¦Óêuœ6F@ák_>¹¼tìþòÎ¥ôy¾‚|+ïýèÂò÷n,ßøúÏ,§_ysx|ü 6´?ĵºÝ‘Çó%ókÑÓ]gOW»zkç5_\¾ÿλutËWu'ýüi óéµäíÅO?ZÞû°@E“"oÝC˜Ø»xù†'åãÿê:6ÖdçW¾p¬˜——KÍûØÑdT:à÷þð“ºDîÿÛ™Úxo Öt!vîjë6†Aúƒ:0}8t> k‰¦ô¾\N7: š#{ÙèLÇÍïÕ)åa*¿úùcËæõâqO[ëáåÀÎ;Ë?ÿÍ–gª*ñ†YÍ(Ô½MObL¶šïÉpеïÛ»oßÎcËå.ü<~òHsݳüëßyw¹Ñѯ¿zvÙW€ËÖ²]Üè¾Yg‹P¨Çß³#ûÏçžíAþž–:ãx3Ýyúîis˜¢wýø½wGWCb*W¯\ήÈß×ßËGn:lYÃè“ãè¤5Ü §Øg»ƒ)]‰®†‡ÂÉÙ_cÎ÷¿?ËkÈóýJ'ŠÖÚˆÙCg];j ~²£UÌóEí9plô:í˜7«X_“B£™lvW±‰ñϪU±>IñcºŸ¡$ÇgôæôN6«à:¼î†á›|ïì”çüÿ—@?@Öžà•ürl.IQbR» NaJþ„IÆ^ÄBàãó²éWº 9Ëý’ƒàÆ7Œv¥*rSŽ&%;ÓùxEo+¼}çñÇÁÇgƒÒ9çù Ú°I„WÅÜÃgá‚®ÌÅáƒà/ýÊ^“éþà;Yy:Sµ×ôgò¶é5o~½zÑ&ƒù±ðÛ"çáøîðãa11ðçCu3}X*ÿéÝ»Í!y°új³["I2dE²¯#Uó±€¹˜X «“òÆrýÚÕ rÆížÃ ïSÐUòóVè˜:6hÎll¾ó{ù~v?¾0ºät¸+näïÐMï|œL´·xÚÞ¹žüìÿ»jál ² ck^¾x~àxDkù|LÛ·E¯ÇNà-hɬŸ¿‡]Á_cOLô`çÙz¦nvþ±ö÷P1Pßó™¡U…0ì²Èúßù(O®’¾¸ß“Íã£WHò9U»y…ºŸt$Öé`ˆvùè';ƒëèa }þܹÙÇ©În`Ï/æh/ÇMé(?ãMl¯=œ„ôþ®æ¡ óxt|fm󎦂HñòۧZÝÂ<àñîbV ¢®žg Ø/:]÷ÕÈ\>ó}&Y+¸MstÒ+< o%Ãù²t×5 :Ô•ýïÝ•¾ôˆÿ^UúêÓnäž·Îs´·B1öðÅy¶ç×:R2‡àú³øÊÌ1þúäq4™ÜA;Ž;\çž Û\h üF«>Œ6@žÒ‡éa»JVõz±° ²»â¶]%Ôo¶Þ+urÞ{hgº‹'›Þ|íÕZœ6Hºs×åB„â@ym‡8ãžæ¨¹òÙ‡“5DñtVÞ6µ­4D¦(i-H© MÐ?Æ7ረ zˆ®E˜M&ã@FÀ˜)&p«ÀÓœG&S:wVõ+€Îq®íŒ¶âPe # á3v.d‡Ìç5 P `jà%Pµ» ù˜0D þ1(Y9ôÂWEª ãý½}°|ù­ž$·À™u±Ê$gþÜ-ûì¨êò '’ §Þ „k›#[FP–ÓÇ÷7û=II¹‚¸nW»akÆ/áÆ)Ap&`“‘d/‚÷dq„˜‡œXNœ}uyåµ7–×Þüâò½?øíåÒ¹÷æÜ‡-)\ßø¹¯ÕÒÿ–¿žÂÿ,¦€© fÈ|Ù¿ûàÌK•â~2­Î)"e›Ï0'œ®~öÉÌe54è)˜qÎ s &/iö…sÕþq|JLØZ–‰`ÐÓÖã‡s‡ò#ÉÅ/Ɖs`^pÅs]³•y²àŒÁ­ÁAŒ™Ð…ÆwÃÙko+§xQúϬ ÍÝ<ÌÖµ˜î'áC¼!|g|5ÕæÛ~··Ýö˜”lÔíÝh\Y©pÉÚìÿTà7¦vÒèúqÙ/„`â#içQT}^*t<±g”h1Œ4˜óJÊþÞ#; ­ž'`ڈ˅Ë7§â—Â"€¦ZA†‘ó›÷…¯ª¶óÕEkç€ÛÑøÇŸ^^¾tôl4.9  g«ƒÁw‹©Ù‹p;†£²ðv̸:{¬àHûŠ–8T ÔQÆeü|OÞ^Þù£ï,g^ÿârìäÙö§ë̳ù#! càkÕœ0G‹ö¤q»<Ü!´ÅÀÜ^?kæökp¥ß.á½*°mX×Pƒ›¯ ø¹·(¶ÎÍ œø¡$r˜£×yN ‡±82ÃWc‚Û–-*fÌ#Aîè~ä_)~„àÓZ–Ìkp9ñÔ¡³Õø\ìÚq¢'ÈÁ-Ñ4¼G¢šôêöáɂָÅŸÁÌ( ˆáaª·¦ÝVë}’\àäe(3h¬—2aýc³ ©XÏŸ5¬Ïy˜c¨Eγ]BÞ“<Ó<Ìé@g÷Lk•‚q`È™ pí…0„ ~²BE)^  ²vÊöcF]k/Ië œÂWÕ=/OÛãñ ~<ÓYJÝ:BÊ’¨‹¬€#)fœ2ã÷87úmÌŠb»¥±$FÜ©8åè;p„_øŒÀûàGüœ³FËчâãÑĵçd‚6>-¨¹R@àbNŒ: mªQñ–]eî“2ÄÉ!òZ Oå¹ »¤1Ä“e,¾¨HŸÀ\ÏÔúNFÀÍ*å¶m]qaœÙö½!;´„•ÄFᆿ7ßù1pU§Êœ`š,KÊ­lzŸÉDÕöþÞ=Êq Rø ¸µ6Kæ:iö1ûáÕRžoÂxño;qÿ"Ö –±ÍѪKràß=sF]"yì¹qqxù­_ÿÕöê^8wtùð×?ø Í‹>ø|=\»U•oYî‚‚pj#ye:Ú8ât¶Á¥èYò›Ó@Û;AÔÝá6ºAÏd¶`žÇ­Ü7¿õÍœ¿—–ßû_ƒ—±+ €Ç>”¼‡&xسèmøJãN =zÚšì$÷öç4 ÇÒ´»n {æ£úñhú³³É𠜜št]N °Ï%]jm¯#Ä£œ×®äxHá—ÊÀ¼…‚×w“qÀaH1zr‡ç•„´¥¹“ów{¿=ýëHYï‡$³%C$€)ÝÞ¾èè aC5.>·=Ø{&>í÷$3^žÎ.+Aä— Ü“w3šo^ïÈ”îÁ‹Ûrú˜6ù;–s—nNÅ-ÔxõWkx&üJnæ@–¹-iÌÙdÊý’•úåϽ^ GË…ìÌÁ ?à¤ö¹æ&XA—ùÐ5>›mI_èÂp¦•vý8æZÆaß9X¦{Oø@Þym o§ƒWtÎù`휢Žqâtà”.A={Φë7øÛßþÆ8ln–Låè¥s9Øäß$·‘7i<2ç¯7_º©dÒÝÙ}lºÃÁpu$Æg—^C—  Ñ9¦!„tør÷pŠî5o°_ðí[ è|À}­¦ßúìY°ÛRüœ³îÒ½ÌÑ>ñœl·sÆLõLßZÖ¡bl=ðŠŸûØñ£­MÉX]ù†9Kö2/øa5Žü™ þÏ.ÏÞX‚D¸ÀÉùJIŽ×êXõ»¿ý/jÉ{&+Æž¢I-üéòºvÑe9ù=$¨mkm°¶²à8:(œ{’I»£-gÚ‚5^¸%¾hÍxíŽüì.<žÁs>9w~äÇ+o~nù+9 ßúÒ–× È¾ýö—ÆžK3žfü0¸qV;¼¡ꋎ?àËÈÓàô‚gôÕ_Ê× Úûó˜àª‰UG ¼Âm0¢k UÕ6ׯ\œMcÄÉÀpôfç]ogûÇcɼµ6æÐå´oödzp`’”Ãñ5…Ï*V}Æi÷ޱnÑðÅ€N½+ø¹VÆŽ]üÑûWâ=lÍh»{ïçˆÝ½ý³??8ûïüËn_“n‡?…Ç#!ð¸üÎH½›ðáë>Õ¼Ï]泫’½ ùÝÔ}®Äø››8:Aó›ùíšvÏZuvòŽºTÐéЊÿÊ–lÏ!³,ŸäwØ…ñ óþî×ð~°f3^¾ó`9‘¾ùñ¹ÖVˆÏîaNÒ»±†{xW…cçyF2Ӹ׃Ì)2Z5 ï ±'gñÇŸ]^œ-»¹ÜÜ{7ý¡ß›Ë¥«Ù,ÍïÀ!²£çDO³7½Ç×îwý®]|Š}×óñ6<Òˆ«ŠÓ¾ç=,¸vµ–ÙÁOK×iIZ`i£=¹ÕÄv…Wä1œ¼é~/ô+Ñæ…η~Öð‹dÁ?¹k`Ò5§ àGËÅœÈgKšˆ£Ž¬Bÿ*añ(rT‚ÎÝû¦ƒE~;þG°€Çt\àâsºZwó×js|£#->ølyXå ðýôœÆ{ïFÇ Ìæ3ÏÖÏ&W-I5c›×*÷â¬è†lôóéB÷ïtÌMúºBª»ùÍ®4÷+·.,ßâ¤ë~•ÜæbÿÀœý5ÉN1AÓ`€nØ5dÈ X  ßè8ÎKUîî(—ké©k’ùäÓÍåjöÍXõ¢À?;ÞÐëÌÑè2‰¡ÏøáØ“ô²»éWk_ÿ [–¯óðÃãÁüáòÙ•ÛËç[Ÿ6=ƒþ .lnˆ1Ç_ÒÿgWyç¸%Þ=ÒÁ°«ñ–Î]\¾÷îù‚9¯-W¯^-á÷zöFE,µ;¿ž,¿.:RÁSùvéºÁ)ÖÚožÎ,K ï·l¡„³Íõ^ðtÆ1¿ù;Úwï^®T¥x§‚’/&gÈGp ›øÞøkÈ"°]4à¬{ãêÛµ ¡pS«v-}·ïƒK­Æºƒá ›ùÑf²¯xÀoÿËfƒ͇¸m¹–®EfêfµQPæJvCm4C’p›.šŽ¨+œÜ›^äÌmlÏ7öÌ^÷êH²“/èògã5æC>îȆ£§© æ“UÄøÃï?Ú¨ÒõäËéV—êªp{*3ùU3£?¶/]Z÷ÊÃ% ŠmlÜ ÞÁ}·@Rû'1e0§ké¿æ×Î ¾3û¹ÿÈ©é–ð`£JÞöv*ãóqîÙw4XÇï«ÐÔ^y:I„‡:Õâ_»öç.ð:£e@}´E»‚©Nh÷*®”ÄJ'–€Vè\7³ñÂý‡*â¼^‡âËù%4>ÍÆ.‘0›Ÿ$90H·¼xT?ü‡º´€Ú"#À~G›ôR„ ôm:V*ü<Ó5x ~9:Wp ÍtFæËu^¶Dö3Ï;”Íu½#¦èÂ?óå/OÒµ¸ÔTæöººù;‚ ÆO½Â ¯zÿÝhÇ÷‡ãOd;»w¬ˆkÙIôW±°z)»OÛp:7:5' kl,h¤K>6² »Ä¼:Oðcô|.U{ëÐñý6W]£éÌŠYøþT¿ã'cO¶7ö._Mÿôl±!º.?#ÛßÜøãŰ]ùHøcÆ_›½|¿J/vß•+ñÏ‚ðl9gƒ œãû]:tÙ£‡wÚ;,€à Y0úeûb.p~øäàxò5þvéÂo9ßúP¸ƒVÑÔa6r ‹Oã·n®I9ŽjcG.ø§nfC+k7 ¶Ó Å”T ³¯Ä:$Q°ÿñw>Qû,Ð{?Ù†|3tðmÙ;â£[ÛºL„W“伨*’ØÐÃá:/³•Ù>tô=uÛµæ5¡6û ?l½ü«x;¶‰c³\ WðHó`ט/„°o:θƒë!}s{/›Ýq†Æp|m8’\Þ®Ù ×ÉEVèÍ\wç«BaÃÛÃ;v x™y¡üÖÆçŸCëüYb.ÂÉÊÏÓw‚™²ÙgbeÖ¹¿Ø¬ø¬ù9ÞèFIÂã—òwÂv’¡ÛƒÍ®¹u#Ó}dz†Ï áXPôÍ[ás^ùß4¡¡-ñ¶½ñš?üpù᾿|ík_Ï{}ùåÿüï,½÷Ýì­ƒéc{'ït5?U‡öan†ó‹(eâlM …¼˜ #BkŠ®– ìQNCâ–­B`˜JD³ø›WH´³ª_É W/¼³|þg¾™ƒéÈ0b¦ý;¯ýÔËËÛ_ûV‚èØòë¿ú?†´—ÿæWþo!Ìg Ëÿ÷ÿó–¾ÿkËé³_lL•¿9l3æe¸`Û"@Œœ@x–Õsò̫Ϳv›á G,%#y%-ya`ùS|¬£éްwæüàœ7& ÷€w–¹–gc%˜`÷ÇßùÃåìë/-ëjvp‚òþ4åÑàí[Ù˜%ng}ö´ùb„S-Úš PBÙ™“*[B ^Í9TÝC0lsNXóÁr²Ushʸ!ª0i•ÊprR)ݰ‘²=ç¶ì^Ù˜ç…:Ãd9?ÌOÕØ(|±™¢ß8jv”M[½p­Pb *)†™­†˜}Šz–÷:/jß‘[e¼ ‡ (o–!W«ØÛ[¬e¶3² Âáâ!Œrë‘ÕÍåK_ø\Œ¢s†S°Ö úöÝZ‘W¡ü8<#ŠÅ¾Ä[ª4Ülmª½Ž–q\×µqîüàû´|ýç¾9™xàíøgÿdW:¢ó½Ã9›x»6Ï;ŸÍ˜ H <–<²½ìU-Ê:Wí—ÿêWÓÙÍ ¦líµq?žbÍ øÛ[ö!Á¹Ý•ÈóñG.Ow^œËõé¹sË'ŸÝXÞì<2ÁyÙE£DS±ÖLSÖþìH=M‰ƒ/`‰¡S®íç–2¸ÆÛþŒ“¥k&©%ålŒ9ûØZSÏLa¶©”ëÖkÿà‰×TÕ…T§Fœ.‘i®5—9/ aüÝÿ8XO5R8¦-’l.û=Fpû3JP8jŒ ¼#å{BTû[óÓâF@ÒœîÁÓÖÖÚÔŽ‘Þ<èñi†·Àú5_°5ßÐu¥­&ŽN9è¶–¼b¾Ö‘ؖ¶¦Æìú u¿dô4° #Ð ÒTÆÌ m§îöVBCe’czþ–œœKx …`‚ªñsí[œ·{ %TОQÖúšH?œåמ`] ÷ƒ­ä+ÁÆsÄÀ[E;µ?PÂß½Ú¼ÙMûOá]žGQ$ˆµ|2ŸÎS•æ*BÇéüñù¹¶õ>áÙŒ»'UÊÚüÆy¤x(<À‡®çdpÎðÏ~ííî[Çf¸RÎ%IÉ®Ÿ6œínµZ²›ËÍ[¯³Z?_­Je³÷Î>S loô=9b?qݱyj(äÍÞÚZs;‚oá ÛÚ$øpP= )7öØÞj-dï´£RIjGß¾¡ýà¥-5¯gé¬3ç"QΜ酶n\»8üòqrÎöŒàäƒï ¦kÖí¾Î°{©€ãgÐó/ý ¯S¥Oܸz¥¬âSÃÿÿÂ'ŽGãM$iógžÙå…nà׉S/-¿ø·þÞò¿ö«eÑþþò³ßø¥IŠüýßú'Ë·ÿêߨRât¸ý`ùü¾XKóWkËødùΧv—9tgâ›9*†žkw¥ÎH²FÑÎðòq2@0læM9 F0F!ãý(˜ËpC³Ö:épж¼,¡ûsFÙo¼9¸{OÒe Ã?­çÉëg›ñÚäÖ˜œ­®€à‰ä5ç'žˆf @€ßºžq1ü QÁ ~ñr­‚£ç à GYƒpÝA }µrW/×m¡m˜éD¯tF¨¬hŽ–ÑÜå°d(~Ü|¦‚¢q$’Ȫfhɼ§‘;“Ýõk`<>üþ7òÎ3ÆÒ,-ÌçÞJ·ªnåœCwuW§Éygvwƒaa ‰Ø’%û—e[XòKöO[–08` Ë ³;f'îLO÷t®®®œsÎåçy¿®5ÂBbaÍâÎTWÝ{¿ï|ç¼çÍé¸ÃT§ÈeD>ã3d/|N½5æËX¶ô•öÔA /ÁG`e‡¤MôŠm*û÷Ùÿ {Ì®Œ “Î (˜a€õÆHâZtÂÑã1t9ƒ¶:Úl3nÇŒõì åzWAI~×3Ž ‹T<Éκmb#8Uñ@¼g93ª@ÿT>Ê¿¤;W“Œ„_ðž© ²åü”çÙw.©ìËÚæÀgMÚ‘éð«ï¬÷ìö!ŽŽC<# çÇû¬^ª]%3 +¿u¦D…9øâùÑÊ à¨dr¡÷B%êIÚÊëó½]1_«£KƒØü<›¶]9d>óµc‘¸pÂs6°g<6Jd-‡×H:‡Øñðàâwò' œ»?ÚcêÜÊSñÁuKóvLñ3uˆà|ÆÿäŽ+ÃàÓÀMžPa!¾ð9ÊqÏ÷&0øÞ@ßA òÐõ±~“!t *gÅÃ}Æ$§*=ýì38³î#›‡ï¦»#ãèØuëÌ£9^~–3Î øh·qPù.ÞFâ ´¡q.=l#£ú”vÒ6ß•–šQ¯³“ç3¦Ÿï˜¥î'ŠÈ·kW‡‘{BpÔŽNÆ<]¸¸ä±8&1¨»êôr­è÷78+®xmàeüFOá;×-,åjsÖà;áèçutiQ2ä³ïßa?Ñ«Ÿ r&¸?Æ9\ÃQGt«?z †²WxË#²,%÷zç?yÈ¡ç~>vâ0•ÀiÑ_u6ñB\PghikŠ#jÀã¥ÅUpüĦÝ"p¢]%®öô÷„ì¯#©N¸K‹üOu+a ò×]¤‡àÞé Éx>S<ö9ˆ_aCÁçy@üx޼N9õ1u¹Ú‹K jy—*@IDATÚ‡‘¼ÉuL%^Úáa¿ôÙc÷NþÂ:؃2ì‰*ºÔùtN¹·hçäñ58c{‚§¤[A‡Êºx¤$©›ÏÚC¾4ЩKZÑ¡ Ȳüa,wQ}Å}ìè¥Z 'ä*ãmô¾mB px÷Í7ÓKŸý,¾ŒF®vK¥w]š&.ˆ[ê]Ù nÊzåÚ‰GȳÀ[ƒ“É;åûÞÇgï,ÀñÆïtfe§v€Éj¯<÷\T‘NMNÑ™žzáÅø­>eâ‚#ñX¹VƤ´iPÒbŸu2Ê»«?ÔË5ù#*äÚû éÿŸÀWÆÿSÌùÿ뀸¯ÌªÂ·´¿³¾‚CȈk:鳤3œÕè9iD+›j0ÑÀbÄW6Öã·ééî&iÊÄ]pbS»wâŽ8<ؽgcÓø\\‘Oƒ¾W®Wþtf—{TJ‰C³‹ éÁƒñ ­«ƒ·T—¤U ALfËÁwžñåxÆ{o! þéÐddñ-‡ž¯Þ;0˜þößù»ôùï¿úËéwÞMŸ~¦7õSkëäÅu:º0=Ï3–?+{äf:]“?â’¬A9îßò3Y¥”žqNLå¥eÊï•ïL¾ "ã•ÝTMÌã«ÃÎ9ßU ]d– ïÇ<š8lG÷DÛÌ›8âË= ûZsOV$V¢ÃYÝ»IËû‹i¨› t·™å4ɱxUØFòfçá~<¦†u|Ætê:öÙZõ_y”Ð6]wá?Þk ¢¼t=Ý#¨ÔQL=-v8Jcó›iÀa ~§©|T7–^ Š–°›6ðµÖPÜâs„¡¾MuÅuô®;£«i¸—ysßÔÜV]Ø#8‡×yðly’|Ü}R'ð°¾QÝÏü£3‰8_õá­üR'¯#@>2¹nĆ}÷¹Ótkd1Ïí¦VZº{T¨6¾Ç€«ÑÍ‘:±Õœ«¶¤I?êpú‡ð+eÞkéR/gVSà4»´•býžòuw¦ g'¤ Ÿû>ÉÓ"@¼#PÈýêV:ôyûzœ>º¿žTãK®MãÓ[éÎ$‰øÎ€ò÷}ÿ"3–)$™‹™°« Ô®Žèœ xŠ êÚÕ3$Ëæ äöµàÞ»A‡®UŽ ¬#ÏÜÁä˜k)gÉ[Yâ!lµ¿Yc›ø¯†qƒæ¹SÛ„ä–&ŠâèQ‚o¬§%«|¼qw–#¢SXÇ`&´oâç•ËW£¥íØèÃðÍyô©û-ž‹«>Pú\ ðTàìøZ‚“‹«itv… <Ý0Áun† œgÕ ‘dðð~&R>ëçQû|6Ù¹OÚGnó#?“¾Õ ÕËÝC÷Iåg-Èaãàj{“G¿T¦Û–Ó(ï­8e¶ûá.€€o]s/]zV"èÈ”Y›®‰µõ­Ð¶:¹uü…… ô9Úγ6;ãÍPT6¾â§n¤êXü·S“+t¯ÂNgÁ&…û‚çÁ'6­~¼@lÀëv±5 Oy ½ ÏSùÅcã„êØvœtžÍ$ÊJêÓ&lËw,r¨'¾´BÀÓsÌ—H|–Níl,ÈeÊíΉÀ3ö.èÙãÔWÀèýGß¾´¯ÞãóÄïW¿ð{eRteüJŠvIÎÛÁNÔö&øÁ[ôÎLO¤±G÷!ˆŒÞä›Ý=]t‡¦›5ëWîT7ÎÒÜÔLÜ©9Ž~×6ð'X ä:•™­à©…ÆäøP¢J€§ ÔÚKsóø'øÌ8޾ù…qLå˜zµü¡™„†JMblôóftxm/ iæ¤䈎+HûcW6P!è³Úã­Ø'þyÁ+MâVÖ›À£8ÞGË<Ãñ­ýåAúÞ÷ð¡0µˆåœRృ®d'?ùA&äf¿<ªmž¤8}lÚ/[­n·Xõ!‰d7¾7ÉÔõ¡t<§ ²œD9žèÞ’ÀŸ¯ÿ7wìA´Óv3}ø(ðãÚOE§‚áá+tgÁ¯@ ^fÒhž˜Ÿ‰f ;ÔB7’“E/ÈÏ\/åìäù4ËærB¨Pl÷°‚±W£ª¥p{Ã9Õ‡R,°dÎõ²ü­bi "ÎwƒœV·†`Ùw Z3£ÌNU™³½˜ ݬ„C¶ÕɯƒàÀìaØ1@•X2±2SÍð¥"²jÝ »ýì T´ÈfÒ9ÔHõ•sGÆÒ‡Æ·¶’y‚ó=$²h&§gA„€Q‡’¶M;„¬Â$ Œ©³»Ð>9¨ kT×ÔzY‘3‘¦ ¼ÅþØÀùiDÈøx¦›¤EG¦Î[™Z©‘1.€oGö]´°i·Phe†tÀ¢fgߌ–ZØ*¼R*m%=’)c`˜^ý0¿íR«Áu‚¬Ž¢ÊØBøªÂoB(¸_["©X<ñü÷¤¶îÁ@zƒ›‹³“© aí9ᵫL0{o)õõr†éE²93æYRjFÌY…@&¡£Og ï]«ŒZ'ã6 ,VV†Áp$Vµ +s ‹gú·3†Y ?òÌS>æÒÈz]ŸÎN•¶^é®àz@*fÿäŸý huú÷ÿúŸŒFÆ3˜´fvVdA\QÎÃT­:“¸Â#Y!*‚qÌ„âÃ| ¤ÎCœõ«pt.NMâ7!ÃLO •!ïQ9f:ìKæì1˜ä¼%T3p¸ŒqÀ<<_æfJÆ(ì³ IX÷¨œH/ 6¯UØÜ¥Ú©½ìW˜¦ú¥sZx©Pºß Ûž¾v¿àœøegh€·V²itUÀ„üNahÐ >˜ŽIÜ€tÓ2Nû¶‚_ŸÆFÎ2Â1ÖLÛ˜.*¶€Þ"_ƒ"S†Â¹MŽL9ºìÒèí›éòå'Óó/¼œnYå|ÁáákÐ%íþÀî‡Ph­G€Û5hn}Ow[ ÛÀ»Êº–³úÕð*Õiˆ“É32À0œ>µàðx—9f­XEP¶¡Ðí’1‹ 6iÁöïƒWŸ ƒrrb,]L“SS88†‚¶Ž ù:ãU8¢‹0Ž 3ÏÕéàKƒTeK¼ üæ; qH5ƒ(òªØëTg]l„ʘgE çà˜ÁU+¦4n2œsÿuDó6Sðø-?SùS/ä)âh<@ä3ADfLæ ½ƒ=\—‡»X<‹Ýêµ¼<æÇ÷ é,öâÏQqðLlÇóY*(-{`s6ûÃ…K ²F ˆ ˃U8‰ÆtTrm’9˜y¤ðÐÉÛLYÓ 5+­šû¼_‰Ã:®m9(¾×PSø*Huœ–0?;\Ô"ÐÜÿ³‘û|—ß â¿ G sö¥±§"ÙÆ¨ÀyVL‘˃va¡@{‡üKÇWÄ=umÑ£Aäõ9GXðF§¶ã»O®;Ž‘QêŽD‘ƒÇ!ÛÝ(ƒm¡£ f9ÉEÒñ>´¾ˆ|63UU…ÕçÍÀÝË27¼X˜ŸKë‹SC±IýOò?Ì›½‡C‚#ßé—ô'瓵”Wéê[yy¹<Èu5·´¦Ï}þÒÔØýôèáßöc-éëo¾‘†.\"y°“`=º%úäKŸútzõóß“~ë~1=ýòKTˆo‡þs~èÕJõéƒÈè†" ƒ£V¹½ -(Ïsà— ·ÝÐFB¾ÈCø¾ž¢óÐDu“'•ÅÍݽiräNtŸ°•m¥åÌ%Ï=࢒oV˜Ô¡L‡ÅÚóX»õ%Æö˜ u"Ïçµ}as•ÿË¿=þ@¸øœÌÿ:ƒ$ø<ßö1ÿr—FÄ!e–wæÐªS?g»//"_²¬gwÔLäê&¼“çCóò9õR$£ò³¦÷4ü‘*&Ê™8t€ãÕ ˜S^i¯ç#¿½ç{;›Û*õgFð<-+ b#9‚O°WwWgÒ(ÞÃHŸÓ¹‰ÌðêB:3ª¡ó–ªQ(H̆ìÝô-<ET y|ŽÁ1©A:/aO×Ó£]4Ú¬hÒHŠJJ® ½ùü:¼Ð꜒ð©qœb\=ÍÄsØs÷GGØ+à‡rKÛFÚ#»,pIY˜Ès•!¡£É£‡2Ò ÆÌ²ªUßÊû•eë8Ôݬ¬9s0êqêYe/c1žÁjDZ5¸óÓ‘£óÙÑ]Ü0ô¯Éæ—u`—³ë1TÝÃhEÈÜLt |cMã“$Òæ½Û^×`åªrÛ}¶\_Äðæs«úYqðmõWÁ¡<âW| LÌÌna  µòÚë4:¼ÇûC™ùL¸ØJ=’\çº&\ëtjÄjy¯¼Ð‰¨LPw6 ÍÒ˜'A`hF$=®£»†‚5¼õ»8wšñ´!MêÔ1 dž¤™w?$Ùl< šzn¬› ÜÕ¹ î¼×½fï‡Kú¬ˆk²änתsW+n„£zœ2ËNR&yÈ+t.:ž’­-¨Xù:s ÆóL«?#É滑°a¤'ðC™ oóN$>?ŽÊm0¾aBx8x¾°v^µT8çzZ¿ÖÃ'¬6šgm ­“Ä$ºmù:UÃqãÕ<"x€º&ë~åX`ì…°71Ezˆj q—ï•ãò6ƒ+¶$ßÁ± í‰OVÞ5qLÀLë$ùPO÷Í5J{uÅáäå_YèN½“k™ ø‚î݆üŽ?¹_ša®À(¦ðõG.}\P\±mbtüâù:Yw±ãM¤pw• :‘å]°Öž%^²¼˜W´Sdݶ~?<âøôZiV¬²Å3’­ÄQO\'‰A…öž¶¢6 eùÙñaNM¼rŽ!úvG%Ÿ—p¯:ÎüÄxyÀ‘Í$vu3WÚÿ#C´… Tq)]þ8î‡ù»+Ü'_vOÔ9Å=ý µÇ –X-³o]k=Õê6ícqL\…§sCçC>ûâ øfÒë=gS'šUcUÈ™<׫“êÀ”÷ÆË½v!{²OþèÝoægfƒSaK95Æ0àà}ëwâiBÜqnÿß_Ò€4xwži¿ cm«p#ïÜÃFtuu’1Ü«Fµ±·©x:â `ñ-üAÚxÜ_¡O~&ÏêÁ÷ ÒƒÏ ~(€sÒèÝÌÄ;ç"ïÖYn°*K„¥:‹àQgz¥[£ã©³çZêähž)xßY²ä!q_}G¿6¥/“Ô®\»Žõ‚öxj—¡N‚‡Ç•in}…OÜv>ò5÷B[9}•t!â8Gçïž…¯’ñæLeÉRЈ¼yd îe}m$dVcG/’a°@Y¨ŸÆdÈe’´QdáEuð@|)؃G3ø$Üp&ôqèñ¼C`Ž+O9ƒ³»c—& &~Êä÷a³SŒ¤nçm½]há¾F²ÂáÔ>Ý©º…rt2‘÷FÇ)öXŸ€>u3_âÈüÜ|üÝÖÖ|¯ž®g½=u©x0¿4I`Œûj\70—§ªJg™LQ†ò™´ÁšL w„bþ&Ðó£¬ÈöM4z*öA{{¾$ƒq)}ä^Ûfg‰À³ÓÓè´òRµÑïm¹ŒÞm—5é:qÄJDVá%ÎJ×~gG‹ýüP|Ro•ÎsÈù¯ß–‹PŸ2Aþ_“ÝBµ¥™60x±‡.É=ê€"õI|ƒk57Áe&¦&9~áèÖ#7¨dæ^å—: rU=\ù¨œ“‡˜u_Ä+ßË#¬Ú6¾$½Ÿù¨Õí2™ ®…¹ûkwæ Ö=ð‰ÒuF /@ƒ¡ŸÐXŽ{155Åu ð&0NÐñ‡?ÿ]6¦ÒC:±äi¥ /Ð×ö/s’6L–ÕOhB‡-åµ=,_ü¿>Þy:%Kå¯v[´ö†®µõ=·}‹b9»¥(œ‚~öÐS¡ƒÆ&äpѦ×RÅ&3Ôq&­½,ìÔKZ@«{{Œ€clb+ê‡5Y ­Ç6þ qBý¥®c“ÊØEæ: ,‚çð½~ïvtàsƒØØÕÄ fHÈŽÀô=ýØÆ ´«Ekwh7ìvÖØ‰oËB¿ÉÉ –H¦ –ÒI@½z•VõóXtÚÆ¼•Ãê î›s¢Yxzo0N$]»ñÅ@{c¹ÙMM t56²ŸŠàm3ïM(s Ç'vÄÅÆ ´¶ „+·í`l)Úê3¦²Ça±‰Ù ‘pç!Å=süß—çÈ‹·è nBtxbr^Ø–´.'1©Ä$Iùá©Gb++øoLÆËbèoì~mÏ…;»±ÒdtqW[ÚCGŠ–õÈã%:ïáëˆêǼTœöµ Ð(k[Çö^Àßú “á±à8Çš›[Û£Øcƒ5EñxdÈVZÖŸ},NÙ¥KkSƒmØÕµ‚tßÛÚ;ÒÀùK̃Ž2Ðê]ULÚçˆnÀ vÓåõ M’¤e#ÝR ÿÆæ6ΠULÆj2'ë˜\®¨ÄÉĦÈì‹ì&­À ¡À€Éž¥lƥƤ ”Õffm` c˜Ö6¬ªCy0¾Ï“˜ì‰ Âêp7DA$€›©6Wj——my ~®²©·ïp¶ ÙÓ/ô¥& g3Óë«ÒÄô<È?ñAE<@Mô:SdÓÁ8:ᬘ¶i:¤’Ý a¥2 >æ(ã‡çÀ{¾…™`¶éPI çQ¬‚Ù=Vv"{‡qÌÊ/m2 –ÑaÔ#Ä@ª ‰ÙÙ2Qß‹2¯ý}²þ+{dÁƒð¶áÞç;Ïb<=f\±-må™eTÛæwH:p}V Âø¹gk‹¬SÌO¼ü—Rÿ¹Ë©gà c’ùd‰s§g' 2^}>’#Tª9¿dgý‡÷WÓø£‡éê“OÁ hãÏþ:WÛeêÀtoÌöóL{³NÊ£‹ ¦ÉÚ÷IÂ0ˆ¼KFšJ©¤¹Ou§˜VaGö¡Š?V?F– ðT¼C¸SA•è* ²Á3biZ%pòäSß~ä¯ÿ8íbG ìΰÏO鏯™ Ó@W ĬƒèûŽvözÜÑp*ohSÄqP¼ñ •å=p;å²L§Úi.-Ì…Pà¢À™ºc‹ã‘lÁ½¶ŸÈç=—<Ç©³K‹"…“wÌ.΄£F‚01øì lˆÞê½rŒÜ1Õ3l*vÊÉŠ¥z¯Ñ•žþeÚ-¦ßùߊ–½ç†.¦÷ß{;½÷Þ[aè›4R¬"ãy¥¦†Ìù ŒÿTAV,¸™>¾y#ÿ{?“®]J^ÿ8èQÅópóà”ÂTE\ãLzQÉ‘ÿ”âx ìféZE, \ /.Ë¿(Ò%è‰0¦ ¡ ¿Ý5Š æGž¦’b¾Ê¹Yò9ð@gó[j|r>ŒâZŒÙ#Úœyo¦øI/ÎÓÎâÁWƉ–ñ–¾žç¸I;:ÛÏÀñF¿$IÆR‘´Ý»ÎY­&a;ƒ÷à÷¬ `,Íh4ë¨Tðóž™lòeѶ,Ʋ%‰>(#Á¯¹ÇV¢ÂŒ[Vâð mFø®@v<³Ò¬î6 sÀ2@Z[Âwža;ðZx»Ìr?„^2c^òq,e *èËÏ ÍÆ˜ÒZc 8Œi›;•¯rƒÂF^æ+£Qø!s–7X}Šp t£ÁÃÁ—?VÜ4ŸŽf~ËãKX¿Á¾€™`—^ŽÍ=&©¬h+ 8ŸbØîÓöýYh€¼:ÚwUÅ9é$~¡-át¨¤Â°çí)ûWK½b=d}+´vR ]!¸Ö×ÓÏðˆ0¶;("œM2ø¹/ Gx\NÀúË‹k±WÂKú²ÓËÉ ­´¶‘EÈZùµgÊE{R`(þ¤É²ûIFbM¶•®Þ¯B¯£½µ£>âMuÉ.|_Z‹¤!õ(ä*½ed°ËÿŽ0楋=œÄ&þ˜ ´iˆ”‘À¥SAÜÙC)v®f$V Ž{dŒ,É!6õúø"¾kÕ’ûNÏ•ùh¸F€,ä …É\ÿ$/8¶Ëjjz%pùÞ½[àõ^ThNO$£ŠšÄ´sÃO°§(» Íé©—>…á¹Á_°]ïƒ_1 56Ï5²'Ð&ŽJ3M#@¿²;‘ô,­‰Cê«;È ¼}è…y–ÌlU¿ åú¼šÔ9geüÆ@i8@Y¾ç¦{~Ÿ<Òà°cƒÝrÄìXá£KÞÁ ÂŒÿu–h\ù£Så¾ 1~l"c*ctÚí"'@Ú¨ åh#3Ä<'Îä u [ô¶µÖñ æ®$¿«Ã 3à,må¡=iE½Áà‘û¨)Ã2)Çdª2xH ¼f—{uVߢ¥X9c²­Æ= M=} Ø GáQ WP]"}j œ½†ïå—4bw|¾º‡(£^lbªr$:Q/uÖ®#Z爽g ˜:Ô =‹Ò¿5d[¼¤kuÄyx*ÃÎSÙRx­™êb/ûËÃu|D•%ó`+x2p¿G580 @²œàOÌ,öß3 ]³{húksL×ä0™˜{”—:‚t\í¨°uìñ™L*°/äÎûXžÎ|DöRmνã6¼L™Žàô‚!ó¡xȸ<Ϭ©‚ă›òM³™^\LíT_ë4Ú ¹Á}ñ9ëTDhä;Œ2RKÀ„¯J’‹ÅStÒž²Aùé^”SïçÝ—]'“ÂqÁõ™ÓÒ‘”±uÿ]‚²‡å|¯®ò<Áç…¡tè‚ÝåL8«X‹/¯–n×ׇ\O“q–)ÁIa`À-K,#I½¡{Ã6‘àÀ|uú›½ÎA—´Á^ÆÞD'¥ƒ…Te—.“yu TcËúœÍ#H,Q'¦p²`84™“ï NyL‹ôá{ŸíÜ]g%4,=à»YòÎØ9CÀs‰§§æÐ1Ñ…|?[¨Æ¦ARØ›8=Êq „Üd}BÔ Åõ÷­Ñ$8ì]p^Z6©R=ÏVÊ:Õäia3ˆ¶¨_'˜í¾É3<ûRâ9ÇNKµóØ/ÖeÀø ÒΉÄ^ÇU¯<ÛéšMŒãüŒõ»[®_ÞÅÛ®¢šð\¥=ªÉ1§ð¡Òü^Ú˜"9}q¾rJ1EZ [†ñÂi¨þ ¼=—3ì`UÆÚtFgÝ€+kÐ>Œ®Œ”%p°«¬=ªB™Gù¥ î“Æ„¥U0õTÿÙo«Q&gg±·9“N#²hñ?Ó3ÜŠ-`æúÄ7èg^IÜ#.ÊßCç÷$-qE&ùº Òß¡M =ˆKvÏ$²T™Q“ú•º´Eå£42|¾uãç÷îíOøŸ:¤ÇSˆ‡êY&xŸpËa 1tÌGgã:´dû^uk+ÔŽH,T¯Õéf«{ÑÖÐù²“Ô¢Œ`^[è_W­–á¿–f¥ûú%û‚,°M¯ú½”®Ë}wþàël}𳳿ýΗ{†È ¾VØp€Ûú‰}ýÙLͧXagb¨~3Š*Öàe|¦ ©Ì ²hkžÔÒ½¿Ñú-˜1”ÁôÓIKê §Ç ü×Á_qÚDÿ£cù¿£ÊÓÀcþÎü?ðÞËÛŕؔÓd°LÁÀM[èF¶Ø”ÿˆI¥uý©Tû§jõLðGƒRÈñJº”êê›áÕ$ÒP…VE±Š¯Ð}¡mùè=’îOOqüÛS®Øƒ=½AÐ%wº:R$Øç¥QT¡|…MéøA‚òÚ8¹¹ ?µÉ"‰IXA>Bß“ E?]-­< ’675¥÷ß7ªË<ЇÄåêÆ®TÓBÇ0Úˆ§Ibk}SjlëVÑ“N8ç” :´ÛÞÑ g-¨/\¸ÿƯÿ4E‚W,œ•YÛì‡`‘þâYúSH( -P.[ZjU]Fz¦¿ÉÐû#yúNv·]ƒAm÷À—8v:„ÇO©ßûMàð 9 !˜„šç~[Í›ØWŠ¡PM IݤŒ}+¤+ÏïÐà‡RÿÀ@ÌÛDÞÞ~zC†ïV_µ¯L?Èl–|%g'·§rð¢znÙ‹‚|4!H½Mšs~¾ä=&tjCÊO÷'°˜¼$×§?HýžÅ>døÇ3¹F˜…ÈÞÖghwì:x÷έvp¼Ë6±¾oå~9~*;ר×oá«2pîçÅöv3™QZÿ’oÜ´;Åm¡Édÿ˜¯³½V§so6Wçežâ¦…v(>…VôQʉY”•£[àC6¸®~[QYÃo®wü­v¨¾¢ÇãüÔÓõ)äsߌ‰0¾õñmp³‰c‘Ú±qHËìHé¸&˜˜²Œ~gÀÖóÖ <³¤ÜcN±äJìZµÇ Ës,œ<ÞC>ðð~Úá«C¾©×»mþ(ÏôàF’7ûçs j C“•¹—/_ ûyÞéXèáºL i ÍFÛt>“~÷I˜QoÇz ‘NÔÕíµAÅy”ø ªdÉV1Ññõmê3•ø$a©,Wf´P—0ñYûYßÁ<³¦î8±n}ƒ>'à.yl±Ej×üòÓ—Òð`7:=GÌã÷$ ¬ {~cüÇjh}ØÒnkkÖX[NÀž õ™è)ú+ŠðõXýÊÀmuoë{ísñÈøñ®MæÑÖÞðX"öè,n3êÉÚõýÁø­ÏÝBÏ„Ÿ¡ ••ÓÙkqŸÝL^ó$«ÿ77ˆ±Þcá GíÉÿ-¶ÙÝ_ÆïQÌ|ˆðdÇŽõÕ¬S”?ÉcÚ:(ô€7)[›Sk[G¬aS„¦¬|×gÁBùßÂ!l1ºeÉ\ceqX$²ÿµ¶µÏðeS&erØÆú"qÊ1â­µ<³4MÀoÛ:»Bµ-”¯ÂVªÇü=ɧO¿úr“5Íõ&4‚×ð @ŸŽó7‰†i†ÄÞœÉjùŒÉÀ9ô›êu}ø;ü$’P¸É|sƒ¾fô.æ‘CQ…®$QÄ‹y©g¾J%¼ÀíVá¾_0èíµ­÷ÐÕãì¦ØTÏžÐ1@|Uº•qº6¼2ÑŽGXx$[%¾z‹<®·¬¤© û.Oõ÷öÎ4¼›‹1]¯¾0ã9¡O‡LÄ–æÍM2µàM¾ÂMaWšü015<ÍbPã ¶‰olja_Á£VôñÅØ«-ŽUØ ¹ü¤6|†'¥èLŽnìà `¥M-Í‘M±Cë!™[N–C¶ F}´¦:é¬\q£ÌJÙ¦•“"DƒUF“ËÃPq!©P«ždÆc¢èî®sŽ9NÛª{Î¹ÌÆj¾âùJ˜g ʶ-ÙuêO²@´–ƺK!dÍZ¬CXo’õùhb’ÅHßd£<3•óraªµEB0åz˜®|¹‚J[àþhçÀÉâ,r“T2uتï¹ ƒ 's”‘YYu fÃPŠ Ï³4¼ šGçÙz¦sžqE,•Î̪`r‚BÅÜ*z9cή5$>ƒj‘)™ÝÌœ ¡BûÈ,¦í°Ž?Ïzïä¼ç2‚§©½%t.}üþ—ÒÕ§_Å0¾º.°®F<ÒèƒÛiøÊS!üd^îp“ ú83Ķ鰤ᾎô›_øõtýo‡Ý™Jì8pó^ÔÀË DÍC…ÇËf(n4ÊãM ¿M»IÖŽi•¨žZðì!ä h÷^æ¤PÑá-ŽxŒNq[…ÉýžÉòC–|Ms±ßûÝßæ¼˜›œ¯Âe,RE¥Ì‹½ƒÆBø„a1#Aè¸o¶,©'Ë[…_EN'«kqŸì¦PIö™Õ›r9ŸAyŒ >wªÑš1ép:ñ Ÿ'A«ˆCQÉÃ]¡,¹f xlðˆyh‡EEÏ,âèTù – met0˸ò´,·*5wh–™N0ÛõÁ˜¦olÌ&ð‡WËÌŸÎa§jºšÁ‚ì[©²¡-ÚÃ^æ\;³ÙU»UÐ=+±ƒ{AVóÉs— ç9wÛ Ü °ÅÞö°3$ül¯B' µbÚfOÊxpŽgôu£|¯,±oG©¯o0 ôKo¾ùFd9?zô0]¹r-Ýøà²CÁyÖ×ÖNË0vjÚª¨ b„J*£?ø˜3›fÓë/_M$â”XáÅÙè0ÝC˜¦ßΓáïËéñ©D?OÆsõ'+1À&ùF)ÍO¥ó}/§Þþ¾týæíÔÛZƒ‚ƒMøj`³W*¼*‡ÂÔV;:ì«øÜÝ2sÎ,ØLY&`Ìû=΃Ó¯€‘9„“CZ`_M0Y~8¸PUÎÌ<³Uè:‚Ðó‘*ie¦QbkÒJªUù§|jÇDæ&=køjL;?•—R„˜NÕFÎfÌJø’R§W%Ç*ç(>nô<…'é¸ÕˆÇ 4—Ò–Ä€É6Ÿ\e5ª¼±¦#ðLe-Ø0—äÄ×Ã=ƒþ(Î('ò)Å!dy–4É,â·üœC1Ò 1x{ˆ v *²&m8¿,ø!” j 5ø\âÓÈÙ˜Û&ûwÀ-a>:¶ :‚Gã¸$xŽß¹7VQ«l©„ÈË»üÝÀ•Š|rópï͔Ԉ. …°”kq½Æx:ˆâŒöÐj÷Ɨʶ °WjlæQf‚3ë0nYŸ´m0¸„s×ÌD5@ZŽFÖ<üÑàˆ|ÀŒÄìZ¸÷ ³¨`‚·°ü vá^ ï©!`™ÍœwhÕ~8Ρmùo‚jdwuuçqNdˆ¹‡Ð§ã„ Í]d:Pz•OâdÈ x¾®ò ÎG®G@ì1<…uNЪg©ç5ÏU'ˆVððk?“ít@Yéèò¿PžQ.Ý;ˆäóðø¤™{¶ÛqÞa@^ L‘×zþ¨­ûd_ªit¬qF„Jh.g°„jYbÕ!{ày_þúÝX»ûõçá%lCÿ°¾Ó/沆SV„ ð)7AÂyI›Ì:pÎ]¼†AЗÞ~óKÐÎf:GV§Î¿;·H|¼^(…£ •êªþà«+fŸ¢xƒ:RâÈè*‡Ž¡¯ÂÌûŒ"«L@ô=&à+i3VÉ‹«Ñ÷¡ÇCxc%³ÊayŸIv²«Ãúªí­Î ÝNàmTY:χÀCŒÀUÖfU—<ÙÎJ¿UÈN [5ŸÂkä±Ò€úi*ïÝXU'-¦&:ÚÈûªáâ·òE=YÙ)ïÞ"ûöäT9€± Mé_‡Ž öË ­ÜÒQam%˜pÈȃxHèéÈoÇ÷\B«¾+ YÑbc˜Ln4ùçù¾ œ­>²;Æ <Ú §‰‡vrÚVá™¶Ò6p¼‰á'1àž9™52G£¨x¨ÓÈ·U¶ÔÑÊI:f êø+=Á-«ý5øt xž¯s6ÐhÛ5ù¸I”& X; ŒgŽ(nŒ'K§ÐþYá[80¬ˆ÷œhá+ ø›lV„óQÃZ']™¦Ó ç£ý`¿œt}fý®Öù”X7Ï·ûVµ:MÃË}¶Ún:Tuv˜ l{ôÆæ¦mâ½Î0ƒ¼Y@Cxéô_NŒ$áÈW…cœ¹)3|١ʹ*KÕÔ5uJïÎѱ²ñÐø.ty<ÿ‰¥$n. s¤¡Pð¹óå$òÀ/lÛ!ßœšcÊÔÑÅ qXÇ—öI‚e8äMJQ¯«%é@™mç‡c‚+‘…ެWnŠWʃP%¹L®K«®©‚ùX…o²¤üeYä¥:|„‹/a(/QGtbê4Ù1 Èeð"l7®aôÀGÒ¿ÕÝjiWY…ýxTŠÓ†=‰Ê4îSרw®kìñ&tãÙ¾¤ ãßÀGçê…‰Ž·3pÏ›Ÿ_Âζ+°÷6îaŠÌ¡œæž:kÏ@Ïyù=¿åù§ì£û\Â~HëòÛJ+yðÈ —JøS–$`Ÿë+¨¯+¸wk D‡1& ^åýÞç¸Úqª‡<-¶ÒË1{¦þæ|}©I iG”ˆ 6ŸÕ°ÿëâ'7iÃH#%èÎY2C.-CïÂÀº¡  ÆŠJ6èX|sP»“dÏb!Ì[\4a‡ø¼ž|†z®::¥¶Ø.|P{>ðQþÆ{çk*Ëà…kb1:þâuÆsÏüB½/þæ­öoGOgðþUœåê{±—@H<ß÷¤[cp™\Án…ŸØâp—ýÒaÚèDW@ð ~¾‹ 2Iüò•«q~ùØØöÐ44Á¹ÓàtuKCêîí‰6½ÇÇå©$pý êxÎÁ½§í¤bð'%ÀþÐËUùüÿçå|ã\½ŒÈöú—”Ôþ"¿\¾4fõ¡x£½ÉâI“Y°Ïyº³¸wBÂO€˜•Ê“ìn ¬Z]¤rl r½0GõÕˆ b‰åtåM º«Ÿi[Ú!èŒ'Do¼.*£‘×Ê`?«ê Ò¶^Mí­©vwüx„NX“&ÆFÒÕW^KõG,ýþï~1ݾù>znsРøÙÓÛ|Ò7Þ'æ{¦CËÓ:/§ºgS¾q3µÏ•¥¯¼÷ÃHgôD}'&.ˆËVKúÔ¿Ôc\Û+U¦šD§|áíbR,ØŽî”K-=WR±ûJ:®"P_¿œVnަŸø©ŸfÿkÓû¯¿Ì~âÛFwêìê·HJ¤ÀJ‰]À 4úÊ’¸låKÒ-Mƒ/qô]*+ÆRaì‹iu&ŽÑòìß35Ó[°QbRNìõfq_Ð;üÏ1€»GžiKëwä LÍöéèÕ'Mh(+pÄ(¯ l„} û´j·âÜïÔõ×—ç±ÝH‚U=¶A:P&•’|äK<îïß¿G‘ÜÓQ˜cµ…W ´°¬"øYC\£™£ Ž)œ›p|θ_ÙôÐ]º'·1?äz¬­±°¹Œì"jõ£ÓÛ¡•}r;÷Ñë´É=ëÞ¤'ÌÒÞgl îZ›ššSœ«UDo`ª·Ú­Ù*h¤ÃÙQËŠh€Øú ¤]éC_—>,‡êè$PËw‹âõéùRŸ;+Ú´ÈU.Y|:‚ï9ßUp¥ ¾®=VL2vfRm¾pý†ÂÖÀ¤º½AÖjöH>±‡Žl[m;šÔ Þ.¥9Ž>ƒ¶Æñ´ÉmßnŒÍøŸºt´ÝÆ^¤°§†jw-å;Êx»„­òQw¶S‡ëòžëÝL%×?H]½ƒ©½«‡ªçF®Éx±GQZ™l;zé¯ÚSþ‹«VæwRYÞ]^L8°³{ɦi×…¾‡¡/G}¡=÷ìr“´!Ÿ{á…Xÿ2Ôêšš‚ÔbͬÝJxÆ=obj6µã#0Ñx‰D[}^ÍåÍ¢ <ü*shLþ©üw_äS"Ð!xë5v˜ÒïãþØÈNî}Ø«ð~»p‹[î—üdŸù›!¼ØNöû•÷ò;ý¹â%,;C#ÚJÊ÷Õä ç)Q×ÇkSVrŽùÆ&E1E}A•t<¦°†u/’ôaA÷-æ182еå¤Sõ<ùŸòâÚRÇ•ŸË_õC8}†ëtTV†ä¤YèMØÛ¡w…b´Uü3&wø\»öÙQ`qi.MŽ„.pã£9"pš6ügr)xò ò`‹Øh ü©©³8Ññü©*£c’h×?xŸ…ƒdf;lÆ”êA@Åp9Ôo¢µº-2yÏf¨ƒ‘xþêÒô |299‡F`*Ìè×9µËÙ®ª4¨Šd¸y0–Îõw§îÎÆT³_ ƒ£œ Ðm)wùÎIVÃæiQÛÞÑ­7 ,¬‘Íb;÷62§5$#°A•º«È¬2d PdËcz›(ÌõÜ [ìsž»ŠtÎSB±aÌd7ÓQbðáhà{“†­ˆ¡" ^Á2‡¢i«ž]2Œd8·mžr8j‘*˜º^·º¼Õ [Ý-:¬dô*zÍœIÞ@G€¹éÉ4uŸŒ\*O+0Z<ÿCÅ˪½h[Oµ} ̺Cæüe”^ÖhkÔnw{@ù³©£»?Æ3SDþ×2äM­ÁH…ÁƒÛ¦Œ™«Ï½–þþ?LϽôL³!½óÖ×Ò¥áa*ƒgÓ×¾øk8 8gc¯¶8``ôËs–Œ 2 ˆÁßžÞ Ì… ýØ=„"C+㡞Œïkbÿ.>Ið¿“=ÜF }VAÚUZ&èñsr 0–ûÚZNõ7[=˜ü×ï×€[Yzõ•¶YÉ„y)É:¨JPlÌ¢l âyxÇ8Ž®\<—®#äÁ¦Qé°#ÓA¦óÂ}§({WºÇ:ìs<¿–ç™ÞügÐKN(£² nmçê@CS³K©µY…í\9‡!Ü™Chß²´|m“[‹ÐÜÕÙßH—.]¾ì ÂêµO¿ž~þþUºw÷vú‘ý pzÂØÓˆáü/ç·Lwšs.ªsjrÊ-NCƒý#öO‡7”ß5‚Ò`©g”Àd/Jp O³d{»; ¤Û‚‰0 I& 3SàŠÀ² 2‰º¨¶`3ÓÈÈHnnd-:BsVtV©È7TXÜc.ζ•f>½´5„Vl‹S/ÓÑ™íâ%ø©‘bk3«¢…6F¼c錸z©/=ùäEÆTˆá‡ÿÉG£=x!3[+²8qòØß^€¹ß:2½žä~aš9ïtJDGÀ+("S’9˜€¡©P‘7Èk@W/DÆ–‚[EÍï=ïG#¡ÀÚ²–ìV˧³NL:xµcôw£4ðìæSœ%@–bjF,‹À¹êåAÞǧòDeƒ‚% ÕØc«à„t,φ½Åú¼ƒ=´ðGA2˜í Ùcy®!ágb޼/Û*³ë¨ÒØ„F¥Mù²t"ý•aUfdì…ÌQf©(tihÁÙ Êgˆd¢Átýݯ³8¤XƒÁB•âp^ð‰J´Py±xì^oCÏVÐæH*1qJeÊJ±š¥!3ôupÉG¢r‰¹»•‘3g|§ØØFJÅÜý€ôà¿(WÌ™Çr-U†(~¿[êIª æÑ(òUùBÈpȸ¿ H*÷*æ]izfR0Åu⚊<b`ÐqDSQSGÛ!Þ/¨ó˜•üNœž«5 ÈºWQUê¬zÇ+®Ã„„d@QüY' šIâ ò@i‰Kì‘~à”¡qn¸[‹²-nXe#<åC:ýí:‚N=e’Š™ò²Œñ#‹•ù‰«&¥(Ã4.¤5@dz]ý'ûÅvÖPR×ÙöIxÙVÓÉ–Ðeмœ>ûO89ñ/œ$ÐD5ÙÈŸý®ïÃðfú胷¨€yž³§ºÓý;×qèœOÏ<õBZ‡_ß½}'ð]I0/€¦M4¦ÁÍ`Ü6Ø[>(KІ ¢â$‡ h6,©ü“O[ªmõ¹=e8 ´|a¼µ˜-Æ‹à·çïÀ‡5l<2¨w¨/1=.#Ó·ÐÀa»íH¯žC¼¼ á—Hè¤Âäâ`šFáž›Žç5aüôŸëçüó¹´²hEóÆÃ×.sv³tx>ÏG9ÁÌšWæcÞ§³Tv61ÊkÉUìqNõ÷ÏÎõÀeÙ&m ÙÒuäå¹ÈeÒà9ä'¼Lù)V'×þˆc.€·F¦\C\Óe’{Á­8|öè„°Ìó4å½t? ÑÃÖØ‚Kg¦N㮾nÄ¡$“ítˆ§ËW/Æ:­ TçÔ3QÂ5©ßo“Xjr©g°*ËÖÉ|–æL2ÍŽ-±“ ö sYFvY©­cÁ¤\ƒ}$5Âßo<˜EÇL8@ ÝHÜÂf€w¢Ùyenj þl°•„G੼Q6± ÀŒ_¼t(“Å iFø~A;:™5Õ ”;VV«Cªó»î¥N¡2”é"ŽËåÁMÔsÝye„ÊØ¼c á "@aBÇò‰´È åhåŒ)fLŠc†tJÑv”$×) MÆëÀš'C˱E“ñÕÅÕ œÛ»$D׳UŒ¯cëÇÑ(ì PöI<$ÓUy¬ÅrÊ>«Ã`.êl¶Y®i¶—nøÌjª“Ð[¨l@¯0qÏ„”C`k¥·ç`+ßX×AWð€¬˜bîÐ#V k$­…ßqmß§,·ë†0v׬ž±"\^´‰¾ Dp@Å€ç¨+pjR¸{&ì|¾Ð÷l<çi•[oˆû­ u¼¸4è@åÚtĘD£³{—¿Õ=Ýÿ-e¶ãóZÀùèšEÖ(eE{iÖ¨m ‰Œ«/Àê÷°™¸Vý4¿±»…kVWV¿5`‘Ô #I€ï»”Š…2cpƒ›è>áúqèh`:_æ¤lpŽþoeµc昷z”ãHç®Ë€Ÿ0çµiÜSùkæÀ'ˆ½)¢»"Còƒ*œ>:È­zW–K3ÙKÀi§@_ ™0;!qÔ{ƒüFÛA>ëå‡O?ÿ|x7}|ë&2Œ!ö5KŠ@“â:ƒVvT’W« z|Žä8yq\×¢»©KÔ°ò¦@òæ]\Lè8‘ªZ*ÕL6Ðah§)ÔuHí¡ÿÖ0‘pÂQ..²_ÒíYé7™DöÕ7ÿ}|é7ßÿÁ?¼ß—Z£˜VM"…îì«¿ðÿJ*Ê ê6åùºp.+u¯_ÅM»ya;* ½Ï$'ñIÞoÀt¿Œ›æ>:ž¶28hàæÍ¯}9’»;:;˜']Р=‹qŠÅ!º?vEe•›­}81N  ü5Àî«k»T;Ý@©à;t]H·ô¸Vé@˜øÒÒf“ÿÉ‹KHFR˜¸6»Š{Ê·¬ÒÏ$àÄ\Õ÷s8Û¡yùŒ2S¢oÁçWâ©@&—WÖ²fh„å2Æv£|ô!/åžmÖmèÕ«O¤o¼ûIÆÈpžë|uë@njé šåªô$“å³îS3¥yDè—ì‹p€øý˜xLÔ’°#ÀÁ]˜{ YAêü¶Óg]C+ôÅ4·¼0 Ý9ü&¡ÛðŸº}øfÑM­lµbOKÞ5x®ŸÂ‡þ(¾0¾¢’VØ$*Úñ®ŒûƬ'ƒ;óÜÑv›y»æÐé@áâßòwþŒë…³kRÎ막{ÕèÍå•TìUP¡]ï1“»{ƒà2lCmO« m<ò`„êî¹ÔØ:À|°‘‘ûSÓ“ill4lae¼¼´´œ93fE5­¦Ñél¯¯?AÛÓ—Á–¬ûÚc¹ÃÜ•QäÂ^ßç}Ð sÔOëgúþœ¿)åèß%}”áo.xÜò“kÔñmýªîÈÞ¨«ÊCíÔ€î¦ÏÜïÔG½GÙÃŽãSi  ‚Vô^47‘òä„6ÝÀ †ÌŽeèfhåÂYYV<†o É?êË™Þä5‰fÀ­`á…e©†B1_.\¶ÒÇw)–`NYÀÜ€Ùõo¼ÏÚ9úþ²¼¸|›iLX¾@¸õ­ç´|ÀÞá£Þ¼Oµf34©/>0™Ëܹϗôqx |Ìtù•s}¾á{“†Œ9„ß=I<338b ¨¹oú‡óÒ;*ªBð`"!£¾‰c'¤^¯¼úZ$,“˜í’™³ãùòßZ£[à4ômG²ßx‰ø_(ÖGì"4V@Q_W®ÚbÜb1÷ÓDЃá]Á3bxd¿Ü_Ÿç5‘LHg$õSéÀõy…vIø4e nrj*ýïßþM‚;êðtôñG{‚˸ ß!¾\“,sðiyeèN¬û Bù®ÿœÄ?æšáL¦«i#‹Óßҋ˃þä}üô;«³ðNÙØtÆW¡y¯ÇnVùŽÀðq¿„‘Áò}þÖ+låÇÄôy ÇcŠÁÔ ä1n†´g'ºà&÷ÌLO7/XÝĶ7ñGú+c(`ënrü‡•À&.ðlù¿Iæ…*މpþ'2G“*ØÇõâ3èiV¶ºÀ)ÍâMà®7Åî¡›Ò¦=’¦™ç:öïÂüRøoÔM­ ¶[ž|Ê@îÑÑ Õ³ª^: ™îøÚY&êË›´åÁ®÷ÛÞ æVüÁ-L ß/üCúQ7v³dyp@[ÌDüIÃ8Vt}ãZÅ…ð01S~f^üð6¿—´+ù(lÚ=‚›â¾ã±Š\KûÎÕ“+‹t]OìÉŠ`y£¼Wßá¾åå¥ùèX+Žªk;OùP]}#v;ûF‘WOw¸‚Í<ëyoűcª;ÌÌÎź½oqÉR[Ì%+pÔÖÓG:8 {皈b¼%Ž a~ò\èêËìÏäÄXìƒv“xg ±±qø ßb}äÞ¯ÙuK—Kð¼é™iìMâkCC™¿œÝX]bM|•– “d§mm[ùÜé\È~' ª «Ä¤~í/q\ZZA71Ãj;Â(c}¦cÇ4Ámùè'linÚ‘ØØ ·oÝI•O]aþåÙ3IŒ³ÁÔ=°YðH«Ç'vþŒÌžKokåA«Á—àêzê€Æ¥Œu "¢ÊŒœà¿ó'ó†LÉË¡¡7Óv!1QZDÓÆ¢EPà¸>$y§…šÆçÂ7aœ£•˜ñÝãôå€a—Kê¼Æ\ª°Õ]ƒð’žÒñÏx¡:®EÎG~*Îmm‘ôN·­Àoº_û’OÙ¹K<ÑìX&ç ãvΔ7IAUfzrœ„‹¾(’xh5¾ïáëk¥M~×àäxœÝ0í*¶±2“r5Èì&pÊÙ§¶ç…ÕTàfüIôT™ëÔV¸Úc § 7à¼Ã9â¬=b3=—ö‹¦ˆi²VÛ9¼ˆr|J¶´b Ó³ø>¾ýÌ¥±ñY*Å©jÙmÑYCV‡ÁuÛFUñ™GžÓ‹  ˜!WƒÁ˜€Ü46Iã8œ/³€Veïïlp­ï NfR…Œ&Ç9g:±¯å*Œá¦Ëä$ÀÒ˜­Œª™—Õ&VÁœ ˆF&"÷om›é"ÁúdR®k“6®ßÊ™Þ ÁG`ŽÍ¶Bò”€Œs2Ø`6ŒÙ™âYµ éò“/P•Õ††1N ‚íÕéÈ´šE Dõ3HØBE¼m:6 h"#SÀ¨´iÔ·´u¡(eÊïg׫ܪH^ºöL¬eìÑC&UT(vž-Øá 2Ò+çˆ(,»ô8}8¶¥‘ûwÓGï¿ ’ÃÄê;ÉòkN‹kó¬ÃÌGÐÕüò{#@võžÃáØC«Ô…´ÎÙGdi©X!†AB2S årî³í‰‚B0`´O† P¥9ðì˜=0‹ç„3š›¸ÎV*(OŒ,pªŠõgĪcšáÙç2Ä*Îuü[¬Kg8sêA±F‰Üç\jÛß“aDV0á›±bf¿ŒQÇÙÇÈ ò«¦m¥¬{¦s¨ ¦ÛÐ@bÄ«PÔY*îÀS¿9E±ðœõ\+U$pÖfžìBƒzV ë$>sÄ[9»ºAëhQ Ô­^µý‡ ,d¤©*ÕÛ©•DuÍ)8§´½. Eæc5  hÓì/hÛDéRAóÔÓQ9/ ˜ŸRÕžcßôWy6s)íz^ùÔ«diáŒgG4”;ØûÉɱÇÊÁ{Œ¤ÎÉ)/E¨¨Àˆ žª\ƒëdX5±¿ ÑÐÈ]5à÷ z1Óh‹³‘ípáyPØÛ]Úô`Õ°·d‰A£ΑE›9[{O«d¾÷¢˜¬§Å•­ÔÓI7€^^ÙgaÔIðølàs3ŒÏõ÷È øÁxŽ|;‚ç( sœÇ%éêîF!ÃAÉ ¤hƒÀô\‚'dUéäFö ò¿à 8J˜‡ß`!ŸSA“×ïìñ9•¢ÍT¨è|¯ƒ™Iië"2‘<Ä^(œOPH70â­(Ô®$<å}:âx i9olç‹Gˆ'å ‘ hª+öjlEÏæVÁž'›SEǶÞV?›©Qnò¢9pÄd:”¯mpq“LI qTï˜Ë>t§<{úÅÏÄþFâ ôýÑ;_$Á?•*f/-‘Y>÷“é™9kk×ÓÅËO¢Ø4§Q—µ d±²ßuí(Çô÷?g”&‰5€¯sÜWÐ%óæ~¯œ, ìU58 ˜­¬Òú ãYë* VÔ·¾£¹Ï:]5òÀ:`lò“íº2ƒ¨ƒ]x*oJ¹þq% cF`\:@!ÖpR¡>>, ˜š¤ÓØÜÌÞТ84Ò }“=´Í¼ŠÉœF vooŠ#N qþâ8Ed´Í-zÈâíÚ#÷ Æ);å—^·¾±¾¹ÆuF _´üäPD–7·Ö—=Öo§=3,Úß¼\71ž¥²çYp:óàËà¸òT¼× ­ý‰Á¯ÄiDþªÓEý– ÚoãBuBÖ°oˆm`jÂ!s ùÓ=Ä5 ð¥|zöùOQYÒI–çù%ͧÇH´J?øÃ?ž^ÿü:ÉMÓÈÇ×ÉüïNã8Z ¶xÔÊ>†W‹nxÃ*ŽËp1o¢VóÔÓÀ5©Å—& ­sî¯ß…ŽTì…¹rú”Àâ²F×G=ô{ÖœC1Ñ>Žè¢5&ã‚~Ê2õ&¡[¢3 ÓŽq룴¹°‚ŽÊ15Êt™䥎檱«>,Ÿg‡‡/¤önu2ê*ât£ôƒËêÕâ´wGOkÀ€§Y*ÞONOcHÒMþ§39œˆÐ¦´Î¬Àôä„ëØGÖKçVVtuw„Î'/ïÔ‹­FSŽzßî.z'´j¢”ci`˜¦îT‚á²ÀúöС*¡iõ¤2¸s\wT î–ÑñDøÔ£ÛÀæÃ¨  ÜÞÝzB€ýÉœ"ÂNg¦ ¬â—¼ÝEx]8“+‡‡œcɳÖɪ^&37Vtpß<þgrb"xK8ß4¨K}×$ŸJ֨έ¼rß[¨bê$ÃÛ.Pò%:¹”uâƒ(®ëœäÛÊ.iAÁ.3Xm {i~9‚Ê›VZµzv¨ bêÎò][+./Γ¸h"Ñ>Žè^:î\ œaúáÔPîÛ*VÃÖJ6+ò .ÚQ™f1ÒA.à ¿Že<‚ûtj([Õéµò¼ul‘5䶆nžýp-ÕÀ`§}6åÛ]]éÅŸOwîÞK_úê[àUBàA+U¾•W=Gí3å‰xAvðÔŒŠþcðÊä?WÖÙÒÛöø¥<6éI;IG/[ôÉM”ŠóxyŽI /¾òzHszë+_ }.‚Æ•rÐ'B?µ{ú¸ªÍh~8˜™‡Û¶*ûa0ª’ª‰îžNÆà =M]S:•6„¯Ž“ëøêOÓØ.¼¶¹•€ÈÂ……®ã ÎqŒàôЬ|j ÙÜdr¯µ*ÃʺHÈæaÙ h'¸÷ñßêêé©ÇǦB7ª¨Å Ê¼Õ ì¼r¨žÏ3åÊZ×{HÂsÓ)¦òé8ŸS/Æ^lEOÀ@ç‡ %Š÷‹ƒÊÞè øYµ2ÆzÔÔÌ\Èwíq"62lždç0ƒC{èVœàÆë$µÌ9…·åHîµÛ\´µw\ø­,âèhE‡”ñ¾gÍ:iL61g"¶tW²ƒƒŽ½²Ú{‡i噇ü[ºBÙŠu©sžLîG_7Ùîu7—¶€ºœ:»kÏ:¦h”Ccà‘´¥ãn E—Š*iÇÐY9|U85ÐA¤È~9?ùdޱ«à/­-Áã³Î=8nK¼t¶.rÔT)v›gòZQ®iÇ^ß…Ëa›j6Á¯·%‚‡À xy¶}5üy‰½uý&åë—W™ÐU‹¿F}Rg¨Î$“[hæ“zÎÃoè´ÅçâŒ+m#“?wðõla‹¡Œ+dßû[G³ÅyørøÊÔKØ$Œð8Ÿ¬²×µñ0ó>yòÐ…‹À`îyoCïb»Á7ö“¿å©gº½,^ù&<ÜÒÐ5ø0LHž­ãõâ…¼CÞ†¸ ›[ß¶¢Ï”ï3yþ–éOPAÿ!àí±q¾–€½¶…Žxçä}>¯»§7àóá_䌭ÏÀ3d›MBãZ;°È#c|ø¿¼Yþ Þ¤ÞéÞëGgìÔ ?ì)W1&ÿòKÙ©.©Î,¼ã(.dOàèÅzõZÑh7<“É-ŽYÃ7x>äƒÁuùe*©èw¯…¿ë^¥"ÌàU3:™Á×g[o¿7haPI;Ú ¹a[²ol"Dz€ïL,Ä)Ç{ù¶ëVþdÉ^Ya…öÅÙXÑɄ˳ üP¶ýuýVº–Sää<Å!e3¸å½™ô!fó2OúÇz…Î߀¯rØkƒÎù<ðŠåÂÕÊFí}IU]•UÅ+pT^ÁxÎß=2ÈIéà”ôz4¢îÚÄ1 cFÑ9vÓ…‹WCgSWYÞêOv x4rÿ›ú"KÉàÏeÎì™Ïb1"tИsŠãy¾´ê÷ò–/×ågêµúþ¸:îµÇ#²¶ñÕÙº[øH“üâ9Yb:´'½Šßò?}e;ø§5_Ú$âï6ã“O?º¿ëžÀvøèú‡÷:÷:™Ð)ÀÛ@Ë[ ˆÚ!ÏW){àþK‡eèOêTú-À!ý{®Ã¹IR¾k @“ü™ëàC¿çMÐ’t¬¼W¿™ÇH8wqNÿ½ÅFŽ7>6JâÈ­H>·[Ú.Á›•eô-ýbêeÀÎc[½ú´ö›°2Ö ÿÓç…? :ŒŒ/–8¡oíå~ù ñS³’dœ‰é$‘ìÓ½ÔVÑVeVQï÷ût)õ7·°Nè…uY©îz¥y°0Ök"‡cîŸÙ~Ü­VY ÀÀg—¬©É)bHtz€LŒ£· ³hÏSt§_w‡@Ú­°7—gÞ<ڠذXi›bë—["Hlu¶>Gå·•Í$MNN‚ó$\@_c=*N¼rþ”Ú…Î_0éÝJÛšàÙy|ÒOAÀ|o¿=õ÷õà›´ uƒ}$H |COÝ uÌ èÅ÷µø—=[z»qç×2—æQæß:x¬<§”¿õÃ8Ç¥¥¥tñâp$ÈoM.60ë1ú›)œÕß-޽²›â¤.¼LªÓn© )Âèýa!›ÝC¼Nô¸G¯µ-»v¯Ï°#Í:݇õçkû 6>añŽ…=óÓ è%ø 1Uºþ6îa|0óµfN£ ,¯ ~ÀGÙ`<È#¶O{C:††ð¥ª3ä)´40_RÒ‚}orDÖM[½ÁÏ=îï#`Ø?0•ò¥%ãˆ%)iqq1âwòu*‹’ÚZH‚vÊMœñe4˜Ä \Äe¨GÎyTß:±-%¦>{iå‰Â~nF|–v·ÀÝXƒnâ[ÙUU<8©ãËxôƒ+g£»Ïõ¥È1Õ±L¤ŸË(6SóôéÈÓœ'ͧ>"Ã+Ÿ<ZTŽ;Tþ PWÑïÆ­¬Eÿ˜þ “Cü3Ï · T·÷Ú2B™çuCOd$žkŒo‰7Žm‚p¦S€oÐc$ÍÎÎà·³Tg‡ÐøÂE†z(«JÉ<_Yæy× Oöd5àT‘ú‡ÓÊü4¼¢Bèô_|–64-d‰¶§¾‹ÀÓP‘οڴ:ÿ(2Œ ½r0˜ BS%¾ȱºLÐ …¸¤¤}"ªs˜xx'}é÷~ØVá¨þ±ÈœÓ¶¸0CµÐùp´HX]=ýÌ%¨¤€vì9-§$žã¹,מz>½ûÕßIµ-T‘7‚Xãiaœ³~8ÿÚàR5Ÿï-ƒÔžG\’æg§im2ñ›v 37có4ÝøÆ0ˆ¹4péE:¼Ç“ü´`Ú'xW¬kFàPu@0o‡Öî=}iA==6A>a².Þ¹ùY›8Ø9‹`éFws844ZÈúE²‡P1è×BfèÔØÃÔ¼G&·0ã‹Väá4†‰å…+spïåÄV {.Ë2NxÏ“7sPç¨ì&É“#78+”£pfèègUÚÌx«†m´u‘M­q6‹ï 衞ÊKhTwÊÔöxö)kLóÇ ‰u©{ú¯rDÁ,•(HG`V´­Ó%úÍm±x[{–÷QÄóÙ"ÖÙþæßø!” Û 0¨tÔ5Ð&óRd|Ñ-b‘shkjQ*`b*ð¥(D ÌZ‚¸¶ ·=Û*'Ä}ƒtîßK¯¾ôJœ¥a"“LÁ™ldBÔƒ·]]AcF©Îcº DtñÙ×ßür+i{fx/‰j)èÔŠäFœŒ¶Ó|í3ŸI££ÒW~ÿôýßóZÛh3ÐŽCÿLE•­>&çB¸èÐ’¯TÑÆ§ EK#H!úÔó/1çû±Þ"JñÆ&LŸ€Í: ý´”› ùc*_’!PštˆÏÝ@¤%u}+´‚CеAYì­AY¸Y¨²5í½3eÝó§çöÓEPLŒOÀxÛ`ô´eYX„n4Î!X˜¸×è<Çl¹T;lâtŸ»æp`ÔPEéµàÌ> ]g§T,䫤48ª3A1…óÝd§mÎHÄÿ è¬"¨¼?*”aðQ¦FH±ÁŠèb:LÂqÌüö½EC¼Þ[\N‡­žÙ“ëůÎÔ¬­Aãže‚ΞWæÞH3:t‹UW× ¯"S<ªBEI!¦QeØs m©ß>õéÏñÌÅtïã,hÁ ¶ðÙ¡¹/Ç8rýÞåOᆟû·†¸mVU6m›F·çj'[ò7¦‘Ž—( vï& |d¼¿¥…ªøþsáTÐX©.ÒNåaqvþ÷\È–ã[<"Ð ßto|žÙuÂÂc>4² Lþ0½ÆÖB›f¢Û‰ýÛ þS ¼¶r"«°8 '‡JÏáÎbÈŽ]pD£¶µ™ ' i´ä Th`Ð`·ÍšßEÜG<³#‚üüˆÌD»Yä¹^§±ÁjqA§êx#ÿ—OàpŒ%œ*Åby(#O\ŠñÖ•¶É–Î °O¢ðö÷‘hA²†Ê†Y¬%$²±ëÇ>†Œ>u“Ä61’6È>…_ixȧ‹ù"3o~öji‘…2ºL§+0åèã?3‚»ÈQ“¶ps„œiêʲéyÔ'þ¥¢R?\Ô©cvµ/$¿û³|¶¡—Àt¢}·Õ‡Uà¦|Ïïþ´³9[rÖäÅ —žÀØ)¦·ßúYÈ´ äQGÛ$`¡ “°r~øj¦ TYúðæé‹¿ú+é˜ Mu#]y‘[_#•<ÊAý¢ý°™Êó˜+´ï§œ¸2¯ÆdˆÞªmÛõfútÉË‘=EÖ©Ò~Ì^T‚OáàB¯ëìl ‰ë‹d1 ,d+9ËŠeLœ³u›²<¿¼Ç8ßäˆ ¹VRòÈòJøÅs­F0 í˜Ò30Ȉ´±Bæ W+,Ôa¦™l€¨w™løÂô\è$ò‡=tq¾’DA+‰5–uþø’þ1uZ²‚¨ö¶e šî?:³\/ `Ž8Öá…:Éœ‹üQ=´ºÇüÒëpvtµ¥‡×o¥ÖºÅç£cÈ!ö’ L,˜ç,2}Ý$™üȃ#IÒõ›`'~{ö™¼$ °-Î_Úo‰:€"€ƒ£¿³^=ïîÞ{t}‘e9(3š1‚¶wWhùµ¦šþoÕ¦Æn¥|ç6‘5F?N*çjà J‡òc3 ­PW„‚Vma羜àèê¦=*Á/ 9ƒWS$2õaõ¦#öaâÑpdOÁ;`=ðÜtÖp"Ô'*ÐßÔ©ý¿¾¬m!S/ÐQ,­sðÄ8¸[쑼AcÙ`¬Ùî7oÝŽýªgœéمЯì\¢îàYÝ:—‡Î 5Ê|cßÕñx4Ž®bzý»¾;Ø:5ê© ÇFG¦¡!ªUCž§46öK…?tuóãümÀĤ8:{—uˆ %å›<[Øé¤PŸ~õ³¯G’€²Åý §ð}8‚Nߟž¡«I• tiÐ;ÇyÕncìcÈ*KÏŸ?ΞÙ^ݵ™Üi{MÔ!T|›’3™;Î t+ñCY§ª ©DƳªX^!°Ô;üí ÚX¡¤Än¸WpDQöÕƒ“&ˆyÎ(‚<®LýƒÝTѹמl û,€•þ”Ç&]ºxõdê*ê-&ƒŒO‡sE§© ¢¥¥ž!Êuf²òÜz3ƒ#pp¡ ÚÀêFHNìúRºµE«a×.Ió&ʉ¯®Ûk­lVŸ19Üä¼ÀžÉt8žŽŠºBˆãRêšHHF7ß@Ÿ+á>çÞͪ÷,ÂìŽpD¢ùù+RoO:…úgºbC±?<˜ÿÕmù~©Mç>™èc"‹ÏÖÁõðîHš¢#…ß $êðO¾Ã1â|¢hôŸu”˜~ÂÞkkV‘ÜÓÕÓ³£˜fAƒl™;g‘|UÀØwʆE’a­2ïÄaÒÝø€uo¡·É˺ uøî’üo§fI²PÉý±íCal­ûjûö&äSž)nìLùئ&'‘3Vêw!¶³}w2•áðtìpþ0F™TÃéú7áKX˯`gØš:­±MŸ?‚>õsØ™¡´«·[çAúƇ7ÓåKx]™°Á/?õdzæÚ³©úSß30H'bvøûÛñÊöEÛEýÖby…¯3GÝ·ã)ÇÔÒº ž« ƒ'¹{Ú Òåû3ŸUüfOH|ÙƒP¶ICêó2O•®°üSÏ3eºœô¢M醑DíKÜòþˆŸpG|po”M¥eÚGŽaðÔÀ*òáY§å%'Ÿÿ +×ãZ†•‡ r|m(iß—²ÄùjÎÑò½_„GLé+±«¼)¬Õµ…þÈ~æ<'ãܡ׾Íd¨¾h›Ï H™®#Ú9˜háÂã8¿kã*Ÿ³÷¿ËÎ'÷9>3£y¡òWŸÀÏÖ#œ,êìîNo½e‹dõy,’>à÷lÕþ÷3[ãª=™›äþ!3áò>n ]ÈÝR·ðåú#‰˜¿ÊkôE©'ÊçÜ—,8ÊÞÂíŠbº¼#›½÷¹·ð æômq¯‰1¹º¨²f=º ç4o™ó¾9hVx»?ާžƒ.öBÙ Žñ®q=Yâ©ÉIì$8½æ¾2÷2ö–kü\û;묔Í×ëm=¬|°»ŸE%òÉ3ÝMþ)0Ü;õ7ñI<߈:žxÄsÄge‰ú­~á­þ&xÜe=ÛÄ€ëÂC½ŽgúPe; 3¯ÍºO%ª,`‰‚1nvŸÝóàÃÈñÏ{ ^ø™cù™Uª¶4> &»+ÂBZ×ÇÝZ  eq%m¹¥07\&Zì­ŸóÂf|Üi¬WzᇿMr1M?€ú¤´DKe\ãý&o;Â:-i‡¯\ŽØ~M?Ôw¨oÆ®vVÕ~DǾ:Š\‡ð³2¿T†ÓÚ ê÷<2tÒL‡àZ>ZvÞäDw%.TÏQO´hÙð£/È'XÏÏ´ 2=¼æ÷\¼µpF=4´\7Ï ù{.\ÚßL×ðQW¤t,õ‰Ñ‡£èÐt²Eîú¾~gâ†:_Æ 3MR—ˆ«Ú—ñÉN=Ìjoiû¸gvÇó8ac4û¡½RVŽ»Z^n^jRƒëÙ¡»Ç™I³Ú)ÜCáŒsއϾÞÜRI:µÚÂ쳕Á…J“]«‚E*ÛÒ &xfÕÄ€sÌ[#7?ÏùÓ$Ôk›:¦q5ù†‰!â‹û%>(kÏød$‰ÀnÔÁÃŽþÚCŸ+)(Œäkt-}Òb¥]³-4д¨Ößúuúûé`ÈÚíÂwrºÇþ)‹è^ûà.ëjÏö=Úï=bÖ y[¼ËŒ—Ú!Ó$ã-um¬ltœî„ÿ‡¼÷ŽÑ<½ûžé}vzï;³½ÜîÝíÝîõFŠM&©HŠd°#Ë@!Aò‡ã $I?Ø0 ð GVlZ´HS,º;Þ‘w¼¶½ïÎìôÙÞ{Éçóýí0† D|GÉÈKîÍÌûþÞ§|ŸooëòZâšè$„ì^ß-°ŽFŠKÅ+ñÌ¢1×olUÞú›J{¡Ÿ–öyOOoœƒ¾Ï·Ÿkäˆû¬%¾åP@J…õÇ_2#àæKþé³Âw‡jsŸ§Éçåw-¸'ÏÕ—>m9XÂÙ»‚à³2à±v„f¢ŸÅ‘vÛâžß€Á´IA•‰5ЭWÊln0 ßµó£þ}О¡º¦ÌvØs¹é¹Z.%ðÇŠJü_“ØßîC9Â÷”÷ã:2&ìßøñ9Þ‹”xÆTwÝ p›mÝŠÏ¡WÀ“ E®³¬ç¾ûK,(†!öµ‚\¾KÌÈØŽ|º ›îþ»¸Ù|‡•Í[<¨°/¼pò ½[xZI,ƒÁ€àŽr™H[ Ù(ÿ¸4heI«²ÎŸ¿@µÑ Ã…œê°×qäaÈuÞ¬H\0&³õóNÁöæ×aä,D*N–PTÌ4ÛÌ ƒŒ, ±Ñ`šõbÐwa–êœ_åå ¶¾ˆ·xÆL{3H?[Œ+Ü!àk•{Öʘ·b,ô^[˜ƒM*H¶T7Ë_kVF#s·*Q‡ˆA{[2’¬‰"š)82œT]:cªj`æd¹»C'ž ²ÙB2 05Æ1‹ËªqÈ’é»4Ì‹1ØO2>­´¶UÀ• al¸?‚¬ó¿÷oQÅ1Kö<Õ„ãÃŽôòë_ "––&¬c›àFSsk¬Yãg,SRi1«XaY]c«D•ƒm2TÓ¯ãwpÕ1Þc¿.¼ðJºyå£tå“#è¹[E ²²$}ôáG8ÈãܼyåýÔwøYî<™Ä9ѲW”^ýÊ_gO•¬*˜ŸNÖåùI2ù;ØÃÂ×< m#z½. 0X•é.Ú¾Ÿ{å«éú¥Ÿ¤¡ûÓ«_ü ph> ÞàœÈôáÁ9p´œ{Á[¸óºÇÕÆ2-¶ Û4{giaivwG8ñ&FÁ’†wµlr(Ë*Õ\1 "Zb 1 ¬Ž>ñŸ#tì´@ÆÎ 3uqTÏ+óÐ=YBd Õ6\pà8ÉT²ó|}ÕØJÉP­ª¥õpP@ëðcQr*¼®æ.²”¶Ã#£MÓÀfœJ˜âòî&¡ª%B§„Ù»E8× ææW§þ[7xö~ªA Ö n?v6-Óeka6µö#¸º˜ªM@)AyÀE]†!+³œŸšå­„ŒÂ˜•wÓ =ˆ–,Èg9÷ÞЊϊV8n*„‘­ƒ‡÷EK¶ººút›¹uȼD`T$ý¾û£ Ôä•À‘à±·2Ù ³­9” «`½Ú,Ñ|hdƒ`¹4í5ò¤Ê ÚJS9hes50œ&UFý]’aÚ ®-“ÔòWiëKeÉ&?zŸghc3z;V4r¦ÛÚú Á……‡àBXµ#huèçqÞ'¨vSa–g¬’¥óRAíý7ÂÍ ’Æs%¼ÖŽeg*tVßÕÔì†'+4 ø˜1ª‘¯ÃëðÉgàȾ7J§-<Ÿ®&JxUØáܧ'‡é ÑÂåÑØB“„#èw‹„ƒð©\²}­ÍJzú ÷DÕi'¾ÖH;~Šöa÷Q8¶Ó½;·¢ÅXŽË¿ûvȘ /¾Ï©å^®îÓÞ÷²åŒLN[X™¡'Mšñ©a¿JB‡%+¶MÖ Z\Ų©­'y²¿ûãï¦W_{ÜØÂ)KKèxr‚ Î* ¬Jœî‡¦×¢˜®¥Gà*Eêfýø H"únðÍ\‚¡0²'ƒ.ò‰5aÀ3䊚-Æ :çꨅV4ڡب ’ãD[hø±í‘× A Ca¤Z‡nd²^y½rͳõ®bƒúÛtX°µs!g¯ªÉ.&måâÆAjðl…ìeé„~ΘiC–I'®×ŒK“T”á¨b(#Þ{[Ïôû*l&hL’9(-<âÚªÙ©R_]Ë,,1-õ (É·ìX œÒQcŠã “ £òÊ“Š¿²Ý ZZ¶•4¡ÅlN#>«,ÞÊÿYÚ*»þ£xq¸ÂÚ,à:ºÈ¨Ì«ë¸§_ôKüó¥®Jq¥ŒøÌï¼·ÿY<ðøŸÌQ" 5Ðë©§ŸÇ(§§áG…˜ôöµËðQîÎìèI%U܉ŽyãòGéÆŸ¾šÚÛhk¼ŽÜ¢u7Ïkäé8¬…/ÃêƒÚåÂVôèûwE©g&: hÄX¥¤“×,WÛ[çÐ ‡òQz)@®"‰Ævpal¨CBKÂAe;ô6Òünu¥<ÇÏT¢µe zÚ©„ù7ycÒ±«Ý-¡w­¢så §L¾Ô1jr”F‰F²­ã Pno@ÛìM§Ý6ßUw-µJ‡ñPõÑ­ ôêÀÞ%ØÇ~t$X%¥3MC¹ˆn2ÕwvÑ8V·3‡ˆòñmæµj_ÃP]PƒT'Ž:zAa–5,R6袤¤qãu+:9Xq>yЧ‰n³$^µuÙ2j4}y̧ì7¹Ðq5RÔ%‹ÔqX«ºPt ÂÛvïÊ7õ mŒPœIVݦf%· îé`’w°tªºz–ÚkðLÏ[¹fâFd˜3ŸûÌœ ØÀÜ`T8Å9ϰ5’6 |~Y?Úã;^± ¿±½8_Šä,÷Áþ0 Çð娾S_¦_²)Wþ» ܘÀ®ø“9½u Kk™#Ésöžn‰Ïd‡,¿ ´ÞºqÝ0Û—•&ò$g‚ÞÊÞ´SæHHê ¼·êÊñÔaÅnM^; Þ×PÅ*¿TF±˜'Ÿ> cvz1ÙÓ„e ØF’=¦§§ÐgWH~lŸ¶pÜKׯ_l…³«ðóù§C>sðò.$* #ÖÐq+ø^¾„ x íKçñ°8ö[J’RWw¼'(üÛý¨ß°IÎÕ$~QÉÐÙE‰6%$ÕçÁG¬d*‡Gz­Zÿý{$¢Nâ$&¡{JvÝ.[â3|h‘+\“zo|´-Ñ´°ûp`Bú©â¾tx€øm{Yùÿ}’™«‘i¿ý7ÿk@®bW•ƒ vȪŮ*ÁFˆ`ãê tíòÑOí%òÏ¿™pàæß¾ÿ)Îô©-ù9ø¡>² ÔAçðMkø‡ôýÄ+þçóx¦/bÇîLÒ?ëÐo#à夫ðrlÕ3ôïÁ‰blÉËdšÌ·øž>0ç Øñw¾Âá̯êï„q)2OçñÏè‡ï #+ñÖñ;ºåó¾½çÂ3žƒöàã‘3>ÎsOæ2Ð&|cߌ'|+¾«Qg6Øì òé89c÷ëgp“¯Vð³”Pn˜tí¹zöV) §ø|Ì}³†H„æ§27Ö<Â9øò—UÀ§\«ß_,°09À¿ù(ÎG{NÝT<‡ÀšëGó ³DÎyôºÊuŠàÿYò…> +Í©äDϵÂÞ@ˆz©E횪î¼x¸†Ø?Ÿ‡~Æ\ÚÏÊÅ}™8$í°.‹ÅÂ^xŒ›ê|”áŸëOR~ºoG}rcƒŠyäD$Öã³><†_ˆ ØÇ-ü•)ÎïÚ⫌ke³­Uƒ’)½ËZüy ;Ï[|®ýÅóv{›$ÍÙž]ÜD1 ýÄg=‹Œ^2œ³p fòû¼âqé˜uåY‘Ìšôù·6·âK>| ðÕ‘@ÙÇ÷v)àêÁŸÓ”†Íx¬79ófCÆ|îÇäéRýÖÄñÛç2Û= ÃeèCSþî¢KM².‡Êôé@4ÇÎ~µÇâY§c$žpãú-ç-Ãþ_]$hÎZŠË‰;4¤-ü\Ó‡ÐýCž‰GÒ“T$>Ù.\|liÅž…Žõ•fG‹PèôÞ(+ŽkÑþ4 E;Wžf…´¼À3P'/ÂïÌvᥥí^€]Šýï•ÆoÎ?‘·J“!gS{× ³IdÊ£Fü ûún¾Qó¹yÓØ_´nGèêé [Ò-ýr»Ü-_5)ü°²ÑNuâ›|'‚—œ‰~“å”&“ºv“W‘ºßmNÙ ¿\œÿ6éâe [MA~ëzµÁ |—W*3”Õòg¯ÇòÚ’éûÓaóIóÊ;í– ìM+ò¥Õ9Šõ«sŽ“˜#î‚81FöC%ãèË6ÙÕ ³òÁ*ÿYŠ =3aT ï´‹–óf´aOÚ,®SœÖ¶Ww°©ð÷…Qè>ÌgB×±¾î(d2¾¹ˆ Ø_€¯[1›Ùêýæ[›%Q”erÒÔ$Iô}-~²ë‰¾ó:ôí¦fñ*zwtî“&Ùÿ4…‡úµÁZèNeçEa1] ÷Hdc/ú4í>¥?g’b4ƒãâ­ Þ èív‘?écP箤Ìáz«ØòyÏV| w:t ýâ]0n#_50mÇãbµú>ä§&â< {˜‰÷j#@ V6céx^¼·©.Cò²ÊC¶ñ.œ8|íEœ½çÄB yæÁnÙ™Íô1;äà—G">¼é)î9Ï”…úvô¿ˆw^í­N¶A1Æ2x¬m/ï žJœÊyå3Že²u5ö×£)bÕìgÙÙ0q ¨ºW÷1ñrþÊmu“ˆ=I÷ ~l®<¦ý¦]ÆŒk Ü»™{"ô »ßT#¹‡<à }”ÂÓš)z2Ü«H-ZÎzð€c ƒTk ”­­”™¤ÅB{G7ƒÜ&2_Ì]e*šnÝŠq³j4–5J+¸‹Â{®Ý¡rÖgfÀ8Ù3€³ÞŒ.ÛìÅÏ¥òS%J¥<—(³¨2i kgpÁP!A>›Ó‰Só°°VýÙ2`—À¶ÌCD0°iÕ¶‰:l¼Ë•ÊÇ*Âi`ÀduêI¤˜Â½üñ=¤ÑÒã×ì ÷g%‰DV Rê¸Zånx5çW‘ŠÖP2]`'c8ƒÒ1‘z»[:àĪoå`ØHBª|Z>ŒÀjpôêtEÿª ²*e\õéY¯ Ëûè]{cç1ZÖ¦ª†Îôê/ý °­Lï½ý'A@®%Ú–¢ìpƒH0>6˜nWà„8Ùí-šZÚ3æÂó2*‘P'©°Þ!Bá=õÛgi»*.³&ƒè&<üÒ—¿NËÉ‘ÔÝw$°fe( ÀVª¦†@_Κ³¬%S§“€SYÙƒª3*Ñú£ï¦zÃϽòùÀ…»7€Év¸c:~:½÷Ö¿‹½ÜÅIUEÖPï¡TK?¤²çˆ}†àðåôÔóoÐæþ©4òâ+éîõ‹é™—>ŸÆGÓ¾÷Eí½ëï ¥ýÁþRw÷ɯœº ňS§ûð©t±køÌ}ÞÅñ„³ á·D ybl8 _û$½ðòëéµ/~=z[ź¡™VÝe+.uJx¾±„.óö8G®Ð1òÌó¯†cÙì7 °H5øÛ m©Ti4غǻVnÞ¸ž©îþ#\â0è|ŒC…¹weLNO­UaH¹'ƒ\f×Õ½–~øÝK?Y7«c´ûÎe=WØkAú•ßøµtâì…hôã·þ$=ùì‹àqQú{ç÷Rc{G¬Ñk pØò_缌ñù^%£u,Ú»tõö²Æò44<­<@¨¾ýö[Ñn):þùgQ”eâf&¡ 0ö~ós/¿F;©;éÖ½;8 Ë î7#øªi×¶“Ž9ÊYs· 4?6ú ÝûPÒ[@IDAT)U$Í¢˜˜9<ƒb³„ ªÃ™-nQ°Ÿ~ö|ºx™NV\#,z©¾:Í}íS\“pãÒOIJ¡Ê°µl÷‰ÌèWø¯ÿöÿJ€ç­ ²µ’ç®\<ò§™—â†ð‘¾ä{á(„iG¢Ïû™I@õ0hÛMpÖè÷ÓoÿWÿ NÌRª²?N§žzžr7pAX]ÙFvc¸ÞgÆ¥-²uÆ.rˆ ÝÝ]ðãáß«p ×ÐQ`x(;ù¢Îk3ŒC`Ã;:ûÂØ½ôÉÇd`5¦£Çަ£é|°2è}àî­pºw÷ kùL8xäMòyƒBúÞ-‚^7IL ›ùôííã8QÀ*Ò èµ–¶Ku$?ØJ_xXm®ÓÚûðÎ_x>kD’ÂøA ŸìíKϽðrÐNm“)f''Ò©³çco—/~wçÂÐÑA`@¬Òk{:•¶hë_ÄÝ£ð§ÑßÀþìùçÓoýÍÿ2æxÿ½wÓÇÜÏl%™ß÷ +I-¶u1ÉiiÎD+*eMgŸy…àÞ{)>¢‚ NäD[‚5gÝûÎP«¬B ‡³èH.Ña<ÖÙ¶PFU5 «.¢Bž„ Ù‘µ÷ÒÐÒ ¤êy…ñƳ&Jßµ¦¶”_…CX˜Ô£Í \V‡ñî9 R1΄O[êŒöe{®=þÕ×ե徾4x¯¾Hö8xkðZÇÒ O€”V‘a«€¼ÿñÈ©“8XÈÊe :·vÔ“qò¨?Hž¢À^;áwyïëÈ(Æ¢ÎªÏ¨Ž à‰ñG$Y©Ab3›ð…¾Ãiš¤¿áaªó«ŒŽW:Õ¡øÇÁFØ$ÇNåg‰ÜU†>Ž Öce´sÛ*R‡³ókHÉ%u¨n#[Ó)ó­ô2 ÛÍ=ê7®ßÆ‘;‰, ós+?mõè®Û-Œ4“-Ž0¯÷vŠ‹;ÀS~¥Á'ÿEµè5Œ¿ƒ½ ¡gûC=ž½ eŒúvtùàüäíµÊácâ—íí›êI|#pœ,}Ï·ýD=ÀŒí9ôÄ&ðÎÊ8 :†MЩ |zD埶J$5ð¹ý6WqØžÝʱ¿¸úš…¶…%'îçÊ “XÊXã òfv;•9‹¹-òlgÕåþýkÎm¥ŽzK.²LÇ®AÙBpÄÛãyáPѶ…öÕ'*ЃVè(e7‰%hY„ž&b—¸i`|Š k ÝÔ…M®•Æ´S1ûÙ7[~W¾»†|tn:|¬€0¿¹¹€|ÄyD2†8º‚¨©3ìþpÄ‚KÑI‡µ¬jj¨MÕØKîce‚½p cê|2™[¹Y†mœµ«Ç¥×+. rf}ðÂÇI/Zâò¾–Ö’4¿›á¼ppèpƒ÷©èì«`:eÔ¡й· Ž5ÌCÚ¿v@WèTð0 ŒÙcë RµÅ¡%qOÚÖùQΕbVŒÍ€³VÈè(σwíŠ;ÁÏ8'`"n—ǃítý Û x··Á/ð£[d¾^3ÇžIÞsßž¼ØDƒ":ègèz´´ER=¼g‘Ê»:ع¢{k8ÆfH`É#Y ‘n! È ÷½º @YG LLAX£ïÉKMbMÚÒÜ!ÊØEèìKØ›oÿû𺕰W뵬è1Ч*ø<»¢³æ©ë>kçl žÇûl ÛÑ6˜T³óý–ÖÖp"špÙuâdúÒWÿ“tüP/芞*Ü xèøÉƒ×Z¦>ÂÑñ‰ôÓ~=°°öpF3G,Ô±ùYÌ÷i¯ÿ³¯^`D>AÝyêâ¨4$Äg¾Ïÿ½ê#ªŸù[~ªLw|釙èì´h¹íö!†Ú%ªºVÖXÅÚ‚o@gµ/[Ýze–W±d…0NÅ‚XŸrB'¹Û”×a“0§ ÙQQ ­*W V¸r· Þ"¯`ùá/v4uuå”¶Ÿ<]XØ ÞÏå…›xöH 7ñ\XHãî]#Þ`€€6;*²Ù³ ?ÔÁÄÛ¬ØB{ ÞÉLvˆý³6 ØiGÕ9ãK«Êõy|«.>ÛgȺ¢¹¤ãv»1×ÿVÎÊWQøŽHÞÏ’m9GÎÍëiäÚ’;è>fbµ~LeµAòñ¬ëRg,,"Pø|ÞéœÃ*O¿§Ž)OV^h‹eÁº8¥XS$X¬@mU;ÏIÛú¦äÃþî¼!Cá+Ž£oWÊ—Ÿ™(¥¬óÌÔ?]§| qüÓ÷]­º|jŠ5âÜLdþ'NžŽó£¢[: ½ÁqÐAsœ§ïyNâ‘?]ø¯ r¨^ê^Êkýû:b$gúfÔ»÷e¼uõÇR/3éÊ ¢g577É{Ð Ÿ-TáGœŸ²½÷`oêæßµ«×ÀC}¡ iؽcK18£/ÙŸ"v$çìý©Ïñ§gêžL ñ ÕC $å46©=wÇÊ=Ï•E04‘á6ûvnpÜk§Ü“Á]_W„ó3Ïœ@Ëèøÿô=|§TaBŸÁw$”@QƒÙ‘Ý®IÁµ¨w³àOÀGÜ7 ¤_¿?ŒwåîíQÙÈ3↸· ψ³â»üÂ:Äyp<›ˆñØ»8¬ „gýêÚTY"•÷ðÀÝõHp±+£¼maΡÃCÈkîªÖnä•ÙèO¡c#[ “NÙ)Ó`æ-t૯5Ã]ñFÚbÀC¾(@ùñOÌu™À±C·Y>æøÈXR’t ~xnaÁñ»|Xf ˜Þ¸~5yúi|š5‘pÞ‡ÏÉÖÅâœÅV¨¼eÇÁ»åÉúóòµãÈC÷q15K)$S/gš˜ßÿþ¼/×ìbÅIÜöSÖÎßêiêäÚ—ž§geL'ŸûÉ=¼"¨ÙÄK|=»ø¬ÅáºûSÔÀ¤4)­¯ÐŠÝÎA+ðÉk7nàkƯ‹=uùÒ'$¥ÒZ›s·ò'þm2kQøßÕ%© æwqÀÇ^ bÊ/£Ã4§ÌñÌÄY Ù•äÁZô±/ö|˳¿ÔEµŒÿh3b4n¡÷#[l›î—Ü£û°›¨-ÙgÐýĪ·Û®Ü«ÀÔ?=í9ƒpЛ‹¸ŠÅcò!&;@{̤ýÖâ‡weËgZ[zyŽ«FFÒî2¶~®^#†iG”a ;xî6ô»Á}Áøô…§:Õü Yµ¸X M]ºr ÿÒ`ظSĆN}š¢ {yXŠ ×+е>:}ˆú)òg@šEœM^Ìî‘=a`¢äää­EEv)ÎÿETgS Y?¶9hn¬ây Sdའ(;$+ &LJÙmëè«ð4 À*;+ V DjÐ'‚ ÂàQšp®šÅ¤B¯º €È0Cæ4 ±¨ìÊ $x°IÃØìŽJ‚.‘ϼ*¶Î.%‹J„ˆVÌ(õ"Asï70½Få¯]ŽÆ„b†p¨¬®ç¾eŒk‰fÐ??§Œù jp`Dh re sHžU!ðªo“1á€`/««(| ï•[¢Md´\ç « B«xT.e dgGwê:òû»¡M ‡^Û±•¤‡{9Øþ4 ìûü /³V«¹tšpRBð‘*Ý»·Ék ÓéÓO†ãJA«¡§r£céÍ7ÿ$î<:úÄ3éÊ•«<ß’Î=û¬nÝF/Ìͤëó'lµÆþΟ>½Ì ×J¾¼Ÿð¯|í7¹Oo•Vü¼íO_ù+¿ÎÛehÒ{–lAôú_ F®ìž u¼gÖ‚ó)œ_ð„ßþß·N¤O>ù€ŠŒÛ¡,à~& ï~ÿût8œž<÷<÷§ý8}ô•@´—^ÆèûÊ/9=áÅPÜÅïEÈרf|ï'ï ø¸?zÞ…‘ÿ澂ҌRY˜š`Óà˜Îù•U'QÑ¿ÐAqçÖÕ w+u4¶ä]O=óBa×/þ$xM)Ù™k ±âª,Ýeßó8Zä+í¾ H/ÂCÇÞƒ_æn*ÝU&TàZ÷hA>};ð²–jñ%x»w½Ø @Z+£º¿±µ=‚žS$wœ<}†÷C cÑéêÔsðX:ýÔ3qþV„e½0Âà 1*$?øáyöPzå¯D`÷Ã÷߉ w°·E³I-ƒý¾NTƒU hù‹÷‚wö¨(ÒÕàÈC’*žN]=‡yžöÿ(ë/¿þeàL†Úò"Iƒ$׌‘½ Ï& ¡a|¢¡µƒàjcê!ÑÇd¡Eð¹—{¾†‹I@ú*-÷Ø@oy(U´9]šŸ ᨂ 3G>=KÐ$Ÿä,÷V€“Z¾lP®º¾ZÓˆ\@Ö!£ÄùqNøEà…´‡ßðáåå9dJ 471>÷¦(§Ê0÷ø>`@añ¹N]å¡ +›½T¦ƒ7AÅüŒj7`£ìÌ'¥ñ­a¤AeË|i[£p¿Ù‰ÕÈeØÂ"A3}’hÄáBþå³à l¸ß Ÿ³;rÅ{]"»“ó•¯YI›e/zwØ&}:^p.*ú&ŒèÈѱ®SËÀÑÕ‘ž£Î¬©éL1lj¨ãêÌ`ߣkLúÄ:{3@àå¦â¼©H=‚qVÈTÉ)g.y?b‰î ÈY¾«ñèÝ4ëD׊ÊÙ›²Muô/þå*ÔOÿìKœ¦¾TÀü»"‡VÛ—-¯ûE½Œqfàe€×ÐÖ #AÄjø Õ^ý÷ï£ó EU®Æ£®ŸîÞ>þà=ìcÙ\ˆüЀ±RÊ ÆŸ/õ¨h‹åïÌg"‰4Ýb–+ë׎ŒFàEšÖAm«7P {åNò¸±‰jPøœŽÀ"Œ³îu4h×ü6ÈãuI:´l1F Æ€úƒ†“F¼†ë‹‡®¯†Öeð~ä–14ïݾG@ žÎcÛç÷œ©‡®ÕÔô8)­ÏaLN„·Ì 7‘=bœÊ§tPêØ/,Ñ)¯Á„¡L‘jŽŒŸ'`¥Á§ž5¼ÒÉBoèÞòd+»$TÜWL–¾÷Í5rœÆjù6º¿ÉDðEN>ù·[Z½Zɽd }VØ!C\§w™ùÔSO"Ï0’±‘ƆG£ºXGºvLòÜLû&Úå9g{{+xè…àg˜“'O×qÈ\ð_ çÑË<Êé[‡ÃåËCüõÿô7ÀÑMôÒþtëæ pUYm@…«ÐÃÔgLüÐ@ï4Þe‘ÚSÊ©'O¤¯~é dç8øÍ×ü&¸é^œÓ€£ëZ˜'ÐHv»Ôg @coµ’Þ|Ã`½2:ÖÍ@V~KÒ·cÙÍÇn ;ÛÈOÖgàÜÏ”µê)ímé©çΧ·Þz‡êÝ*:RUCK|_é%v ë9ØŠNFR4øäÙú}+¶M¦Ðޤ\7Ïßµ¶'Ÿ3†çŽ pR;RÙÝÙÙ†l§¢Þ&Ü”¹ðAïЋ€ßÕ>‘¾p.<â:´]æžËéVEr³üÑÃòå?<ÀjIVÀÊü\}V›_g¨t¢ì^ãlMolâ¾Ô´ 3žçlç OMyo÷#C! âplç ü>æDÀ\]7Y9Ä2²0‹jUà¢ÜwÿÚ{:‘sØ»´dÒ&zŸJ[·ëdþYå)´QLuÆ¿enUµ•>­Ê’>t¦ḒöÅKƒ Ò®Éå8¹ªM }îD0Ði’Fmá‘Bá;a™AãQaì<ñ@úŠN|èXʤ]ŽMY¢<ž%8g©‚lnkç¹­4F°¾µ·;9ó$vSwÈ:»ï™D®ï¢@ÙšxçGûÆOûâÜW4ÓñÌé0W¦åxfÿ~ê!{å‘ôŠ}ÑÜÜšFÜF¯ Þ£. ÃÛ{=å_YàLo¨N ê4ð¼…ó[_Nt¿Ç¥#ý˜ˆèäHaðdôÚ yóûŸŸãIÓòagàWüDzd-¾Ç\ßQ~Êß²ïòÜÖW·I‚w8²Ñ×Ô5|ã°—&LÜ.g·h*è‡Ó×)fÈ» ú\왟ÝøÝÏ |$æÍä…>(ÛVg~,F`Ï!ÓåµÐ$Kø ËÇŽ_y—{òxé̽JSÚRž‡¾ ñU_Nìvœ7#n·wm›ôcu_ø39'õšàÌm²SÈFö%ocÖ€…:—ó«Ãä@Ìž…ÇA4}¾¾Á;žŽüZÒuª—H¿îËw]·‰Ä›ÊŽC¸ épfóSØ„ÞÁHv×Ñ¿ZO—ÀáÀ—jCÆòŠ"}&wnߌŽsÅè_Î¥ÎègíÞÅ-&ë>;ðkÏA¿ˆsd×–eAšìzàCð ;ÇLŒ3Rb uyeƒ„œPwÑÉ®/μ·ˆ@›vƒ¤‹H7Y‚Ib $t™8|š3÷u~P‰g³.vÀ4€ã1ö‚²Ñ½ˆ[ž‰A`y*GÅßÊa82–ð‹õwÏxnj&ÎD<‰ÖÐêŸ<ï¾¢ ³ãêkìòâË®1Ï´ýÔÕÿb3Àгs âƒ'ï¹n1· í‰ Ÿçê_.+ôfÖeb£çå›X €b8f2¼¿+óäâË.ÉTâöþw¿·OŸÂVX™|yvÓ?ùGÿ `â—1ç®þÁ ù§þ`§¿«ÀuÄüìMØøœ0Þ±ÎQ: ¿Jða({žŸ4®œf¾ãxÒJp–¯ÙÂsÍðoÄsÒ…òÚ0~A’«6Fðý²™¿É³ð¥î膜@À×é\¹ÿ50êó.Ù€¦kϱýÔý]hдëâçÏù4j2Ðúü%¾'o~ÂÁÙài¾”…{ДûÍÅŸãËÿŒ›l(œ„.å+&ÇÍÎLo¡Ø‡ôq s[˜›|-\œ<’[X‹vRønø¡M’Rñ±HÏÒîÿ——<~Xnq­ªÅEÄPÖVLdç~myŸÁÊ ÉR®r5ˆ«~c"„úç~0k7ÝMôm`ê~mõn°1Æç\•Cã$;ŒFRd6í»í*æ¹°­vÄ­!P-͆îÈYÙ…}i~æÁH+S­<¾Ø¥$£²íœE*§a ±¹²0U4o'/ý ²º"Ú˜›äÝàˆÃín£ƒ£3Éóâª0à__W‰•„ K¥ýyâ("D$õ"›§9·iq±}˜v]âd6…>ZiÄ@ù2g[Ÿ<ŽNÈàöz ýÈv<Þ\_Á!y§°ô¼+KžÈ¥E%6 ãé;·ƒš‰Ñc³ï뱆 e ³×&ôX;éó°ZºÒ—<ØØYN#ø2¼S™¡-b|M®1ÿ2ÏÛ}¶ŽªéL7ØÂ¯HÇ]àa!Œïéû“¦µUÄé:: Êó‹˜éƒÃ|ÔQ šïÛiá—UŽEŒO¿ü‡ØMúk%ź[èß&Fm‹¼v‰n}vÐôj¯?39L<Ó_ÏõZò7ÏndtÝd"pE›X?¼liÚÄ!¿£ÏI?ï,QÄY×`¬|¸™®ú¯­¤·W¿M)>ùyüø“"éŠ=ÉWõ‰²l|tKc­Æ Œ§ LiÆq,XL`Ó?®\³Í¹òvYèÕ3œ+$%€ôÏ<êzÚòcå“:ˆ `vêS6ËŸ*Ø‹ãZ´(0N¼ šQwùE$m²f8 ¸Ìý‰NÚ‰¾ p"Â1v1,g—y- Ô‡âýñ9Ìç7<{¯ì,áœÍ.,‚Óœ§cÕ××üMÌÖVÖ&Îî?KNbÚ¡^‹%\Ä+a@ßp‘ç™´‘Ã~Z;Zù¬4}òñps‰ëË,j(¤{/t@§Û‚ü,±F"R&ʇ–€Wè´{éô‰>ÈÙ„w*ÖÑ_—à þnŒFz5aqª…¡“ñcÀ¼y /‹ ¹†:ކuÃ4—ÃÙm¶ˆUæÅ8k©NY¢*ÝJX9º籫7<ˆ5B€ê8—kÜåhþrÜr ýUéÃû:MÍö)‡˜¹(òÑÖÏV춦ôðäÁ @HKò ¦TñYd…1Ÿ·6œ~UTœ.ÏŽƒü ¬';(•Kðq#R™?Æ~m/K m‚2QÁ„¿‹2§’꘽êiÃ`kð…™ÜLÛ¾BÁzôÔ0·§ï@*˜š…I£²îÆU!Ü5`OÀÃ2È«ð‘ÏLŽFðAÁ§¡Ze ö> *2ó#§Î“ÙÓ,·"8zðð± §á8A)—Xº;{9`îH@øÊ¸Î<ûZTž$h$³›dc„onëÎsßW„U($Ø%œÒîÞ¼ŒÀ8Š›ZqPr&‘å!zZ2F¤ž¯{hl&‰ÀTc×S´ó<çä3 !Îâ—Êš¸áKf­Ã2ÞçoÛ›·°Ÿ$ø~íÚÅ4;0’~í#ëî3ö‚ß"È}<™02X)ìL°=³¿wv÷F{Ëüw~7ûo?”ð[*Ÿ{ù 1¾g)3'%¾w©z¯&ØÖÝ{˜ý˹ǼušV²½éÕ©ãT‚“7?9^Vbdy‡%ÉÓ,{“ óUîùŸŸjhÝ}ˆ¤ ð,³<2.§Æ„ÃÃV-¡lƒuhÍÌÈpibàZZØ$‚û¼óiW¿ÄýDvfhïij]]¢ ÎÇÛT„ß¿w'5#ÚœZ]l¦µ­EZÚÚ¡)î߆iýèíFµìö©Æ&‡xǃ8§søÝ¿Ce‡@ÿ»ï¿ m/(x4 æùö[o¦þôõ¯Gù÷ü JÊè'h/óÊç¾mÛÿè[˜¾üůeøL0f›ëTmïèâÞëîôÇü-YuúÂ~9§·0Úžû‚[eví곟€~áf îswù~NíÖ œX-iðfbb4ºN4Òr]¼—WDЇ=*@4M<& ©·Þ| ŨþÒtŸ‹rc‹ýã$jØÁB>±Dtü{wH˜@X›ígP±Žä ÿ¶z©†@u%JàøèëžO'Μ !ÿ¯ÿàŸF×…_û­ßIW.}œÞ÷ÍP¦f¡sŽ<¸í A¯ð‹Þ#Ç©|9äý¢V„r.¬¡ }žCg»`¬â<Õ±h{¡5î±×uf…¢ÝZ)æK'ƒ²HEEÅ]Þæ³åt‚PØJs*¹(HU( „G`œ—+{•3*±2V¿7?•UÉgýL™h5YîΪ £Tt6‘¥Œáù×USɉÜ1l¨n†¾Ù¤^·b¨F”p•ô–ΖP®UjUÆLP!ÝÝ¡ óë´24¯ƒ…@™‰SiÉÄ/d=x^ZÂÊ8K×USÊBN*uËÓí"`À Ê¥†Î-$tjË(œO8ÕU¤ÌGÆb(®¯*GUj²VqïpÔiè”Σ\ô“FÌP–wÙ²­Ì*>xÅé ¯¥yíKyÓt,!ñ¦½“ÎðïÛÂLÀy¤®¥¥Î®2f;÷IUs5ÎòI\¶K:át¶‚Ü .z)6¡C=5m1[˜÷ÔÏÌVTW2©TgŸ|Ic $X‰Ï™ƒ€À p‹ÊLœå$<©ÛÚýH=Á€ˆß—ç¨óHf³ºþ =F (ôhÂMõøž]q–Mf…ÏÔŸ§eœÆ‡U9óTÄÚNÙö½)vÃ7åRå(M›ðãZ5ԋܯsÈËä×þîûáÈe&¶ª_³¬€©U–$S 'ÝÿþÕ«HTe.uð çJtNuú•0l¬¬uZô<2~­äOê$2#¦ÁuëtÖ‘l ãžÅßÉ§ÔØªç ‡fðê]#&ÒÊs­"@ï_EWçLëkåY\d°W¹ OX%‰lÉÏÇ9Åï:³÷¸žÂ@ŽNiõ|ïSÖbËk³¡ÍÒ6;ò<ôLØC.k z/–•u?û 2† ºnvÓÝÕ…~Ž=ƒÃ€¼÷&rŒŸU0”ÒC{ƒ uõŽÂ\ëÇŠg°îC^Ë©Ä9ìò·ºZô2“Ø4‡†è†42F†8X]R 4¦ãŒ8­MÓÎ:¼ÝŠw fšxåF[mà"ÞŒùÞw¿Íʳ—º¹v’hû/ÄÄS–¶ˆµJ[^0 ÝÝ ýàl'èE}^¹®.­3M‚¶£ïÿÁ¿øgÑÂ.h Z°Ež­'µ¹&§lg…¡W•pÕòP:Î'œë$‰pàŸ#èÀ„íK—®¦»ÃGPÒ5yÖͬßCØ ¥8R¬À·Ê¡]ÓVƒ&“˜=okCe›ÕàûN7ÈzÝÿp"`S(cb/ÀFü‡B_~áåóéè‰CtX¤­ýÀkŸ“¾÷¨´yêü™H¨¨eÍmm­¡_8²Šã`M%bßsíá”åLÔ6€ƒm“ÔÓýLWtéËDH.÷ïP3ídÛù ²v†$ ½mn2‡òÅ+zLÀ³˜Jz”Ö­˪ÞÀAô5fòOñR{[zÔƒ¥¢*¬¯ïôZž½ð lª›Wqv$xÕüÜ6˜åTw˜€ÑÄ=†ÂX½ `Ê8y81£ŠguÆœœ»úŠxê˜Ò)ž3QGzkjkÁð+rûï¦iº0¨3ˆÏæmu+ž%Ég&gÞÁ]†]mQY¯ëcÖ¶ëòEžÓù(ÞšlåUÑn]IY¯¹ƒaphzr æÃ´2Ãݰ|ÇóÐT†lé>kç²ôìuöàù?AN>—wÚ%è…ßuƱL:ȵ£Øá Fßö*¶¬ð çp´Ý¥-)Á -cõ‚%ô9“ë¥ IÏØ6×ò#y‰{7)k³?tôÚú°›á|J-\‘TàÝðП³¨êgÛÆ‡ó•9<3ñE\÷ÅRùÓ{9V¦·:4…³Î¡ó|š}zKþÄI‚ânTyqÚÞ+Œ‚xO\\‘gˆë~&¿„ê0¶¶öðÄ‘Œ¢Ó |ÍgÄG±¢ ’ΞCÀyþÁà#Ò AcuõQÓïJ3ò ¾Ê{«‡MÚàdƒÇëØö¥Ý¾…>£¼g;!½’ëÚÅÓÍëØèl¾ììåòe¨ sÊÊr¸~Ö¾„¼c< Àñ\d¶t«LŸâ3þ[ŵ²eK>2TýÑg]»¾H÷ß…ïY¡í&ç„£˜µÿœÇ@º;ŽûÙWG^žÎ~d&26ƒ‰cföšwN!3õ?~‚kêþ„¹ôsªSe:ž¼Á31ðì–*è˜ã™;¿ë70#³Ý#ÙË`çk Õ€4«bÿž³IJY]·àÓ–ëÜh!gAj;®gíæákLpTΔ„ƒ/¡¬Òz\“è#àÄs¼ ñQøÛºú$z”º>G}¶u]«~E-v5Ì…ßZ5j«ÿ®.|6øƒVIÔŸêy%öÀY1¨xçÊøú†àcxªC‹Èº§ô{ªsˆGâˆú¢{ô<SààgCááY‹_þÏ!CœX?ªp6QMX¯9|„iäêâˆ/ÇȨ+ãíÚ úË XÑÓ’³Ð'|0tBÖëó&¯\5àí¤káè¼®-™ÀÄ*sN#BAkžë0!Á½y>®[ý]]QlŽŠÜR­à¨só8012ƒiÀ‰ïhTÎGb)c¹Fý⺉4æUÃÏ`*npJ¾Í úbMÁ›²GYïºNσy|>®Ùá}¯Ö’†]³Ÿe‰ŽæøÚ živV®/ðбøŽÉ®*)ym°Ý˜z•Éòºx´ü3ŸšÓøMçÇÎVê»ûº·4éĮյü|¯ì9Ÿ_%Õç&#–¡].WÏ‘O,Q¨²ïª¬mæ|Ñ h£ŽЫÖñך(,/sÒªv¢>T«¶‡FÆÐçõ?®‡]Rˆ?À •IÒ†wšÛ¥Lôv–xepÐ"X£Ò?j‰ZÒb  ÏØ•T^c…»ãû6â›”Åÿ"t?m"ŸCBîɤ“ /à'±(Ÿj|ðÞk1H¤§¥öAØüòò+ =Ïâd]”Nµ…=G;#YMí³«åµsíœpâØá°‘fè¾788|!;f‚;ËÕó•Óùùh¢,WZöÂЮ Ò‡üÕ ^ïÁü{•áÓðj®2Ö³ \MŒUÞóXì±…õ†m„¥ÿÀ ¤qmyçÊd»IWìv3ÓÓüˆ«9ü  Éë+á#´ÚÚ{ÁÃç|Ne´åÆÊ5ý»µõYàY[H?„Æ5‰¨žá>ïÞ÷é5¡¾ ®Rêg-À/‡õ㱃´ÌbÊ’°Y—¾iº¨ÔªyÎÎI‡ž"ï·Z^Ùá97lk{2ݹkLk° Ôª‹?ä.r`©õ`OgÈ,aþ€sÙÝÃo\FWd§>^“Jè‚fëùééåC9Ê2x_„ð…TÀ¯Œu¨³€HP¶æ˜d¿Å?¯Üµ():W~DÂ/¬M1àuhv×¶ø,c»FÇðºÔB1í6üŒ½+á}žZð\&§Í9öïÊ#ŒsÞ·+ü›’&žÅÇÁ¹g}.ò©]:?x`fm<þåêIÛð€uºˆÂð¥CKà³¼M²0ͽÉ#KàÕUU¥ÐFv½þ¬u®öÊYì&xBÉž¥ü[Ÿ™1BõIØ€^ “³¸l„´2³1‰ù|¡ÌÎs¬ýæµ;åµñýìF®<§ÔÏBWÁ¯ÝLÐ(< ÂØÙ톱EÿðÀÝ 7}äv_ˆd9èhwo ùG‘š­‰EïÚ¥õ«•u¥ʉ0éôª"p§l²ÂÃR÷pÀ ³` ßa³¹ €Yû‹T¶¶˜N‘U‚G2zËßZ2B•G}*+d(|e"~®`ö@Ìô1Ë€¶:…ÙçnU¦“Þ ×%ˆ¡b4›ÅÖ fÊ´ ¨´âÖuVzp2°0âøÝÌ<•¦¨f!Ú.aÅŒ/|'‘m¤’H‹ hnmG¸oÑÊ‘»—+ª NLÌ=ž~îuswÓÄØGOeM¶ Ü`jûkn%Ûeuá03¨ zo-„Ïúò@¬½á 6,ó0`¯òÐÖyõ‘Í‚R齿œÂP>*ݬê›ÚÈÄl£MVWJ *÷iPÔ*K‰Hä“éØÏï¹&a-R»`Há Ò­SÕ³´Úë ¯bðP]Í÷T,£²“±}¹Ïý—0õ%c2³±§÷Húå_á^g਀aÃ6(®Í3öo_"þ¢•ï8ü4Þ¢úGåÐÑ{š*뎘?ÎxX— O…­AS[n_úä#îŒy#Æ58&±ÉÄ'I4ì;z2íßú½¿‡–*&öb嘂A¥Ós¿LÛ†sÏ( TRUŽÉ¤cM¯ÞOOŸ%xòùô§ßþW©¸²<ÄYR˜·ËQeMâƒwsÂwÒ‰§N¼wJ›­Æl®Ì:sôlr×¼-9ÖîlÓý%»¹£!=h ­AÁpS(Ö»´„åÌÆ¹×ÜJñ{fx;}òý©®¹"Cqr$Õ·ô =FÑ’çêÇïýiºúÞ·;ÜßÚ“º»ûpêÏá¬â*îa÷žÀ7¿ûM˜MN89o~øî\JOž'XkÀ¨²ê­B'ÓÈÈpæ½Iü—šhÛ*½(>DWá)üe|:¤Å?V¦«8ü*€~ÙönV³Ž¢Œ3_ LÆž¨ŒMqöÄà4;1ºÛZÓMŽŽ* Êãï|;ýô-R$õlSy« P¹%ßJ CÙÚLý·/§ÁË??8_x‡ëרÒÚÍ+'`ÿ(M ÞHu\§0zÿŽÓ^“ g’sÔµ†óu‡½•È*®&¯„ /ª" ÊÌ2x™i*ôå#¡3r¹ù•œ4VbKîëá™:ªÕ£lÁbÖã8¥^G‰T ÏÐö¿£ë`:uæéCÅôè‰S¡€§ú!pz ÿ>4!y‡•ís}]ºt9ý³ñ‡é¿øæßJ]|ß c39S•¨àGÀÿ¹—ßÚ‘/¸ÎNž5HýÛ¿ù+i¾×ÜÚðÞ#G™ƒ¶{3Sñ¹gd€[Z•NÌ0SØëÜ'm«ßG‚ ÷îÝ¥bñV:ÄÛÛí(ÂdGÂ+Lªp½þî«•D'¯w<×d÷ÛáÈ/XtúàýwSWOAŒ¦tʶё¡À£³çž§2‡3{]]¡*–â:ç­8‡û„¹†$qQ…ÖàÂx¤b!æûV^nl]A~§ƒ}ÇRÿ½;ᘲÊúÆÕK±·.®{0´ïHmê×/~ü~œÕDµó;(òN3æ ˆã˜t>騂oÐR1ò>zÎ)®`°Ì‚ {9´n$à#Ÿ4ÃSÅ&3äI¤â»•dŠÚÒkÙ6WÌ£Q«¼(¦zÞH´'†:]S-¼iš¬}iAÌoòe£Áoùë s¨ttÌ“ÌQ«³Çyí0ãÙ®N¬ϲØËúŠK )Ö¦3@£ÅjÖ]îâœÄ _*}ÊiÙd2iÝÈÂÂ3æQdµs%ù$}U¢\±ï.+B&— øiØJ¯Îó(Y⬛Ÿfm(@ébkQ¡53~%på±X‰5üeüô§³Ñ²=ä//ßÛ—‡¿È5ïÃʹØK|aÆ~æËØß¯x,/i 9Ç„®ÁÁþ4ö¨z!áë@}úü~5£ƒÊí«—ÓðØXZ¤ÛÄÑx(IpÔ«GtjHKÒ›ºH!›1±dÔ–•ïà’T eŽ>ø¿íM½/Ï·Ù9Œðè \Œ~ôAÏÅ—:J´âçÕtn /eŒ²Ä ]|È;P„‘Lïܹc”rÞ[Œa¢’û­€çÈÏåî]ºÑb k“ ²AKfà3Œä†<Ð6óeп/ © ø½¹p*xá0 ãZϱ.|m1WÜΔÁîGÝRgE‰±¬qŤ'õ;e*˜Èÿ\::&ž VõËV¹bf ºÝ$ÁOþdUöOßÿ8x‘ð÷îpŸSgQö© 8¯‰5¬'D ‡fàØà¦Î “¬8u m ƒ œŸXyëìÓ»ÿÊ ØvÚlla——§.¹Kç¢I`z9ªÜ|Í$Ú=d‰r)tÖ¤ñ CÌ¡ƒÄä„lCªYÑÅ ŠGX¯Nø9›7x,ïQ^˜P¤CxéÀÓ¡Ã ì[]]Ç;¤Ý¯{ ‡0kgbÆž—QõmmUƒÂÚŸ:¥@“(„±rk Ng/üj±ÇfÍlkÜßUŠ-36<Œý²ÁÏÁ}é€ònÜè✫¬6ç<jMdÓú‘¹gTZ¢NÍ_ü.÷¼ï^;Íû¾ÂPC:È!öÏá°5´(½¸ÏJd°÷@–4ܵcˆãÁûçIÌUWrmÚ~®Aº¨$¸àK£WGÇ£#c!/tÖ ß÷<´ÃÔá”%:3±ýJø{¨t˜Ž&8 ®¢‹ 5íReUè~ü®ƒ$ε‹wìE[o!/KpÑA-Þ[±§¬Ñ¨¾€ÚÚÛp˜Á_ffãžFéÔ3´â¿·¯7Õ¸öe…øe—ÏÂu¯T>&˜{®‘ ò8qB¸[aª>-LµyMf–Ïd?”–’°ŒNâ>S€ËótÖ—bS¬³ŽM¶-.iw"A±ßÑÓÀø¦5à¨tÏ}GH–®³8`ÿonf²V»ÆqÂyÏÓv™+ïìS1kb(÷Ð¥*uŒh¹½»“.Ot4bn5÷¤9öm8#GmðŒó!ÉQ8ÈÉTeϽƒ=9–0ð=…$ℬ'™6âñÚ‡£¤BæóŽÖiÅÆ^^-ªÝI€O!øgò“thåTgi;uÛTî3¤^&OÃÃt.zÒõŒctróÒµT$çý-κ÷ø*Ú€'ò€÷ ’™#ð¹(ÍùˆCÚ̺…ä4ÔÚÝ‘:ó’?ÌÕX· æíEò®É”ÒƒðWGŸ¬­!!H<1q)ü 8Ãu”*£L¦”‡µuv!?é~ÑA"O[{М×3MMs/¯AÖ`‚·ø _¬Y\V†¸æpªóŒñ™¼Ä¡€‹gå)3S€\¼ùÌfýL¶òiêÞ¥y“¹Ê±湚jlh ä R `ö¡Înàôß²óÒσm ÿL¨¦&h¨2%´:+…ðéP[0‚-ŒmÁa3~¯ß ºñ=±x›Ö¨‘øÆsÎ/i'h z…$´û\L"ð'œ­<Ï[&÷/#“–Ðw}™$í¬±÷ÅñÊ*¯„ñz32èrçè»qú‰ ‰"ü@^j¡/ñ»t§S7ãáLÀ![€ŠßÝ‹|RúÑVpßòV†s>Cîò·sA¾ÁÿLÌ-Ãá+Ï€4ÿ‰€4|Bž¤Fäºì¢&ןâû®×$xÏ5þ±+¿ä3ÂÔWáÓÊ`ÛZç3¶þÈ웬žòü–Âß­ypbïâ[jY¿ºŒWCoô Ýïù]¿#g°`JÞWÈ¡?ñYœëò<«0£µ¶cóÏd;×LMO2³>]·'•Ûž·2&2à°ëQÖ¸nׯãgœ;ï0’0SÇÊŽê1þñ¹xçgâAàæcˆêD<sÔÔ¶Hç ££ØBºÖ¾„>šèêùGg& ™ìº<_>Éà°e=Ì£þ‘Éì6_RÏôvq&øc xÄ.p{$¬ÅºÅð x={¹y‚Ïy¯˜$Cõ"éÊ Î"¾™sž‹ Ù7ÿÕâß¡À ?ëVB 3ÏÏW¨,9ªs¡3¯Hˆ®9Œ¿/ûÔãÕïÅó€/óçÑîl“$Sé«8¼eÝlžý1º˜ë`±œ¥ï ‡¶ÀT÷ù•:¸ÿã æsNe ‚?ˆßÒ±Õ¿áãâûÚM‹Øzî¥úqB´SÄyDq~·;”Õ«˜?ÃF&óGàŸ÷\S¬“}²~ic›¿µ L&”ßXµ+-iÃEÌ‚gÔÿ݇ûŽÄlèßE»í&ç‡ôÇutvE1H{W7~3Ï4À'/³§¤5ƒŠòÊì*³©~ N{±èO=Ñ9œI4 Üã÷Ÿç%¼³3 HK|$‡3+©¬µØ>½ô@{fàíèW̵ŽÝ%ŽU‘o I›Ëk$׈-ÌL„?ÕõKWÂÎà§+[ÑWIXÄXÐ_\ç06þq;¾yÿ´:Ë6÷¯_xõ%®²l@ÇœÛ-ÚMgõ©ë•£k‰ÚÁ{èšÚl#nnÜe}Ú–ÄäßĨ®–E&¶-xVÁOÇÏrAúɺ£É í|†› ¿7 <ü­]½_¿žÄYõtý×ÓSttfNí¯°ƒð…Y]ƒ×¯­[y^ÀüÚqXò3øŠ±‹¢€5ºeãN&Ò‚qâûú5ôø¦:a]˜æVmyÿdƒ8 ó‰_2(Ô;Ó™yx o} &#ȃì(%on£”öÖ,AVñÝêr“lÉvºº1‹±Ž9v «$#зjuu}üžX„{wƒÆËt¦Ê×RžñÜ2_4צ¡©/í§6‰EVê.Æ·´Å-þ€«¼ì>&Ÿ×¶ôz+¯ë(áüô×Ûõ–ïj÷ø2¡Á„G»7¸Gá«/D^aríç_@¬²„øÏ2vj]0ijRþ6™¡^cœÄ+6äU%àÄ~âã«C©o¬`ß°n«Ça=À~+-ÏÇ\Æ‹2ùÉóØ”žÿ > _úœŒ;‡>O´ãØÉ6j-V³+·ó©·"yâyí]ýFâ¡q_íañؽˆ×ÿ262í³åLªû=ï:wƨµ©A._Дz‡òQlmkk£ÃEg¯¥¼šªu:@˜ÀÁÙ“…ÉZä-È4mIö²Î÷L:A8š…ƒ@€8Íä*ƒ@½Ïo7‡lCp8«|_ÂõNóp~€|2ô-î´°7ý. Y ŒHh£zv–ÊN«’ÂÁ+ƒ¯û^¶}WÑÁ¸føùŒöù=šNUR:MͱZ»ž í6óm­ù]æV˜ð=ïÇðþâ ËüÔ0ëÅ0-–Yeº*È:•æ§ð™™®­©§åø™tÿÖZyš5Ž8aJz[A=m»`„†‚Û-Vëæ.Q@IDAT©TX=cf”HL €š½SM`e]¸°&ƒÔëT¬z?|Uu=w0·§Æõmmp‚¶‘Ød”2T…7¸LèìÓÏÆs¡¼‚VõŠœšsdíž„¨Î6ÉÙw%Šjö¦Òš}jþr‰dŽ´Š‘Fº“ÙÖ¹¹¹]òâï`Hñ+¨Í|þ ¢c ¶Í~™`¶ g(ŸÏ”bÂïûÁÈA^•xþM@€ì»¦¶6ý¿÷?ÇßÍDf?ë$¬SÖ—ã¨t¨ÈD*˜f¦(ê=~òTú»ÿðbÜE2Óª`ò5$.è¤6~ß5)P;ÈØ7éC†‚ ø=NÿÇ?øûéìÙsé÷¬÷9‘^zí (6ÃéÚ¥ù|˜6ÐÞ´;æ™þûwB‰:~êIžJ0C€ãÆG ¥ÔKrÁ:ŠÄƒû×S;-ù»{~#Ю]»N˦y*ìhãNฬ¬6}þ…/£HM§ïÿÑ?¡’Bgºœa) fÕàî¾ð¹4óh$M q·;xdËÔà½TBð!Ú%oX#huq:Í=××È΃ÑTÖ4Å‘­8‰gs¨Ân£LW¸u9}ïÛÿ2‚Ï?ÿr‰ƒ{ƒ./~òmÎÒR²'ÎC:UHLrGÕ Ï|æ}év@8JÕ ¼’zèeàÒÅã›ã´´­'+pàÁwlŸ¤} ‚vW5ÆuJÚBãþ*YÆJa$«@CçÙÃÖÖ9˜åVj£ÒÅLÒm2óIzÿÝ yƒ: ;ºF`¶¥½‹@;w‘S-ßï&­öÃlçZ„]Îûáä4èxÕBºDëÿ<²Ÿº{‘À1e¾êöTƒabçŒÛ7¯âD£»@UÎÎ5pëRºß7„ ÷~>¢|tx„ÄÖôä3Ï‘PR…c™Öå8¢ƒ“GŸb<7ÕêßøkßGZ{gXœpVŒ¥ÙGA>­hûï2ƒšô­?ú×$M4¥ç^|™Ïò–Ͻö9àoˆ.xƒBd'²§…áÄÄÃK¾Òm°Tîìô!£ÈŠ{¬‡êðÁ̱ /¼DBMÁæÒg®CZåDa%O1 lò…<Æ–úÒ¹üÈW(Ùò>;qêLöæ50"Ü_ýüÁ;®¸v• Æ@ºðÜ‹ÐüÙôƒ|¶ß1s׫¤1•<•b «é¥ó3VIà1ë¾¥­•ª£Ip½ˆvDuð›L¹ºÂy,¡ìö8ü\Þ<4П–™»ˆõ[1+ÿª„6{õr‡íÃTã2¥ÃkQ*¸n@E#Z§Ãç[ZÛ#°¢Qa›±Npc”=ÌÎN‡P¶»ËÔ¸c Ü!KU.]ï¡Ã‘D#Ÿ²=|nn¸V©°âÌ€w–¸Ã9Ûrž.¬mvŽ»rý¼²‚ [ƒä*{à ‚"üUøt^+/=T0 ž‹™œ:Ê+Sl•lKÜI`¦g%WΫ˜ã[;;f ‡0^uä…Áϸ»8kZZÚ0t0nàÉVÊç™@„œß¢êVqe@rAä5,$x¾kИ͔å40ª³‡‡ÿ’¿Äeå´ÆŒN#7i,‚ ¬ÿß—¿È­äë„ál…è/òµ¿_•s³¥Ûà«êRV}õèIÍt;i!i§¹2=1”~Hg•÷Þ~‹6ºÓ$¢Cõ÷C%ÀÙäÄ žKèQ¶3V‡õ¥³V£>œ¨ §mЭð–¯çr·­z„ 0ò&«ÑÅ ƒ­¿:_ ö2'“Î i™Q¨%^ËÓq1Î? öhOÈß¶2F˜ñ|vž4¨agW¥-ð_ã÷L–3ˆ¬QµKÅ´’A¨ #u^f ÃCcÊ仨š€'è¼2óÚ‰Î'_:M]‡ÎÄycÅ9ÿEpжÕô\Ãè"Ç: ‚éœs]ò+ 9×£a¥ƒF®˜5H{‰ä-‰Êai–úƒ˜vŒò.¼Y²ƒ—L&„ǨJëL¥ov4΀áÔ˜›Ä)ɘœ5¦Ý‰”ðh6öøY?Ùæýèå!ú†¿‰´òÒ Œ+ÔfX¯’ )¾/Ÿ÷Ü üê ÙÔo÷àõž•¹m ÕÌî6(­¿›«}¡ÁËXŒ!^ &»iØË_XIS¬©€@¢Áw7t"ù]+Ìud.c€«—‡³ˆ5¹nù¯Žr:2[~«\TGц™'9Až\‰ö}ŸÓøµãÖ=Ú™k´Š#9ü|§Ð r×d£|Dœ!Oí|¢s DDɧu{™C9ŒU`áú ÈOôášü'o—k?x–ü?ñà›ñtÏZáûê :£ƒ#óýdŒpV1¢:>vn–ìâ߯ósÞ C?sÎ*™M‹[â®0v{q7:0ÑN´Ò[Çg-|äSê.â¤_Ò·C…r_ñ=ÛÞ_üä*@_Œg¢”×4I‡&}˜tæ˜á˜c¿&LZmî+Mø[{©‡ÌÁgŽåVùtø¾xa§÷/¿ö§-ák ¾ÂýÒø1Iá UÚ:+ä)¶m.AÝx…ÁyÀõ6÷)ÿ_tZH Ú× oœ•6VÖ ÐätÄ^ÑbKuxkÎ:W&IdyäáÀWþ*´ä/þ.¬„»{SÆ{~”·c <ë9PÓi(¬ øëO× È/ ‹Uà@Yq8üPä{誼« —Cc‘€¡¾£®êX3dSïÛ·ê :±K‡¹ò׳ß4PÀÚ­ZR±[O•.:wõE¸~ïAÜèÆ–7 Ý?–ÕrYœ‰‰É¿å;œ’6‰:”A»Å™Øå]˜;Й‰Þյܙ Ýïlòm`ï–lsØÚX‹O€ëG°ÙÔQÛ¸‹otd$­ OIKì ΂ä²{÷ûI^È®:ýÎ>÷|:rô‹ñ xÿˆ6ô¾ÌeŠ8çÀ#‘î3|sù†²,K°2Yâ³÷3ÜÒðÐÚêkÓ±MgÂÕÅ UéAà¡|È;¹¥Ceüø“C1ˆ•Ûâ¬zä¾îšd÷õõál¿ê3^»®ÀGòÔ?óIÜ¥wy›ü]_š±»"GrAYå³Ha\iĹtìfIÕTȤ 8¦î íµ´¶„ór`p0ॆ©£g+Ô®­3À¨ïÌ»Ê#á$Q6)3})Ûåê`òçæãà]S¬M<òé}ùâΕw±nx†z’zÿ\ݵÖMæa.ö“9PM 4 HkPù0t½AÂ`!­@µ]GÈa€"ÿPO‘¦¯s]œëͪ’”{R<kxV)|Ú5èßTïpíê ~tÇX©º)cúÊøL&s„„ÿ÷?ûv³)‹Ý³Ž[Ö®»ªÛ®7§ÜSÔ&Ïx‰ßϪɼö£ª”Ùê]òeÚàz²•«Â¤ÝY4d9ºL¶>$›‡þ'Nø]Q|¾á{¾%¨}^{R}Ôg”ÁL°7Åqa ĸ¶¬Û2Â/»zÇå9+<÷ÀE;ï˜À”ñä òxÝ!žÉüàŸ»¾°Ö–·…=ÿ=Â.DÊÕý€?ò “1aè |&>d¾TÎRÙË{Ê3·"_·²³½à˥ȣdÏÍ›7¢þ¾??Þo\WÕ˜~üî;Œ“(@™ŠêséÏsÞáMÏX,îŠ9”…œBvæ>,AÕØ“X"\”ò qÒ= w¡-¾ç;ÒŒ2[û¬²l`=&ØÎš¢Ö;7ó(5Õ·F`ï;ü-t°Jü¥µàË|à¬úk˜/¸ìº×8ã©iº§TTw†Ùü¬9¥#×Éìuªû°&ñJ9/méß¶îËóõ¥5£d‡gãÚµ3ÂÀß²ç"s]Γñ²ŒVW±^?$¯±{ÍkןÕõµ‹âÅxÚSž­CªÏ:pÊO< ÛÜup:þ =ž÷C‡Öêt®ß“ôŸíZG'uâÀ+ÎÐ=:Äúùoœ›bñA%¾Z !~ËcàCÑOgÇ…ð“Bc{t!T§«u·±›Ÿü[=‚Eœ,Öp²-øŒp‡ÍŠ*?›Ûùž—Ë•¯oÊ)¬tOVl»>³Ój.øXPT¾ç Å—¥ùɈiäÂOõÛ­ÓUÕ¤ mˆÒr®ÆìC}H¡üËëü&)Š“gOÒAN}©çÈ¡ˆŸÜ¼| ìñ(ÈP®­Ì>¤;r–Ô\€þ«­VÞzUzÕêò,xæ5êØ$ˆC0(¯QÕö—B „û/d°YgQ´Éa™têyP4Á[ýϳf;|Þ¦bC»šM= º›`iøœ9#“ä§Â=ÔÒ°+]—¸ªþo¡¤z™üËVã-­­à×v´WžhCÈGÔÍ¥ «ÚCfÆâ"Q =Öbigs–çÛ!Z¸Ö”¬¥Îúüt}À8•û! Ÿ¥òƒC×v¦5v/G¨ªÈ’žç((è^HÍuÅ©½¥žåçî.³¯®@g±#µþûQìÑ9ì2õáZø`VÉ Žëê 3ZCCMvV\¹¤\T~ÕÀKåò&uôˆ/²¾À `$ h«¸÷°;Sî0#vn¿Å\CRFÐ7oÚcívŠE~K­×)Ÿä&Æ2ÿ°íÀ¿¥"ü®Å) ЦôáÈ[º»ÛðwÓÙ{ZžÜÞÖ ~®$»øýjŠ•|ßïð•ï‰Æ=¶ö즰°)ã›ðW×ý{L m‡î é×q_&H³òBñÀÀ®x'4i_¢ ÞÆw ý>¾1he?®§Ç”åÅ$ kO¬b›åéù­[ð¿¢ÒÂ>`ij^+µß,þ^q%gÓF·z[V’=ç_ÕCír|€k äù®sŸŠ;Êù\x–E¹v4މv Ûði?ÚaN¶]¸]\È`3Ša€‘Ý8Š!’ÍMô `¹aҽܘ«æd2@º5x]ÀŒUs£wª;Éø¹Y×HíXý2Ê鞃]ÄcH&ælô…H—ÕÕġ莊þ,-¸6ëí;t(koÏgÂÙniÒø·áyîÒé8”u“Ó~ÃÿgàZg_!–øEP23pe 1àV£`…D‡À%n•e,Fç¡ág‘VXõáøìê9HK î€å@‹„#1ŠÔf¥Ëð= °$Ãl:>Žyx3ˆÞ 2#Ì ²µŸÏ*Ô–g—Cˆ¹&> ätƒ"£LSduçÔä8êi²]©:&ÐïazgTTíi”ƒ¬•ÕTKÌXÄŸ5ð£pц›÷Í(2øRJ…I)5>tB™cŸ‡Òàý[ôû'è¾¼c†ýÖ Ü,£ª˜Œ· ‚—fTj¨×Ö7°!h[S‡3Œ6î‹BÈ€,3~ß¡’Ÿ&$?×`H8ê ô%‚…1*|¡ÜÀ°Îyƒt3(n"‚È­±&œdnqv¼/<ݷʉsì¹ÿ{Œïþ¿ÿÇgBèÌZôŒUê<1_Þ¾§Aå³LÏÈpÝWÏÁ¾ø®ŠÊþþP$2çu6¦ ½GÏ…ç^ •ôL¨ôìù€ F'l=÷P†œëñKEÇ Ò/íW!`”VærMŽkÀòäOñ$4AâÅs/½c¥<G¶ê(¯ü¸ÐõÇrî”?Í8GAªGÇsž… C§”¸´¸ðJ´ ±BóGoý ]¹q=½òÒËéP_g·—.^ù˜îËÐcaÜy>:2HÐýN8BkIP¸}ûzK'ZwõN“´æÞÂȡؚãÁk‘Ñ­32§ "uj fQNàtެÌU˜uß±ÓéÑP?­ÿ©6¦ý¿÷1—X// z¨” ºì%/½øâ«éÞÝçï¾óŒ§1ZÇk¬¨ˆ=H§Ÿ8‰586ßy÷-2ÍšĶ.Óºýß @Óç zz_÷w¾ýG齟¼›ÞøÜÂY*¾š:0p/;wž3;“þù?ý‡i']mIdè$+ˆK¨øÚ¥-¸&F>Ùo3»ÚõV!<½YÁ¹G²ÌÂÊBŽJÜ‹3˜Ã`.ÁѪ󺜯`˜$Ø™—¿™:¸;§´3=ž hCõ1ªÏáç_|=«´çÙzç–ôgKiKÜÒÉmv¨]6lã­ñµ@…É=âÂKŸÿZàU¸îSpíòÇT:…4Û[dZÆgímíéoÿ÷¿8ÙÔÒJ«Êô1’zÂËHŽq¼éêîþÓT«?În}ý×~3}î‹Ü=Nò‚¤o½#G£ôTƒf´¡Àƒ÷/¿ú:ø ~0棇ãÁþ»ÿá÷qܬ$!q ¾“ýºv%+û4".›gö㪕ãGŽõKÃÒ—É,¡eï8HG€­J‚†œsûSú¶5| |Ü—û|Êà°07›O#ýõ_ú ÷™àí«C™s3Óùr´ª ñ¢>kµ·òèæõ«§*¥žÝ<ÁVóÞqË!׃`¶Â¢ªø®X·Î”£G²Ç’ÉèB/Ÿ<á•/û÷ €¯DG“Åt¸˜ða¶«Ê¶J>‡È’à‹È³mUp›ZT(›g¬È_wøÜJ9~³kûoÛÆF]¥Ü3èn2‡’£²jÅZ-¬àe¾ÌA­Bªë^Mh™¤Õ¿œXEÌ;‘t¨©¬ZÉ'ž´4roó€Úb [9éPÑ™™vœá*óê(Ѹ°m®Ê…üPgËRefq±úJ  áëÁ8æó‡'$ƒu¹>VJh÷$éqlqî*hâ…Á8ƒªVUÌaä»>½›æ¸[hƒq`w™ÒÍóÿ1¼ÄûGhûâi/¼^EqóÊ‹¿¨—sÿÅÍJƒ[ÒˆF™:‘‰$W>y/Í>€ïJÛÆe’]êÚÒ—¾þ[©ëÈÑôÿ÷ÿ• ›,W¢ ®ƒƒ›àĦÄzHiZt`n #4V¥%ÝÛÈópл้0:‹T@\7ôÕ,KðW³q•å0ùÀ+sú¨+èôÉ]Ò‚!ù§šr¹† âЩç\£—óþÆØ±µ·€4–IàÚ%£\Ãl§‚¼"ÃÖà•Ý}ìˆA6Á±ÅLÏdy´´‰–V;ÛqÉ®Étkk\¥„z,®P% £“ž½f Cb‡ß•¯ØÂ0Xãª&¦ðÛöÎìÊ×mñá²ê$Æ€OyO´FIÈEä“ð}³€7h}6Ax`* ,cÁÉsØ4{‚ïXt-T ÃCt–s|yò&ŒzS¬”ðÌ69/H&cmxgsLÒaCœÑ¹ƒî#)s¼_=‡ üuêKðv+šínáçÂÁ5Ùæ]ÀGBx ƒIøéðX¤ªÁ–z…$Ê7XÏ»\#½¬Ê5é´ÒÎÈôn;glq¦:™VÉ®¶3KØ%<«³†oð,º%Cè01` C4«í¥“Ù,qy¥Æ¿6€Š€ŸžN§±€ND´‰óW»ÙžÚª4qEÇkã© ‚4Ÿµ+ ±Ë€‹ƒ0[šßïÔ2ýšïò¾0 g}̧«Ãg\?;amÒú´º€­´ƒ~™Ò}ˆûÒA,ß½3w£ÞJ;׬¨íR@%’gkàIüW¬_\MvΜãY5Ÿüy©’} gv.÷Ð ‹ùÌ®Õu¸/~ÚžW˜ªkÄç—gIßÙ}º|\28¢#(»òž×iàK{FÜ5(m`5îÏÍœOÅ¾ŽŽ°@ˆG¬Û 9#°FÛ,1ñÄàUÈ]þV¯ð÷‚fV=*oíc¥¤üÂ@.H§ V3H--HÓûNfpÚ,êL€+Mû]aÇ÷£5$¼,ö Qô€Êʬ#‚8o‰Žäÿ›½÷ Ò+;ôNçˆè„ÎÝhä09b‚hQC‰aå]ï*‘Ô––¶«ì*»jøËå*û‡«\åõVÙe{˵»ÒJZ‰ äˆÉÌpàÌpfcèFê€Î9øyÞÛß“"Eo‘Å; ûûî=÷œ÷¼9Û®¬‘|À3:‚í° ¿TÎäÝ<Ì¡Š7ÒS Ç“fÛ6Š;ÒÙŽBêV?Ù$ìvö]ýÏ„!…µÉyÚoâ—­”Ý“<||’wÛb>*ùÌd Ïmn¤z¥^¹ MÏ©'BÆ<ïØ#×Ï4Ñ£‡Ók]à=~²ÂÞ:Æ:ƒøPTx'lkƒ\êGê-vÐþà)ùÌÌD¶/&»¨Ó¯aG-À³Êq`=yx…8¯ž·éì²ú×N}&ÙˆOl ï–?ÑWš—€µ]H‚ÐÙ«^Ó»} íÚ½‹äãé©Ï Ý`IÁ£´Ëd×jµƒüË §Elz“ÅÊq씣#-óžb‚÷żg/zh3 áý}½qS üMÞ€æõqP’eþÌ.HЭ¼”ž…K.¹àg6‰ûñE¼埰iì–¢Ž2OÀ0ŽpásueŽ–Ñá~ðJ¾3\0!Cž6ŠƒP­¹Š)y«4'WÈGà&“¥<èöÙH:ÕÃÃVàY>†§º7È'Þ]ÈêÞáGã>$`ðjp¼œ*µE*寛ÒÚÚFAÁ5îñ.ø<« |lƆ-*ãh°sñEÃÃW±§Àqø0s"Ðú þz&pÈ‘†ÿåwúqT¦W¹væÃº ëóKƒÉÊ/<¬Yð¼ caâ8ÞgOþ,W?§Ç,†nÈs3$Û+/³`‰ïà)nÎô=ÆA>Deïµ»Ñ_¿ø5ä‚ö úv:ˆ]•‹[‹¼KŸƒíÝ¥]&ä‘«t ?Ö¥Vjr!3ø—WØ^ð¤XðÉ^ð>×.Ì”ïòz× 3~• ½ÎöÒ ªy‡ø$nÂ#’ˆFG³g€SØðV×:OÍÎXžÆÿÁñ-Èjy«|:ä1k†^ê–&c8wñJ¨f:B¼*[÷†îÆóÚ{î›ð3ˆ¼y]‡ÜÉM̺Pî¸äÊ/‰Ê» ¸›ì¤Ì0·0Çgðdu(4‚o»WÊPeRøúø×ϘpfèŸê2ÇÕáÄ å·$íP÷<ð‰ÏýY]­x ‰kgœX'sQv«x î]]=Ñ¡ñìéÓìuÖ%È€‰2Í}8Eðv ?Œr×½ˆ9°WÎËq´™­ÆÔvЧÇaf€É}–wЇ<2Æç„¸xâ:Ü/¿t_üL_£äø­4ÎzÕo|/ðX…Þ^{í|ij©«s{øB®â£P¸ìJ +<&ðŒq}¥s—¨”QêyËèöS˜Lòò;÷"ä6ïõýâ¤íücŽ ¢î¼Fju—X·Å½.‰+0 ø…(pØ}R‡´Û‰­À?â§…l èÞVê¿(fê¦&5”½E±ÅIdëñcÇàS­øQÚèdj÷aœÀÿ8R†ß#™X½ødÁaùðå>ÿhëˆâ´ôócÎÂ%tNÆobŸÜ/Öí~ '¦Åçúº€ï}êù>gq ûf©ºá-Ç£&XÐq}äZà´¾µ&|ªËèƒZ[³Ë¥S í6ôjñM™8nghcÏ,œ§ëQΛ`f?ÁÅÍÁwy¶²v[ì—~®e‚Ó…$¿èõ¸˜ò-u™èð*Lõ³RIZB‘ÇI$ÓÄ`–@ÉSbÿ5 § éY|!êP‘åÙä·áE3ØšâMt1£«d±–eøRaÞjÚÞß „lBß·PR>È+ѧÊÐÉ38dò/K~AßÅïYÂMÝ–¡?Q­ŽMSLVz*|¸)¯P?4fg<åàk6ÕÎîeðÕŽ/'f(öпèºÍít¤$nd—õbõÊúºz|â ´¦·Ûêí4@¡Hc—&Ï>·zVþ'L”Ûâº](M$¶M·´vãÆ­À“ü#-‰›âº:«t—¾»¾R|qs‹ê𩯭<}@]>T^Š}À¼…-·|dÝàCÀ^yU°Öö]}é——s6ø(ÅnE4“פ™| ë#ô6‘~i»YÊ~¤Ey©Éž n{tyf5>ñoü”_JoÚeúDFñÙvüÆ•Ãú(Ôç‰ùèÍyØŸðÐRŽY¢^^Ç¡®•¤:޵[ÝŠïg‰Jnb+³Ø¿$­Ré N,SÐPXH— ðEù¾dt“¨ÎÆ— Ì €K“ú´« ’kŽ ßÀE ¿‹ó2yyŒŸp°èУ·oG2q•Áeî¹rízèvXY±J<;Û{†øƒ„¾¶T€ÜÀOSË‘ža‹°úºCÆÁ‚ןe ‰½[ÐMö®ó»HêA/±”+÷™ß{ù/à XG$#«5…·ŠGdçpƒÏٚݛýÙ½±—&ŒûiäÆÁ‚¿|Îñb^LÉßÜ¥²ëzswºŸ×t~Î!7¸×Q½‰ËýL†ã°Ø|¡ÎÜ=¶¯–âpîÙÍÛâwqØ1rß ƒã*J.ÇCááyÏÛ¾c/I 1oMVd‰>'cʈw÷Gá(4Ë °Wž!cõõïH~þÓééÃÏÀ©*§†F®¦—þúÅôËC~ìpjã÷®Þ6y\êL×Fn¤+œ¹²kÏ~‚è»È~¢ªÚH…·­¿ß;ö6I§ÒÀîõN*=ÛÃø9{꣘÷Ã>™N—6¾÷^;ò-XCQzôð áf¼‡9Ÿ\c¸•d§Ÿ~>ýñÿ»¸ï…_ýðÜÀh¸œZ"x.ß0û®‡Àî›oI>þtdû|õ«½Ô¥gŸýT !hù<ëý¿þÕ¿¤uxwÚI%º õìéžvGàÙw~ö ÿ8ýÉŸüaºMðãо}! Nœ<‰pÜH{vî„Vj#çìÙSé£ާ½»÷Å\&#Kíß1šU¯˜QvæìiøH~Ú{ð-Ê;ÓÑ£o¥¥âÑôi‚úò3â¹ó—Ò‘W¾g›õî X»o FüÑÑZ ®d­éÍ3@k oláífºz½T„/$—N“96¶€+Å9ûÈcO§‡y2¾W@ˆ;ⴸ仳#PJCÙÑåCÏ>«²-­Øn»Süöyß»†ráU³µ!:Nç¤tàe§ߣ­“XEÌÀˆÙ_a8“¤PUm‚Î]ÚÈx‚U……syÁy×´…$i'2@å AY&Ä\unØê¨2maÞþë^;Oáé}Î5yN˜T­ñçï&ûhbdwWÚ÷Àçáyþá%êá:Τ/õÞ2ðPþïÞ;†÷”b€ÊÛíœrñë™!‘ZŸ…—´¶µ¡;äǘ…æä=-¢Ÿ8”k:ˆÒ³Ï?º¦ÉbJ&'X…/ï×P²Å¸kpG|Ÿ §gïu‚ãòq‘èÆQ‰¬ÃéåyŒŠbåÇ Á4“_ó©È«†ÛYCùëžØ×ÞõèC¼/ èúî˜÷&ülo¨q§!Án`“釒 p §³ç/†‘g°U]¥‚w ]JÌ]ž¹µ¾.l•\%»Ž„öŽV:%u†“ÿ*ç­¢‘O"z‚ë8Ê×ë#>²E:mÆqrŸã½4yˆ{Rð $fàŠÝ¼ÊØ/ñ&œÅâxa¸×ç$Îå·AHÛšmõL`€–oÊí$÷Ä}wC2F¬Ù'Ú1.óÐaˆb“™¨b`PçŸò>lðB<]fÿ¬¨’cG;6Öt"˜º\3¸o;qqΪ ƒ•‘鎿\óÇløeä÷"NŽ¢Cìè5Ø|¬¼A6‚_:;M Ò>Ôbp¿üÎû Å’„§U,Ê5ƒõºµtÕËuMs^¦rÌG¾±6¾—oÈ3¤jÂËrß–h YŠþ^l¥ÏYÕ4¼ÕQµQun)Õ»VŠtÐTÆïâVTóÞeZø±S+ÕC4¾µ9tüâ̤¬ËµHSçÐHe²­ÈW ƒ×ÒŒ—¼BRF°…{æ™kœu¿<.”%¥[ù.n¨—¹×Á2ËÞ®³‡à¢U*NÌuK&G=õ9«óÝcù:—u~€`l8m ô™|âL’Èù¥þaRKÁÂ3ù!–X§m‹9'~šc[t Yñ NèÔ*Dt_=ÛR/BÏÈ"u¬Få-ï\Aw_‡FÖåã$Wð ü³nœŒmT¸à…ˆD*é M9pÃûÄiÚù.¼S×ö 5?@¯áývðY¾³ˆŸ£jÕ·¸v`¡3IG\w?ŠIÐWß‘ÎMV]`= NÆTî°'âáô• ñNð•ïÊqꇽ6´/¾ÙV­Ÿ‘©ÁÁç{®½v¹ÉE&¹¾ûÎÑ '€Ý‚‘Eè‰vAééïIÏ=ó<‚.x"gN—²vöVY•¼¬3d< óRÉ]÷þœûì?俎ïN l_+Oñ³»3øù¶¿kc‘ï›EѮԡ§®‹ë;pSû)`'´ønƒ£’¤ùàÝ$~˜ÄÛL·ÆH2Á¦Hו &ÝÉÛÅs• ŸHÌâ_j”KÜèÆÄ;äïú¤+˜tXÇ$d4Ü¡œ.Çf°Uj)Õ^£t®“×ê;¨ÖNá>÷T^Áßì±¾±YœËœ7 Œ´í¨ÝP”›®Ûy(“¥Hd<+%ƒòÕN)Ê,õuÇgI!ß…cMàÏ©×iÛùGùæûœ¹|91ðS¾d,h¾bEyæ8X„!SäG2E‚šp“…ÎâHÌ!’²˜ŸG®‰Ã:ÔåýùÀ"lbž/)•f|ÕÊs¸b¼Çñ,ŒÚîƒë 5…³<@¾.¿ ]%dœ2˜0OSÁ÷Ñü]¾"¤9ôfG]»6·lm'm¥­]ÆtFç| ‡¹Ø94Ûw §à)-øsÔ3å_ÂGùj ÞójMRPŽlöÙ%:f8¶6vFçÂ×=‚_³§òMžJ…ìö¢ü“Uû({O²8þOqÀïÔu½ E2”G¡ŠÏ£¼O_¶2ey#¾éÓÐ&w=^=ÔbÔǤgm•9ô0+ÆÈô,Ób˜7ϲ Þ_ Óq´!„X´ù‰8,ÿò3÷‹ga1_»•WàÁ|EMTÂ[_ƒÕ¿ÃÃt5AþhWo¬sLÏ¿òíoýÖQ"ü¤+4ä{Ä ù“É(Ú7ò%»!¨wœyàrÌ™[×êNäü/Ù}ì°nÊâõ¸ƒ÷€;ÜïZ\§pöòo×,ÅGÏwßý\ºC£ Ú6åÇ@ŸþŠk¬ïõ×^Mÿä7‹bŽ>’}®óm¦7sœrØÚ*&ÓyeÒ) {¸NÁWƒÈù(ØLÙÂÏìJGFio™lŽÎH°@«)×Òç¥ì°“£ô@þs…Ï`ã†/@˜1¦Oyôqÿ@ø‹µÁáÅ[ñ‰Šó× |»ÊBý÷Ê ‹®¢Só¬ô‚©L ¿åXÜ«NY®« kõôüº—H€?•¡×‡2‘ÉD؈݇jÖ¦ü”6W (Òl•äÓ‚ÔÙT”Î^Â7 }…\„w˜Ô5>ÏÞ6ôØ<óÓKt0ªOw5ã_og-åéÏÿê­ôâKo§$-,Ò¢ÞÄòíÛû1GqMS~x z¨JeÒ8e¶’ðºc ‡ã¬ðÇ2?6&× ÒëŸÈ|à<›(žcøoëwTR°¶`'`ÌÁÇ-"€£“o©À^`OÜ—ÄóH‚¸=aõ³r”ýšK¤’¦2ù s v•tí)’l6ð—Rý^FÐÞ⪒2‹Ë´ñÍà+Š6à$À¹·E6Sd» _Æ©Óçðɼ•çÚ‰wHXWÖº^úaÜ‹°élÃÐg$uwë/ V@8×r\Ǥ3f–j+²_V0chð^ 6äð7åð”nlïoÒ¿:‹ò´ŠFí,;öfr…ùKì»òP9k @ß’A™üSv!lÈ£Àm¾’}!‹ø‹;ýN[MI¢ÌËbpò4m:ø‰2šyòjnPžù[Åûâ¸-¾ÇQ„Øâñ^ø/OÄ{Üïx‡ÄèϬS9(î3µ›_bnD•9‚¤æheº6¸¶—…/^¾Z¹ãü=VY¿ˆkŸH£E$;Û Ø¤ Mâ.)#.‹ßHž¢.;þß¾Åq Èmõ•††­ª©kâ^|$n´uõ¢‡gô øDíVºmÀg—ñ1蟲£Up2§¸B^Á%£÷ü HŽEc|CÈ27L¡` ƒw~Á¶xfÉb¾Â$Döö.„×V Q³,È,1'„ ù­æQYn6ˆŽt‡¶ÍµšÑö*¶/ÞÃy¿ó´ýX"«`Ke}=”Ú¶´±@!mniOç—qú/Þ ²£¤`DFëwm—|Üêá-éý㯤 Ç;%…lŒ­Fh­1 ìqÂeÕ00Íb2ÇN´ÑiÅ&Z% ‚º…€`U¬ù#à \(rlBƒ¸™áuœ×C°…ƒè¹W£sì6A˜:‚*V¨xºë¶²N%V˜Jÿ"¯?”-抟㧻ÝûY<÷e÷Šl<Á=ƒ»—{÷ß{Ý;¬)û,þþÛÿuïX?ÉÓ?ì~?‹YlÎ/7μ÷‡¬ãÞµ*ø¼å‡=ïØ9…,÷a鞆#‚u rFB¾Ž¥b–»W¥ÄëÞŸsßËHr—Ä­byîÜy*ÉO¦Ï}î  bŒØ ;ÏóçñÇžJÿêÿþ?Ò·QbwEÀ¥¯¯,¶fnÈOû=žví]N'OH/]"{´+„²Gx„€s9ñRºpîRúì?úrƒ®Nž:éBig>“~ù— Á[žvï(˜òmØu /ÃwïØpXJ=´æÖ¸¹EÆ£†TOß@úõøéÏþô÷4=ózçÎìlhác›‚ûÇŽÝIG^ýŠÄÊà­ôÅ/ý³Ô#ò {•m¿_øôgÓ[o]nåv)xFÕ×ܯAØ¿ý[åŒórš€é)¶ÔÔ§g©Pï')C7;~{ÿ`dêYý&ŸâÞ}‡v¥^ølÀY˜jðx½ûÞ÷¢úþÊ0Y»ÏîF[ú«$ú,Ý!‰Á¶õ3éÕ#¯Dy<ÕÙjE£Sf(bŸ™3FCh¨0ªD®'¿³%HÜoGN(Œ4€ ÈB‰X\a(ˆCü®RéÚr—ïór|þÊ~áï0dyŸŸøÎL¹QŽTP¥wñZ…ÍŸW¨à¹wÜþú™c¨ÔéÀñg×”çU³?¾_ª|0›NžR‚£Î€²ÇZØea> }8^1~6çëºt”ª8×,sëqý½? LÂLìi‚çv˜ð̪‹Î!¼¶¿×!b7 þHHxò92‹Å×bp«=º2˜±ë{ÃhïeNwëËx«ìž+n€N8 q¿ŽLy‡ðQ™2@íÎSÂ(pÛ2àaEÛ4Ž-ØÕ$äèü63PùçÜpF™x0`Ðâ­AÛ ®®Ï„â¨Ì6 Y"ò Båi´6‚f[ZÚ¢½­xæþ[5ï~Ûi£ˆgý\ãY§¸UÒEðkö`ï`! ª…Ê¥¡(áyi4hKØ»Þú¶ Øãûñ/×eÇ•z8Ãõ S)îç{åœÎâç;p‰ÿÄårô£C>‰#$j™ˆt~°-"‘˜bÖ¹ Fê£Vrö"#åW×®šµÏù¼dã†aîI›¬NY¬±€3QCikc}´{÷Lt9áö¾íèŒ}~ÐZÑW­Ž’wø-Ä@2ðv‰ ª­Ê­Æ5Ø_„îkuê!>Š=îèìL£8X‡Î\ƒqÏÞÝTö§jôbhò"%V6··0ž9}!æ«Ó¦Ìì{¼%<¯+ZH¡–T „?UºƴͰ0ÿ.¡‡÷ ¤}d,ðÔ9“3x¬”²ÚIþ¸ˆ ÓQ ÃѶz&óŒq~„Ãoy ^UΣÞî.ÎÔ#YéL`‰g¤©“L2ßËç/3ÿ%ΆnL:˜úh¹g›íh{m y™Zziÿ,  ×¼m†#LL`“—”˜ø«¼4)LgÎ… Ñ×oÃèjßî&(^Ï»=ŸðÎxÖÞm` ÇAEz÷ø÷¨´@nó’½ìóGÕ8†6RðGà¬Î'ŸÎÊ6Û!zN¦Æ£UÊ‘¤Äû=ËþÒ•aöŠDd‡òBr“LwmÍ8~™êÍ: ²'Ê0«£\»v¦òMƒxä<Å IòõLvº1|Ö¬çUÔâSQB5‰Õ××®\ Y%ϲ}xqK øApJœ5‰fçÎìcî“2L¦¡Ru8{¼SÜcä5¼6ðQ×ùeøÌt| ¬3Õ$‘¹ÎÇK8û.éÕ=£;Ž~ê¦:Y¬¾‘¶wwÁï¬HåÜgžSÏñ;aŽf8T6y3ë'C^ƒ Ï] ò‚Š#p#³ÍíØA'´}÷ te‹Kes LÀ³#ˆN®ÞöððºH 48!¬åÙ&€„Ÿ1¢3k6ð`ûêyöö:I àá*ÜLÂÑA>€„‘zB¼×=á»-ð£jæ<ʽ7Æ© ^ÂZú°mâ¶Ö&tI[fz±U;°¤àb]6¦¦°WÆHænŒtºQê+:÷ÓÊI÷{kâ“t-o­ÂiX†3Tg¦v“ÁBeI·p3æ ˜·üRŽöc£“ ”1ÎEZÛb"°lí¤CX+¾è‹ß;z4hÃ}¨¤Â®ÇcKo7þ§ ÄqóË` V’±v@Ê%ÿáçŸÃŲy{v‰»êþ÷ê?‡)Ý'¯Ì ¢Á¤å©9ÚÍBÏ:”Õ ”ù–23¶rà ]œ̦nXí:ù`'•‰[p–ÚŽX¹(OÞL‘að8 Þƒ{ù൉0AC¼G@$ñ»èœAJ¼Õfö_eGæÛ€k3¨fÏጥŽXhC>*~É×¥Eù§¶Õ•KHæÉ:!zìƒI¯úüâLkž«à×¢ff³ÒÚ—ž¬2I[ùhÒ‰Â/ì9Ö!J;'y§ósî<Äú òˆpðnèÔn7òqnà .>‡ã[Ïø»0 ›“g[G¸÷ÛÝ!†‰gXëÖ1~"×Éš Ú«ó™P¥/œßòrÆÒ±íê0·êÊùEb1ë²C^ŒÇû´äÛ.àçN„~ʼ¼ôø£A÷(:´ÄœÑ˜CyÉZ´ñ– èú«ÅäqžõꞸ÷Ò <­–¹ s᧺ b-"…ÃÂ| ÷f¾ß¬ëNmø¸ïâì~Ö§¬ÊçÅQqXEÇtŒ„‰"Öaò—þXíìŒghË3Ò¼?d‰uè Öë³Î*êŽå*µÿù1`ÄcŸ…³—¸«_Z˜+'L‹î <'ï÷guDai dqn4î%0T>(_•1êLê{ÅêHÈKÏõµ 5l[æ”KTW=úΪ?å™0`j¬%“‰î—ºˆŸ9gõƒžqñ]2ÿI†óâ´û#ÍäÖ‰ !ã ß¹6qWÞ!}n@ôÝ<½‡õ댤7î­AíØ1(R¦×_5ËWã~i$hm•Çê¶+/ôh Æ[ž‘•‹âˆ:ËÍÄxâG¸‘Ç1{¹û½×5©K3~c~ŽŸÍÕwC뱫îmæsÞŒ9¸OT•¿&ÔÕÕÛy0ÓoëêðS@›>øp_•Í^Ž!1øÅ¬â•Ž/}Z!/Ðo—Wβ׿U‡Ê‚ûò)Sa.øý\;B>âžSF¤caz°³Z_XFÒ¸•ÅU€@#É¿{ñ=îܵ;½ÿþû—·áOjˆ‚¡wßzÜ‚.œ'÷Ë=Ò€Üòìø„Þ%±Jóê¼ú–Õ…A€Îøow(÷ÀäRÏ 7I ûœ0Žû™YΙÁvUãhÛàÎg\»I1ŸÖÖÖ˜wÐ7k4˜i·iט×xì>TŒëîê&X~»ñöìLÚ½³?5£Öà6CGgj3TäÏ“ ®?Âu-#qÕÀËfÆÅÈ\’÷Ê{¢«“|1ÙG=Û$;:”wN(ØAEéø!_²ëó-Z±÷vT¥78¢”ÄÕÅ,¸^î8:>“ž8Ø•=ÐA+u|ÓàOWoOª"_ ¢ýyj–âKtSü¯Îçæév ]…šGB(û±D‚DUuæ¿S~ÛYQ;yÏ^p¥;~÷Ñjz飒y‹G®]\L*§SlMÁ$¶Ûç×»L !NQÄuûöüËxÉóŒU¦Î'gDHJ¢Æ82ÂÇifÊ?Þ½øÌÞþî£Ä /ßä2æžyeÿd?ÇOþf^TÈÄî»Wîû»Ÿp/¿ä ý€þæ*\^Ÿ|æÞßïý9nÞüË÷»GeÏž9zz·GZÛýöòž:ðýóŸûÓkG^)+=ýÔ3óޏÇy-°×îñîÝ{Ò‘#ßI/}ódz„ÀrT“)H]=½ÁÈ@o²Š[Óÿùõÿ•wíMÏ>÷… pr@™…8öèc‡9‹ýûéAùG~,Îݑɷ¶¶§'¾.5¥'Ÿ|:]¹|>ýå×þ,í?ðÎâý0J²ÀpbK›5T—«ÙjúÍ7Ž`ˆ.¤OÿÊg˜'äÌ×ö':]ïž½qVRÕþûé‰'Ÿ¡¥qÿÇ0@ã€NWRwO_ºH ø¯Feúáÿ”víÚÃúáФ[bðó ¨ßùÎËé;ßúΨ: z7xpæORI¬rþÚ‘WÕ-Óg~åרì XÔ2€«öä“°˜þôOþ(}éw/ ‹0°`š¹KzôR‘ûäuïÞßû³ÎH/?æ^ÎM¥Û=÷ßÑ8Îù¬¹¥üû›éÔq4Š2®ÃÅÙOþ{>›«|áÞëã¹1ŽŽk…Q®¢JÀ†ÁÅ\tb¨‘w¨ìhœÙjYÃÓ1ÂბmaßÃ@>9:ñ1îu½>“Í2¨x‘K4Pa”Z9ãÑ Qà½Tv:plß“3ätþ+S–Î_Eõ ƇT!¬4ìíü Ì  _•×jª:¦t# ôsf¤Š„ß¹>^-¦\|g÷öHˆÒ² ß÷X [ð{Ÿü]êü·µy„5¿7 W&© Ý¹çÛ‘9ê wõàìDv½ùÚ7‘}žUN'™Å|*Æôhƒñ&|ÄqÅà¦rÔ£–ghåÕt4MàÉJø––¶0Øé ÌxÅŒ±-Z5ÒÙDYX|ñ|PÒ¸kpÒ¶ï-­|Ö|8?”YúxàrzkŒ¤Ô \§ò£„öcÂB€ßÅfåk_d¶„—N¼ÁÁè!§8i$hå]v4@­m¬£!ì&üò™òøM“.üÔg²c-šHøÃð…¿ä³að¹8YÌŽvÝ'VŽÝ#ƒ@”ü.º±¶ö™N#Àðø[GyíÜ®^£3g'"rm³*«4Ó\þ*ï1Ð:¸k­ï;™7AäQÌÏ”½!{8ßÞÀ€<Õ6gʾ¨D:Mi÷â€8~ôXðÕ<æ¿0› ê°µºÁÖ‚·p‚°Àp™À†´A°Ðé·c`?Áó¿Ôo¬V‰–áø·NãŒ2ÊY€V^ÊËÅ[ë÷¸QçÏ]Œ6ÁÐÂQ.ò®[ׇÙl+š•YÞ×®\¢è¯“wî j¿‹½ÃÀÅF4Эœ“¥Ae´,[œ*p6‰‡ù$U°±çu‰#nԿ˱'»;h¿‰Îõö›ÇSC9tÜ[`¬\[ ë‰ Õµ:hµN$]¼·§¯'ޱ ]Á}Ñ償CÛüLÐÞt_”å"là+²=¦glŒ£jø¯»«“î àñRº9"òÙw`MBé»ÇGu²x÷®éÀÁýÀ}ÓéÇ\M–'°SÞÊ„ãJGn†NX;ylÌ7¾þµÐ1äi­í­lº B{Ái¦³e Y…çéÜÅa*h/È~˜˜¢“°Œ¬û<*ú=tˆ½¦ëº­ø¨3Ñ@‹07)Nž¢î%v¾ŽWï>[q{ûv~޵Àÿä)§¡Sc$§FÞÙî_8{‘NøeE ɾûÃ)Íš³N!$áXUáèA§˜ÁÄ#7Óëß9‚ÞNRïµCP I‡C/G"‘¸VÌ“ØõÙX™N]‰S+ýW·uc1sÝÿÀþÔ¼­ ~¡Þ‰ZSÏЙ+Ô©}éÂÅtì軌Ê<ÁIñÚ€†tŽÛйåQí½Ï ±ßÎE— =Mm­éâ`$Ùˆû#¨(_ÇÞ°ë»ï¼Oç’nÀSrqéHW>Y]¡hySfÙ“dÆS¿šct‚ê7ÑÎØA"õß/ÍAÛÕÅ•i2±^±ÿ‘'HÌÚ{e ìpÇ3GÙ’Mþ““¨~ð³¿Ä[ÿˆÚ4Ò`ü{‰ó¼•YkøØ´³ì*VBbÕÒüžýW/PÒ· h"ÿó38H’NòÍàì û ì¸+ÛfÃä¸ß ñ¦§Ž*ÛŽw‰'òb?³â\»ßyÚ׫–êÅm´VÏŠ®U×â>S‹9z&±‰¾CÄf„‰°õ?éBÞsáw+´å÷®MŸµÇÎxøgGÜYô[¹§ú&jÑCoT§ÑßUC§Òzt-07t ¸CØFà’ŸçIº²¨Äd’âÜž²®Ñ*KPÝ.ï³³ˆx<“µy´Aø¡X«‰Vîy†_êYÐÁ¤–ø…•Éú× ¤([]‹rOxÈ„Ä×3©k{_úü>ŸÆéT:4t9 ßê¼íȹ[tðx>}ÎÃ9úÎH?2þäž°A&ªK_Î#ðËYr_0!`olÀ5 g·CÝW¼t².à²_pOà3sv¯Â_à{Yœû³äüg€^:,&üQo5°(¿óñG=óÜ™3t ݼHý8`Ì&ÀJüÑöʃß.¨—:0Úuƒplyˆó¶#®¿«sJÿ¡¿ÓŒB×âë vÁÆÄ÷±i"û]Ú–.ø-æbÐfƒ‚‚ö°Gòƒy(¼ð«Ÿ1FÐAäñccà'©'¸6»©C9¿à›–Ô³véŠ×¦UøÍº`¿›(ÜÖÛM"Ùl¢(öÁ º=Õà^%Ý} oöo)µÕÜ]ãèååò4½X—fWšñÇyŒ>#n*²pØ›œm'°UxoOoOú{þÝï¾™ ‘·òúxyŠÙ¿¬+…ðÿIü™Uø…´Ÿõ7¨äÕ_()Ŧ¤«”¸¢^oGÖRÖmõ»r[f ~ÛÑ@nWRFÌäÖòTy°0ñC÷DXú}È_æìÞ/ë[4´c¡þ”·&ßáK±›q¼ÔML »ï ø¨½Oì…q”èúIòå'›cëcr¨EÐ\Ä.¹×}]!Ù¡Ÿ„É,Bô>aZ\J½G¤ç¢`ÀåÏâ€2ÀwkFÒ$x#mëÇQŸSö¯³¦ dº4b"›ðq­ü=RÂxº© x$¶vµ2O?ë¢þ|>Ê!õɲ2õR¹{–èT&Œé¤ÃRY3ïe-ÚiúÐíŠkç´!º"·-^1ÁSÝW¿5R&üŒKû „º¦<2ktz˜ac倬2„£N‰eÎiTÓi¥ SXiLZ­£®•mš‚ÀóK+Q$È´ÂÁ–_⣯SɾºŠ£„6 mfÄÎ<‘Q¦ªâ ³T%‚ØfF¦19q=hëõƆ¶´´¡±­QàAîV(´ âPÉWVJ¼»»7]¿6çk„Ú~D!ï9Î5´¼ZG¹]¡zÝì‘WŸ@¸úmíéÃ÷PK1ØÛ9ãƒím¯n«ªT /ªŽnÚj³q ¯%Œ[ðVâ”RÐõõ†€P!P“©™õ #Óñs#/I„ð_?áÀ¿¡4Çn®tà3îÀ¾÷âs…‘Ïß½xDòŠq¥‚ì×OÜw÷‰ÿ¿ÿ$ÒI”Ë’üQë^*€w¯»pýAß½ãÇýäÞ9¦Âñܹs¡8xN¶J\ì÷=¸Ç2“Þž>2°n¢LéðÌœ]¾_åhmM¤¡¹5ÎÕ‚7¸>Òûü§~%<ô §=8JïRiïϱ¬DU{~¹‚cãÁ¨dØž­gޏo•¼¡¥­º,¥Jí6Ä áÂgPÛñ¶¢Q蘫!k͵Ûò¤««'ÞíüDE ¾QÞç¿^Šfç@,€AÖú¾¦¦!Ž•p.ú—.]ß 6l—(xÒìU‚w´ÙÀÑ4H]F*­È50Ïâ>öÎÛ!ÌuÖgOfXéÇûÌ0ëííIÿößük”žôÂg>Ïz.}ÆDÿ¦¿ô.º||§°ñ úægÿUéìrÿѨH†¿žrï&ÆÈý•©†¹ßþßÿúÎÜ{à Xýà}ÎÀiî>Ï]$:êùrsÞwy̽÷åî÷ßÏÊwïw~æÚ4@Ö!¬r?ä¡ÑbJ 8¾ZB.¬ ìüÞKãÕ{”Ù¹¶& !x‡8® qî* î¯|S8û¼ŽDòƒÜŠƒæ7ßm Óù.ñû [1ø">øð,*ùuÐoc®:M”9>ñL´wïš[Û‘$•0¿{Fµ¸íþ£# Ùm¥Ók·6D`Þ@awï”ïê±>ôß—GuûÙ“œ°Y¹Šã×N-Ìîµµ|woê¡{Ãå QõÖ´éZ Ø™ô& 4°k_tŒ¸xñbFŸ$8n”ï¨úGþ²ÀùCÒ½ð³"CGŒ{¶ðRARÇXCÞ ùQÈ1 ËïÐ%Ä å}!ç[e.w1“‹ò'ù—÷ø©øºD%rà3ªäü­ÈbçÛûõ’¬¤'ãþ¸yŸLÖ­_ï§+x€2©g£,“ [jé‚–í3ÿ m$yó¥1¢1¡«‘§h+&Ò¹"ÌUpˆ:(tRAôœ­˜)Ëž«µ‚vav9mÛ¹ZlLy襰$èP'z c`ìÎxŽesR-Ë.`0kÖ7ƒÚeÈ=¼9œ÷KõüÉn:*ÕEe¿N†8Ó‰õ™U.½uis(ö rÒ¡ú¦æaHz†$zÞm…£Ïh¸ùnugñ«ÃáqÒw=|ªÎÇ>Dæã8ása§#\”YžWgö¾ç¦ß¼=–ÚZ8¯ž²ˆþ¯ÓFú #å¡ãò“Î áåqÊóZ!/Áë_ýÛĤ“Ç?H]Ûé:€ÇY¤Ó ”?Õú—jÓ* ×ØC Ð Yu­S¤³«+ŒYÁ¶dÖ¥"ÔQ¦éYvþ«.î1R~/ïÒQìYÜê :pšÛÚÁ§¯“lÔ•ËÃd&£ßn2ï .,Ôsæ0òêë¨LƸ¾V°LáRâFò@ÖYGãÓ@wæ˜Ë=a<³·ÚZ&c„÷è;a${V½Î ªsîÿÖ£ÓPžòCñâÖðˆÛ°R6ôõRq þi‹™ gÖ¶ü3dp7!IG•v—ŽiÏó“ÏzÆyÈEƵÃÉÀ@!8nÀáÄ :±‡:¢4¼µÝÞ|ã­àçV¤)|èpº:ׂ|8xtRçÑQÃXÜ øCÖ+RynuÓ¡´£Ìà°snmi$p8˜>xÿCh­6\¼DB­Éòwâ—2¸ š\s¯mK[lŒgñbеUcÃUcßyÛ0’3™X£‹UÆÒ¡íú²$2àÊý~·†±ÞØÔHû‰1ßA&—Ðòt$,:IUØ‘À¬rœõæœûò%÷EÜÏGŽï?´8tA»òSÎ/êHðk°œ ðü¢¥=÷xÞ9 1Áyœyà#+Lݽ½éôɼÏq3¸i/ØÉN{D]N~j N¼—&<2ÀùCÈAó­Ñ!À3*Ýc“çyÉëœ×R9tÏ8ã·Æ£;˜ÎZy‚üQg± *2&@È—Å_ •e$^öÃ#š¢BE¸,áHvÜ‚JŽ-B‡›åŒÌco½ú®¸ç»Å^å” £èpqüó^ÝÔ V®B§·nÒÈßír ]¶÷l‡”Oñ€ ,žÃÉÎL3½Foˆ%®oþô3ÿG^)®ûLðg>ƒû÷…™&/ 5¬Fs?­PË`–KzÒ¡·Jp9£{lèRZ0@¦UfG&¤­eÀ.‚aI3€_>Ÿ· ¹4Çjp–¨t(Ò¥h ‰¼2žaÏ|èæÌA¹`µÕ §R\ŽÀ$Où^;µ)C K¡ö]?—G> 0àu|v몧¢ÒK§©ø,­J—ÒVÐüIb”g{Î¥Z!²Y綈ä;¡Ä#ì8~’>n|gð$æÆØü|Q~Á-Þ6Ï„¼C†IVƒúœ]_¸•u ×ÚŠé^~—Éqá9EÒš:”|;tNZ=ëõ‚'³W·ÑÑ„oè“ÈÈ%µùœ‹>Ýx¼Å`™Á×lÒœ…TúVÅWu•Ѐ_dyQFY‹»ãXêƒâ&ûâüÕMÃÅ‹mŽhÃ,¬´ïô‘€òÏó»Å±=‘(ÀˆÊ1åOkÛzTæt}õ½H"á;¿—²±Á~÷€"æ$¾eŸ‰Gî-øOX¹.þZÐÈ<‚þ€©û«¼‹ ‚¸‚¬t„gwwO:álµ$Ú'Øl]èFú:´}žÀÀ1Ôͬ¤slCx*>/ÆQï®ÎRYrúâí{㼄‹x“Ó%Ôc Îe¾tnÆv-¬2ž5€2-ªkßx–q9Ý5†Ãß¡of‚„Qå—~1aW€¿Ë/”¹¾W¿€Ïö×0_ù†puÿ„À]Î@Úv²K¹ëÚhaGùŽÀuqT½çy‡7‰ƒÏ,S5kü Ã7†çe®]L¢V×Ïh²®Çž?s"º±J«&zze{klÛ‰÷‰71žû¶ +áiò„:Žsdø°)õ;Ù)ÉÄN÷V~¥-#<ÜßÜ:XjðBß%¸Ô þ§â9¾3àc"‚ß;®‰Ìƒ;S <æÌéSø…ìn -3 ÷^xøÞ  á¼è³Ø»àšÝ8-ÐÓÎ’º?¶’wm›Ër«Ë`´tTZî™Îð$â&ev䳊:|·v–f9þ·„Ø÷,MÝàýü4ß‚Èç­ž,oÓ¯mkú9㯘=ẳÉbîC¦_šP["O£ÐRÛB‘–û¼VW›Á~WXIªÏÉ¢–(æ$fe€´œw+c<öKù¹žÚ©B}1 FÀ)Û¯»‡vt“v¦ÐÛV¨rW¶2qÖ ïå]ê… ÌÓD»6*·éŽÎnlæ²tÄÕbÀ¹©mn!†æñ¨v|êÉÇH*nždÁ©v£M‘W†œ¡Ü=Z_˜HõŸZ]H7FŸà(ޝ(oª*‰ ŒÍ¯¦­ÿòhª¯E?ÂGº²QÌјTx»+A÷¶×0w:ÕÓR›ᥕÂ4vír*'åéúzì’°¥®5-NÞ’õ$rÀ/llÎ ¯©æñBü‹C5¥ü-Mð:p =ÌÄãÒ]ÝlÙÞÖÞœ>õKϤãø%Æ)ªÇU¶ÚIÁ½«VK[a.¾ÙjœNuêlvôïKØÿ-Ø )Ï@ôà%òŠ #à*Þšüg`Ùc«•÷³køW¹Ç1¸1`Xd|^#œÅ‹Ð‰üÄpý+ø{ò7}Bák‡‡[å‘xŸðسU ”±v30Ç{€_òí|lqíÈ_<Ë#«&;­â‡÷¶ØwüyhGÞª_ÀÖñúÈ–ñÇÝ!qØN®QŸí2üL:GÄ]RäOÚ‘K؃¾W|7Ñí ^/$é@›8fz²;ûãÝíÀ}e¥]pgàŸù%ÀÞl"¸…7ÂDŸ‹^²yøìÜn«[iÉ>= „®Õ Ô[¤1=Gs{þœjº}—= 8[Q¿ö,zúIgúþxo– r›-cvD•Ž"‚Þy°€C0{n ªˆ­ùÝ ˜bur‡VÊbCCST·Ýº~• 6„R¹‘Û6s• º8E€«¦½ !>Éâ2å@eB†±1›a¾#Cgie’ÁèfUW±bàÜg€#sA˜"RE°¡©EgÞVž=ãëø¬«ãÌÂ4³Í¬sÚºÔužNMW¯Ý€X¨À˜{|žVËí +›O•ŠF»ÙT¹jòVÛH ų«nRQÂøž‰vèáÃQh€.C¤vv@lQü ²2ëÍï²Oî~΄CY²ºEØ| 8•+‡ ~òÊ)w?wüø øÜýýïãO*‹ êÏ|éî_NùüÞ±w8[ólT¤©Š{Ò’Wî>Ö‘l€ðòå¬Õ»BÌ1¼Ç ¦{ë¹6ž÷hK˜cdžh¯¹3½~äÕô_ýç_IÿÓÿò/Úí‰=ŸB`»rùBzõ;)}î ÿI¦ À=«Øyœ?Ås’ê§}Ts¿”ž}–3˷јµ4W0 _¶H¿ú™Ïâ4}%½úê·¨rê¥ÝÓîMüÞÀ¹x5Äá¥:×××—®^âû=Q±"s2™ÄJ ƒìã£é¹ç?Å/RéÞFú@¼ÇóÍ (ª`Ú sö:.?üð=Î`  }w+燀un¾~~/ rðRn4D ¹àS¹yøRïÓ!u“îògysÚÂÀ ( Ä-Æ+¡5%çe‚Ù< ž&à}+uu÷E`Þyk uÑ’SGv[[;G<‚Ý6ÖýiŒ3²š M•,×j§½}ýR¼è½J€•ªÎ¡°Èàx‰â’Bãmàiõ‡ÿú_Ä^lœœÇA» çý`¬'ŒpŸ‡bÝîƒóßÀqdt8=–©†ÕRBþVRÙ[E0CiÉ(qzò^¦hµƒ“NÅYØyžÏäâ8?ãd@Îzn¬ðÒàÑy¥Âh G£K¸Øú]Zµe‘Š ûëÛîïKLjÕ" Ì'hìç?ï{)þç?›OÌ ø"U) zK[Fç4~g4 ÐÅá·¿üŸ¦gž9œÞ@}íþêI èxE *åqP þÉó¥Y/â¢Aº&Q¶=FÀ$¦îžî¨.´•™­?=ž"*W˜Žª¨vÙ4Œ:Ñ[óè v“ Þ:€åeò|ŠâïòfòèήŽ4¸wÇúTG›ã¨Žaò1ñßdUí/çW_טžzê‰ôÿæ÷SG–|ÚBå–•”Q:ªPú‹0~6ÐÍu,ÌÑr ÝúzßöîÔGxÐkÕäzu˜Y­¬ÏqFšc©c˜ücàMÃÈN%P%P ,—צÐx¯p~8L uƒDÅtöŒÞ¼·÷4¥(7ZÅlàv]eq:·5²tø2 5p$?gPŒai\ÞàK|®¡Žn!+T9ÄÜ]5]¬¦±†oÞy_Éž’” _Ìd­Ž×!¿kǯ"À± ¢©‹Ç^ò^yˆ¸ §n¯î£Ó«X XἇinÁ¶ÙÊ%°Å';ð0pÖÑN%û.aP›dØÝÓ…£¾Å>Á yC67ßž;¦}d¢GIâ³:…°Ô`Ö¦ G#üO~m›Ü;èGÅ$YÚº>ì.Æ^ÀмD5ÊÁJ : ÕHhvVêªÿ´uô/e*<Ÿ©èh-ÁiÉN.8@ä­\:w”WßðÙp:!*c\äAq!Ž2ê§'F9Ãpk8U]ŸŽu[Dëø»}‹#5ØOí&Û¼·¶lC&•ó™Áï td¶£Ž6“"„¥{òœ¹¸þ2ÄÔ¡töE'AKs[zý¥ï¤pkr\9‰ÎÁÜ”Õâ—öƒÎÀΜá|qam¹Ï}ŸbÎ5Y ž­‚Û:­V)å;+ udÔQõ~×\Zê9oV‚Ó~Ì+¬#˜ß?@ û û?EÅ7í ѫŠ vN×.sL‰¯È:ÛDîØ9NÒuäµÇ_”b+Û #Luq(nHÿfúËË‚Xßül–gòÂ…ÓP+pœÃù2;wœÎtAa¹Îþ¹Ÿòù×4 ¶™/u.iÔï‰Y`]^Í»yo4U2uø]ðméä‰3T¿vmDbzâûlOŸ8Á-0v9hïn§ÃWcÀ ¢xcB|ׇMéììJ#ÀæâåKᨹM;v«Z# É¸“$bø»‰$ê<o°ú² vôÖ-º÷œL7ô1°GmØv®ÓiYEbƒ]÷Jäi".„‰|6æÍtî‡ Äôz?Lè>˜ƒtªso¹=E j?”ò[¾·!^B‹:_³à2¼ÉÄaðPÿZ1r«~.øÑÇéŽÕE¢Ûñ”Ç9¡ò`í]“ c଼P}Yž½i/Écsû¢C4Ï3Ym@Ë@¢º¸[6­|œ÷kCÀZx¾Éý£cà»rÜD“Ì‘o@¢¹ª. /Ò.ª­§¥2Ï¿ø³? 9íQ[Á«à ™sØÁ' å°±Äåà¿.CL:W 2#¾Sû¼>ïçGxY&Ûçpºšh(OÉÙ˜NÕ¢2‘!„½Á+yN sw®Úf±nù/÷hû,[A‡¼’/ ¯€ɇ• :ѯ_»pñˆ-ƒæpö„®mlC¶(oy†}pÞþ¬ “KÄzXŸŸÊû­ðÏô%ì‰òZÙ Ñ~²ðG}J¸ø>‹ŽÄ—è0ÃÏ:£ß5ø³üK9£­i€ÙÄu÷U~뺅•ü§[¿‹¸²™‘´ñä)¹õŽ2f GL_ØêPfî[&{ ¯,éó1€œñ$e Œá“a Y"}>8¤L±z‘£óÒe:­ˆÏ¹`ªø¹¤N L”Ñ ³ã× n;gîU®ò^å²þd'éÞÆþ‚kÀß5 ?ÿ¯æ»ÀÖî%Þ¬ÓÕ”!Ü>clß­ŽaÁ”í[õ)ÝÄ‘®®ñîÑ£è ú Ä«¬Ó0¶•¸—z²^[7ªä|7–À!ueqÁw…ïˆy¨Ÿ;ï8o7P&Ãóìs%üxäz `¢ï1Nà ¸¢¾çX±~éŠ1á7¶§50ÒÒ½=ŽÂ;qòdzïøqô–©ðe;ŽçšàçQ&yHwN&€á×¢¾¨mžñåút2x¤ƒ'×(榌Ëd7°vÞâ*+—ekÚ Z;çì|Ôó혠Þd!^«Ñä>£>ä˜&Sèõ\Üà®Ýóèc‡Ågõ :/qÚ#ËyÚõ#—0cK$èÉÚ¼¿„»ë'x„ufsS¿Ïp)»'wŸóGÂ7âÏ®UÅ!þ‡‡1uа-ÐW6(Š’OmC׬%va2¨8pàɺœÅ|ôÍð‡ÖÕZm·]µô!ÃïäÀ¯Cùáüä5yy܃>¢ÏX<ðÊV?þ1{#ž­Ò%Å ”WIyMàqÈä„p³cÛÆ:]…¡Eß©Í0A‘•:f>¿³ÙÞ°Æb\cØ*Ó3^;rãfßÊ+íþÏ£‡¡¾Êín¸¾ŠN„~VTÈÑ6ì/@Œ'_“†¶Ästº °e\Hþ¶j;{:°©7¹káã84tÂUtòZy”í誧›G²ki¦ssª$Áê ‰Ù“éÕc×ÒÑãWÒ¶özöÉïè^«E§_-Ôç8J›{Ë:|øÉ¤í+6~¨-+²ã«U¿&-äS^³v-Õ”yñÀÅÎ1çÜ{öÉØ"‘×àYt[]#©ž}Xã]õÊ&>¿=~—,`ØE.}½*ÝÇ—CU€l[äâÒq¾zðƒî´p/«jIÄúî\ƶÔWœ16ƒ½äQG¸RèÜ7œÆfÆÓäBgª®ß¾"Ϙ¿°µ „Çf.hnikMO!;ß{ÿ#lÑkàœ4„÷ØNÖà¹éú"lgïšµEä“¥Ê#ö6º°ŸÚ„ú|ÈÄyµ~ù¬—²+dĦ,ËÉjå‘”u ø`ÔQü£<Óž[c>úÖà‘úSõ™L¤NQú| ¶£ÿHN ¯tÞÊÈ(ô¡ x¸xmq´‰Cy$.:Žü1´3`ƾe§1[Ï¢JyPG7 Úkè{“ØŸÆdKèzçû°Nù×#)ôœ¡›7økƒfü^Q¦³ñ.pX^bR±2&Ÿ±M¸Ñ¦ÖFÒg!-¡¼ªÇoùž{,ï–GÚ&ßim?ÿøì] ô?”qIãVŃ À‘öڹɤg(.½tþLêìéOý;w§¾ÿnðžæd'C“Δ󅞡SCû±ê­ TsŒ3IÛöÖD$~fŽ3Á`* ÈX™ u •J¸Á­u0P%Ð¥Šc•À€A„:ZÐÔšƒ™UpÞ€ Hc¯(Ÿ€{‘Uß´;ƒ8k¸ïúÕ‹ Š=ÿEær q'Î%r*çâÛJÇ Ä10™.¢²@ŠÂ«†àc…Ù#ã·cžeÙ¬–p …œƒ°­¹#Z¯{¦B-DŠJhñöÐ3hŠïóu̬µ­xkëÓà"!Eà€ï Œttõò«Ê{ûvbÈôãH³R—u0÷LØ25®{Î>ùñ«8HN>+r¸ùb?›+²ýâú»…àÅ‹Óé3§iSº3Î-Ÿ%ùãÔ©ÒC¶_‡˜sW´DW‰€Èu½ùæ+i{o?] vð;í(`’îþЕ¡x¤»«7 ½gª?òØcéßÿå‹Ð®AµÎtåÊe¼£éÙgžåâe*öü>þsÚÂ'Ò×þâOÒoüæ—â\u3ú ²Ÿ;w&9òíøYãóÔÉqô¶ÓÚý0ðÌcaýõ¯ÿYÀä‹_ür:zô­ô_þ×ÿEúßþåÿžâpèKy¬í'§uG»{… äy¯ûž»ýD?ý¸9ý¸ïï}ÉßæÞ{Ÿûa?‹Éò¶ÜåÏ?l|Âq×=÷ªØþ(žÙä‡ MÿhL9ŽßÙ.ÉŸsŸña¼îcÊwb:I|ÖëÞ9¨,Êo3cg}þã‡ù*Þº=Iç:–5ŠÄ«œrc;V¸¿{f•?Û–^ÅÞÏ1×Yàç¾Û–»ùÈÒ•Ž^*ËÏGÀBãÐ ¼mÍmÈhÏ•ÉÕÊafŠŠÙžõ´Ÿß=V…\¥¦Y[‚U±+@y5à¡<3A@ç³EF²ÖoâYcSsб] JY5&ÝDuc:OåjsŸ[ØlÙ#PP¾l©èp¶íu½¾_ž¥1oÀÎ6DÍž¼¸H3ÎìÈÎζ% Àp„ ð©X/Qªc+œhŒ¡ƒÄìeï)b¬‰;ÃÙÞ2ýûõ·T 8V³‡^.÷^4Šñ×@@øèˆ‘ŽÝoåUrq™VOòEÎ…Zøþdè­áátôõWéiƽ¼f°Zé$šïå଴V×id#8Ä™F茵B4(ŠØ#×Ó}‚ Ü'´¹½¯?}åw~+×pžÚnÜJyiÂT#²]û"“½·§—¿í,Ò1•…’pÆÆñÂ:˨„£S5%®P]šÏ„‘Uß¶ÕQà¼Ø 6:pã vïÛ•^yýÍ´ ¹É"¡gð¾bdž²Îêó«×FÀµ¶ÍáŒc©ÈW wà…çf0vÝ$àáK'FT¢l>£Ìhå ûNpk‚„ÓPSlyVªÀß„e´3æ_u‹ÀÚsâuÆY9S…“É@¼Ž,«×Ýe9 €îI‚/K°g$§˜‹AslgyŒÁ éÜóuT"·æÏ]J8ËÀ¯Zl q'º_0žG¸ÉK 7µn#)Ç$'nëÐÈ%|ÀÿÚëžù'-d-| eÀpÀÃlÕ£ªpLÚÙî6Ι2ည1ñ% èwpÌp`óE†·T2¨È€¾ 8O]h-¾  ñ½ò̬­.÷ƒ,áĆëbôÄ:øŠÎJÏ8Œ„~hÔ$ƒ7®EbèNÌÁjTÛuÚÙGžæ¾Š=Ÿìº`{}ÉÖ®? ‚Q¾ |àî÷ʤ&hæD„¬L_»ãÙ¾¼CgÒµ«—Ó•‹+$õ?HrL;|ø&ÁÔ=ØH•­œ:²t— í×} 5v¯Œæ¬èu:›ð£óV›¾$ÐÙO¯õ‰3‘ Æž[±&¿-— ûž›Ã Î*‡ÂMêóࣲNºÏ‚ð$>× ¯—I)ðHØê[Ú~vH, ñáÇWP˜¡ë"Ü3øX‘Ÿ¡ä‹[*áíÐrD9À@xÌü‚sð&ºÉ@C3è5YÉ–½Y0NÞ•ÌY®U ží†åý¶Aç]cè]Ò ¼Hç¬0ò^ßÕÝÀ+tu&HPÇ®+“¡æ'ÊAe)#³fž…Ö•I:ˆcXà ì(„Kn~~ûÆHzê¹çÒ—~÷+øvΧÿùøo8îÁ4 otÎÇWÈÞ­Ý"/yË¢}©P—W°^a.o1°óçs^Éܘ#8ôsnc/ hò¥bÕ¼K¹î±òD÷̤åëµp]}wøV”GÊzþV:ž¼WTºÅÖç8™î ¼ùã<Ù/áíZ|·ðÓ¶SGOÝ^Í&οaÓò]¶,–¹dÎ~ŸÈO„«ÕÏêö« `5!Ë««{W}—6®Ç†é76Ùȹ*S<:ƒP™s Ç;Nnç(¾û{6_hX‹«Ú¥8sà¿úxjpÞ$ï.YU1pg_¬v³‹›¥Ií ÄÇ®žîб욧¾Ã\¬ܵ{WøÃ?úðí¯G =pÍ%U¬^bÂ1t`º ó/ÜËl_³µ8ôsÛ‹À…MØ[MèäîAÜÇ>™àY.ðcøº{zÒ[o¼îuOÛä§ü¿“~çËÿœ¾¾ò»_ŠŽV¬ÈÍ XÊDr8&¼ä^‘`ôÂÞóYÐÓP†…ÏRœäy‘ÿúG¼Û‰Ï¯â_à ߺ'â›òÚùæ’fÄ¿líŽ nŽoOؼ±J}r‚"=Šqžxòpà”÷º&ï‹‹1Ý{b}ΘF)ó71U»ÁŽvkÌ—å^›L¼ŒÜÃÀs8 º°0qQ¡SÆØè&è]Ò®8/¿•§Ä;y‡]zB*ÆÊZ»6ÿ|ðþ{i?é§)X²³¨•”[ki—^ë‡#á½e‰êáµU )ŽpêN° €¥ïtŸ~º‹…0?m9[ooPYž‡ PìúºI£$4àïÓÄ}Ü/“+ˆ¯è[Á7¼†l›&1c ýðæð,þëátîìy옭¡+¸„s#I4â¬G/ZœOB5/&Ö£^Äñn؞ʞe‚ÒêêÂdjR»Ti¢¤|t`ht|œà8üÞË®GÊy‚ 9×<¯[¼ºb‚Ý6èüw“VïU„~´­i+t«“™ÝÞ`˜ä,¯7'È*pe•¶éï..•á#@Ö­Ø­N°ŸYO#cRS´¹Ÿ]:G¢*þŸšVô*ƒÏ&5£ÿC;®Å“‘zøzV1¼äJÄ3p…-ëÅ–šAw×WÒÙÙÞ‡7°²à ò+à*M™Ð"ØC;eȃDûÐ Ô{ø^<òsùŒôiÉß„qó±ð!Ÿ¤JõñÂÄue©ö¤äáw‘FÔ´Ï–M áyõHùŒú›¾“ÛäCë$¬l`_¯g¾å¬<9KÚsDø;¸f=‡n¥¾aâ¤Á•ø“IÚ£3Ø•v‚ÀGüÖ' €›!ž · =MžS&k/²¶Bd˜ð+Ʀ*äØ*! Gå&v8˜ÉðÊ-}K£¶ø/(зŽ@w=v§‡ŠVú…w`ÙéI]Ãî%u$ìˆgÃW‡¢óGÍQ¬ªNiá¶¼$øŸ«TÖ!ì ˆDD±ÂË€’ˆ&á*òl× @Ìø‘pççhÏZІöÏ.`üÉt2€PiA¦Û<ÉuŒRg+bø§´lŠª¾æR“cL(«Úù œJJæhMJ›ÞÔÒÃôFlH1m’òh (‚È(|o5ÎO«p¬Tñù©‹g˜-N¶Æœ¥8Ø$…l5=eí©–ƒŠM ú¢ZüÑN·©¹#­§:øÔ‡Ñz[~¯ˆÖ›¶ÜÍZ tÇñ !²‰8¶9tg…›÷|2xü±÷?pÅïbÄ/®¿3OT`­l~ù[/¥={ö¥}{÷ErŠ•QG޼øõ0çŽ+tÆÆFaö‹´ý è¶òô ŸO/óké7ŽPÓ†ƒ£eÄ î&åV>M’ô¢á%ó³Õºn…ø$NMÏ/êØÕ¯ùù‘àq‘ ´ÌÞ,Çë#ÜíñJzèAÏ/éƒæÖqÀ>˜¾ùò7ÒÛo—3ÁŸ cÅ`¶9«Ämqqø™_Šw¾ÿþ»t\h*õ›”ÇŽ½~ç‹¿GÏGÓOOwo:ÎgßÿXç5R…EWWO(·2¯Á»p–Ÿ`ü®]ûyOAƒ;|èáGSo-(붦öÖ®ôö[GL=Évì­­­qÏ…‹t¨€hT~ö…xßKýbúìç~ F=cMQ1ÿð]L> ¥®¥×^ùëôõ¿Š"Sœ8ôP$ÅØÞý½wßIÿüŸÿ·À–3»{Òoÿæ îÿiêïßÙÅÂUeøG]òƒŸöº÷ÙŸ~”Ÿöí÷Çs÷ÂàÞ¿E²ä ¹Üw?ê~•¯Ü÷9%÷Üü FPä>¼û‹<=wÅx÷챿û½8[Gµ r1æ€2?SÜýÌïÄïw>^:š˜øý½ïñû93 n’gj[Å£¯7Ê’ ê2†x=FâX&ðÉÜ·]’ò)*×ZÚC´(BþZáP…´,r:ì6!¯Ñ­­oNT®a[Èó e²çmïhÐj|¨ÓnIŲlé«8xm åùuŒçUŽá¤ÓÝ>òµ†Ð€s„à2ËپŻ Ú˜— ƒ„ª1ÄuZý.RÅ.ß´Z¡‡Šçš©‡èè ‡zˆð23U.Þy?ÿåþÚu@cDÇ‹U€òÚÐðîç‰ßs“Ƥg«»uÈÙ€6VÁÃ%ôÊÆmíTï ¼åÆtäÛ߯à£5œIúv™\•eØ:¦8VÃÛ·÷SÑ× ^ã0ÄÐ['€­‚ú/÷EGŒp+|t:HQΖaTö÷öÒr¬4]Ýž¾ùW•æF9‰yÚ¾›N»÷îÅØª&Dò(xm•§Õ¨ôè̼½v‰÷jÄPœ ãðÀø‘m5¼oï!žKT·\GþüEš¸9޲®óV§ë÷5Æ[áûöLÛZ²³®5Ì!&RQ­"&hŽ©»ë4ÊŒ3¹ÐžsÞÕ±•5fÑGµÜ-Ã&(&áf÷¾hQ¼Y~9}ýÏÿ‡§ÃãÆ‰¹w÷ž4¸o57D@IDATg7†ik¥£“kc Ê<­R‡Åã èûà=-â 1Y1•¡·È rB¿ÂBç’í¤Ÿx²3í?HE†ñÑ£o§7åÌyÚÇb§ô×ñØ) ÷½ûö¡oÕðƒ$ôöö`_Y9ÃB1­´­¦£‡£#Y˜gÕ_à*­ït6$u^ ÚvèPV’(K"k¼íÙÞ›~ûK_$€Ní¥‹œÅ|"hM|Ÿ©LÖ<øÜ“ÌwkêêꌀŽô¦Á¾Ä|u¤F x´Õ SŒl×(ç6€+ÔIjñ áyõ`Ïâü½ÿìŸqÙEdÙ\º6<œ–q¼é|O½?Ž3à|yƒ%ðÕ=è“:²<óu[ss&/˜§y»èˆS&é ½øH߯ÀTÇE pX¡ Í{ ¬~þüt½+ S5344„|ÕÙÏ÷ŒºE ~ßÁÝ©“vç;û™‹‰ÔèåÌs …¸&¥ûñ#ÚÒ2'q:èP$`.ÊheÄβÎîŽôO~ë÷¡ ¹8à§©>Öég%ª¹ØCêúŽ¥wE«üí¢†º³4èåg¼Ÿ¹XÁg° Ÿü™µ•ÌacðñüGÈ~Êh'ù4•&ÍTÙXoÕ\붆Œ~äKü7 ÏÕ±ÖΜ÷íßU êàæ{+Îuˆ[•Jp¸XM¹lÇ`ìÜu„š<áÞÈÚBòãO?–ÎSáouâ,8c’¾m3=/Ö}MÀ+,)‰ä!«4]‡ÝÝ]$ʶ„î ƒÑ 9+­€fÑ™B<‚Xõ)~þIÚWž Eó,ó3¸1*0¼à " îÛ‰]Ðxnâ%-k&:ê€g¸pž™4Ø3ÐG™87W]ɽ©o('H´¼¡$NÆŠ™2œ‹Q q@^j'%à"|ðñ§ÓN’ƒò€Uv5G×Yÿ¸"ú°”_\‡  ¼—/ز2]§­NSƒ7êëpG64KÞð‚—áT…ŸÂ?슡oëÌi»ÀñE’¬H,±¬\ºSg àG"‰•jÛÄ»9˜°Sïæ^y¢NRqP¾&¯²=»]Çži®³QÚ±»ƒÅ&¶3_¤E§IH>”mL †zø]¹h—²Çà¬^ÀÏܯݾHE˜K¬25iÖ.8ÎË€ÉN\Y-Ô¡iD™á@ÎSŸc8Cÿ}§_És¥yÖlä;þQp¼°¹ä @Ø€Êûx&`Žþ¿ÒƱWY'¯´Ûƒz ºûŽ鳟ÿõÐõÔ¯ú÷c{mA¡JPÀqÿnÊ_ǤBǶ®Ã›ø=|¬,ǹißøõ¶Þò™ÃñHÀQù¥.è~GgÖ{Å\=¦2’‚ ß{Œ‡cÖ¢Ÿtu“0Jç2ƒ,#Ã×¢ê¹$nm¸¦††ðÝCe âäÃãÔ%Ô÷kÞ…§g”Fbx`RZ8½…÷ÏgLT ÓýgÖñvßÄuy¢5ê>Ü 8ÒHÛÜvð©,l¦›##i ~ïÑ([šC—¾ïJ^ë¹â:ä•=£ƒ “çåÝÑ} X[ÁÏ,æYà‹_cŸÄñÓ$vd“&•ß GÏÇgòˤse…NvëÜŠœñIÀ"ð ãœÛ[›žã8ÅgŸÿTzù¥o¤ÿñ¿ÿï°q­öWºf³Qïeì A¢À5í\` ¼˜°W˜œ^†ûêÍÿ{ïdçy&è}sn 3ÐÝèF$A‚HŠs‡%r† Ö;£)WÍÚåWùÊårù®Zo­×»5»£]{4#i•s $Šb&(0D$€NhtÎ9úyÞ=3RI.f5+p$°»ÏùÏÿßû½9úë»Û 3p#Ʀ²ou,Ç©D¢=- €9Ù6½Ò²s«{ÕÖÖšî¼ã6µúÓ}<˜~ïcO»º¶wÝ}w:ñú«àM0‚Gí¡Ì‡¸ ¬ÇÐ/Ô/9Oñ™•¹¼€Ÿ8+ „\cÁ¡“²où_ÝñÄU÷È7²8gdzüŽûâ×ÀCiyC)üìAÂcâZG/)w –ØÉ(¿\žALÏÕ9¥ƒœMt ð¥ÏgS_|f»M›F L&-ÁƒÅ–Ð[AùlÉå!Ü0l½à¡î~Çw‚ozfÒ+×lºÖÒ‰ —Ôû¤ñÄà|Ñ–/¿×sùbêÚ³/óßâ£?z._J}ømë›Zƒ?¨ï,t'ϰR‡ ¾ n¨û¯jTroñTº–—ûŒ¿÷‹¯h3ñp‰,bÖ´‰ÙÞMôBWçGú.‹åToª¤r[ª£÷ØÈp¹ÂZðo“yjY126‘ÆH_bÿ}[[³àÿ̰¡°ªá ¹ÅÄy}¯ø…·ã÷v¯Ñ^›äø`SìXÚÆn‰¾ÃYAFhmxˆÐ— °“ì,—¿~B~â0/a/v"ÕfQ/ŸœGG¥¾:<çg»z»˜èäçÈ=ýk¶6®ê¦¹›Ä»8+;O‰[~ÎqÇž„¿÷Y|OÙnR—6wïWWXU͸HtNÛ€‹×â”ô  ©}&/•¢lo'o0TŸ†2”âº(ŠáÚšlo\a*’жÀù*ÕÖ³#Ü;‡n…øûè ¹ÉÌúÑéœt²w˜€§]˜­àžÈa¢Í9ÄH mÔy|X¶0¿çž»2ý”ç„ìžÚbvK±’¿€Ñe‹´ô¯ÚìCoâ3â 3À>\UdR±>ÎN¾<1‡-¹J§0ö022H«~Ί¸Û"¶Áʪ‰¡´µ'Épzb82²±Î ú—Vµð…`ÜW¤¢êíil¼Då3gzúÝs‘˜ÔÒ@{õ%ìL’f©ŠV™øª|Ùà^ãÄ6+ ½UÞ“†§é°¼¿h6jÆ`³cX쮦ÿSøî¿a?q—i:AÍ ÃWb‹«Ä0èP… ®m‚l¼þú-†@0Eð^æuæìYœâ7ã´ï:Ÿ4¶Ž½=}ú³ŸŠÀ· LGxO¡ìkÇŽöôÔïþ~úÖ·¾–žÿÑÉ~z_?fV¶á¨ó>ÒÀ]wß´yòäÛáà•ÐFh }3Ê x‡cûit£Ð¼øÂsÌûí70žJw„P¡U¡Ûݵ;ýÕ_ý Yi¼ûúzÓG?öGaüž;{:œu=öDdík_DáÞ…!&=ôÐc7Ào[°ªö®àßüÆWbÿ0*ýÎ7Ý|8ög0Α O|è£éå—ž£5æÛ±çW_{)F!ìÛs ½ùæñ˜ÍµŸàú,Ìø;ßú2•êw†=tuˆ$ƒ'C€éЉhýW¿ú¹ôê+/<°–+'PL¦€ÑC? Ž\xéÅçÓW¾ò¹->ÿùO§z<µwt¢pŒüï"s¯.}ö³>ýé¿Lÿüÿ4”Àè:Çñý£þG£ã—}ýCÎëý®×I*?o¥áÐQ~* ×ÒñÖK9²õ¬øÉMTºn: ?@ë\miÙJ¬ `*&»Hh ´ìÛ(WÛÜì¿ñætéâE*ÎUM¸f¨±2:ªm\SWO ˆHr‰ªÐ2l’,á ’rL®9óîq‚SÇBÞªT˜Õ§ìÕéb°[%]CÑÌF«4té$sn»Š•ÙõëTð]E¸tj~'k7uæäë<ûZPp åìš+˪5 Ö –3É,RÝÃÀ¾Y†îmyq‚•ÿ<ˆoA÷ï§JåÏÄÖªãœEǺ·Îúoe¿‰O+QïP®4Aæ•b ƒÿè¥ãcc¨^d™Ž…´äª! Ÿ&1œOœµS!×I$y`0a˜ qeple«Ciåd â¨çNÒþ½‚ ”FÈ ®±-ð Ž:Ï×ÖXQéƒä­J1¨£a`ë¸þAZœGr§x¯‚½å<‚=Ä~ÌÌ÷=~: ä„q =‰ß#cCQÑ©‘%îk¤©#{½Á¤ „ÁÁ†)ã¥7uv«;ÍTVÇÕ©>aâ´ª.™åèìbþKâ«I´¡oséxÞ¡#£ŒÞeåÕÑáešD g#–S5THêfŽó¾¨vÇÁN]hYØ,y¬SÐ=83x™‘f^ǧ*&<[~ ¡î9 Í-ƒ¹V~Ì1o¾¿g(åvPiH7jGÎs'˜iÒàIKÀb‘YjHî׌g“róÈ€—'wוð0j(¯f5è;:ÏtDˆEEVïjDFjgŠí‚“};íÞ¼÷âÙ^[~˜\ :q= k[Âè„Ñ Uz«$ûÊ£l¨D‡‰-ÞbÆ÷·zÙ½.ÓCÙaPXCÕ |-IS& N¦QœÊ…ûáhsØDÚI+ã$@UÅè¸çòפpö&Aˆú&>ÛÆQ#ÛõÚÑ FYqÏÀDeŒøµÞXA7ƒSE<ÐíêÚ….XÃ|úäÐR:Ú¹ŒµáDngdÀ-‡Dꆈ Æ7ÿ‰}òYaãÐ0  ó1˜ü4kݪïyäˆ4)’Çh>—‹ÕÖV…Æq½¹¯OÇ_9Í2" 0tÂÓ€îäÕV"÷FBƒ/¶¹ñÞ`€x¡“Æ6u9<ËYr±6pFǦwtsˆ\ŸçäÐÑ©•rs´ï³eë›ls°f_·½ï(úü‘¯ŽW܇ݟk±‚\XGU km·'NòÈk,þĺ‚–à¼o‡‰Z^tùbOúæW¾šæiµ(ιyƒX‡êzºãþ{Ó®ÎvžE0 üÿ¼Qæ|ñƒmVýÙYÇp¦HÊoÉÊ.ñÑoE»^¾o…kÛŽ¦´­¡^·œÞ:ñUx̸„ðÜ“ð¯¤ÃÑ;nŠ5šâ¡ÎcéÎN:- ®ø['ÿâ‚A8„®Ÿ‹X‡ ûâ§<}ž .5HijiaÝÌ/íOï⤲ú`” ³Œwê ÅÆƒ<·=ö¤ôk˜D¡Ž"mErÌÚ@ÁCù²ïË/òÁÛòå¦}ºaÓ‰-6ŽMòJ8z¡âÀG“]:÷2¶j÷®°ÙÊá1VíKóî‰8hTÇE`À~Å íÞ¥Þæ÷M,‚éçt ÂÇÀ–3z•³ž³ÏÌ’˜¸{Ž —8ÄWÁ })7ì?€/ä ŒŒCÏÀ†ó;ò4ø{œ5w‰Ä7ూ®k5Z,ËñK8¸¹—«”ïûLYz¸¬½„çúà·8þÕ«ù˜åfIÙ:ïýÜ=•—0ñNÜ ’¬bïâqÚ-?õà#aG}ò/þ]œµ|]4‡°9KeºÁ÷v'p? 1 JZæîÍ—kÍ7" l"@Êw< ÷g Mg!ÉŸ…µû¾r…¹áöêÑMÍÍàýjâ½-™º«k7ã÷ëKÿ‹ý¯R[w×5ݘ5Ÿvm0ø™ ÔK•ì‹……_€¿‚Ñ™M,㱬 ]‘óÉvàçÒ®7Ñ Qƒ ØÂ< Ê`hÌçó•H¾?B¡‹þ‚·ñ±­1*Ñ&--­ÜcZ&™ìþÓë/?g*ÞÇYó@ƒ.Þ_8ÉôzLîA{Â`³8¬luÒÿÏÖ}mÅêM!¯ØŠß—–…¯zŸÿ„x øY.ª¤YåtvøÉîòžAaM’|ë¾M öxÒvýv:¾ð¾&oD’ðÐiâQè¯<3¾‹®à>ܶ‰íyy?ÏæÜå¶ÕïâÌøÜïÊ;y4´ˆ^î}…›¿£XâzñÒµûW¥oÈ.¤WLâGÕçò¾÷Ý‘¾óõ/p~‹T@cßqëà÷×…“6†ÏYG§´‚ÔCõ[gèdeòŒ0øe^â¹³ºY(ò8-"«ˆÙ¸ÿ¼B» ;l-ÖSѰN…..’ ]TJw5žkKdù¦¶Ï4Uº.^ÒÊcOøž€C‰—Ï<+þi‘AðkÛ!l˜º:A˜sNÂMiE ÁÞøï ñ&|“ü Úäø•žÕÉ´=lã¾NWª2t¦ l[{ýUÂÚÿ9ŽAyWN ²ØDzŽÁ³—Üõ¬äžŽI6TÃÛ‰"ÞàbJÔ!…­¯µÝü̺~q× ‘XÁ9›ØNd* {.K"YdÁ‘ø½6A‚lð,º5gkæÜ__¿Md1u3Ÿ u©²o»õü,K´–6ô'hßèÿ(£s•‰«3©6 •çÓ}€8¦9cÐ(¬PEå l‡`î"¯Yë rs’äé¾´jgêØ½žJaàú¾Éµ5gé~¶­©|£e÷º6mÓÁ›zªrrÆ8[ªÒÛ:é 36ÞνðÀ›S?sÝI¾žÄ?„>^Ý–ŠËèMÐ&~ÉŠš†412&‡Ï“p1ŸZ«hsÏè\ Øà´ƒÜÃw"ï`ŸSâ¶Óá[oI¯¾üzÐ_QQ>ìöÖ‚ØÎ>®^½Êº8 pD^ç÷ƒ—@CžrØD uí@å•­ÎåÝ| <Î|ò?yBtáõ²zco,aCLÀ• &¢Gûrxš¶«|G>± ýÌ![W¡3ùqà#ƒÖáwÞ[³üGÊNZê;®A}Å„w}Å®7ô%üd5àA)#X ß—›ñ™{cOÅÅïÝüÙ\˜_Åâó?Ã×_pd‘úž:]&ƒ%Xuh [cÍÚèüæÙ‹ÐgrW9²ˆWÙ…´ŒbkLdîE^2ƒmÎñE,º1p¿­gÃ2ÁõLWþ½)—¡kõ[“å§ãÄÎô¿Ø‰­¹¥[>“œIB³ Æ ÓÄ¿…S¾ÂʪrΙ ÔÃ*ƒI©ØMS½V·­å¬(AI–©d‚.2-Af[0 à Z¥ÕR±½Àë Hëì±*|d7›C@M÷{5T¬Ø*®ÅÛV®‘ù.² «C0ãT’ïZx¿€Š"É•eÚ?°LÁò Pº¨FÊG9;tô^æN‡ •@”À5*:ºö3kljI‡Ž£E^gjßµ/õð£ä ·#N¬({%lýñ“?]vF)?ùþõ¿®C@è0©Á¹ÙLÐ)Æ(˜*"àænÅí(±¯Ÿ8žÚw´GÅ´J¥ eö2Ñd{ºÿþ‡Ó÷žùvúþ÷¿*å¡C‹:üÍ€SÀj¤¾ðüsQÕÚÑÑŒ^æíK¥lŠàXU9.œMñÉCýæ¨2w=¾T¬ušvtt¥›©>û—>ƒñPœ~øq˜¦íž×BÈxTFP­n÷Ó§NGÛæÎ]Ý¡|\¹ÒÊu{2  µmG$ìÙ³/=ùÔÇ‚c=f£8ìÞ} 檿ʬÛÇ?ðÀ£(#S¹Ì?+ î¾÷Áôì³ßMÏ=÷ƒ`èyú#¸®³ÒÖpÎE߀ùcð–o}ókéíwÞ^­éQhÛùT&(H¯÷ÞûYÃõé™g¾IÅúk4¼ùÐQZµß )«Äªl—Efñ‘£·¥¿úô§pä¶¥|ðÉ!8þx‚°¼þú§ ©EÅQ\Ù2J|oë÷Ÿ·ó¿ýܫխøé?Reþᄂ· ËLÀÄuv¡Ð¨·ÜÁÿµU€ZZvï¦ÚéF2~G‚vÁýf¦k(Ú:^¼•·8»MNÃÊ‘-Å´+„ž5Ž”¿½—.2f¡çýh• ²8c:üÛ„¹R”Fç‘IJàd‘Ö=QÆ}çàG*_-;: $N¤ï}ç‹©Žöcî2œÅ<£]G“ípÜ» ˜”Ùùò#ƒðfßœQ Ô1";-_Øâ÷¿ ¸ÿÊ¿ÿô9þÄr2 þÄ[×ÿøùP×Ì2KÁ œÈÛëŽÓòí"pmb|$ŒX營#°g°¶cÂ`KT€¿Y‚ 9²r… ƒãF§r±§7îo÷% ~3§M¾œÆˆ•®t@ŠqlÈWÛc¡§ã‚Ò]’›¦qpèô²û‚Î̾{öâ{iå,stÑ9ýžŽç«ƒBtaü®sÕ—ed/ç@?üøŠ"•|ñ’À0Ǩ@žYÕ#M舑o(— Ã×^z™Š­ü˜Åm+ªhÍŽbÏeAOl8tþ†³‰•›H`@Ò÷Vª˜Ô¦!gµ•í¨½ÎLx+Ñ¥Y‹sÌK[Ç!lXçë›o¾NŸ»F‘´nʳÒ~°ÚÔvêêì"®W§„ìÖX±¢M£/æý©ëïÎÂZ玜°³Mc+öÜ[^£S£±½­9C÷&À,ϰ½§†*ß70kk²pŠE0#H§šm\ÕǼWÆß Ä™Lq-¸éT7€ŽÝu-Yƒ$#øœUqSÌ?~íÇÀŠï¹.ÎlÆìv Ñ*ZÉû¾mpu›8#L4å»&?°üÇ|¦ã‹³QÏñ;þn•’Ž Îwç… ÉY¥„F+ÃÑM•ÞÀøõªt9ª¬D[ÄŠ´2ž Ùí·*ã¬Ì’8[ø¹F§ë¶‹à|8Þ쿊$ÂZª˜ç¨®2¸¢³K§_ÖvÖY“s$Ae3蟜ÙvŸ|ÚW8ð•íæü}–?†‘ ®9ËulYß"°Ì‚%:›Ä*ÔLоz‘®e5$Z³™Òci8nI!ÀÓÏìÔ* t6i°›pRV61–ˆ³ðà3ËðVg“¸j×ÌrÔ„Iâ¬8ê•îÝj³að²„õéD®€Žmçmû>gÙqÎÚÅÒ«-iÅ{ùI8‡¹^ÜJÿUêïQa\loªü‹–Ì$<8#Y™mM^&œ¦gÓ46°ø«cS¢-R‘´,— pÏàŒ20=•W‰qàšÎ¾£¼Õõêß1>ß£Ò |wsy­ë¶ZP½‚ŽnÚ:Ùœ˜Á@8ܽÑn¬sš+xËSÀJc;j™™ô¥ÃE'?Xß÷=»æH¿&ü¸ᡳØÏ¼‘ŽT;PèS˜·E=g;=Å93m«=æI Ñ¦èÞ×g®3|’=˜•Yy¶º´]²¼Š€;¾ éß çé)kÄå"Z‚˜`«o¢­¥ù’Uezå8wwu’L“%4ÌÁŸä‡ÞÄ©ÍÍ̉¬›ÜjY“\–Á#gjJÓU8Åõ[ ^¡›Å¹ ‘,­é¡ç¨>d=¶i¬BW²‹OOÿ`ªª«'™¸^°LkQù‘¸ Á[»E¨÷”/®ÎÃgá¶”ž3dÐVç©Á }TùŸ:‚¼žmÄÚuGpX(? Â*“tÒfÁçk{äýv1?UÙ©,öu›Lèšä¡ÈÉSð#ŸÁöA”<÷ÁƒY›¾ÈMîü˜€­æ?~N9·EÕ¡zç|@Y«~æ˜ÀpT·ŽÂ\™pFêG¶Ö—´ô|¸$Ö_fÿž—€tÝòHy~À†{ˇ=:}¥>È= ùì˜5dð»‘/ß_Äþ¶2V@H+,4‰Ñõƒ‹ÜG]RÙ¦ýÝHsWW7ì,p»èYBß®I\Ô¶pm¾â¼ÀŸAj®§u±¶sœßs©êcÂ7–Î:ÕWøLX™ºEïž™‰çêÖÞ£çb_|fb€²PC›EÝx˜ŽK&øÝB‚CÊkqÁ©» +uµBüíñ&çc²­É8[ëñì½N›@økÏÆÏ%)_â@‹ëD/õhŒšØ‡ÏŸ#9Rñ]Þ÷~ÂÅgG˜{mèRê|dÕ Sèøß†®ï𹯕©«y¦Â)Óƒ¹Ž±ò(a#½Xµ `ã~~G\¾:x%õõôн :}…±“àLKKk² ñÔ©·X+£·V À¢‡­Åó´Û¤kõÉÿ3îﺢxâà±/áø÷zqoG,ÍOÅ9XÑ­>Ò&•Áët3X"Žãè ìé"Bð|žà­>%;8ØYEþëÜíö824]‚¤¯&s=¿ôg«¯åË‚5ôo*™y|Ê%pfR¹T”‡ÑŽ>· cF2pöÌäùò?ÏÞqE,]¡•yiض+WÅ1>Q7Õ/¶¹‰,¦êŸÚ.Ú£&|ËkÔ³ÔG…™º°÷U×óÜMnS^©«Ê»=?TUÖ‹œÆ–ÈËq´©2‡yty³ÂÆNÅðè4Ã-×¾FR¸I¹vR(!9ݵh‹™˜…ª&Ðfºš$e~Ž{6˜u›Ã¦ÈCf¿ËðÏ&º)YÄ66Fl^š–®i í^0‹.ÙÄØ:ùòmÕíèTJÂØv8ÚäþWI˜ßÑZ—:Ú›SOÏ|ù­Œ¤¸bÔV`r­BžaAô ÿÄ8_þÒÕÔYÛk®… T2ꬡ5’íf§Fé È3à7)‡ÊðÆJPQÍ}÷¼³û`À²§Ý½¹¹µ¸à¿oæoÚÒCˋө³©(  ï(WkI"¯ªçsÎöêÀ¥èÖ»­y'÷Ü–N¾±Å„¹èeåØÑˆJ@žjÊÂÈ׺ T ž“Ó.Ÿ?GRËzjm è?u–Q;ÒvtoŸŸuÉ#þÏ[G'ijn¢ûÝ #¾þùSÃömÄNŽÍÛe¡ÛYÞãçò¬+ÃãõuUüNÄúMV@уǛl/ãåü—çiÿÀ[a[Þ»#°+JäÈDxú™²(ärÈ»w²nió”Ÿ+G¹.Šä|¶ÎËÛìÖ°ˆü ÞZ|`â³ú¤gëH×C“ôàâ™öFø|äeÊ2ð\ü¤d#š+Ï40.¿ÏùÒ2Ù$‰%7ŸØ놰Y‡´ˆ½Ä•«ÀÈ¿µw:<Ò"*gØÉ”{‡Nè¨ð(üàÙÊ:–Ê‚´moתÜùLº)-•ÆY:›üAû[õZ~nÒ —„|åë0^‚.æÛèB\³‹Îм'ŽM_Šä ô^F‹ˆG//ãÿ4ÁT¨tŠ„3f`¦heM#QƤJkîàpT ø^d©cTû”I= [Xˆ:-Ô*/.:ïìrž¬ ÌY«{Ü”Þ$²¿²4:3;œ[¬ò¶½ "%;FctžV2*q Ž…Á~†»ï „X]˜ …˜tœ´ï†Cl[]ÛâÌç„f…UTû=üØ“©ïâ¹øìÐm÷„#>Ö²Z±.J(=@Î.ö寿®CàïñG…Ó ÐS'ßÁiR”ÚZ 4ñK¼ôåïMÍ0§ÍÈbíî&Í{¾ØÞGåN‚Ý„ù9s:u´w„S1®ãšLÂyŒóHCVá/Á75·Ó–– ,Ÿ|çÔ¹« fOk”~+ålͬCÑ—ÏRú’XÙ>>:Î|σÑ6Ýçù¬F™÷7H6B5»ý(€ÛÈÀÕ`X†i”y/:¤>³¡±-æ7kˆ8EF]‰“ÈÀÂàà@da Ÿ·ßúAºï¾ƒ—è<µºþ$ôÓT§ßtèíM§“8Íu;?ZšÕ™p–Êø¦æÖXwsóœš5éwOF‹vƒßVðºÇí´½•´»ºvÏSé4•ó*³ÛQJ æ{ þô …Ú ,_xñ¥´wïþÐ|ðó öBìúë· É™b‚"pêï ƒLÄdßÿYß…ÜϤ=åT í¡¥M[‘äÓ‰aÒšïYç̨¦¦62MG©k‚w0:°¨tçX3í*+UNüÿ&´;„⇣ŒFÉPQ9{ú-ÚÖ6Æ,tïaK4ËÌ©€Â‚#MýÀ“2ÛÄ¿'-çÒŽ rŠÊvç¤oãÞudyš5Û°³#x‚ó÷äAÛËióÅ^®RI*O”ž40ÔC4¶ Ö2ǶF•Æ¢N a¡¯òÆ<ÏR‡ùU¾¼ûÏ?¥_å“;ï-¼•kÍË´u*Àñ€+r)½‰á/ÿýÿk¶Óaå ã¨ü )ì*I%*¿Î0ÒégqC+EvŠçÒ€3çç†#º=½u^‘QŽ 4¨H … fïêL2aÅn*d¤šÍ.¦¹†‚j•T[•ákåe´ ':©4HË™ÅUi‹]î«cUýW|7)FgÝ­ì”þnÅhÏÊz½N™¦â¯’/4¢•5Y‡3h×dÙob4àÏ*fÏZ­‰ÓÃ}•P:«l[¥ñb¥­N$ç K_QUE ><>¯€ÀçA¾ÌöfAï´çá<[:7ÀªzâìÔiR´ú²ÏÙ‡† I눶§Ò¬A¾ÏƒÃ°YÃaó¨0@u¯a4¹W[Ú›°«S ³ëµ‚[gpކ¶+à6:£Øü…ýǺàIè“$ÆÉ4’'tvà\àçØ¼ G÷1t £ÓyóáØã¶ÅBÉÃuæcä/®—ﱡ°Öù=—ïËouÌÉwÄ©«½=Y•:Ž]¸ 9oíá®]‡^´àäz§Óص ÷#/ó âwp–‡óÖ Ž9 eGKèAÂ%vÀXx†ž/¾º¶Ñþ~‚³©— 2_‚ÜöÏÂIœ4( 1lEÖ®#–5i-;&QxVê©V2XVÏ•JçYªqËêÊY ]~›Ìl ÇÄÇÁ¾+é,mÝ=¢ öåþØatåѶB³F¸ì³U<ÂïV£Gq¦&t8¦Ã ·á™ÙOÏXçUuY^ên¡º‹}™¨íøáïµÂÀç©{nÀ/Þ}ýD`–* ƒKÜ?=É&èœ9Ðv.-­ÒËP 9ðGÇ–‰ÖêµÂ̽«OëX7Ã)ˆ³#ŸõëÐRÿóŒW7I¼aÝó¬ŸÿÁ .œïãÐRŽsÀß ºÉ :ØäA:óÑ. çNpœ!ÊŠêHª–¦ôÃx6F¬¬7Pc’A8õcå×ÿóë ñCª•ÿONŒ.JÚðºÔ ÖסMåƒH þJïÒ3‡Î´Ë$™Kçz¸bE$ ÏÚ­òwéOýßêÕ~W<–pŸ½—˜ÊOe4Çô1È85¡“km‰êÜpùµAey’ãqtžäcݹð/ƒ‹ µÕáßSwRz_éT™+ʳ­¬Â Z‰5>©Ld)¬Ù€§÷>îÉ £AËèZ,äqëFGù‚«W‡FÚªÖãÞê)^ã=Ø;{ò³¥GRHU'R¯à.¼oàUÿfðÊk<˜åñm [XÅ•Á/ýC‡î(Iœ#´µíêÞç°ˆNebï²Ax©ÇxAëס®æº•Çò —˜9§u–óò™×ÎÇçxÌ¡¹n¤ž)ŽÈcòènQ„ü5ȳ í»O%oðCø¤¸å¹FÐR˜ S:Y«A‰^ôõ“1ÕK£S×8"(+Z :g¹&e…mõåon¦ŒçLSÖÛiC¸ 7u&Áåï>[§¿:”ë¶#’þbïçgV*Úžtü¯&€oPf?Ð>3÷陹ý0Ÿ¯¦ïö\¸ääç G»0(GÄ-» SßWž(×£Ï3‰@Ü÷}'K(á×k¿¯]Ã%×e1™A;¿zW¹na®/Ïõ®r¾Ò§:¥Ýyºwï¦KÐUlèfFÝÏà;M°Ó¿&.û‡Î/©­mÀFßžú¯ŽGW+Ÿß¢kã>=×83¿N0Áäƒ)èü[¡u²/ƒ%â`a!ÉÀ莎f¸,Ì]ƒ²Ôuˆ×œ pfã^=ø Áû`þ!¿R¾+ÇLî‹¢0cÝü'` LåGž»ú£ëÖO1‡n ,6‘¯Ú®)ƒ`’Ôƒ}‰#¡;r dktÍÂ&ø𠼦¸@ýD>(ì]¯Ëô3“€­Œç/þfß¼0Æ<6®¼Ö$;¹ZØ;~èAi³ðmqÀF“°Ïœz'u0*³‘ÀÝá#·‡i"±üVZS§®±GõYÞó3Æúå ðá(/Pžè‡v[: þý^¬OÛǶìË³à…ƒ‡êwE“£Hr_Aa‚’ËÌÑÎ/©A¡}8AâµIu1®Ô¹Æðý\¼ÔI¹žÍÜ<í³)à”ÿj» W_HœG¹â‘çsŸÒ<;ƒ £¡¯ÒË  6~±­Ÿ]Ž$L=ï 74é<Ÿ¼ºœÁý)’€'I®uDˆ|_\’Ø Ñs’®ý¶ç%z–ëÀÓN&y¾‘\æ°›#ñþz:Ÿçoù¬òʳRöÉû´ £P ^ Ï,]=“u»oeš8ï˜ÑRyY> ×À+år-¯bd•~Çßvd_Ú»§1 ŽL¤½p&Í!÷°'„Ý£Ç ÚÊ?G.±ÆÙíißn|nEi`˜ä쮵µyüíƒiµˆ@°JÛ„Ý:ï+Ë â[qÞ3<”>ñ‘Ûi¼/]êOßþÎÓ?øš[tàš²\ÀB¡=xg·9;˜K/óœÄˆ;ð>¿É^"A›„´¢Š†´:ÝO[ìñ4:>›Êà¡„qÓ2:~~1d[w0*z¥êm-ð6F§qø¥çžAO>™nØQÊá¥uµ¥ixÛ‚žNú®\AgÞžÆÑ§ž}æÛÄ-v„%ב_LΛdï^¹˜v`â²LkÓÃiŪyùÕt0›'VÒ;¸HÁ#s³™Ý>z•„馯€¿øPí\{ÚâÛ]¦¥µ…±";RïåÞÃs$¢G»|ð6ºo,ƒM"<ÂddáûøãÇ¢SÏéw/¤3§/h iSîØÙÂyÔSåL7Bdëíè‡édhd]]eúŸþǧîŽ E.Ò‰÷Ga8Vfóˆ$lñ([\ʉÄAqÌà4r-ø6r¦_j›Ë{¶ü#úŽÄû­"&õ}:vö‘NMøÖ–OðY~’ǘ¤²A,i}•ä<²GV˜k¯ÌÑ÷"ÍÏ—ˆ¡ ÛÒƒo[¦ O?ßê¦a¢ôoS-˜…!ßå&£™ ãµ٢+wÈCX_Ež‘²†°ï±ÀE…Ž œóE/T2¹Yžá¥A»›£Í.O—ß»uå–þ9V´ÑåÁù:ͺ×é¥, %V«jrrJÂñ(ƒ_Âñ.ƒ.,#û‰Ï¦§ÉD'mÁN2/Y Î [¡kd—Òÿß±‹³ k e¢¸x9 ‘deíåð.Öy;:r`d2ÆÚm0!n*Yš§&ÎÒ ƒ- —Ð>ÒÃDÖïokd¾9™Œ*Xxæî¹y«¬Š]g÷~ög‹÷º0îÍÏUˆ| Hÿ]]‡À/P!v ÌW¯Ðv} *´­·¼Æ—Ä^[[ŸzúzÒË/?ô÷¾;îþ›k¼Va8NÅhãɱÒôÊK?Jí»bF±Ê‘?iiü~çäÛÁ ß~ëx{ë éJºnÛÑ  Xžš˜%Ó߇ӟušl"óô¥’¢±°ùð“Iï¼u"8ñZT_Kÿ—Æìo¼žl¯^V^Šóì½tñây˜Ü*·ë Q²Z¡¿qÇ Á ìW0.¾w>ÝUN•^ŸëìvשÐÞÑŽà!¨6À½*QRò£†ŒÐª 'há ¯™a½½—C¸:‡íVæÈ Uaf[Ÿ€íð+ä[Ûvd!‹³}W¬É}è ÃÀ·¤ÆŸU½á4Å–qºva?L`rhh¾S—>óéO¥§?òq2ÏneËsôy×_¿]ðÄU@TÓÕ/ñK¼V~ù粊Nœèà(o„Â+›¼¦SÍ ¶J¼Šƒ¸?ORÚq*!ô½s´"òÎmÈëu  ”“ªz38†uئxƒ€šU›98}o ]¡µyzdèÍã,Ÿ'Èf0Le¤º–Ää°–í%3eM#záÿGšàS©ñ§ÿQ›ÙìïîW¥Rô¾:È 1päüó™Îe•Mq½wøU½~õ§ü«Zùoò} 逯¶*>IÐüü;'/ô§ÕɹԼ÷ZNgí}Á+'®\éöK•Ì*ª!0±sG<*ïžÂ°¥5ߊêÌ hÄa§sn™²¹‰Š!¯ƒÄ*A³mÍt_$Sù˜9hqS9¬îXJ0]ƒUÙ56–-íÞ$žhˆ¯3Š@¼eÜS(úfŸ›ùAFûsÛF,³¢8åzƒåuPü  wq< îç|]ùŒô¼AV¸ô/­ªûªÀÏ#‹5 0Î$ ÷àÿ }ÈãÖ7ý.Nénƒ5h0dsÂÑ¡1>ò¸Ï&r6Œ~îë^ à2»JçL¦#s?î©ÓU‡Ngë:攟:|y­ëÀ±+ñù|ocƒd7Ô‡pôø<ÿ¹Æø–ñ,Yü­Ó6 8è¾¥Q•ϼ6ƒÓÜ2öï—ÙJ´ûŽÞË6h:;å{±WþãÙñYk×Àô ™/~g}òš°7®ñZŸ‡«CÙ nù•ÏÅ Ä.ÒXÅd‰5Êó˜ç–k°ûÁsÒ±Z>{(páSϪbÃöv:"…5F÷âÁ¿ãsÅùøÉ j0Ö=_7“«qÉ=ü\ýÇ3Ž¿yž{ט­Â] ¯Nî'Êe¬‹Ï­ªö\…:•ûñœb\ï¹E›9¥£+Î8¹ÆúíÕá°Žq/qEÊ#–_N³>œ9—²5*³4F}‰?ž®çQ,\›0ðS X¾ûÕ‰\ˆ6K8È$«pw-%ì×¹ƒK4Å{q/s¬fzšUš^çOv÷jõD ü}˜sssqò‚a:¢m*Ÿ™££Q{QùdjuEúÓñËyãtÕˆFëM¹8¼VçWD€¾€ÏÖÁÓÙH ÇÑ…SÏJpÏÉ=zÄAËÓ®8:` ÍõÂB~cÀµùnw :Òà</„ùØÉ:G ;"E¼oÅ…‰1€³°B#ÚzÖì õœèâv)rGEÌì÷̲ê&ÚʲŸÀŸÕ‘` âx<“{@qÝCàžÕ &‡.<äp=ç/Åìè@ª»Ói„„œ)Çïa’È<²„Cô@þ†/Žqóã…ëžêˆÖ!%NÛZVþg;Yll Ð¥§Ü(J?zæ»iî¶Û™iòá·¿õtë·ÓAì g nêÈÂ9D{Q“} óŒãÏëÿù5ƒ€x ór–àÈ,ŽWõ”5…Š'pOT;Û Â\‚¥‘à,4€g OPŽÈ3óùÜ. oWAt©YaÊ*ã¨L‡ðqà¶NÄÌ!Ÿéòƒ"dzè|G™à8Ĩ†’÷€Ç¾”$ÃïÀï2eµ¾8ÇQÕSI8 1ÏÎ0ÿ™‘V¨\%ùPÚ¶¥•˜ÅŒ`|…ö­ØÞ®E^×øÖõÂçXJ|nulÈ&øÅV¢Ÿ2 q9rŒŸ~O¸e/öo! vHkòvß ¹#üуJ oá(¿òšFÁG Ò8u ƒáq7¾£œÔ/yáÜ¥ô‡Ÿøï#!‚Î~åÈ'ÃKðìb<ó€†[/x—R,'økèGèy&ºˆ&V Ÿ¡ÃØ%úeÊKÝg$»±Ç8oÎѳå¢X‹:Xz­Áí¬JÎavá*{ÉäŠ2ÆÄ æŠ?pÿ$]Hßûö7h!lІSì4åi蜑p`a<7“yFx u qÔê°H’d확á•z©rXøšh˜C«gãÞ¼'mPá—éh ÄX¨°„žñ¡'ÙñvúÒ§ÿA‡m/¨:cÑÚšµuõø©F€ÿ.A/unáåäïÊqY;Ò³´êîÛ—kq¯¡Å|ày÷Ùbo©Ó‹Z`ã~Õø‚FèÀâcëI7‰ÁÂŒ›o¾%Fs°éHT¨  ÃquõírSÅ”iÊ7o*Ü­‚ȵƨháPÓPI®Óø[ý[Þ’B­ðôpƒó >qmò…HFá³°Y8;ÏDøy†~æÞ¥ùšï{_í¯,ˆ‹¾ +‹ï’œ*Ø»ÿãˆn`ÜË©èœÝÔøœÅòmƒøìKüå~êfvÉ¡{Ž|K=ɱ>W}Pžésùòµý¢?®}ÇgˆkðÛp¸ö KíåH ¨ÊO$Gò{^QÖÁ,¿lÎGÜØùc€±›­ÍÐsUºB¢£tlrä<¶uTíó饪’Y×à™¶ø*cjòµ …ŽÌ1iÚÙèž« VòÒ%Îßó4Ë.XAç†#ŠÀ´£Q žÏ‘D•K ®”ÄÏ}+ñÊ€àmІ]AħYì ×"?®¾œ•¾ŽÎU` œß…É·v&+`œa¿ÀYÏÛ¶æ‘üÁ:]›g案Ÿ[ü´÷sHì--«_=¬ ç{C¦XÝ»žƒ®ÌX·w&ûSÏÐtZ˜ZHÝ»šÓéLhFÊc*ó—CU-ȃFø{ ™Ž°VF7R ªÏ§‘ jÁ›ÁÁixWM›Ã~bÍ5ز³kéOï@:v´; M¯¥oœ¦pl7çD[wx´>Ÿe÷4ת¯ß@ëÆô@j)»|ch\8PìS‹€€ék0–Vît©YDÏ]A§¡2}AzçL·5T#ÿÖbÞ´É &×é[œ›*L“ùë©ÿò1ÏS@K2òôsEè,©ž–âÓKéêÙ³QÔPJb¶ò¡¿ ô3–·>—Ê5òêÒöÖb’SÄ>¨æ.³·’8 ‰~œy.3´«ªë9{Ûy/¦ÆjVË#í&¥ÏH?êúærúèSÇÒζZÊ£LEþ@IDATóÐõ` 8FäªO©»u´5FW€)|J¥Üë]leUߥ+é{¦>vGªbîy{K5~ÕÉtüÄÖ,/„îåз6™¯\~¢åñ2ÜÓïš?W.Jgü Þe¾Ñ-æ„suð^åh|¦Îæ5òY|)è1ù$ÈàfŒý3åD´Ÿ{­£ŒÉ¾LÞ”¦ò ¾Ñ˪þ ä‰'Êku‹Ã à]ÚQÞK¾ùô8È…v !j°JÝÜÒÆa`lkðR]nÆ”Aö:²a  »`7*.”3KUç'Ž*q3R ˜;#½ŒgK¼Ãpè{ JÃÈÜ|É(}EF=Aô<€&ÓaÆg[ŸÇE×ÿsÿHG*6ÒxuìØ=ésŸûëô¹Ï&ý³?úDjïè€3#ÃÙI¶&l ¸¬rñÒKÏ1ƒïF² 7Z+C´}ý½éò¥ ©³½=]í¿ˆ0{ÖaÒ#>Ï×uþYM=ÀéêîJ½tYxõ• Z7D@@…¶½½3ܧޥ*ûÈ­(¹q Úíï;kUQö~©mÛyûíÇRG{GzžVBg¹ÿþnŠëF¯¼ú~ôÜbžºA_]Ã̱Âõð-GÓ{ϧ=ÿƒtÏ=B»Y›S@—†w{ûδ}[mzõµ™cþùôèòÒ…@Òq6úówÐ~þóì÷¿…p*¥ÚѸŸpWøýøÇ¯’´ÐCp½‰@úU®ûNúý?ü〧™D¸½}=Ì”¼€C’°?Oû)Œµ»ï¹¥¥0榟=s:½vüUxJn:vç1‚úWÒÿõ¯ÿeúŸÿ—ÿ-ZÓ›¥twüˆå7ø«H´Õû¤0€iâ›üߨ::TÖÌ–_ðA${Hw&¥ i¤r:J4˜hC≠»=h$T|²¸à•*6›X×Y]]SB_­ŠuÔ‡‚=‡“žgŽá‡×9+fùWåð¬PSr†X×z:Åø7ÎÒ èl$rvȘ&AÆ—º„g\³à´%£÷ðO[ÈF $ ¤€tˆ# åxj’Vb¼å¾kÈ ÷úë¯Z§Š÷â"ÝSNŸŒD®É‘!2p;Óí÷Þžd´0 ŽÒAc»³kw´^/ÎÁÏMüG¦Qi‹?8?ˆˆ!Œœ˜a&T qðQ½Q|Ÿ$áG¨Šõ|ïeq*ÄÑ[u2Y´®15‹“o•äSãiüw>ôFú"t .­Ì×ã±Fù(Ü:ÀÆGúƒŽ¬Â^`æ FMY©•Â諬Iü^ ’É–p"µURV&x½:µ-ÎÊtuØþž ¤éâ4)áû$ÆDÐyfÇ««TÇjK—r?žçl)”íp°¬C‹SŒe"åG<† ú´U“Ò mÞ q ÃxFToAg•¥$Ç0g°qGÓ2°ÕU„±XŒ„ï2A¶HÂE×ÈÐõ N?Åï¸Hº¥â˜µYm½D²žÁ¦|õr¨ýaœHÓê#9ð5ãé­~“Qx¾:6„µŽ—ÉQf ì,ö`»tŠð*¯]TF³#ƒw¶Ì#ºŒã ‡ß¹;—ð_l)<‡ ò¹MgÑbCB]&x.÷’çŠ/:®t’„#ËðšŽ6àžý\gžð0Ølo»¬9ƒÑ„'mœÌä'_]WâÜq¤ñýe«òø\Œi§ò`MÉ=X·ÏÕn2083Gà žoàÕ5ÄÊv©‘éµát㾫ØXÜ&^qÞ<ÓóŠùÛÜÏu™©õî÷¬–Å7ÁoáÁTÞ“FÄë­$ cõG½´|™½_MÈŒe²Ï¡¡0Ðã2§¢:—öÖsœ² ÖÄÏ5‚´5(ÊÉZ½å¨ÕIg6=g<…ŸòF¸êÀß\&ù…uˆo:¡Ë5&h‹æá|Ñõ î ÏW£}ƒ=oU6¹|*(ü®N:¸'î»6–çW‚³Öv‰âcîŠÎl[„C»Àb« Þsäê™ü;·²mDpÌ3Fîk“3ŒD ñÞ+9káìB£¢Žké'þòr=®ßùs®ÕŠçIÇP€ë‘@ÎÅÏËë"Xâs\gG‰ïEÐû ÄkØù:ú|Ì–]íO¯ÈÜ܃÷µ‚@Üñ¥#ÏÕ¹ï,çoåQå§Ã =D<ÐÉ,nÈÓƒN]A<½†ûèˆY]óþà:p·ƒ•{À‚ÏÖl¬6‚ÀRntÂÉàêŒò^ñ,õ«ýž+tËËœ=°-æ}iÆó˧¢ÉÓÉ‚Y@Ûçê£ß–8>ÂE‰­[ á9Á›ÀDä»ÂÚöÿêd: ©ÒA$˜ÄT® *§À¤‘.*v [.Ây§žÄw¬–k úÅÖñ·9ì¡à¬ëOï)]8šDÞCŒºŸ;SKc}Tìs¢¬.À?¯ÿç×àŽ|(K478£\†ÞQ2IF ^"Áø‚ÀÔï#£ï Z+‡&HLiÁ÷% è31/ $ ½ÂïB³Ò¶òÅ—4à¨ï'¯òËò*eÉ&ZI­úìv K䉶yð5éÔNl |‹ëèkèS!+àÆñô÷œOO<ýtºïþÑeæ’¸Ö½{O§óWÿ`&X§£-žkg>å¦r›Ûó^Åþå±ybÈw>ôìù?¼É ¿¾"¾ÃÁSùþ"Q*×Ï?ƒpúG%{gЫ§ª*ÇÔ”Óè©-M-8ç›™}ÍHMÑsü,ä™â…ß1.O4ä‘+Cåõ:Ü•M±h~dº:pãzƒ²[0—WkFýÁ=y½¸åÙ‹k~ÏgÊÓÕUM°Û“z’ûÉG缺:»Ò6ƒk8­«)Ìš¡(Âvºê:®×ÖÖÅðTmJe‡ÁŸe‚3v̼¡_ð<׿˜v"šøåž/y·É|þé;!gØ“:wtb øLöð{e‰tŠŠ „e{ªÀçmg)‹·ìúè¸ %ëâd_ºé¦?£³aCìßõ8.$p dÒ7³uC—Æ_n`;èÑÏy¶x üÔ…„aº£X?kôÒ¨§ãµâŠŸ¹Oá±Ä°À%tä¬t$Òø¬ãÆÓî½ûÒO~$=ñÔG¦Êë¶;"ÿhÙ—øAàìSåò õ=Ÿ]@0Òy·â‚öQè™à©I\gà:m}í÷ X¯… å{ê3Ü2> ¼Î®ôj`‚ÆOmB_‘ Á'â§kˆ{ò<[Ö«§™¨°N‹`ù7ŒïL`«ôõõ@sùƒÎÓ.æLJiïl7Ðü­søeYt¬_\ö•õ!x›ÀàÏ­½ä€Ë^ã&" Î~ý\4΃÷ ¥1ñQІ‘d ¼Ân™.ƒ;BÏðüM*ÑŽ5ðÝA¹¿ï«CiÚÊà˜±q#‡àT¼€+Ï£Žö¤v£OÀž­;»òúoÁŒn݃l+Ä ©8_Ã+^_b´R<“>x€>Uýšê[v¥fg–Ó½cŠä õÌð¦HnðòœA¢KíÕM–˜º”.ŸZ"¶±­JÝMY²úÏ®.ƒ'Î^MÕØ†“´³@v™HÐH—´Zƨ™t\^¼¶·Õ¦™Eì:+Úë¶§9dþÏ«=祓i;ã]+Ë Ô³¶1ºiÕÂ?Šà ³t[Ø^I@wq˜*‚â!³à7¸‰­<˜˜vèæé’{*h­ö/ â¨B§×>m%9g_Ôëé…Ov…dî â®ÓÓà+k!üT޾³œQn¼U>^QMGC\»}çÏ¥i‚ëœꨄfs´¡—ú¯„üêFòÉ8'Ök4ïéï—*£å•&ÖÃsñQ(Ç À›ð·@SyÜÃõlⱫ›ŸÛ4¤¤ÿf“ë$:Dò:÷ʣÃÎìòåÙD’°ÏÓÆåÉ\:+ó%“Uà‹ÅÅòÀÌ_â(÷ïß|=Fmo°&iKŸìŒâ}Δ6˜üBU¿ë_ázV0¾ì÷µYÕÓÜ·xt þÎCö擌âá,€`ÂHý(|#$x°®Íôc錛Ž;ÔíèèNø°Çˆ'™Ä#¯¶+c¾íò iÉBâ†0™»•a(gìÀ¬sz*:ƒt´8· Ÿ–)Å8ÔÜpaÉrdÛÉÈÆØ¨ŽŒÅ9ÚÞÀLdBõžs3£T¥7EkL!ñÕÔ“ Fk «Kkêš™ùÕŠ’1¡LÌA›xnÔ7c$Â| b)–hUÙ¶£ƒ6 ;©n…"Y.%‚Ä ø÷ÖKáãËCA¶þÞúüúÏëøe!øÞÍÃÄÇÆMî(¥m{rizßw¥ùþï´?DEhį#¤'PÓ ¯Yxÿø?OŸùìÿ›ž{ö™ôøž ƒfÅèÒ{¸ÏŽÔÑÑ…@¥ù¿þ ­Õp¯öÀa[üÔsýûn€1­¦Ã‡oGìÖç·ßq,hqëi7ïîÞÛÜÙ¾+?þ2­DN¥Ý{öÁœr#|çÌ> ÃAÚRɰ¿ôÅOÇO3mߥÍÐ{ï½ÇlôßI=üþÈ̱ÕãŸÿù¿I_ýÊ滥={÷§>2¬FÓìß–¶îûK_üLúÄŸþwQ}/IzöG˜U~æô;(`ydxŸM/¿ô\T«;GýÌéwÓ7¾ñeŒ’C¡œlßÕÅ,ÜÑôâóÏF[ya¡¢qüø+Q/|Úwvãõô&Fû;o¿-‘LàMoüøx(Vt:’Ž¿þrúÎw¿J©³ª¬”ÿòW¾ÈÜ‘sé±÷ ÝwßCá—Oý§Oþûôgÿ∌{³¥ÿ†ßü²ÈóSß“ceœê§>¸þço-®É/å–²N…Y¾£ñÈn{÷߈óf>ÚáY©m0Ýä4ewÉð`ÑMÞéë¹Dö|5üg™Ÿ—qTÔ„PŠaT¬Ä}‹˜Å¤Ç5ŠIŠ蹜:º÷¥Ûn¿3õ\¦u빓ð&Œ[Öµ•9¸³½3 ö±R ,i./g[çWûœ9Z€éœ¶z«ˆ*ó9‚~*è:KÈôT)ôŸÊ•U9GÝkÞ&íÉ¿µ¨ñOnãâºÁÂ2•®Eåé±GOÜÿH(æÉQ…ˆ^ZŒÑIWâ´nöô¤÷.œÃðC7ÅÅXïüyøy pD}OBíØÕI»Åó‘Uë9OI§œrÔ«"³}[ãY²³šDGÝWʶ’²Ý¥Ê»Êx8¾4¤GŒ–Î+MqMüÐAgË(éV£J‡¨ÙüÊ­ipÞ¬kÞÄ©ªÓ`“±$çÒ×¾ô­TPMP‡÷ÔÉu*—áØ]â>:çžzú)Œg .× ¥i8½`8¸•éo]<ª1LÖ³=žŽ4”‘X›‰6‹ ³qÀ¢CÈS¤¿0*tX`Á¸V¹ÌTÓ˜ô:t”é´µUºu´ÌãütVjYç½­RÓY¨3™Móûz³YIT`s½Æš2uãS~sñâ¥4F qá«Ó+ ´…‹[l¤]»w2“š‘+±ËÌa¥ü4øä³¼ŸNnù¤F¥çÎ@ÎE‡¨|S¾âºµqä-ò dy”pÕ¥ÓKdžÏê*Ïøèø1ãYü’GšpáüäÀ>‹ Vµ°¿p,e÷´òÇ_; dŽeå8¹x¦=<çj2ܵ6J[DK¼çÞÄ1u§—8— ×2•Zq6oµr«§ Hx™ó¿~Û_´?e­¼F¾{+eÿ Yà¾/?':ßw¾¬Æ| {7 »L,¾Úwâ {Ñžv&1Šs1çÜ𜽷pÖá'MØd•¿5pu‚#ÈÍc>=ÏÅÍ0.ÇÁe;;÷0Ï=C ãPýÙ5{¦®U|WßV×» ýkXçã\_¦hW`°ˆýK‡Ò 9_k¼gpWc?Ú0ò\˜ð9÷2™ÛÀ°m uœ‹çfžÛÒqƃ©ÎA«mqXl·‹·Q3ƒ^"—7•š¨“ÉIñœdVÊÄü\àìï G‘¼¦ îª#Çèyg!Üà üø Ì:ù Ê’´£` Z7ìÚ ê»EqS7KòŠ2^Ÿg&î…Ó/èPVDÔŒg /W 3ÔqùØýð×n\ÈwèL\¿–iá”ä}Û Ú†]ç¤ïq<üÍù q_篩¶ww†÷3 WÚs^eÌßLå8guÀ8øO$ˆ|CûC,²gý±§ýØLÞÇÎ ëàΖ¾n’öŽ­m§l€\xZÍ-,r9{ƒ&Òö&IqXçÍ:ú>teõ‰~b`ë™NxepBY#ö¯®ä~Mâ ™ݘt+[å5†=—¾qû€üÖ ”÷÷\.÷\f6¡Æ<|m0ôg+æï|ó«tW9Grò¡¨Hß³{7-á· !0®¿~! ®Œ,†VaŒðýW8ó‘AYP’ ô*šdáñ8œ7h’ɼk< ~%Þer þåá´ŒàzT©£Uà“0ÙäƒÒªÁé¼h fö%VΈãÂDºS.¹V÷§2ŸÙÍòp?·{´ä¢í®¼‡ß#0 *·"ñ :vË•Œð9‚O`¸¿/…[:tô£t¦"ëC·ßqWj#Ø%· îÇÿàÃ7ùçÿö߯¹x@Õ)-Ü6€HÆsà׬œ=I¤òPÁà[òvåŸA6×í½ýŽ—ùOÝ$|°ðºÌaNåÛ2XeðEyd×Ïî½{#@¥1§¸°ÿÑ®h·¿;v¦^øbð±{OÔGh `ŸG"”ºŒÎeu’ÀÎÃ`à x s¯Ï®!H¥ŽÀšÃ)ïYð–~©žzÀå¥ìS&¯ã —×ê:|ôֳνëþ'"!!’²ØëλÒ|í\º÷¾¢Ý¸{¿ùÐ-éÃOÿQúÂgÿ2tU÷o°XØ s“ç0d/ÂU™ÁþÄa•xàŠ¸ \•î)û¿Á‡•Õ•cÆ"Pˆ¼ &pÃp°[ØÑµ{_tÌ’Lh,H}äÏÒ#=Ž>žéžÚ­}}½$à+çž‘°ŽØ‘ÁýˆïÒ[$ܱ¶x v?î#sîgz™¿«‡ áÌoì ˆ¸zq%“ãêÀ~.].ñþV"f#8nɵtíÞ{=šlhl}jèê !ë­‚u&•xŒ®_8«ãóÖ+ ¥Ó,ÁRœ)¤í¶ñaor~ÈkÖ,r.s$òp3ñ&Î^3]Èdš W"ÁÔ÷ Vù}aå¿­@´úƒUöÓT'„ž¦|¥ªv[Ìö.ò»•µ÷o>”¾þ­¯¤[n°Ë+V ßÏ­­B.'ÙÅ®TîKü²%·¾Ò \q¬šdpøŽ[Ó{ƒc$Âh§ ñ»|Ê‘¿°§ÚZ^ST·D‘L!44Èõù…ËiWÇl3d"úçÎ `€Ù‰“çyîrzâ±{Ò®®vªÄ7ÓÌð£tÔ5²sU”¿†ü)IÓœ&/~À¡TÄçø(ëÊrR]#ö5z®Ituõ-èîGA‰±)ã…ÚI&ø±‡ú¢Ô\_ž¦æÖÒàg–‡ŽŽlQ¯YÄžXߨ!èK—‚ÉaðÁ":E‘ÕÆ¼u}™£Wé Ó^C!nIºÐ3lKA7,ðhp˜™èM©—XäFu'°61I9¶°ñ¿kw7zÂ8cí¦‚6í¢egèÈùÈ4±zÏ\N»ÚZÒ‰.o>ÁæMe&gF9D:s¾¢ Fªð}ǽ9ÒL¾SUUJüâßïIys“©²¶>­•ñs3ùPÛWýEyµåÛ>}Á±â§:•‰ðªbºI»~ÉƒàŠ±%eººÐ2ÏÌ|Еòו—ú,ÌP³K€²Nù«@A}AØÅQ«wúy4áØmÅŒ7JêvÒ&‰xì_¿ðò" c=ÊNuAeÄvµövÈìÇB|ÑëtµYÐ÷†\•’óê# ±ç”úqä‰&Á­¬·ƒ…¼S((KÔ‹•7ú«¥Ky£Áâ3ä?¨qæñ±`˸hÏd“¸šŒç•1ü,~Æ¡ø=¿šV ~‰B'€:UU>†åÜøL(á¡Xƒ,3´š’ý+vá@o CÊŒL«ÓUÀfS„¹®XIÛi¾AvÀ& d—F‚m-ÍŠr&Ý:+5Û;è÷?‘0ãØ —B,!‹ªmç®N>8NÖÊþ7¦Æ–VÖ~Í9ÀC©P®×ßeZ[ï]ûO(Y Ñõ×uü—€À¾‰Q}ÑBqÿ¾ýP¬ïlFÐ÷¾{Nß}æÛ1k[#ÒÊË;ï¼'”A™ä ¶?ñ'ÿ"}êSÿ!µ’,rí Nœ0[{3uý"Úà`¦ ùKÿ9=úØÉxmƒ^ª¢Úº²mô|Ù1i™õéØ2ø®sn™D”e“3´­«zÕ°=ÜèÄdš&ã¾jÖ•Þ<Ïœ-dhŒ¥›o8H÷•[qD?Ä£CØ‘ä qi0ª´œn0ÓVéCßêót[9«SCÃI8i•±çœ áÜâ©Vؘ1lf»s§V©$© Hú¾N¥0v0TpxyŽß—ï” ‡Ä^âþ5µõaÜ8Ï6j¸jÉâ5¥#‰gih±êí3èH _ÿf:ýæéÔÂ<3Ïœ3g ¾ž{óÍ71–©“?¸ÿ´|¹Gï[„-Àä?fI;]ƒ’ÇÄs…¬á¦=bõñ4 G¾Ü·0 T[~gmû½mæ¸T7 ‡:0r¯ÞÐs¨Žd%GáLÅó x(òOP&2dg,\ø/ NƒlÞoœ6bxO¢òê·ÞN¯¿y ~ÙÂ=pV€˜á8ãZ »£÷ÜËŒä6àÇB‚&_È? :‡ÓÊt6ʵ¶·Ìª[Å%Èž“ó+³ÌqS ¬¬Â8ÕI |½NDà\c7¾ƒ³ØI£{³D ðn­Îq?Æþ ž£!`Ï=¥+u>ÏLx„³•µüð |ßó/µ';¢sùõ×^O=Ï>N\+< Š„úÖÖøßsð&œ=µ8½IÌ`oV€([\ƒã4°=·e }ú&bIóvT+ÀYjÐÜn|9ðFûÓ*Qëœ:÷rÌrפxƒ0ùR¬CǘAyiÁßMÆÐ!c5²°òyÒ„:±rQýq™v¯|ÁŽQ ¾‹»[Nèq)N: ®dkÏÆÏ£ƒëÑѾ» ›€ªep6 ‚ôa¿vEÐÁ!ÞÛRö¤ã& xdAÛ¨z/“s”íÚɬx/-áÍùpöž“pñ#“HÄ­¸ŒÿXUcõ[T/²·:)ø6h׽˟›ÙƒA9gƒG€}hãËK<+ƒr&Ù:r*”æ ÊÏ¢õ*wtž¥kµji3µ’¡µ¶9Σ†±W&ÏxÞò ¡ðqÝ$$m»OØ‚?K< ûôc¢‚N»èè4|À£("æ<ßñSùеp–‡ÜX€"7"ØH×<“ØwUÕз8'>ŽÁ»ghÇmÕ3£ bNÒZw”I„¶˜Ï*›èˆ2™=š*ë·!ÓŠÓ=÷?ŽÞz;tHÐ <1a²OW¹gKÛÎÔç&—¸þúu†€4%”’÷Gð>+‘w(wåm¾¼²ÈxüÃö±ò¶2“7À7ï¥= ÍæVò7üK‹çp›–ÚYÐ-d-¸(’æÖ˜Mjçå*VàŽº‚φ䟈½à¹Ê¡%ø¥òÑÄx+oõÈ_"hXC"®úÔ6ü>ô»ï—#0têä[i=¤‚®’ç‘iÒ°•è¶:5ñO>íšlÏ+½Ê—X§kP(«L„RÏ‘ï¹-uçikÁgÎÚ ŸISò7——D°„ûI¯ÒbÀÆëù\=ÌëäwòÊ1øì~ªé}ÿã©·çRŒQééëKgO¾Mg»Ëéö;Å·/ÀO]£ÞÄÖ¦CZ^ç}åíÂÍ7tX{†ü?Îνm%”qiìMY£L ÞËuÊ ?‹‘Ê8ÞsݾÔ3 Zú§ŽjÒL`úàSOÓ’–ÊOöibÒñ—__©-¡eFλÖoôÖ_ øtïÞp—×EÂ4üÇ@¨:ça•ûQƺ¶¸7¼Ô}y6$¦qf¬S\äGœÁyñÍëB—9L…$rv¿¯-qˉ*wuïæ t.{óÕôèï(γY^œ »Bš/`À,…SÓÙ˜“N=§+ƒC!Ks8§<’¯ÔÑAåxŸ"j”Åbd–6ð<¬Ü&£…d®u¬Â½œúsÀ™‰G×@¹ ú”OeTùZœâú<ÓR’øYã¶Lôç|6*Jeœl)p£„çnRºÊzå_Å$ƒÊÃôa-ÌNÿ¯€‰1/œŽ³àµRQ6ø9·‰³Ò~âMö€Ý m† å¹âŠÅe/(­„‹öy¸#_s4jþÎ]dgõñ}X2«1@uPÏ2+–8KEdŠ äš)êA)Œ‚AqPìf¤•m a©? Üù!ÎG5xn«µR±4·˜ª³®ú³Ž®Õh¾Aô)ê!2NêkQ̲ùm F=VÍÉÔ¶Ì3èD £´et5mÛ=p_"òß}mý½õóï~æï™ûéw¯ÿ}¿¶ðLCª¼oßÙŒQB• ZyõÄO‘©ýÒK/¾{ï} X+D}9¢ £á‰'žfÆÝ×ÓÙsgQÒû{"ßû¨œ(vÞ·ÁÝoëkéþûN¬À6%r•+É:;»ÒóÏ?›^xᇴÍÙ™ŽÝu/|D(séàm„V÷í»1}ùËŸ…9UQýþdjki šR!Ñɤâ|øð­T„?Ÿž}öû(/¥´a¿G]+3ÞQ& Fw¥û –÷ôÒBýtñÒE‚Ýï[eÒç*X*X=òAè/§Ï|æÿ¡º~'ß{á†pÁ¯¦Õ´|C§é}÷=åV•œûØÇþYT›kÀ]|ïB8•5Ò¿÷½ï¤ÙãØøHúýßÿ“¨ôbv¹ ³Î0áãŽc4Úc„ΫºûîûI"h^ë\síèÿ8}á ŸIç/Ð*…N^øÐC¡ˆì £Qå¡ 8=ríö_ ÁôÔï}”óÉ*Õ¶ðà—â¿ýÖOr³¿}ÿúo×!ð‹@@ú5H 9JEU®%9Åî/“%UZ¶7¶„ña%Ì$•$U$˜DëW®583°‚lŸg¾©Ž0…w†PÌBE’¶B}—ÎñÂa(0³tº8zÇžx†Yßҧζ¥y x€ÊîâÂT8iOª>á:U|Ôä\o)­™T¢4ø 8DåÏ’Ç9‡f‰ª´0ž¹&DüçÌϾÆû^§¹Ÿ ›ÿZeÅ(Ùà‡rc cÆ9aʬEþ‰Õ:‡˜½6MÇ,íÝKѯ Áàˆ¶ ª1Å›è䩄ϪÉò.F§5ûU¥ÙJÍB 2õB€1p‡„Nk'$;$-u[\"N‚-ÜHCÅVàýt0ÚþÍ—zªÕªÜ>s å+À˜›K6t¥áí#1&x4rÄì* Ì-’mm«N¿ YDÛ," iÏ }=ÙLdêfþFûUnjõ0´M<6Î5úÔéDÛX²¯¹ {Ý„æÉ¾FgÑ™U@ÀUGz%óç4äå„}iAì|xþ&U„á.¬¶ìIÏ*K vˆÚ¢#ùÚ™ƒAS_Ú.ÈŠ–"=ÞV#`W˜E¿JrAb\ÔŸGu?ç©*K?àZŒE.ò…LŸ°kEIœ÷&çVÛ½pPgN-Ûêø÷?á4çLf˜AæÞm¡­[…~­'¹®€=äbüÚÖQøi åRY\À¬ó"`éyÈ{tÜk˜ù|³Ÿ×±ÂÊ &hÈÉçlh@v«Í"oK*¯u¼ã"ÞÐXÔçµê4êUêAù%û:‰OEÒÁ÷üç]„¹AJ ãŠmå–u1ÿ\ü6ya8:KYÚZ\'­çí±A¶QnÔ×ÛçÕ@µ‹Æ­-ç :˜±U¬ÏªCæxzª¶…·dp€— ?ŒeÏY¶z”Σè®ÀûåVíØñ@§†· 'ùTŽäáhÑ¡à|ÞhI ­²”žÔq…‘ÙV• ×QpùTÄçþ­ìz½æ´Æ{÷ïM|³¡…èÈ.éÔR‡x?5ÀíJù,`ƒM ¼”—Â× ´I ~ßÊýBö î«OêÄ´#‘²«”V®l(Îg+1A‡«Ћ\£žo²·i€ïè0/ òÎcWQéZ!scÏ:HLz3±Æ³¼tá"0è™Þ¢BÐ’ÈNÆÐÚq÷ÞÝ$³ œñlÄoa) çû¯Mž³e¬0tƲ8›%ˆpf ¸„~ãùmO¼§÷›Gλ_ù Xh[{Š¿ò0Q:×p$ÊÕt ëÑ4a(ZsU§"¶ð‰³‚~œ}º0‡£ŸV»d\Âlç3ßù>£ zqê1ºåö™Vn΢³7߸›èžp¾»wƒ:qt0Ð׉»åGðÙâ°\‹|_§¬Ý7*¡£ P:$å³Òû•îrxOþ#¹Ûxæ¡+xr:Õà0AN¾"Ï6€g≠‰VJ õ§|ï¹4Í/’n£ºÌûr¿u*\èØ1Ћ„ö’‡ŽÜФ…JŸÅ°ã TÛ)‚ç&»X­6ç2ž“ñSù®ç|ýõë Pl’Nì‘Ñ”çe§íîMd¬WÈ·íX0ƒ#¼ º¤šÜä«ê{ƒ>$«Côö‰Ïw?_w>]ºðf|IlÌK#x6ó»™[{àŽ0¾!¾Ym(¾ªÓíïSL€Ùg‰¬‚qä%Ò¥tå‘L”F/Àg”«ò?·KÝðÐðüHŒ'6 {îéAnÍ£SEÖÀ«ÈµQº[R¸â¼£:@ŸsÇCïâ¹ê’â€toÐKDöÁŸñc€“I°ß®Œ_Yw¬Ñï0Nè"¼ÊC¼G˜uŸ!vî­‰8Uð•áááØ+Ç4ÕÝÛÇø´¸ÏzÙ¯Ù™i ÆÓ:¬"—ó3¿Ÿq;‚îÀ1ÆŒ9€sÒ)ãË‹œ€f ã9¿t1éAÝ.VÀž‰¿âY$.g{ª¾`§«‹2qŒ >ÞüäI`é‘Ft†Âkò†0„ŸzT$u0¶,¬”¸ç^Ø•¤>hN˜;U÷»Q_›Eý[\Öi§°~¾[B¸²JÜ\ŠótQy\V…ÍÈs#ùH×ÂÆ=ô¹ÒÍôõ™HtßO°óÔAåŽ)®8þûé^óFº>5OwU’›Ý¨¶wй€]!ÇmÇñJt­Nyœ1*³þ –⪠eú’רrצô™s´‡(œK DîÑݤ Û}/pœÈü"ú´¥\оóØ/}WÚ EE¡›ë[PNkk;Ž6– RÊ_á$ÿÙ!9H{ÁŠkƒÐDZ OÚЮDß6`/1¨iªvNÀ›uï4`œLÙ*-D߯¿ÐD ó<Š68ö˜÷ÔÓ~„uí¦«“:+#Hº3`yöÌ lÌ4>> Åoa;êæ¨‘ž£iŠJôíäXyÁ2—>p–+ªš_øÉe Ô.DlN˜wrœƒ|Q\ z·ò c¦&ÚVæ¦SuÉRššÏq^zajo©HÝGSaÕab×£ÁÚû€\Ô.²íÿ"vŠ ÃeU]ÑAÍnÒUeòzZ•WLºFÒ…¼$V™Ü½Clr–¶^ErOmYjkÄN'P_ˆÏ¢°„#I–€v öäá6ªûé °¾Y»žZ(œö…Àrœ¤¸CÄ+<ºº¦ª»¬$ýôù'9®’ªbTý*÷¾ÌMº[æè€yyt5 ¦ t2-mÑþ]›„±ôyˆßúEZšS-Uú«ËœÏ%šÌbrؾ#m‚-lÞñ9è`Ÿ‹ÙÀÇ£-aÑ@$¨ƒOÒ+›¸¬Î"-+—kô_aKMRÉ¿G¢Ð*ø³iSyTŒ|سÖÕ¯À9‹¦´eß:–G¦h)m‹^€]ãñ Ž¡¡ tìÊë‘U€ !oö8Þc K°‹ð—D9î5°µÂ¯¤i“Å”¿Å©¶ýéÃ*aOŠä{àªmæµWM•¯È³õ¡ÈÛGú 5°—ÜðÝ¢#ÉJeì}1šèg/—5‰j/Gb‚¬‚ÿÓf¿V…o˜,ÎO¬]ºÔúïræËR×3Y¾mëM0Á§±©-:›g f<zb€ø™ úäø0ÓÊlµB³itòD–g¡:•K™’ÎôÒB'ˆ¢ÀZß@às”“cñtª¬k…yPQØ¡ÊáÈ´@Õ/åÕM©¹­ å¹€¡lRé&‚)h4;»z8Û¦“ÀXWd½é`’‰z¶Œ›_M¶ªU$2?´s-iÁQQÜ÷ºö ¡”¼ãFÇòúù÷ßqËÁ¯ø[A@#dATÃT‘Q!PàˆkÕï¹çÞôo|…Ào[8$CÁà)â¡÷ICýýGiAjñØË{"&;0p”vèƒ*ïË ž{É”²×,;iüÚµô)ªÊ­¾vž2ëŒÛh‡àíè85ÂÜPò{ûCé–qWBƒŽí@ù?ñCZÊßI:Vå ­$ËÌQ¡1t”*÷ÿøŸþ„¤ƒ'Òÿ¯ÿM8ºåM-å®EEAÓ6Z/½òRºû®…a+”×C´³é?w÷ø‰“Á¬¯\½”~J¶ê­ÔU¤ä*ïÍ‘åý¯ÿRã¼ÿ“îø‡ñÞKÚ•Ö÷/éK\ß#‰EZÕ±ë:³5~=ÚÀï,‘39y-’azûPu1ÝŽ‚ÜfŸæ‘©Šƒ|—¾›šÛÓÈÐ`Èsùž¯ÒˆÚF–õ]E Ó©´…CXÇŠIsL:S"ÓQe„×A¯qZ —U‚|*R¸.Pʬúr]ê"á¸F¿°ò«0WçB÷—øwzý/A³§|é=! ã(Úæòªò=>1’žúÙtù×0´ª¨Zé£-ß¡ccV áŠlcä`´;§Å ØT‚È¢¯×—i÷7<«âýZ}(1ÀŽDŸ…n-•1L pzFã&ø§1¬!¬cR…[Ù§ªâ¾…\4ðm{Ï-Œ[w9Fr/«žòsÔ»ü2:G­Ð‘+Û[™Ó%‡So•ŠúÂpB {  sn½"2ü¥¾+ gÕºYu³gÂ) Œy¿U:ÄÌ‚×9®e?Ÿù*óÍÌÖa(}ªçÀÚĈ7!¦˜¬ð5Œ›²Ò5Æ‹†—ÁNs:§L¸1xnæ®Ý lœ¢8pÂQ‚Îag' ¸ÒH“4ëfšutÀÛsVuh{Ö–° —ˆü#eaÔaGð‰\Ã!RÄ>Úâ; ôs¾WWCà•ï,1wñ‚á¢û™ú¶ZבZŽaf°ND&yY+ëÒ!l«e+Ù s—èŽ_MÀÂߣ?ÅbÏ«oÜ‹pl’`›1uσ³ZEGQ-Õ=pPðKW °ÂÆrß|ÎÛÕ*>Kç™{<…ñ.öU¾â°0“wú™•iâߦ-ßIR^¥úccƒæ²âœë1ËÍØl&8á@ň4ùiðòåtêæ›OÜ›h™‰AkKxŸãúl½m2Ú B#‘=æÀ¶fÚnaìƒDªq0ž)ßö¾å¥Úm$g®ËhÒ¦®ÌAƒWx}5 —·U‚ÖÌE^ïú]ƒk]±¥s@¯D ã¤( ‹^úÖ– §!ßÏ‚î<‰½fÙ±®-pÛöu{¬ŽÂ3² Às#Ьޜýè¸TŽ9€‰ZáØã[Òlg¶‘MƼpº3^þžp“®¡YhÄÄi:¤ 0”@¥8N7ò²öŽ»TFé˜Ð™)Ù)@ü3øº±¡³™dÆÑñ° ïñ:ó°zS‡ 0ÉA‹Åy$·àxLoÙ`]ŸÂ‘’G„“>eõyŒÇ3 :íóŠ¢|ûÀ@X({£âAž)r¥$6ŽáÄ´óÕ:U;ÚîÂÁsÆ ‚doæÉ|K¸O~§#¸ˆsu"ÆÅÜO[Éù˜&Ih{H¯¥$îØ¢·g²x­Ÿ%Û{æ…“s‹±”Q‰ÃþÕas˜ œuêoG””è´„g1?éÒc)L>jl¦€±çfçc%>>É+ÌIÜÕ‘O G´NÙc 2¸³'к:”AAϰ–Uëœ çóÒobµ‹´³Zc2ŽbðÈ$_ùnæÀ^¸ìà÷ìB²Bò’É¥Më9îKÝIÇ®‰ÈUÎ<«#qGúæl€ái"ÅÁõK ƒ¡û¦¶CÙÞŽ 1çÌ¡êy²¶­Ì#ñ®¸d¼ƒ%*õÀ5]‡{qT6pÞò\ЉIx¹8\ä—âi>|F=ÚJmXÊìˆØoIÁ]ÆÓi™É»x!Á%}n¶ðϼ‚‚[¾ 6àøß\>“çù]®ñªÈv.õØéѵâ‘G Í¬*<çø22j}¶oaæÆŒý§j;’NY»:Žq ïñ›àU9ÛàÂã?âýŒ¹_ûòФT[¹»O7ÑÅ6lˆÚôéöŽèvã1`ÓØÔ77œ÷x®…KÞ'þ:ô1`åi£¨#û¾“—ýÅD±•[²ùó·,Ôué·Ô6^ôÛr[ì™›©ž®ÿ$ÕìsÄBXO ïAŸåŒƒN¾ÇùÆâ-c £ÀuçÜ³Ä øšp‡eºAF“L𻘗 ÔýݦèÁâgÛЋzcÍ(£vÖÒ­Ýi+å–{´¯4¸›h|äí×&Ç˨€^A7sßê)äÐw¼@§åâC$2•g¯\¸˜Z?ÒÈ&•Û =½Û`»rÎý7jŽ,ɼcŸ´«,J³ ×6éä3åèâ¤íÕõc{Ô‘2Ð$²°7€i)z©Ç¹‹&t++6ÐÓäC›Œ·L55¡¸H†-ãÙ€ÛŸd† »€° ?dr ÁúÁú¨ÆÍl×áò=m"ã“vúÈ/TnK—ÈİÝhÍLDlG’~òC汉.XCh„?®€GVø¯;íù’pS\ÚekøB¦®Ó‘ê4Áì†4=ô“4PT¯%Y”vâùiJyÊ|+é3½–Ÿ^~úºËž'ñ­žÏ±Ià§míÍÀWþ‘u©Ž=fUá[XM…£Qy¾‰k’ïÊ&IØÓå{ÃinìbtZÊåW§Ãý'ROÿvçÅtî•I 6è¬W.¦†ªtØBªÎé¬G€2‡ý\N¥¼ûÓóøA°6sEi~Ù£d˜G­²1‡¼&œí¾7+ì ’r¬]CYÙTpä0ì=D‡ll©âü%ZÒOÀoÁ‚ü¡íüø¥qÐûRu]cÚÆÙÙ² 7\HŸQ/3áh <Ÿ]ÜNu ©»s&­lÕ¥]ào÷)é_ÿ [_¤&WÁ©=bóÄ0+ÑÓ«H:Ÿ£¥® èŸKE—b÷åѽ`v~9m,ÃÇà)òL}M7ûUÐòÏ3è­tUÛâ,ùµùkQE‡@*ïYttÝ‚#QÜgÎМ<#:ú@3®Ã¢#}[³å5%œ!^ŠœÔ,Ï–ÉÛLÞƒ?š¤i÷Bý·ÚèÚµêUü„O—$¼­mÖ€F9/Î{lƺ„#(Æ^©©nöqÁÛLÊ߯W¡ŒÎ¸<¼ÛK•O,­ƒ ²$¿§_LÝ5ì}ð]¿‘GðÉG!IéµñUÀÿv0’=fASÂ^j_™¤'ï?3âÇy™è¯O;»ˆ7ôÑmãë‚c‡ÿY^*/W·§ªȧ]˜<¬ {MZÐ'¢L-œ%UÇQ´g™–¡n€b5x1›Ô{äç-Z*Rf˺œæ­íÝ“w]}S[­ë ×i• ½|²^Žø"8GöÏOy3Ó¡ä•–æ‚© ÖÂD5¨uê4qSl¥@aê&ù]€(ÓõrCö7Þ埨v™ö…§·øP˜Š«ò^ï¼/Þ8øçïqIÜ‘9ôU©2°+Þ¾Kଗxvôè1Ú¶ß^?÷S2ë&£ÕÙ;‡÷;Þ×DÆ nÛ€ÈT0¼öÇTA^!ÕD+ZÕÓ3ShŒÙ4"ÕÙ2AŠBCƒd aœˆûûteKsç¯âQSSÇïV¿¬Fp^*#•—híîY~×`‹IJMÍ Uç'=[½±N%¹-èô‰ÖHqöNFº.$æßÓ{$Î+?|¸‡¬ã3Цª6ÙC|ßqlßnÀÍs»tÜJÏÂCev—s3»ñèÑ›âu ¸ª;÷GÖ輜·ïuvŠ÷mqÎðp>e­o­ZŸc:âlÿØÆ¶fó™ÂÖK!ÕJUÈÅ‹0ª–Òÿû'ÿOú_þ×:Zá <ï\ø»B p|}¿K:y¿»öùÅÛcñ%é0“wÒEÖò§ZÃÞ O߯£ˆ¤ÆqMð[Çÿ‰7²UžÉpò¥føÔ‰“gøÓ9WŽ×¹Vi Ç,FפQ¤CIG“3—i8ÃAC×9Œêªà÷Ñà(+(öéÍØWOÅÕÊ4iqÅDº÷…ÄÛ8øåWâ·x©£sa}>=üð×R%™¼§OÜ{ýµWÓ—ÿê¯IÚl%Óô:˜sðë²mi¿„i Ñ I ò Ž`ùÆÙÔ mçp,TA†Èç3G2|ôÿ5œ—1+«È8ÆPPàò;Êp 0ò‰¶¿(ö^;7ôjqf†ŠxkOƒ_·N+x@òøÛ 4ñ9EЛ4ãø¶ÂIm­!”·Q½À3”×¥µSi¯¼ü“Ô0Æ9f¼UZLÊgخ͠ŸŽ'+È ([Ék_ƒD:ôV—Ý¥K™óMy |˜¯´åYv“$á)/­¾Šê>)ÄÀTg/Æ ­Q£c/·c¶5†‰ þ¢¸bÇ`­‘S^ 3Pce!!ô«äÕÈ}ÇJ{ד­Þ—É] $4øàM"ò>×° Žh2­ðº¼<ps…ß‚É|æó„½?&thÔŠÛêb9ðÔò[5sÜêVuu5éúäu‚r7l§€¨:F'{D0WÜ‹ï¯àˆ¶Mz F»´SÃñ\¶šf _a4²ÇרJðŒê™ÉIŽÃÒè5‘ÂŽ#Úm8ª1F=Ûyœ×c×»¨ià 'Íê:9ÅaF̲ç4X¥»Eª¤+¶u( S“6M<Ñ1¯|²º¢ª¶y0ÞãÞ æÇù‰7ðÙª]«ž›9—sÚíèìg.8 Š/&‰TT•Òaˆ£ ŠFÂØ×†µº;:˜;V”ú\*¬XY\ÀEßõX«C ¾kWU™à….L T]T‡¥gÔÚZ5X8`e³ÁWy‚åáúvÚS/Íֺ̜ ŒÚ À]Ó±éùq:¶u€7`ŸôÒ…U+ÂRZ–Þ ÆÚ6vya•÷·"i̽ =ü‘w)WysRÎç‘H°ÌØ:Sä 5à·:ýôfõ¬ëÎá¸R?0¸­ÃP´ô&ÝÔE¥~–`â~ŠËòƒlVcl<Úã>Ô[ÖáÑ:Jl™.ˆ£-Ðr<;·Ë²;[8é¬<¶ÎÕ½t®:iM ‘7mïX¥n¢pV°"ðÛÖî8Aùn>x”·‡Nã÷\Cðq“&¸‘ùYÕ$½ÿ‹ßéàCG+÷Èî&)ëðroç©Ò1й ̯\æ¬VÚ‹â¬vNV(›¬PR®E›Þ<‘é\¿"PÿŽà)¸ÑÚ~<ÙN£Wƒ_ÉGmÝi׊|xm)x½»ˆ³ Õž?sæÖô±{ï YþâS?N8ËåÊiÚjnñJ>)m‰²Ò‘2Z}$â¤ãã%œÝZˆ׿Õ×ýñ{a+0Žú‡¿«?X¹¾•uáÈl‰wøÏÛÒ•<î:7á ¥{¶ÄÔ`~ð.ÇóÙÒºß Ï”®­Ä¶«ˆ4_@²9G¾<#­,TYo Ê›ô%"Õ¼2}ü¾Ï}/‚ؼësƒï0öüIϽ†~A—é—â%{õw×su àd’IPòáåw\s éÍWýL¨ü3ô&ä˜2Ä9Çÿ1Åî \ÎA™ëá÷࣬_ǵA\ȧµ 4Â$›¯òª>ò¾+÷ŠD#ÆÎY°)ûŽÁ?q(«¼&ËÁh •‡ù}+¹¼GØõ;7çn0*‚ƒü!ü\—Û„ À\Ô=uf›È œÜ“ì}»àÈŸ×þå6ªsûžoï㔯úf¦iWo‘†þ.õå“°v/§2Ä5šPøï~0¦{›=×"ðEx³öx>ÁqIx [êÎÛà›¶AT¶£ûªÚ'Ó©3ºq½ÂÛŸýu¸Ç×§×N ÖÏZÛÚh—^¸"`jþδîŸ @#-WÀ•W«Ýõ¤$ï«üs/ÅÓðežÂ×÷ Bĺœ?7ÀB÷BS‰3Ù­ÀXâs4¹%C>þe"z»{zy/[›8¨i×Yf0ñH'ur'.Ì3|ËèEÝdy(L…_ؼšd,nº.u7ÂÎ^Â9º‚Å΀‹Ì?Þsl`r:ÙfåêSê”Î_]Þ ‘ú€so¢‹–Ár?Û¿ÜoŸë3ܧìr¯$n ¹+i»¸×ÂSüÎZê«÷ø,n•Øøžº3‹¹8¶k÷ÕN?þ”„Ìtzðµ´u¼H:à5£ñTÜçÄYæ%½Dòø(6Ñfï—ØçîkM}3ú`%ÅÃÐÑTè©9:p¡É.ð{u<׬Æ¢Dv*ðš—÷½v¬SýÛ½Z]˜¢j˜ãÔ˜§üÍsÂõÕÀE¼ˆ£» ÁD°Òà®ëÒ«Žd°ÚN95DIÕ-çiQoÑÃÆû°4@æ>"Ä+íõ2túºF:O°ïž¾·‹½E 4Ó¹MB~±(q³‚àºÈ¤íáÜÅí(q¯¬„D'>—¦ÊËjy&<“÷í²l%¬ú}ñné y°„©æ_æë][œ]ÇžFÂ18`BZ$Á¢KÚmÌu¬]£(U;ÚJwW±­¦.«$)xE¢>{TEàV=O\;ʱs‘¶—ø0GÂÄAùÒ¦ú›ÄcµyØšÚj耪nôø-ä•ë/Ä­Ÿó|êooL78’VÑ«z;±O×èÞ4N aíC—±Oåu-é¥×/¥+oq*gœÛ™Ívñ5øFšxvð<ô}ƒúç²D»­Ô^¿d~ú=ŠKI<`Í_´›£sÖü ÝÂæRAåáôñ»î]Ú¦Û$y{ló vÝ<þšc{éÔÑVðÁ÷«œoÒ>ß®[Ë$Y­­œ,3Aži[Mm±‚ Åê,s7Ë8~¿®–ŽT+çÓU ¾¾ßd5] -²Ã¶#Ê-;½08²ržóÙWgSîÚ"ÇÔ‘¤u,­ÌÐwiš“Ы9"º8Ó9ØN‰^èÜ¥…ŒEQ/Aäuô XÀ^~ÔÈñ(ÚÖ«$t™|P>¸v¦ÉÎ5ÆuèøRß±»ÐbG-ë-ÉKo]N¯¼1û“c#¥+»×Ü~t uç)Q§)mL©»+MNÑ=cŽ#®á=õM éÔ±nô¢í4ƒ¯äúAi¢Ÿ†GãÈ œK ÚÈâÈÆű_*«ðײ×êVáû§¬²üÒg o3Ƀäå_Ðø¶~6áç|•¾’0ªlƒnåùYW3èzŽn>àmø%ø¬D{•{7ˆKk“n¸í= Á® EàK‘tÈOðzè\?‹rD¦åøòùªÚ!øðã…½hðöÇ.e™aö7û^‰-vå­7/ú¹Lç†í1"SŸ‰ÈtŠs,'Œm\Põ"Ðmð©«»AŒ³¡šŒ ¤¾ž,!嘉¢bWÁ}Ùó,ˆ)+Ol™(Ðt¶+¼Ì07 î³6ró“™´PNøýç/âý.«$~þ›2ê)2Q4,lK¨²[ÄÙ^<<óà:€ÀûA@ü“0Uxfp|¨´Wèõ}Eñ;.ï¯~žçgêç¿ðÙt”lÛ¾¾þPRÅ;/¿kP¨ ‡ÿ¸ùƒÇ¾—ú¸_<Ì(˜Æ€’*IÐ_ õÈKœuÿ'¡? Ê— pvžssº»{R%Îô/}ñÏ"³ÑöX>Kå¼®A¾ë£‡¢ô²ÃtÊDÕkSaÿéO_IO<ùx"þàÞ?Â^Ji?õÁ~8Æñ…ìN3•«ÝðèÃéÅ—Ÿç»/Ó¶êx$Ĩ<8•9²3´{{ñÅgÓ÷¿ÿí’·wt…2jÀĬÑçž{Åi3ÝvöÖôØFÒÖýžûc DzªìÊ•·¨ÄA®¦jv<=õÔãQ….Ì\§J­÷šñ;::Õ¡ÁK0x«S äe1•»+WßJX—BÇj·±±Ñ¨Ìß¿ÜÛ±±±ôê«/%Ϲ¿t©0ýÕ_!ýÛû?㬠£J¼øû\èï½_ÍïJ^âÎþïïµ’¿+Ž„a]8RŠÿÚÕ$Ðd•¬š$´¢"¤™v€¶°úÐGî†ÖJéHÃ=Рç6k€¼yîµp^4q ‚{‹$¹¬c䙸c¶²l¼ŽçÁÌÄX t ¤3ÏGŸ›ÃÙ.ÿDÓa¸Šá`&s ¹Oc¢ÀlJ&ÛU.aXaa ¹¬Ý0Ðxuà½ÀyðÙ/)ÄWåãçå¡#Αæ¹e£÷ô„qbp§„J­²ò¬…ðʬ²Œ–¾:ß쨠±\üy;]Ÿ¹N°ƒD, ‡d‡Š´rSÝt–Vy3Ó3Xo&Ê–_s8éÂ1mZhå Æ‚öÎŽ´H {šä+ÒAµ@…{tHÂÁ¢ã®¾Gÿ-â4ˆJ`Œ‰õ8ØͺÖ``œË6Á£ ÚxcÅ…=ÅG¡{ëhÕ°Pv*o§Hœ{ø»ßI+ÈpiMy§sJ¹®óÄÊé" ñžÆ5BfìkDD‡ *Q­Ö‘h6¾¤ä8V‰Iw:L ²¹“ãS<«‡ƩٸÈPÿ^yðeªçdåÑ^OÇ„|½2`j€RÇ¥ŽÓ<2‹­ 28·0ÇYR‹Šy–íñLõ½FÀ^gGG•ž¶8œã3Öf"û‰;%]º:Š6ßÙ…÷,IT˜ÛM §‹¼n€î*|¦£Ó*zƒæ®U‰g´ ùR>ß)Á6Q §?º6Î5×gYÃŽÞiż:Æg*ê ±B¶‚Ä Ûrg06»;B€¡ûxþ] ílåK·øŽF®lÏf¼>2–Úúz"xëZ¡óÊTñ&ΘÆÚ]:~^zá9Œ]czháÛ:1V€c^|ÞuÚ1ÀªÓi‡#kÇà ƸgGktj¯¼·£O#-étH[ᡌp0¯³ýC]•^¬ÄÑ™èÙõâ¤xUÌ@ ilt"mA/œQa¨Óšû*qÒ¬Ô]À‰áÙqõÍM¬•Ä-ž±Ìοq.èÄö}3Ð^mSk8Å´%MΜœš77Ñmí®£,sƬ}ýUª(Ї×m‘lšZhÿMâ´é„VCް¯…T}\¼^`ìH°É¾IWµ$  Øf¾¶¸W.æ0Ìujj¯º×<)©“™Šœ_Í`0Þª(Q 6¸ÖéëÓèëF©PD?^e^VE‚ $)mÔ"{]C9ö s8`Ö’òf8äL@X!»^'œÎcímu€Ï~dÕ ¦œ¯Ñ¡QæŠ3ù‹¯Vƒ€ì¡Ьz0«¤$€Îý6nØm9¬¬µÍ¤x§{µ›ô £[Üq•Ë2–ÕeÖ Í»çuðV]ŽVaê¼0àìÙy~ÇÄiÞýÛÿöøÌ pá¤.<\‹Á5}eÞoÀ_M¤ÅY»‡,-¡·[ù%àaýØYü¢³Æ½³rxp~|=ø Uè®QvîÏ!Ììh`€ÛùYe®|)åÈ«a¥C凉LÒ§ó÷’òÈ)ò ¬|÷‚˜Ì/ŽÕ?¬\óo©¬Šßb?uÎÔ£SéD×Iºpé"k¿;®)æ¥_"‚'r6x§ÝK qX ¾u95R„`r‘°RF\¹|^ƒ3ˆÿî¸ýŽÝê X8?¦rpý’C@G£ìÚ .ùž²a‹D=äœz²XòÊ%y4ÓÝÝ ßìŽdý±‘áðµ)Ïu"º÷aò}Õ“XÄíà<Ä´•Åò(>Šûä ònÔ•…Žàcò y—•ÅÒËúˆþ@+‘CÒM)²!Õt "_ÞïŠñx–¯ËØ õTTÉ#Åw½:T­ô ™%uÈcç§\1ˆlÅå:¼Mz‹sÁKUÒ,'ãqLÄ@¼—óW^Ѳny0ìp G•>[i»Z-æa÷tG¡Ý9 ]ÊyYÖßd‚ÌG;6óòr=^Ê!ᮣÖW“[¿Àû®ÑÀ›4 eó;?ÈýqÝ_Gt8¿ç¸‘´q¢[cºOÑåIe-è;¼gPͶ£ê9³8Õ39 '湎ü‘q÷çë\ßù;ÅC ê8’òGù¥Nom.ù¦•²ê€;ÖÁ@îóûñ¯òr}ÆÙÞ¨nˆÎïÊ ƒneèðònñº€#ŒªÐ9N&|BÁOM ;ÿær ŽÃÿ?sé68±ŒÞUï.!HæWõ‰›:À•9úl´ƒûH¹Ü/ðŠfuR‡v|ƒ«Ùã3Ý[Ç€Ü:º°2þ-¢ì÷(4[ŸGp€9:¦×»Á<>àu÷üÚøXºôfºãŽÛ÷óI$19!t öß‘GÛÐÇ£ówöšá¾8útÀüL^â>x ‘/»lm’ ¶&ÞÐggå=îðÒG£ S¯ Gä)ò.×%t;¦Noâ%¤“áz€ú@è%ÞïwÁqi,È÷"€,~q›ë³cI…Úp®:k[,ÞÜx&“¢ŠÐ·¸Oüôù‡I{ÂH]C\Õk°Õ#^Ô\«??®ÃýçÅDF‹µŒ“ ~>KÈÍŽmÐÆ/ø}<ðÕ$Çp¤ð·:¾cñã«—ñ«¼6žáç"ðq~î±ú&J|/îVŽ Ÿè[ΛÁøn#}±øPŸ{æIî1ðë™ÏÕ¡‡˜4XPR˜Ñ „!ÏöÌàÒ²lì0l«ú¶În‚ÃT­ÊÇxÖ/rܘ· Ä3m&÷V|/%>{ ;».«:W×ôÙ,{“Î¥<`œ“_h Ïcã¡%îë'îí?ER3è<úÇ«HöU'Šd]¾§Î«V@u¹>"ƒÕŽSJaÉMÌI[Œm´b8R-E$ŽÁ?Õ»‘ |Ϲ‰#$“äodB»‰ððà$h!ºú/{ Ì´{“ÁíR䕸&›ÙY+KºgdÖÜHªEeTeùØ_¥‘OE¾—þqqE¸‰k5<ĘW Efuõµi|ä s3¦Îžíæ6cñ.]ó ›ha]™áKÚv{[izôÕèY"o¡pv‹Ø1:ÃEÚ¯c»dÜnù;kiv‰#ÇŠ–Ð}kÒØà¾»­ÔF'ØAöñüE|ã$˜Ú•mzÜ€zû:#€•VÂP~‚~°v6î3ØŽv@B§^ÅØ³åu­Ìe• ðD^OGN7¿¿üÖù4ËQÑ…ÈÜB”,Ê=Ñ[“ú»ŠÂ`›öB×C0{“äm J³YËBj§Õ¼g×/-¡CkëA‡k«¬;¨««ŽD¤8†¡±Û$EF ‰v«.ÃfR(+XN§×aÏåÒ¹·æ#1§µû8éø(ñUTo¦–žä>6ÀÂljä»ÍØè¹œç¤OXDÖ‰5ðp.m3,Þ¶â/D’§7pÐÒÆ‘•ø<ú»:À3i¼8µ÷öpDuGê:ÔÁYètèÊÂCMÆÏKý½èqÄ[V ð¿rîsÏ€oòÁ:öºƒÎ;ÈŸÉÁAÖÎQ''ºãx²2ŠEª8ǽÜãy¯¾zŽòq$ï•¡ëi9x¢g‘gpéÑ‚ŵwwèÊ ÿ6YÂÄD(;lî-dxÞ'ýÈÊ•!×IJƒg6€Ó…$êåñ³M×é/z“!pC÷ʇ檠)¶ƒk€7òPè9TÁýò }-˜z¡¿HËÅtcÔ¯£.©\r8Ù”ÔÙø"8sãùªïëï*®wmú±Ý„àWð u$»{i;šì//[´c-/Fç¶pDøŸ¡ ¹Ç¼õ=™¸^Y‘Éa夂 èKTG×3ùÍõZo†x&.›A£Á§5‹:t…–Ö8'’/ªä6¶t¤þcg|Ye‹Àp‚ AÏô»V®y.œ‹ e‰÷VPœ5*u4†°dãÌz Ç=»åþî%£ñÊÄ]üú ÿã8þì+b8cK F>ÃGWÂPnïè„$ÀÔ¨3Cáæ¥@;¸ ðn·öñã:}«À¡àÈÐ%j?SøÊTuvxn«¸¨ƒ¾‹ŠƒÏüž~"µ·u¤ÝyW¤cz¿ ¿ õHÿ±`bO?õg‰2 ™‹g\jìôõö‡cË6Æoœ?G ùg߆‚Žð‰‰y¾4Ú-Îh@!yøá‡p|×SÅsˆg‘­Î8o0¶Òz ïH8tÍܾšNž¼%æ<<<˜¾ùÐW#¨ð‡ÿâÒÇ>ö‰x†çª^½œúúBá³²ëé§ŸLúЇ#ào…hŽ3;~ôØ£´šëIÿø}1þ,÷]¦Ë=ü­òiVì“?þgµŒÆ™i:ÿå¶F‹,Ÿ{ï}€jÇÒÍ·ÜJ+÷‡ÒuöÖ;‚9ŽŽ¤=ñÃ48t9?z2=~S:GPïÑGI÷ßÿ©¨ðw.]ºk·Uõ8?~–ÀÃc=YTGcÏ.^<‰*úøMéÅžNßùö×Ó§ë3QuîÞŒóÌ/}ñ/iÓÖŸ>ùÀƒÌë, ßK}ãkéw?óûÀ¡Ê>îãÇ»áÏû½÷N~õ~÷|þ« éÞK|ÉŒ7§Tï½.•š}ùöÞw¾Ë§> º°â I²ÒçÛ^Ôk@€çŸÝrëP r©»çHÒ:Ò—çBÙ¨¬."¡¦%:ÜMJ”2[óÝ~û‚þ=Z¢¡‘öOS&ó¨ ´ DZ•©aj&ŸÎž0F1,\³ãkë"(ÆÛ! \gd“=¬3@ÇrÚ$?ùíüÚ<ógÒï¯XØÁ?¿’Ð(¬@7ý§ŸþgéÅWžKÏÿà»éÖSg1žã<[¢-Ø¥pþé¼YžžÅ#XΩ[êp±Šaž*dq©’³‘t¤ض2]Ô™™¾Ž®j i )2Ÿ[›#¡ëpwOÈŠ|‹TZIZe;¶T‡SCÙyþÍ7 ƒ.hÄsÕ0&®^Eæïâ|ÕÉi‚.ëYlÍ­Ma ˜ ×Bsƒ;¶ùR‡¶QGøÞ.N ffgÛ)÷=cÛö\Ú°º¿ £ÈÕõQk—c¹ \ádâ¹:15’¼\«ÎÙøŒ'èÌÑi*ñ„³~ ¼ü¾ŽÚŠê¬šÍù(Õ TƒÁ¯L5H¦Q­¼î¾J‡¶d7óÞŽUIðW²¶A1F¹tš9u‚ Gñ~Sk£³a Wè7žUT*Ä™¨Õs´œ[ õ\9θBð+ç7càƒ§š`µkÑÁleNz–|Už¤Ó¶±¡ý¬+’{Æ'f©dvn<£SÃJžBø‰wa”ÃI`ÐÂÄrùÈý ’µcŠù;ÎÙã;ò©àÛ´ ~ üJák^ÂÃ,mÅò¶þ¾6’ªøO¦±kó茣à‹pe¾ìOt â¡øµ ð½Ý4§ÃŒF[Î S¸VØŠýÊð$ÉÀ3Ø2ÀŽ9jà1ëJü½ ¶¨œ˜™AÇÉ‘Œ@¶úM7ѱh> LQÀšt*áxŽ*cŒk» ,à6Òª çå\¬z5p+| ,èXÝ luoCSMØOV©ÿò5ÆÑHÇaÈ\6I‚Ð3ˆ*f4åTôÜq4ÕSeþÍ«Í0Óʤêꀇx» ìJ­gqL;ÕVö8Mº¨`ãâÜÂ9ª³Ⱦ·šÍDçg»±þMæ-^¬®Í #_a«¨6Ò v`½v(ô[Ξ¡¢ ˜®B|ÎGKYžoÒÂüÔ8“ÈÉ!ÿÿ.Þ”u×éz244ˆ^@‡HΤ9;É'Ô²ªm}‰Ð'tÇ ÚÖÖÑþ‚çxoà;øvB1®âޚ訯LYèžÃ+ÞzäÇÈðPèCÒbæ;þWŸöüpuAqE^!Jn>O8³Agùy¶¸'A”ŸRt¶øëƒßÀO\ Ý¸[z•~Å1áž½Çð†øñÆv½Ùq•<…ûö;Ñ©‹™d“#N}ú üš©W8½=Þ;Åg<ðÆ;ÎE} ÿ  •‚ñwÑÅyß31$³ƒ„«Ó—ÇÚm]œÊôù©IHŽ-¿uÍþîwÄ3@ÒîüAÞ z3IWÜp^âˆg›Ð×ÞqÛa”¹Y|`œ$›¿]«ÔE¶èfÕ©¼Pÿˆí†79ìò[9&—¶õø›Ãßæ~Â\ZµúÜ‚&0t¤ŠC‘Nëå xoðm’…™òÌõ{ÔÕ¸–O°±˜}Ê+¤Z•€Ô*v„¼±¬¤:ìæèFÀó¶9oY{¹–NC{Àѽµ3N¶1g\C°&p&ã¥î¹íí7уMÎ.FŸßÚ_Ñ}¥õ]ªÇ Às«jsÌß.ZÑá>f𿔣}67éìCàZ{»^z·“›©Ù&›zµ4–É!«®é¼ŠmVL@Ùívß·Á¿èjFgæüÒú°#w9ög“õ¸~mAÛлþ-d´]fšéš69Å1Ø»!ÇIèÂŽ¬D×Gß,_ÆgV`K°C´fVã~±§Ÿ˜ün ¼¶—±Éövé* Íoq¾¸þ¹ë—Õ’¤à1ƒC³è›©÷ÄÉ´˜+Nϼðl¬S˜¨SkwE¼‚Xžû©›PùSý)w¶Suþ -åé&³–KuòõŠTÝx2àsé¡tuŒªmZÇP%?vþ æduð6-ÚRyÁf:uŸÕü’–´ºMg¸ÙEô_ × Vá'ò4ZˆÓE¡£­&‘_2E¤² ZbW¡ ;_´µ×¤žÎJh /‹óæ‘Iê;ó$LLŽ!cÁ!lÜÆ’Ñ«vÓ‰>Žj"É~m}ŠÝ-ØžàYÅÈpZÆ/—ôŒLË».®´ /óÀ÷J’ÃÀñ²¼Å´¼Ù|FZV’¬6AEF“Ó©µ5ÕqŒ]®sLÙÉÆK샅HKÀbvözê"±\?ÉàØtºHÐûءꇯÓÀÄ“ÊRh~ëø5à®]í&SUisê«§˜œ¿–.ÐUyh!€ö—6¡Ic&ÆGÒ’¼LÞÉÒDø¥ÀÅYÊèBþãyäkøµ¥ƒïúžo'}¹Ù`¢QmG"ƒ´Ÿä+&ói3ZGlcóGÁ¹nCÝÈöÆ5É¿2:øÅ#?üç¿ >íNíÛ ý%|7æn†ó—g{uÀmÜ«ü˜£³ƒ~$ý7g&ÍìÃè À=>G™TNb¨=ûìÓèÈhúÃñ_¥;n»ƒ@÷¿*ÝJpíÉ' ¡¡bö‹ϥfÖh»u™-ÙuÎãë_an_‰¤€®®îôô3?&€ß8fÝ÷É_ù}Szþ…gÈ€m¦Mô™à?õäÒƒŸúT+o½íCŒQŸ¾õͯñÝŠP¾ùíoÄY¸¿ûÛÿ<”÷nÖrö–ÛÓ¿ô‚ôo1ÇÛ ÂŸOßþÖWÓÑ#éwÞŽ?Âõå—Ÿ' ÿxTyéÈøÁ÷¿I ¶¹¿û£÷rÎùMqûW¾òWé™§Ÿà½{Bù}衯…ãðã÷܉EY»ùÊô¹Ï}6=úýï¦_½…ýßàgîùÁuwƒ€8¢"¡œÔ0 %«ZW±ðÊŸý¶Š NÖríg?ó¯_÷‚©¤pí˹P°à?&½)ÏͲ³Ý©•Cu»ÆT¡øéðÐð^FÁ¯&PhÕy9ô¿ SyÕȾzå<¬^iÅUÏnh¬-=y{TåjÖª˜d‰K%åÎêó0Xq$ì+Y¾¢ª`dÕç:¹äêÈF8ø÷‚FÀ=e8[n:qsšŠ Wõ·eª!F†¯D°º³³-‚VZI«ÑïÙK¡ dõr8*”sf¡êl2ˆ­¾k•®Ù¨VDˆÏÊb­öå÷$²Rš«" ¼ z¯eó<ã+¯u莎„¡ÒÕÕ8¼€"¯!.ž:–-‹¥JY @h€Š4£‹EšÞæœN¶#ïîíÅ /¥š˜ŽP듇ÁKuj+%¬Ö´•¥Æ®†°Q= ™¦t€ë¬5;Wc!(Ÿ×È1Ž&>êç"Å8:5Ô}–÷¸VW,ôP‹.:ôÂÄýá`?ìB³hÎm$Ðê"®Íà§m¨3ž¦C¡˜¿%ó”=Ã4¹;Ûí>Þ‹£C…à‘FðòòFœDïZGϨŽ3;¬ÏÊåÙÙ¥Ðk Nê¨PÿðläúºJôzö øé˜™^ÄÐ5ˆ†¡…áîÔ‡tk|i Yͬó&ï†NVì¶¹OO îØØc[¯G˯µ¡tÖk;Xýl æFæéz**¨h¯Ž€Ã…‹ðÖr:þ?>ÓÛ¢ZB=Ñ9iôi3•W°Ì=ÅE©¯·•*ì' ëζ¦0&›ÛêÒÐð4Žû7q¢ÈsAóìüè0”;*û äïé\+Âá€cߥg‘Ì—¯ÛòÚ9x®——¼×jWù½ô§áYcÿLòz2ípÀ—c¾SÂüñÒV&èxÑ0U'ÔáX‰ãÈýê?r(9Fv?Á yΞšYŶ4K{MGfŽùæ <è¹’$“mæÙÜXCÀ›€!°°z~b‚LüæZ§õ´$Í:Eè°Ü€®y7àiI… ækÕ~5Τ°ñØÍVÎ3846:Jw éÕ¤ =Ð1keAaÔkÌGRøeÒÆ^®APàT00ÔÁiåyžGºNDÛÁ+G…E$j3Çîî6ºuëÒtdˆW^ŒÊå8c=‡cÇy4 âR³Bãð³<Ôe$"46VB£ØÕ:¡ï…În„WzeÎhƒa:ǦӲrU' X÷^ç‚°ŽóGÜ¿ ø·[a?œÀÃ5¹†|’9L¶>î¿ëãÃà%Ÿ¹¤!iB¾¦Á€u~¾G=!ßHà\˜ñO2ÅGœõ/nŽnŽQE¢žÁ“ÂWqWMò÷Ú øÀ‚oÈÕÜÝ…ÀÂ}P§±rÌà„D¾²ûT ôÒ@b”vÙÈÐUäApš<3ŽFÎH{H;O¾ž+`ïpJ[P îØ‘ÇsÇÇÆ€‹gVFÚ2:° vv" <Ê`¸§íÕ)Àߊ2;Wu÷ôAÇ/CÃ3©ÿ(úA†1ì.Ѱ»Ï,ò¬»»ßiÝ ¼;t¦¦§oA—¢rùìëq˜ƒ~)!éüOéX‚î,óìÈëÓ8M¹¤S÷Þf-Idµu iÓ mÞ_ƒ>D•ö¾2ùYìR»l¢O47À—¢Jú1ˆ¨SQZS®1+£ kõUåË…È ù‰t¨#ÿ4xn£“ÿyÌzöøN÷ìÒêS&nµ‘(yâØQ|&íÀÙ$FaÜœîM?üáwÓwÝ•îüðGƒ'GÇä1âÕKù­d‡¿ð¹?ƒvè2Ø O/cÞL´§‰žnÎ^!Q„ôƒm!¤OR^i< §¹[r£ÜÛ™k²•³ö‘œÇ˜·ÇZpdp­£³3 ÜrszáÙ§ÒÉÓ§Ã!Oð øg¿Äïþ*ÏWORÇ}ä{4¸–|àÁ_JÁ£Ý\A—‚$kT¹ÑÅ…{ Új’05ðéðÊI÷)‚QÀÈ¿C^€WÎ9ö„uÔÁ{]{{;üŠ–ÄvœA·jïh…O®à§¹Ž=p¾[ýVøìã‚{ ,Qˆ¯o$ýî·¢Øãº`­ …ÇW¤îÇÒ5xr+]t,0qòÖÞØÍèXÊ“x“÷Õýöu° .Üø‚:›ì±;]øÇ[­$zÖ‘Ô¦ŸÍóûŽIÏ?ódø|ŽÝt“5/OC€ûÒŒ¶,r=óÒÕ+éûßûNºåw1Gô‡ æ!WêèÙ@° ˜7ÖÍ3 ƒ"—‘aÎ'’EØ;΋Ð7à)ÌÑg»&v{mjá£2òÚ©ÈnbìbpÉı³T¾í+üõOãËŽÄâK.€ùˆNHð·° Y̯¬üâóϦ¾£½Á#üŠm•=Én´¥à½{£^lB§0ôá…oÛ$¹C²¦Á:+ å äTå`¡KC Ä9cÄUõ:ÎÇä;çè¼”×&aXnF}Q¼6o¾yŽ¢ªL¹Ç1\›W௎?âo|¶GRÁzàPºãƒ·¢§Ù9 Õ&¯ þ@õ6û n$¾kCšLës<'ø'c¨où,á'¯ñ _­ãó“u³ è2NV!Ÿ Ä7i%WÁƒºû2Ýä*¯fü¾ú›åׯO1ø-ß5ÑZÝZ>POÌÆêiõ “t<Ö¨ûÁ$+aè\öÇAßçgšéQàmØÝ8TfÏØÏHF±:¼äSéÔ`´hhÀz‘ ôþ Pr/Ä Á¡Lymø…L¬¹xá<ølR‰ŽðDp ™¢ mîi’žË‰9íåeÁºêjù+I0•e:âÛ.-VÄç¨\3)]ÛÚ„ ízÇ5!Â{µ ‹&Ë×"ðF%ëö¹ÕÃUµW’¯g0“Ôæ¼LÊÍcœH'€går!òjÝÏ.pâ;Ô<Çý3hl0;óÕ‹+â-r þº¾i°Jx`©ý+¼Šï(,¥ûœ:b:v}çátåÜ«©xŒû©úݤrxy‰dæ2r!‰YË™ÉÒðd’‡<úÅdÜúJtû|]7Ìo,²*{Á›Ü0‚´v5¦5ŽWxòùóÐÉÕtüøö‹g‚;&~un¢1óÊE„)ŸotŠÜ&…°Å«Ø4ˆ÷—¶jÑ} –§ ޏ-Æsœ·ÞÚz(•× Ü]Må7òñ¿€²õ$˜¡Î3'è·D0~‰Ê^â‚&K¤=æK2õú*Ml ·Ôã^*¨L7IJü-!¸,Íioë7¨Áž.ÙÀ~ç6…-ótŸæHFÜa½Mêðaàì.Tc[O]N9ªÜËJv©Â'é£údê'n¡¯fdð5ô;‚a«W–ÄZr«ic}%ŸN›ØÓv 0Ñâ[a§^®¾oBáúÚ"Ýðèj@rûÔØ öfEºýæÛRkèêÀp»#mƒoo^ [ÖÒFº ~uÙÁôy|K£vWt+hå˜+À==M·ÃqÎÇ_e§ƒü' ´Ö‡Ž ÙcTDô$`yì€zŒ(xQ$–q|™´áç&ðí$_Ž.&ÀËHê‹›´Ú_$É`ÿØ iIßJ ÷z¿rœƒŸdǶéÑžQ‘x^˜ ¦Þ*­ûžt(oˆD6hÔ䃬ÀšN_Œ·ËçŽ!_±‹< ‚¾¥]kâ„÷úëé7>ý›œÝ~W,Ï|2€^‹¢ÖGÕº•è*8U(Ǩw¶µòêíHwìéóŸûÓôøÄèñc'‚˜ `¶ÞMú=ÛøñÇâE‚Þ·Ýñ¡¨:—©E»V˜]'‡V™?õ䆞¹›>ñéßfýýá|öýý5é·I0 ¿@+–‹´±ï§]н÷=€¢–}R¡ùà?m>¾üÅÿŒÂ°AÀý2NÝôíç­ª÷˜Š;?|w(]ß{äÛ¿)2î÷_þáawÅZUÄ„á}÷}’yý8 ×#GŽò=úÁuw‡À¾¯Â+ÆÆÇ’•M$tÙÞÐ.|’Æ¡©}¾àh5*žïv)sUTLÞëz·oû È?ž%¿R>ÃÀx>\ÅKÅÒVÑŽï%ÿÛ⬲h¦Ü_àÜbÇ>}ó­‘¼b%ºÁøîîžào&Û|æ÷þUТʡã;NJ½FŠYêÊnõ…ȼçSe*3ÂCÃheõ†îÀgÎûàú‡ÀIdZ(ìðéZ,¯ÛnÿHšã8 V2uŒnl,E«®9œ ‘Ð ÞÜG+$ÿá£_:lêÅ]ŒÏU¶rFÒ ¥c› l Ý@ŠŽŒ6d‰g+ê(ñ˜ÏlÓШEy6ÐlÀúé§žoù#a§—ú­gÙŠÏ?ÒCUUÔÁm+*7ÈD ¬›xŠ8ÄÅøBOVÄyT‡‘wVÄë2”¦¼¶Â›É¬ž$<Ær ö;?r'4Ÿ€/è §cJÃ:#²KC‡¿uÖzžõ ë70©¨ƒÚ„0ϱ³k6PN…Áù7.Ï æl4æÔ‚ó¹ û20Ç*i²ˆûɈxÂ×Ýx ‘ÅÜæ©Ø&&ÓÁÁQœ]↰Í¥Æ6u6xŸÎ7ƒeП•é®Ñ t8Á›Êp/o ÿQmŽ9މ(Þ+ öõrZ-4È~šýÞ#?tèaÈ;?ñÓ`Jn‡çò{5A9kààèÈdTë7èRQ.® ?ƒ×ÜÌ: ·qößò­»Wì¦ÄY¿Žuj+ÎUøû¾SÖyå!Öp¶ŸR²ýãØ þ!î&Œ4Ìí:ÜF¢$‚YÞ:?šžé2Ž2Žr™f IÞaK;åç"t$¾¹—¾§ƒ —Ö­À†,ƒ~  K£[8æ¢ÂNd·ë±ªRü°½¦|aÕJxªûu|¬¯ã¸á’¿IOy<Ãýè8zpâòýeÎtöu@MëØP§À/âCS«8<çpŒ¨•xœ€w`LªpÙç’1ÜG«'¶ K»l™À“©ZœbpOÑŽ¿´-&$¯¶ÀÀÉ,íò]› ¶µ ‰ë·– MÂr{'Î<Žw:³ªÇ¡ '¹T‹ü©¢ý~+#ZÛÚƒl­^†3°«»7'°22<~ƒíÊ”pHƒ‹âßë/¼”n¾õ,6Ð=ØKýi ¾®Î¤í¤#몃F‡‡H$î ÙR‡nf`ÔN`þø‰Ó©]Ñ=6×/3ô'É ¶pÐ^Åçt)äâ$A›|hÊÄT;F„•ÁËÐ ” +­âݰSºœÙ¢%ýWð—6’ˆäQê ¶aµæÞêIhÒAëÊ›€rò²N¥Ü‘B§9øƒíSw ½=î=ÔÑžzzúÐCpàÊC‘ê4 ui¢j*}õK~Œ“t¿kïèàsx,ÏSñU]¬|î™§ÓÃßúFºðÆOÓoýÁï§þ#©]Cy¤/¡·§§<çÝÒ©C^üÈ2 ÀÙõDž¡Mäg|´¿µã™<²ƒŒ(“ ÕŸ¼W›EýÃ{”IvöÞa´OíEhå¾Ç=ò4ù½Ò7~z.}îÏþ”® S¡×ÙÚÜc7 ’ØÉNþ¿@EY5¾ùö#'MÑÉèþ{?NRŽIUÌ]Ê~7Â36Ñ{Ô»Â7É\2“É ïQç³õ´¯^ê-¶Þv­:M,´2Ú„‚Ó§Ná{ÁžÂ®Œe öj¦eï³Ï¿£Ó\"‡¿W|‘W;aÖßßGrBþ’Fäz ë´H£¥¥-½òâ éì­·G‰{%ÎÌ¡KÛêØ¿ ,êg6yÄ’ÁÑÑô¥ÿüy:CM§cŸüDÌÀ‰mªèΈ_ÌÅiõCÉ(®ÀóòUÝÄýV†dxšÉõwðÇyTQm}ô؉‹íÈ·C–”îhKO=û þ´Ç‘mTb64Ää()ž«ßÊ$re¸E%&Ÿ° ‚³#é{ ýjúŠRLÆÒ^¿ó8{öf’i¦yu³WÝ ’x¡¯O˜¯¯fz³z[l†ä3å…ÉÊcuä&V þ—±ÍìmÏÑ_ÕF0ýìÙ[ÒÿÉH’2®Nhpuuiœ¯ãÈ+šðZÛ¼¯­`¿a?jOJ#´JÒs'¾ϽÖ>ðx·Ú¢«Zxè:0ñn½^”\_øêçPÈ6ɬe*Ö:1I'9I}è&[Qæ íƒ?!¯hÍÌQnvÕR'Ü#¨,o/0Q‰53}‚¥T³Ÿ;ðýÇòm›h-¯)«ä>Öa¢»~ÒçH˜'8í± ‘0EâCŽ@Üú2‰˜ê²lä5ŸF©^ÍÍ¥Ë×Þ`tôêlÁï!쨘fŽ@“8Y ß©@W¿‘ˆ ,´§µÕÊvgÑçIöF^íDžßªN«Ø“Eë—áÇ$ºíD&AJ¾RUßšfÐW6WÐwÁͦcùé"-äOßt…˜°®oæý"AàEØÚ´X#²@ºÏÑYeØðNn½ý3¡ŽíÀÞÆ&%ØNîÉØì5pȺéQí ßRÚ6ÎPÕ ~Að¥·mÿN»óÆÒèJw}†¤ú¥Ât¬£‚9]õ!”RŽýXΑÕÌ%Ÿ ôüÅ³Ö ‹°ð9mìr´kò˜ˆ–Às“XLÔž›N—éä¶¾y=Ír–ûñ£wÆÑTkÈ\&Dû{޼2˜žxñt×͇H–LiøÚòR›‘|xË … G$ âsïJèöPWÉ1Ô$BäèLT@ל KZÊ@7i &°ÍÜÖ U“àLðüòbt3å“úb:àoÜ£9d‘TÍ8úbAt¾i¼T:ƒ&B°kòÝ‚i™Oå»Â¥!ìµ||lâq»3Ê3ä‚þDr`>8a.›ïÓñw3Úcž»$˜Î,øÝjxý:À¹mPðd2ƒ¤ùŒk•Æå­Ò£|ÒN1¸­ÁYtY’,¼²dEX‡ü[¿€ºŒ´²X‡ÿ¾nGTy_ð‡Â†ƒ+ËtŠ¢”Ç~)o 3&a‹8 I#þ s)+§ž†+„ÚÙÑ ÀvP(§C×–—߀°jÆ žï©Œ¿-@Q@;'âæ„`á}_÷[Æò'¤ñ‹_!H72ÜÞåkÃÏ”¢±‘!žOËM”t O¥ÑKǃﱫÑ¥¯o§ÄPúöw¿ŸŸ:us(5®E…óà:€€„¿¿ ·ä ¤øž8í¸ÀÏ6)À«UèûNÄýïªØxFÊïüîï¥/ù‹0À>ã)D¥ï•nl¨3æ…—^ üvŒìÌÀÔ0fŽfqž8y:*Æ<›Ûêp g3†b,«HK0\O¤¯|õ éæ›oO¿öৃ&ÝYŸ·O7qdôÿØKwßýÑtÏ'îÇYVIvæX Fï7 ½ˆ³ûÂÅ7ÒïP^P2 !sVÑ­¦zäĉ“a½È¹”cãé·~ówÞ¦{ /š‡QÈ vÿù_ü'  céŸx øˆŸKçÒ¡¯G‡Si‚ŽÿõýëÈžòéÜ5ôt÷¦ËW.‘Eþý¨¹ƒ¶í*V#zL)"“ñPW7çÌ?šF†yæçÈÿ­O£ÖIå¾Ù:þÍ7_À¹Šö´¨>Æš„ÿÏó¹>ÓjÿŽŽCQ©¢—%9dN²xèÁ?¸ñE¼±¥Ï“Oý8ý©’n.ž1câÆØØø úÖ¹oÇöç(rïvé‰@Ü»}(#àÙ"§½ã yŠWædɪ8¯—Ÿ©œø·™z&i\Ú•¦Šƒ`žóª"f ²««GÎ0U}¬-ZC{MT´8†jI:Ü=S¹£ÁÖ“yœ­¤A­ÁíC剡àà$,âP¦X?¯K‹(™7æ¿üó ¢ž¼¹²Œ­²~¸£3õutàlÅPA¾öÆO0>†Óè[—Â(n¿†¯^!!r:ÚHbi]ºx1ª uíŸñ9‰<Ói¤€ÜÞ²ƒÒ2çKun^zëb&áóҔΓ0çIq /åãÈžƒ0®ÇFC&Ú~N‘ŽOéh Zç¢ômž¥¼“õ èDPCÇuüiÀëtxó D8¤q~jpØ K'1NŒ«±dkÉ.²ÔíÁOÑF9s²êhwL¤-+®6pT{VµÖ‹sö{: XochšüºH¥ƒUº&Äé°´Ý´-Àæ ß‹—.§’®m¥|æô‡©`±ò±>ôõ l:w]³×uŒwu„ t[b D"®ÉA:UK Xó™æåm¹h¥Êððhúö—¿ÎÂÂý3Ûdj°—ÚE$°$CT£÷tÓIªgÙö¥Xƒžqn¥¾î*å­~ØÜæ*Öm€ÙûV#ÍàHðüF/Žå†è$®cÊ}ϋԡåÙä´²ÂIL¦=êЧ˜¯AD LÏÓ§£ÅW¸:ÉÄ+ñLç¿vS‘NYæ¶ÌÚWq„×ÒöŽÛÙ ª#Ǫ(ƒ|µµdè;¯©ë³À€*­¶æ€ÍUVnåh˧ƒe~a- ^§]aI$èð|:×ÉÐ1–ã»¶eðI‡¦ÉLÚy¶D<£¹Ê°"Ö±½ 2r¿•LÒœÎvÝ^fæˆ7èlõŸUl™ÉçÉeÙµ$ ø¾²Á#ëÜ[EKEÛ/NOÏ“œÄ´wäùŽ+¼`öñqFùVXšQ‹c¢Žê‚åQÎÅ1f2Á" V^FåÎñtòúôÅï$IÞò½Uª3æ¡iuqíëXú°º»¿ë´ÞÀ!ÎAPÊsÆu<ì‚,!ãÄ!Ö`5¶Ïv:l§S<',¸§Œxé˜Ó¡ CPð)Ù+Î…vºˆWêÈVzLp¤À¡V*8H¶XZÞJ•µØËÊXžBžÝÙÙDP¤‰QtÔ­òä+ë4 <=5Ë\i?|ª?5uÖ@GðpÇ–ô+ù\‡µÎõwqÑyD×èèv0yB\³”ç‹Þ#ú¼© Z˜Sap½ãPNîj[¥ØKa¥ deÏÌô ŽþY.ô9°¼¯3Tœ¶³H$ý±?ëxåÔ¿ô ”ˆr¯¶á»œÀû ^ꬱU¨iÕ#C¼<š#ºíñûåKož74µ"›ø2ϲÞ÷ps=?ýÉ«¡Ë˜hk@Â`»p“dZR å(I¶^48f";<Ý`ýÒâ46ÅAøÁd‚ål#«!¬¤o#˜T?€2ÁCmƘÞÁ?¿¤~ÜØ\ÛŠ¤øÕZz‚ç%T]騳cÉ.NGPýÜ&Á$ë^³CŦ|¯8@·x·|]\ê"@V MÈcäÏ:I=›Ó„+}za à9옑Ó #þÐÊLuà˜z»|@z úG^UTR1ÜØ ›Å g£´ªoPy)­)מxê‰ôõ¯#uC“wÜùѸrt„cYZƒ?«wèwøÞ#ÈH¨¿;uvw£ÇÀ«IP²E}´Í†Æ”Û!¥=áa²Œ~BŠgñ=n Ç´½­ä50ªÍ$ÿҹ뽑Ìèç|Ç$bó»T1í9ÛÆN”& Jsêr:Œ ÀxĆtÔÜÒœN#¨ûô¾þ66©õUsë¬U|ünXþæ3§Ý™á{~êÁrÉ?å›Ú6òi;“ø»0+/µ˜"#TyW>{§CÛvô0 æO—¦Õ)àMè!ò¿yøVÁJõ“1­¶ÓfR–µqf«Ý0>ÿÙ?N3SÀ…j?ºýé—PSu=u˜r`ì.kÐÛÕA‚NwðvÛóÊw7ðuÈ£Çzê€W‡Í|¡™.£^úxçû®Ã}aÓ.ølÒ`9ÏZ¿/)‡Ù+·bØáÇäEñ©ÝÚ áW)fЗlðÐ9L°µXCþìç³³S©»g€Ä¤nèçJš¡päþà­Óà†8nœN~‹QÔÝôEx¥>¦+( \bÐJŸ‘E/Ó þ1R€ƒGÉ`£·L\»ö«­y-4QV©_¸—Ò‹sÿìü“ôø#ßåXÓcÔôˆD/^yé…XcwOG)žÚúÑ¡ãéKéì>‰tëðqQÿ3ÃLã+Ç<&ªTQ¶E‚ zý6] –—˜#Ôd²8oúšóQ‡ª€ù:Õ¡s/©ÑÄác’ˆÉ}Êl“"ººÚÓ¿ûßÿ·ôå-$´â3œ™™&Æñ6${Ù1vš1ìà56:œ^yæ¹ÔyôHúØÇ?z™Ç`P0x¡þ[MKš,Ç—! =ú@ùë~øc€|yjò²gvÍ'ˆèä?¥2ȸÝ|X—t­þbð.0Ìøh¦KÕ’dkðÙÀ—ßõ8ÖføÚ™[ΤïOßýæCágp}n¾<,ì!õ/~Lx>ÿƹ ý€³3×Ó‘£Ç‚Çyì—øçUIPúÔ©“éÚ¤U¨tD‚gJ»Âv‡ù;ߘ>دnò›‰»Yà‘?_„‡ú…þØÝØ¿L×,&PW‰òò¥‹éßÿ_ÿ.düíø (†‡†b®îƒô(´7GpÍã¨<O¿Ì:mäí,µ›GŒƒµªS‰Û~ž9JÏÒG&ba¿à?EØkîá*‰Ð%Ä…Ôa«ê8F‰ßüß ×.<@}M¼(‚׺ßBÁ zÏ—å#âêÐàUZQ—“ u(ø‘4òœñ¨;pÙ2¾‘£B<>Øõ Œ™yצœßèJeòÕ û¸GRi6‘AFm(׿o+ J<èœïü7 § )ÏVÏæ &îgOxfà´#ÍÈwÙ¦Xsðgì@f—®hÙnõ:8©þoG9mCeV--Ö•9üÃ\ÀkÇf¯•/ʶ­äÅgé×®ÈúÅ"Y^ºKëú•e‚ÈÜWC‡ò{nq5ÎÏ®Aç7¹n}‹bõgtƒØc‚±Wò°eÕ«s»Ï©˜.g~#|Tþø<ã„ÀÓ¹—^§Sî\è êÚ/¶“7ÃÄ»m¨4èíµß)[£š@·ë(¯möleíJ«óÕ…º;O`k§Ú\÷=Ã7±Ó/_7RKMAª¯¢óª­îøLÖ8û[Yip»Ä¬ ``Rñæ† ¹yi|º]Lµ½]¤ˆ`Û¬‚çÂbz™d7’èçÇ3køI²o©!¹¢šäCøyYUÝ »È‘¥DÁ9Ý1êÑ9VØ«Iªûé*Q¡íˆžE ÜÖâK$·B®!;Á›I 2:ì› ¼€ŸOÐ{J#ì|ŽßØÙnVÚÐ8û-Mılï&¶ôcæï¥›ZRc>©k‘¶ÁÑ×ÚÑú·9G?[娅‰ößµ7Ò½d+ò­ÐÏáƒ(¥c@)I+¼›6׉*[ ›èqÍtÆ*çè5 C¤ûÌ \0‘©G¾Án†Ý«¬ö÷"³^“.ùZ¢óè m--Øm4å·LÆÎqêAßìW¦0ÑœéJ›PÄ¥^f&˜?Û¤\¼x!õôôSx±‰ï|ÐÁïÿè K¤ û}\V8ï3Ñ;Ã?Ž1À0=÷Æ5˜Ò:Uãœiùóx ÍhLÜqû8L¦"‹Û¶¯®e çTФïcH` Ÿïã¥m?š9:Agµ Da-^ûy6' ”™¦™¸×ÆF"ÓQg£Ä½W;=5¾Æd · DG¢Aa Na ‚á³¼êj›Â¸ñ³4‚JþòIÍ4d¢‹lΟ‹ª âý¹»¿§ƒ-„9´+ÂXg|çŸ]fé“Ôê ØY)â3¼¼Ç}ðÕÖ£:&ttöödÁ~•7¯xf g­ž€‡JfƒI ™#¡ÀXx¾65µÐît£¶2u“_‹1µÿ\(ï1€·£šØ¶½ýo¯/ž|ðÏ?vìã±ôl¢ÉK/¿‰â²Ç èÌס›%z-"iý˜¼¢<ÖQ´?Æ{ÁR¼“dxù™K‡† ‰R t`PØó·ww©.…^âÌA yé]g`ÑF(ÙæÇ:V–ú»AÇw.ž›'mG–9ò«c-ª+€«òl›³Ë¬v°­¥ItÊk³òÍÔ-Ça¢£A¹¸C%ÄÖ†2‘ñpžk4Yde’F‹Uâ…¯p©Ð\›gUª_U!Ó ÄoÐ’]ÙŠKjû¹šÚî[ghT¨²rÏJ[%³½£³þà XA±6V: ­"µ}¡:‹UÃfú¤¬Åyhp— `òî¥ëœ¥n«u&ƒ lBÓàb hà̪W÷Z  'äq¿]Іø*ûÄÜÄU+fÂÑB€³œ{@.*s0Ò˜³ülzvœä>?Þ£Å{²ÝuÉñ¤Äý†n…ÏQ«CÏ[ ~×7¢‹0O+*Ä)ÃÑêpqþû›UÖí¤é¹µtzïPŒ·Âw—h'Ï^£¿ŠEê8w=»;ªKxV.ÇÚ”%LÒVlêw»kÌ‚eºv0[[´:ãX¹`Ë=ñÍyXÝ >fÏ]wªxÏsÊ–©@©ŠïŽÑн³£Ú&¥::³ÌzqÔjòUœà†ÏYäÌs`&­øªCÞ§]…Ý2Žœ²³ä¡1Öô MHi¬Šµšˆep?«Ä÷;Ê:@¤;éf§P[V‡;Î[ðÖ½Uþ­°u`³íu”‘~|O^±G²„t \“øõ¶3ðB'²ô.íØ~Îs Þ,â•h§~d'†å|*&Ô p¶˜$áþKû&ßÅQ0à©É)òcyËv<\ÝËýq~eЛOµý¥Ñ8Òƒñ–¨à&±^|P.É­èÔ±o k•ïJ‡VÚ­ñÝÓ7ß’Þxí5*ÛgHtÊ/mÁÁ«Wcï…_-pu¼ZÈ?y’'Û:ö<[ ôô Þ9Âs…ROww*£:¹¯¿?ø¯g‡ ”wÇúÄ-õàú%‡À [œý¶mgq)|Îîò$å¡—¸¤ªˆ=Îδ¥r Y&Q½^aòŸÁóš›h ÎwíðÉ[àµtæOTZ‚OÑE_€-ã×pô›HU.n•—@žàí>];CTR§1Av7Wvþ<ølpªºö ÈS§ò ¸µ¤|äÎÓ£tN——å£W„‡Á‹/ø¼þÿòRýªcƒ+ž¡®®¦ŸÑ€©t¯œÑW§Ì;E]Š‘B·@6¨GB—ž]nB¢ò h“u«ÇYéäy›8ç=Ï¿–×Ê‹·Ñ- 2XWGküRxŒÏÜg´5QÁúúÿ?öÞHÏë<Ô;Ûûbw±»À6`±ˆ^H°J”(REZ’}U­r¯otc3“I2“ÜIfn2¹IfœIÏÄŠ#ÇjW¢lKb§(Rb ‚ˆºÀÛ°½÷–çy?ü"l™2íȦ,á#»ÿÿÿùÎyÏÛÛÙžÚZÛÒÂ]wÁ'ðýù”sޤ)™<—p 9À÷BÇb¬|þž Èoð®;ÓÛ\Ë:ºõ¸O&ûy•üdžã¹¥VUÙ…ËIµ¶¥e'â;Î<‚Ý $D™H^E€u =A»PèÞ={“¼"ƒ„?V"[=¯2@k`S˜¤’‡Õá¸0™rY”,S÷F'2ù°v}}Èÿaø¼AæLµwÜ~[$vH»ýýÝøè:9Zá©àåòhåL/¶ðEŠL ”4À³?üÑïßý˜°"¼ÝcqLÔ2I‡\™("q¹ˆãÊI+F½¬ÞVïVî­ò¾I ^Î#*Óùþ*A— ì$ý[ν‚3|Ë„g>tßýØDAßgßàÞ“GCžü¦›B^ÞAwÒÙFªŸ?û/_^={UÏúHãÙ•èg÷HµüYÂ(º8 @â· =žI]À},!`/ÆqMÈN[êœ)EWV‡Ÿe®úçÅé°ÉÀù•´IÈÂ¥¥§»9‹.„“Ÿ?ë¬")M™ØÔÔ”:8Îñkÿ÷¿KôGÿŽ$Åh1íÞwSœG}öÕWR1÷Ns„“^̦ µéÆC·¥ö-[ å²ð[ƒ§ú ¥GñÙÕíÛoˆ½É:qèSÌtrõ‹rtœel#ìúc—XŸ¶Œú?‘ÌÌ=uØNUðãvLS”_™$ÓÝו*¯ W@߯¿v˜c$Þ|÷ìÙ³ÓЖF9~J*O’Ç(÷Å›øÜ€m ¾jÏZÁ6˜ð¼»ûÒ¹Ô¾u[èto[Wà¶Ô]ÕËLè´íx!ò§@±6ßêO•L†„Ü*ƒOÌP«¾'_€‰Ä}âµ÷øÚÄ‹ìܪª,iÆ$ñðYûÏiÙ²;øÚA,¦Ï²29Kм¶û…G–±=Œ—u7ò ÊÖàC¼jÅ~®‚k&F‡Í‚îið"º· ž‹ÑûÍ€¥t¨¬Žî-£Õ ÕÕËó©žU×ÅcÎûQA± ösÔF×î„1BØÍ´Ã–'éÜØcAö™Ä³*]Ûó“D‹og§“Gð*Ã:xÅ °´º_; XÂ×”v_ª­ÀŸ^c¼Jl­RÍ^òÜ"t}ò™bhm¶Áž­-éDßDª‘ï¸.¬¤­[É1 Â9ð˜„ÀBcý…$£Ñ¦&‘¾sÌ#÷• {†G§ TÐYç|ºíýŸH;÷ßÈJҥγð¾£lÎDÚL¢vI±G¡§7`œ"!b}×^Ì_Ŷ%Àî‘%CãT`O KHp²ÅT™k³ë3\Å.So‡LP@- >~e [K=•­MÿRÎ}Øh îÏL¦UÎA¯Až½ç=tž#H?7>„L¤ ¼jßëÇ$mÛs#k8›zºH2ÓÏVñ+´DP—öõÓS½Ð/ú†v p ÛŸ¹È×¼Lº±»W¾<¹ÞZ|™ä8ºIh‡£Ýè¼Ç\=‹½÷Êx$PO³w#ù7±{ì…¶JÉÉàÀ4Iê7óøaJ Ëå%’7˜Ã2>¦i’'Á“pÝG}.«a$Ø‹+º¾ kJcê‡òÐ1 Ê,𮦅ŇCÛ!“xŽ òu¼UöHY”³ŸÄRîxžïeI=2":„©¿ÙÍÈDi \Ô'œÉ¼5áÅ”¥î±p”îíPÈۼΙ¿ äMå‰<$|F%øŸÌ—÷\zÝœ£rË„²B˜å(¶Yø¾x–t=‡OÚ¤ÇB‚úòvyœ¾m¸Jø¨GÛ !ëêêCΪ×è‹óÈÒà7Üïc u‚«´š¥-‘[×@Ö%ûM›vÑeodƉ6GåöÍÛÒ–­;c“t¢è@rÃrL9÷Ûµ{e¯Ý‘_Üå˜:ytz\û<¶¯5úÍœ;îtTž8xsºÔÕ-ÜU®KËa>deY>2º˜®`èjÄhEHààú‹¿øÂrG´®Ö° c_¿~ý !»ó2«t´èd¯Ä·kqCF#cWøÈüžzêÉT÷YÎCçÞkéD\•ÞÖãà:~üh:~ì¸×¸L›ç©$Úòïä‰ã÷BzâÏ¢¶¤ýÆØ>;Æá> Žìï‚ô ϤQMí™×ÎÍj¯ËÝ—©îþP:ýÆëQÉý±}:°Ê‹Ž±žîsé{ßýó8“M§˜g¡8x?‡˜YˆÒœÃµœ>ýFddÚ¶ôÅŸ£ú.*·„ €Šœ»ÞÞžP$U”mW}’¶a7ßrGÊÎ/”ážnÚÆ¿Bfk9ôØÌËJn ñLÖjVÒéS'‘ß80H~ð/á]¥8œ¶Æ>xŸJŽÕs&Îlß¾-˜ñÉÇ¢½¢o÷EA§òtäÈ+‘±½ Ç”¬’è\œS<“7UÂ<;W%Ë6QÇë ðhjj§©"Ó1å9¶Þ¹k7Â^ƒe.päZøsëÛº®s™·¦R7åè&Sæ ‰nðE\1óÛêK„Gž¡£ÓÁJ ¹Á“ÀT$¬b×r4ñ3@¡¨8[ÅðæV§:€<ƒLZÒyîX×Kþä÷T= F@G€cffFœmVâÎÐîÆ€çèg‹]¾HªÐ—™óAË®¿˜³f¨š,A)õh•„!Ò“ô£Â£©ÒTTŒ“ƒÀ©Ùù•8f†Pd®SÌßuß©ûC)¯Ý2^þôò}e¨ŽÑèè.‡‹#5½„ÏÇrsO?É…{÷à5g"â10Y;SQe³€ad¼íŒÅÛȈe [”Îj<¢ k¼üVv(×ÌÖè $͉ƒžl•´¯u\ˆóZủϾg@YgFy(ëÈÔL&éá<Œ$ž™»Tô ¢kŒû oÙºþÐË0“‰#kÔ±1øÂqÏÐ9nØ×0‹Ã‘V¯’ÝÞ}igâüeý‘¹ì §¼Ä¥’ŠÄ#r ‰—Á‹{?t_œ=ü•?ýÖ5üƒïx¶4 Ô§°ÄÝß3à¦kßââØÈpn{â·‰êReeÙçSâm?k°y”ʪÖ%<–<‡Œ½Õ\ŽŒíÆô븗÷ªCªl rî·ç°ÎÏN` 7ƒîôÞã˜Eü#®VaˆO@®[“¢o$é;w4‘-?A`o£˜@ kÖ~?$%“t˜ÏbœÛb¶Ý…÷$p°Ç¼:*íF óZî¯áh€Fö l…•—»y‘]Ê$ç:ît^Õ0Oãlox¹ûiU[$/ñÞy9–UŒVðÌàé… ›œ t”à<¹UŒ÷Ô–|ý}8¨ô°¶ë©¡¢I<]ÀQà|£‚0E§‚)ÖfÒ-Ô¢í°¸!ïp}Kà’ëQ&‰p#´ 7 @LÔcŠ_ðlmy†Õœa{ë±e¸mÔ•=á\`]‡“޹žä›âb[ˆ€W“fO<¦¢’“u‡, Á 2IÌmgƒmN}þÄÄç®O±fº¤áXš£*Éä«îת8¯ž Â86ÔÛRn‰óÁ\ÑêàªËl¾Ô96m¡+:ªc-À„bÑ ˜G1¯µ[¤kŸmÐߪƒ2¶c>Ú&¥Éó<ïÓÀÐöEçú?ÿè`Ëb¯Å/uoƒ0&UÑŽ5sp[ýKÀ°ÀHÁ •âùÐôvè~\넟–D‚†ò×sL§*#äò“ÚsçTªKx¼„úôö·Ý²>{ìZ:n”M"ÿ (ŸäMò¿ÅèÆ3D Rš–N¯p´€ü\¹ ~ ÒÙ)ïÖ©]Ízê¸W[XùAžà™C;Ú„*;U……àì0Uº®[Üæ–æ¨Öç‘UÅá{5Õ) X·—7tæº^X0åæe·õé14U®Y‚Ð*òt™{ üÎÍ_‰`’\»RŽG/ Èëć7À7”òí@\‡z•²VýCþou¡•¬¼ ?ƒ°q ÂÌY{{;e|1Þ\3]'ókdº–04É@ÀZš¶*sT#ƒx¦Neõ=¿/¼Wþ-J‰ ‘¤Rì\ƒò.õD'ƒêuúI¢Ä>µCÎ:ä§²ÁNÎQ!¾ˆÒz$+ðž0òùÂX9Ö×Kµ&¸dâ‹•´êW8’ÇyÙÁÊÿéÂé ;õv“í¢¥4¶FŽï«÷gg`£û]ÅœîÁÚùô¤NÎû]§„S‰gµdnmnŠÄívç+O·s~0ueõ[m$iÛg¸¿CKcB)àV-^ÄwLJ0Ad»_:r ai‘Ž2_™çik ¶Ó|BïÚõÉ™"Ì â;ÑìX§™´\„Þ î.™Ìg¨Ù®ÛDí7úêå•d7Ðã¶;îH7ÝrÈåÆ8á§ã…zÉžûøŽÕèè´¬[š± A|LýGšàóŠî¬Ï„­!í©Ù«ÝCd&':ªûªN¢ŽeñÀ íÓ¥ö±¤ŠÄ >“–ká“ê @3ø½:„5AØÛèdËÿþ+ƒtÝÀ¢?¤4½ïž÷Oéï½ >šè³Ž$¬ºτ̨4Å¿ûÄÅx|®]*jnÙþ iäŽùòÏßzy·{3Éq˜>Ïo.ª¬Ýˆï…!ªiËk7`c`s³¸!L œ{޲vÕ 4oËuiMš$Œ‰/V|å…òŽ—oK‡}å9ÉÚ.pa`e‚¶ÌRf‡Ég.çº_øZ§ Ø-ÏÑ).ô5÷~žEПýÿ àAòWe•ÏŸ6óŠßçß×–(ç¨(1ð3u{ƒ†ÚaƒÒXÜ.dd„Þ­Í&ÞÈãE áUD’Aà¸A[*— ,»Ê5ù›Ï0 ·Š< ýÚY…'!9ãyâ¦#Éû´k–ñ°¶ ðÉ೸$› žZA Øñå+v*Ê[ÐÖ ¹CX3¿2ƳsQ ¶…›æøÊa»\¸Fe¾8Zä`ð³Œn®úògR!îò¨ª¯©D®Quî^ŽM¡cg6Jy |žh}3ç|Ë Lª-CßѦ¯$ø=O«.£ ‰Ï¼òHt IAAábÚX]pf|YiV€h§€Â" n-õ$°'’.ÚÕî*Ø:Óíò _ñÀ­ïI£]ðìB`PQ[Ÿ–ñ5ºï‹Tß—¡°+×õëh{n{1Ÿ[lá~GG3`,^*‡.Õ⵩š„µ*ÇOwö§è‚ÞKSµæé˜çúVÁýX ÿØÍ¡½¥…$¡ÒÔ?@ÂD>ÝöÐ ñwT 3ºIl³åÈWî7YE9¤­º 2Ô=VÍ_ò¾CŒ»´¤½ Ý #„\½´©VØ{ûÅ+2ɃžÝ›¢BŽSÐÀÚmL<3¸,LÅ—eyHEœ¨‘½Nü1±dýϳɵÅ®—¡yõŸ%ütácW~")ç¡û" ³LDÅÛÅ>¸^t!ì?}M‘Ü|—®@à«]#õʯ=F]Ú˜O)HR”–•‘ã$$™ä', méÜÓÝQÓNcÓVü,ôw‡9)ØVWBÑo#(v…Íêå¾víCÀPÚÏD*˜SØÿ£ý£ H]}¢ŒHDW ÀöòOžaîÕq–˜mgçYÖã¹:C´¦©Oíí›ÃÐwì½_YÉ»ÿÁôÀw¾ÆÙ´—Ò§>ù¹È FËýׯ_/ÄŽóÆ-èPIâüŒ$™JvN¡Õ`ÒyVýXÕ|… ¹K—ºÒÍ·†ƒJȉsa`ðÝ&‚Ä:'¿÷à·¢’I* 53….`8¯§R¡±~7Ù­ Žÿe¨%²ÃU”Åù˜ôêFäå;wíyùLétEf׎ÝiÏÞ}´¨hJßøÚŸÐ2ð`8ìubÙ:ë“ÇqÜ&ø¼ ü}éÜÙÓé‰'æÌôMü±ÆM›ÚCáVqéïïK=ö`ÚA®¥µ•@ÿ´i,}±í? ÇŸps¬§Ÿþ-‚Χ»îú@ÚœƒçšoÙ²5p®Óï«@«Œr®Ð… 碗û&ü­2éÃYfÕ¼N+˜ã§O¤çŸ6ýÖo}<”ì0î®Îíòå®´sçnø ­â˜‡üBüÈáFæú?¿vÖÅ'ëPOÌ@Ô4ÓôСCQ5 s1‹t„LH«<ŸÖó®FF8ç ZÖðT鯧µšÆ©øþ3W< EújÀPþ]:¹5200àQœo~ö3£üÜ7rüKǵg¢{1,ë1@ƒ"ƒæ³isGÌ7ðZ=QùK­2ÖKú¿ùÖ;9îŠm ýÍ¡ŒµmnG‰­Jo?̸(øð^L&/E@ݬc”fy– Zðèõý ûý=Û_¦ñĵk/÷(tEäÇg»o ±j({J‡”~iéÂ=V'¸ÒËkƒ±³TW&qCžzæÌ)ø³•?•‘”uæÌiœ'ëÒ 'y$[ˆßV[i ÷wG›Ü›n:”þÏÿãàªô÷ÈCßËR )g¡"ŸöYàÚs{oÌ;”î¬ZJ:Ê9*Ôuîz_à?kRþ[AáeðÍ€±Ÿ;vä¡å™FyÀƒû"ð}+£UîK8 €G|—ïd¯MÈÎ_2§€³MB‰àuE´Ï«ÆF°Hê°ÑTNPKGa¦· +îO8±V¤SªJø*¿l}jf±ç\[ã^‡S'¶]UV4ñš)‡!«É9æQ½µ(l1ˆ„W–U\EŽªøÝp(b¼è81øjÕº]¯j Zjp|„„’ ðNVOeú‘mL­(¸Ü3DðŠ€#ãØ~Ë«¹¥-wf¹d¿B ¼E2¾å‹îÀY§Œ¦ÚÛùéDq¿¾ð;¿‹.²Ÿ{ BºÐy>=òð÷ãÌåöÕ`Ù7ßš:¶v¤£‡ã( ¹‚Žèl¥šAƒQ£,ŒC–ÊÒÀa·áFO»°£Z[›ùÙ8|êÔÉô=ÚÅnn!AJx¡³Ò½*Áñ£Ã}‰@²!oáäùÛê#9çUýãdó‡#¾lÐDÃïÓŸý\´#û.mv—4"qnq¯|Z=Îß9ç±UyÂÁê·¿ûNް¹)ôÊ_xýóòUÞí¤ Œ]þjßÒ°»L‡$÷ÓöüêTÎϹ.À«¥ ·\ šÀ_6(NPH½Ó÷ öâ\uÍ&†.§±%^ƒ›­Hg“qþÏÈàüíð0žðD [‚óè]¶ê—ûèÔ†vrÙH»lƒ :tÇ x˜PªóA6]…ø#ûŽüÈ„ß7k^cy5œ²›ÛÛC¯{ö¹ƒs[â~éPx„{uŒ{Y¡&mêXÄé›9e‘“ÀÄvóŽ9ê|€—KÀ½c×ÁtÇ»î@—>H-èéÂg–õ à^ÑNž¹ )Ã1]ÆwW ¿ÎÓR{{+r¾4õÐ"Ÿu›ŒÝØOéXú°sS¨@>a@X‡ƒ“vt t“>ÜC“ t.«ÓË‹£úž1Å3¢3Q¹-®Ü$Ó{Àî+)£â“#ä;ÂB'ª{H• cíó¾]Ä• @<À![I‡š~‹[òù¡ó0°®hž52[Q²v¿¨< 8à 2Yy•Çýòâbæ<ß_ÝCñ3‚LÌOÇŒçØgAzœ¬IÝÌ@¸'LÜc“~ûz–ÓëÇ8އ@üÜ.ÒRׅΰñ´”õø6’Ùyþ|:qìX¼–Vu\Þ³}¿ ¦pFó¶íé¿ø×ÿI[“æo5Ñ%Ëqäë:†u_¿þé@@#Þ‹3¶÷ÏtzpždYõy±:~G š´mÐÈ5õ`ùÀfºÔI»*¤·99ú"”Á⪕ëÒ€2ÔŽ2Ò¢´ì8¶oöŠÊº8U¯³ \\Š€lXç¡|¢Ÿ†¼À UuqÏp¤(E†¡#Ú„–AcÜ'Ÿð3TÔk Êãd䀤ùuðÝ–HB"†srœŠž9Ž,°AX°³”Iw¦ÁX8àßê$òÓrx›Qé{–v§aK°FÏ"ÕimCYb7ýòá: ß±ÂO¿M5²ÎæÊ(+Nk [Aiׯ+TÉ·30ÖæþË‹€^Œ-ì›ZšÓTÕOèÊ7»µP¹›ùwhÕ.xl%•”ÂBþ娯õêz’¿«X¯{âœúVÖ¡G }—u¹>áïzƒXƒþ9rü™$1B1 Ïàª4®nd×uQ“mfàžsí3„| ësÿMR6«k˜Œk È0¼8iÂß"C硞¥¼Ÿ€'§¬]ø~ÞFøÒ:`Uó0±ÄGÜ´ÿƨ,öé'Áž¨å|>kb<'ŽÐÕî^ùîÔ“ØšàÚsúK *³õòv\î³÷¼õ%•b{°.÷G=žLvnç±å˜ƒ¸Éªx¦|^}Í$èÉÀ{×9ôŽL’à-&0 {ÇP¦™Ì"’—O£WÕÕRᎠQËQPÒ¿É~W¼.ô ve05è¦/zeI¥ì-úšö·ç“/èiÛoÛ¨¯æ$†_S®}–Ã?u)çá¾DæÏÏŃoi‹¹êIåS%%¶·†q¼qÞ*:>_ZŠVàÚÌY ­ùУ ³vñŒê‚9äÕçÆÐFá3$Ø¥ÎN'-R7]Ÿ¹¯„Ä‚ÍõÈ&÷x¤ç sä'øô:’4œ« ¢òU»3ˆW°j‚»™ìb¢ðHïYáy<ƒqáld:ÛA 4mDn(\»ITÑ:~³¾®&`m,ÁÎä¡B“øT™fƒëLð-áh±9:F G×ò)záÆÚ†bfÝ_;K¦fØ[Z·6×ÐÌ<4µº ÌÌ‚êIÐÃ&6aµŠ`úøäRêå5s›K8RŽ3Ó'Ig/§(ІUþȮԓ\.žäçiìby@ÿ8¾É |#œ‘¾@ÁzÒ<Çô™¬ ¯‚‡’´E7¾»vÆ£ãØ[p|žý©£ã¬I0 ™&¯œd›©Ô{@@IDATXf?Z7íNs#©ëäypªž sð9åñ0 0øîc^:Š”ò³cKÖ¯ö¿¤|]´¼ïA^/«|×$ÚÐõ áÊjðUþ'÷”Sm__gW ޾¤ˆq¼ìd¿$œqÌOFh ž¥½ª¯DÛ¨¶¶ŠïÖ‚c"³Ô ®j_™<¬<Ž&t2}h(+Šv Òƒú“²Ì¯ë×™Ÿ6yI9‰]$9ƒýž<`‘9Çã|6L‚À‰,ŠÏñ<`¯. ß’žM—(וUòaG\¯2Iù#ß÷ÙŽÏaÎ&by´– [ÊX…ÿCƒèËRfY( ?"ÑÐtêÃÅøyFñ-˜D²í(cõ#Xˆí<ÔÓ|ˆïëã3y)ŠX奋çð=nAnW¥®Î3‘¼e¢DaooOØÈxXIÚ²uGjÛ²•6x#é<ŽíŽm»ÒÆ–6†ÈÎ ·“ ‚Hà½ÓW1TmµbåékG^àTëmÜ•žmbåKˆµZÙj`NåB dæÁÐ0D¡ ¢uóÍ·…Àøñs?H@¼ïÞ„“EÅÝKyýúÕ‡@fˆhx¨Ó’¬—p4z0./v]H‡pÌK^t.S²}s{úü羘^}å¥p¾m$ã334AE¸‚g–âùÀî_KÒw¿÷ç1öîÝûB µ²aSë¦Ä;ö§û é±G¿—žùÑÓýþ­P #¬l?®2ÛÆý¶G ]­ç¥‰ë:¼}í9NMÍ-¯DnN«Ÿ]K?þpúÄ'?мÕÝOþð‰ô›‡o&oÂJ‚û.œM2·O|êsa°˜ŒòЃ‘ÞÿßÊÆþŸý›ôÊá—Òk´ÚÂ'<žþGé,­›?úѧçž{:œOÛ¨®;yò$ßÿËô±áQ^yùE`Ä[yÖ¹ô™Ï)Î௧Ï~ö‹œÿH«hæÿøc¥»Þ{OÚÒ±=Î#ÜÔ¶%#hÿÚkGÒ-·Ü< Óŋ鵣¯¤{¡WÏ37YÆÖë<ôÝôéÏ|!áYžyæ)˜n>°ÿP:tóíx:×0½ÿ7>Y žGô£§žáòî;zîÛÃñë÷-ð•s=Ï=÷,•&Àž¬P”»I”+×m³è¼8×y†Xüës…³ ~!nÛÎï~òÒ‹7­Ð©ÖÖæVê—Ã9eK·õj ð/““´úÅy%¾[eV¥:N†½‚ÞãK<A>’ãQoBVÃ$s–½ù^fH©ü[e–ãW×~þóþÖ± ÜW6*øÃÁÌ×âµëô GóR Ù±kšlÞ„A8Žý¨Jäû&¹ii 6µl†·§öŽm(i%ð€ó$¼C¹Ý€“‘3sU®x¾Fˆºµòàªê†Ÿ7í_Àg×åü[1‡wþþ›/lÕ.~ãÈ® Ü=SgœŠzTs`h›Õo‹pÏ¡ÓQaûÌ4 ð]š0Hiûg³ÚíÈà{gÏ‹¶aŠß%²_='ɇjùy[Fš°%k¶k¨ÛÞ±}Kxx%䀎u;e¼NLà§™},ÏŠ+¯Pô‘¡êŽŽ%Nk|ùZƒ^a! &áA‰û%²VZéHòROÐi¦¡¢ãKC¶·ûrš81–ÖÁÖXË*#o«d|û\õ ì.Œ+^//CŒã{Ì>àr¾S¸B¦wÐA“pŽ’­½@{u«Æ âG§²k—–§ & ga1¯ôh‰³:= }³Ž XoÃé¡L3ð$ý2Hg9®cÞb¶I0²Öôõ8äVpñƒS¤ \Ôéo{àñQ*ÿ¥5èl ÃÜìpõi_8Ä«´‚'õ°sçNtð¦ôê«t9Bîª×𨩠™¦¼?çTP–‰CV ¹?{÷ï'™àPЋ/<Ëš q¼”=ˆC8¤ï¼ë®ô/þåï…Mi‚Ž{)>èàNÐ¶Þ ç«3IÇÜíõ¥Ï)Ø¡{©p|˜¥í¿<Áu ã´ï£Í½ŽçpÛHQt²ø¿ÖǼ°@7€–”ïÒC$^¤(¥jCä"­þL02,? {VÚÄÉf×ù dpð3ÇÍx)Ï`Þ«¬ÓKžGæÏÖÄü­\tŠ K¶Ç1ô ^ÂT^â|—yO'§+Zéžk2­”¥ÛÿÉ+ ÂYåjgݵ!{ø†ä):¡åVDœ³½' 2[Çü/O÷>ÿV®øw¬…çûÛƒ¯èø$`Žã5цTÿ s´ú Ü?ÖÀVÄþmòJÜ“ÅÀw¿&¥M2Á’<ñxúoþÛÿ›¤"½òÒObü9Ö¬Þóä£ÒÅë†tÏ?pÛ»ÿ †·¥#‡_G޼Nü6 )-D­à Èœsgi^9[Ã÷rgŸ\ÿ÷ƒx5ÀQ3ÇŽF¯™Š V_w7zŒçdgGlÍjªŠî›ÛÒYø€Þ €˜,ØÙy!x´UèxÅ#mÐ5䦎úJô#y¤h¼Ìku 5*šLÚÑ™o0¢©™*1|¶ö”¾£"C ž¤µäòZêr/çf˜`Ó@§øç“ÊÐPÅHÜýJo‘TÃ8uTÚ—SI¥Ã_ùhò‰Á¬ ~ëð­Ö÷Æóåê\Ò½:¢|Hªì®nmÁ±™Þòì^Â3Ô½Ô#™‡µ[­lÀ$Ïs2iJž`²|üMU¯÷@U¾DE½2†µØCØYkB¦rKY¢n§ì+.ÍŽ3±Ú]ºRê –ÿ×q¶íò³8†ŠyÉ öÚÂ=ª[<=’›Ð¥O“„¥Õù¶RwŸÔ§ÜØÖO•Íš„^®p5Y},ÝB½Î3«K ʆ£žÈ¼x8ûB@çëR/•g kÊ‹êàý è'‘?««8îÈ?Ö¯ÉPàrŽyek6H‰>ÍZÔ´[åu~f‚„í»R’buúOÅÙõèíèÖàì8dT]xÐoæÎ9_t‘ú0`c`Ðq ÙéÄï,’^ÄZ*ÕåãœvöÍ}–G«—y©ßèhwýVÈd3ÁnˆÒÍøDYðS®ƒgÀÔ ’$öÄ`FTÜñܦ–†x¶Pr6„ß3€çþ©—”è*¶?aö6xTލ*E®­æ¬ÅðË$Pu÷Vy`šõëkÓ" µ´MQ®(çjêèô‚Œ/*E€†x†ö”:±ûç¾Ð3¡Ô »Á†Õ{Æ (˜4fà³! S/´š×¹”PykB¢‘v Hy<“ß%t‰Óßès˜Zм-äK9‡Ù ¡¶Có>¸ÉF«?œ¸™©ò=uUt‘J¸Çk°ØÛÕÉÄŸ6 Üdë®&£4î­ðÛ ¿«ÄGÂúllÞˆ,&Áa˜ø†­¿/™ÔÉ÷ä7òuPi#tpA|1ANÚÖ·àg®Ñ#6<âH8C³ê4ƒCàŸÉ¶ ÎZb[ð³ÊlÍì9îaK[K쉸¬MhÐ*Ž#`º’÷ÉÛLÎOJ§Ú’åáÒÚ®={°G¶¦£G^ éÝw¾7]¢²W|ÞØÔJÂÎL$cÄå@3lj`nWPy üÛŽ~ê/ýÈqµ>ÈýáÏ¿¼=³úÓÞRæÍÆ̤#± ·åç&Q¨ËJÂV8ºŸÓ3&!(S·ÎxzÐ4:·ÉZUìðž ‘uœ÷a&T´uì@¿Eæ€;êi‘ÅÑìWUÉk+Ê6»ñ`žëkðƒ7ç€åk׿*,²8Óõ¾”KÔÁvD·”6´fgAðDc 8ºI@ÃÊ3;o8+ÍEà|ü-œéž˜)äꌪ¡Krò½ˆ'úèÕËB+»ô#äçU¥Fö¯‹ÊzùœûnTyºI‘&:+«mÃïÑUv¿ÍÃW!í¢ kã£D­UPÄ"[S¦ WZš;6r\ĺôÊ‹‡Iø™ælvϧ[Å7êâC<Ën~ÔEGrqÈ `ÈTðs#ƒ M»(˜d`r;©-aËåißÂ÷¯ ¢>u ÇŒs U‡_r¶<üRßJ´ÈGFÔTcÃÒz}…}3bvf ß ¨çä,`€ÌǨˆžv‹óá…ýó!艡ɴžvì•ëÀ“ît÷ý»g1=ýÐé´u×FòV!{œ4G³‘¤^I tŒ½™*Jmì!s™åóTJ±>ö¹«gUê«Òæ6ÎFGFh³X%=þ‰Ï}=þØ#ÑRÇ“A2U•A/«»¿ð|)}ûÛ_O/½ôçÝ­Ãê :>}žôéÙeâÿ3Ï>`à¬W>÷˜‚œ ¹fqzïÍ8,gùü»ù@!¯S ¡’ýÁÞ¿ß8u’J©8zoK_á,¡>ùhºý]ïMÇŽM”wîÜÆ«cï¡’}=s}õUœB0JŽg~üÃôá,m…þ^?v8`tè¦[ÒW¿úî{9‚ý:ÌU¨>ðÁûa‚ÍËá¬Ý¶m'p½H`úÇ$³Üʳ‹uÝ}Ͻ¡lwï ‡Ùw¾óõØÏGø‘ï§û~㟑Ôp3L6Ë*©“0Z¢`©P«è OM™¼F% I*å8›J1H Ʋ ÞÛ_Í_Ÿíõ×äþí•Ã%ߟ¼2\`ï‘m!cøŽ<ÝŠÒ\k>«Æ8·®¿ïFÁtÈÛE‘Þ<04ˆá–9‰lK§Ñ™a6:FùÅ â³å ‚:&gÑçLÓÉÕ;ÑÅ\0˜P’3g Á1‚g£­ê{ƒÄ­ÐŸÕ§:° fëø±]¢2Yy,~Kƒñlæï:4†\›)‚0R”Ý8\ŸÎ]/•ó<²Ë¥ÇГùŽNdpªrŸÀpÊ¢¥û™pÕx Ǥd…P5´.½LR^ópÖY¬—Á€Q&K|ºí´Í^æ–X{‰ÎFØ£ÈFœ5ì2Õ³}k‘:t­Œ0QÔcžÃçÜ4˜t­¬z6Ï„¾u¼›ø` ÏóùlçÞ±ÝÎæAà LºÞ®¹ˆý°BH§°kÓé¤f'ÃI Ýç¾+,ÕË‹ ðÙ&e>`>EÛí§.sž í óЩaëNqB½Fظ>[|ë„Õñ žxYQl[Ì+}Ýi#ΗQÎ:äñ±N÷MÐg Yv´û+ +¨’}èÁïÀØLTQà  >̾ŠG&˜ “ѱ'§Ò±—Ls$ð¹.¯_¿ž ?ø |ÿâÅ Ì#ج`ì›Ñm@ܦk Ì}rÜ@D í¾ÇÒ%*îmm&—uláîó-ˆ 8¯ ȉk=— ÐÌsLÐkÀ&¥^<´¢áêüt–âdÒšàÌB·8b+îçŸ}8Ö'SÑ5E§´ë)ŸuzzIçΞ úòŒf@m÷…ÃV:TO¬*v¯ Š›ø Îöõ’02@«øÞ´±m«ÃÍã¬s3¬bŒžç™~:¨J P®£"÷Bg?°#韥Aá`HV{Њ§ AöÁjÏ1æžÑë*2”3QqN*‹ÜÓ+ýüÎÍ«úJ4¿+ßsâ‚.]êJ—ººèHt*‚êAïÌQzt¿ÝwÐÒÒ‚¹‰ÒgÒ¹3gÂÉÍ-:³óo3Z'À¸Ã¬3ãuBUºÖ™hkf{v-ðYÒAŠÁ1á^JE‡wkçÚþw‚N —ºzØB×@ëCZ4×VéDÇÑrÅ!.:Ģ„ÏXã˜ð#ôtp[5Â^ÀKL‚“'°g¯Î `c†´wÇoàz„—Á0÷ß}Š}敎iiԟ³ü­pËÐ~ßÀÈܤm«7|J>l+HSžÛº¨ƒbÐN¾ce¦lƒ:¤GwYœ<œuÈ«l*-˜ŒáœØž˜“íô«0–`{ì‹ß (L$ðŒÖr*ªLrŽ:´å/Q%ûs]&áèè1ð"L«©˜ª('ȽY|ñKy¯ªº–ïaKÝÈ}5q”Ç@ùzØ-çvpÈTu._5èÆV+gÃ)Î2 ¢ÈÓá Q-i’¡¯ ÖŠ·Î­ˆÊ·À‚dVU‰wuõv$áøÐe¿šç/ë” ®‹ãÊ?ú("I…±D=Ÿ_êð´Â2¦¹¹%lÞ®SÃÏ•V&Iÿ<‚_¸n“gM¼3ÀgPÙ`K9BÇ`nл<%«H¢j˜ûMr„Fè «2ËñMŸ,Á®2éÀ"ˆ8‚z÷Ú1ôD+° .g°rÙ,œµX…pÆÉì:ö‘±S'k°þ.bîêm®Ýj@«Ùm¥o’6ŽK™ZÅž Wý޹£äÝÂ#º91ŽÁ:?7¨ÝH‡ê%åTTƒ×Ó«Ô§òi9,6·¶ð¤5l ‰8 ½19Ä8 Û&ˆ#î32Wø°ŽÄ4õ|d³k˜ÄžàAÈ5`¼¾½¹œçƒOÉß7ÔF vý% ÙSuŸåsæÅGö<æNrj U¦q±'âN8ea[$Æò<£ø‘¼¡ì⧔難؅A^bB‚vÇ ÷ ;éÀÎ 7ËQà]$±/™þ7Ÿ`?9¤{ª>"‰‹võ§Î ƒH·¢f€‰ŸCžT~³‚|]žm³Ü£Œá¦œ«ª.O·ÞÒš:Ú7DвmcEj¬¥ÚœsÏ›è ¹Žàß<þ“ÚÆÊÔ=½š^=r’9à€Æäv''µ7<Ò@¾9G‘2kµMù$UåsÃ3©ƒêð† t àŒô¥•¡ÔL¢Ýæí{è"v{»¸‰j­'ˆÌÙ`’ç—Š£cœºÒMX+±}TeOÓ¦¿˜cžçÍS~‘ã©nüðN:RAÜ3šë oÚ×Uü†OŒñâñÑ´o?G~qv:ÕÏòÉ+ÃsØŠž«N5ñÁyŽX&€ŸŸ­Â‡&©>^ëçh€Ê¢´usqzõø9‚·ëÓ~â Ò5ÃãFú9§Z/,ìâÜñÑ”G°˜æ} Í †äð Ú§ûkÒŽ6™«‚İ‚Öf¨Œïèæl÷…4NP¾hÉvøŸ M“ÄèÀÃX§.’0Å^päSY£NNc† Ø=¦¸\>E§±ÓÇÒGûŸ§o¼)}íOþ(ðÁµ‡\'Â=1~€ã>cŠ€²6‰|Ϥly†ö“2×D½8ÂÛ@2|CV¯2€n[|m©yö*Žç§kÒ‘É‘ µ„¿ ò¤e’Tf‘&QT‘(¿Ù0BgóYŠÊл´ÇÙÖ%/&ÉûÏdy‚~!åu$IW£ø óHÑÏz¦ f¿„œ˜£µ¿Çr¹†Eà¢<Wõ i+CFqé)‰–ñÐG¾Ïfý¹‚*Nµ”E&6®F2b ÅÎSXêÃn¥ˆóìÇcM6Tô¥ÂŽŽ­áhªaÙJ@àÉØœ€œ+2ú˜˜qLHnÅåÀïô¥ÓBs…*µÎógÉ&ªñpðËÄ@fÛ©©0 `?•"¶ÀUQÏâRÚE Î×gp¨xåd ¤YïíA:‘ìQ‚ ·Þr[ÚM;]çL™~çáðNïïâós{†8¯ÀôÒ˜ð3™‹Âw×ÎÝà gâ`„³ó>B ËÄ 2éäpFóôb×EÚMµG+>|î:¦t8ßóþ¦?ýÊ—Óƒ~ªž;¢‚G¥×û(«!>H°ùðá— P?MkÚÍ,“1G“>WC®µ¥ê÷ŸÀ0 Ò{©Üö=Çs-ÎÇû Ît~ýøë¡@|é_ýAò4n¿ýNþ®:úÔo1=ñØ÷™G~´GÝ»÷@¬Ó1Ì~–©ȸá†]é[ßújþïzß½TïZñÜöjœH›Høç_øéë_ûÓô؆³ñ“Ÿü,† "‰±ÚÛ;Ph}Â\?ü‘Gàü;ßþ-ÛÛÒ{Þ{w05×à¥!´xÞ|ó»Òƒ}ø/¥ÜûÌûÝa¸)Ü„Ý*Bóö;Þ“¾ùÍ?¾L«Ånªì?™Þ{W6ž Ÿ°k (y뭷܉óØÃþòÒÝwßþñOD§ƒr÷s{s‰=¥jX¦+Î\¿~õ!ô h´ÙåäØë¯G«aimÏž}áüðˆ…®‹à­„ ò;ð™cbιsçBYn$ak”lC«¼¬ÐµE«ŽiÆÖ;â•4£ÿ·‚p&ó4ˆT]ÿv¹¥¤·5¤²Î ¾3gÞü÷¹fIJ¯oõLß÷GŠ—™Ø\É: ©pWÁK´ÚÈ F285X^}ù‚S—Ã@%Ð12r… Ý®X¯Õè~MM¨dˆü«X×OP™Äààyׯ¿?rûs-ÅqE]Ë÷uÔ æ,ŒéŽ^¨ÓÇï°›ÆèÈ*Ug8ÇmˆWØÃ!œƒQb`bHÇ÷™ekKÁpZðÛVçtO3·5¤!°(*’u¼é´°JÈ€¼ŽÅjþ¶bf ÜPC]â;(å¿:z­25[_}¶¥µ5¤7¸e«ô!’›tLjHˆãþ¨çjºêjÉÏY°òM#Bœ×¸Õ9¡‘¡á¡¼¥Cƒ7Ò¢åÊua§üÕ),ÝéÈTá÷^¿£±¤2 âï̈ÍÎdŽj)Ö[‚cÜ1 šé ÐÁ]JËyÏ{þ²+gO²öryÜ«A¼ŒsÁ{¬†÷5Ó=¢§¨•í: Kã˜À+kí¬#¼ˆ4\ %+l‘†Ñª‘QȯâBò÷ß=cÀªõrcDVøJ…•S:/¸-öV§ç’Ö0¦{TÀ¬(·•¢p‘gaS…ó‰Û0”àuÌiQ.ëÈ׺|y}£V+ž›¢õ¬Ž-õ†%Ηóm—ï{(nxßxh5RÎÉ¥ƒýðË/Å·ÆeËeºâ½?îÑílÿüo“åßÈXeƒ{™¿A6@¬O`Kêõ…€°ÑA#Î=ýäñ“{F-8êI&ÒAbE´ú—ð²+:[ù.Û.PyH±eW£ÓÄËßÒ¡ðÀÅ|RûŸÿðL÷ÞÿaZÒïKÏ<õ$miçÆlýîwtT×unNà´´cc|ã«ÿoŒí?ëp€E’¦ëÁ‰èÓ½Ç=S7~z7Éžà]æ`ОÑëü¤¿'ÿð¬î%œ»…EY{àÿõúC’Gê"8­ó×'àÏÑmDõ=H.]ÙÖSÜšàlp“2Jðþ.âH‡Ðß„v®c‚IòžË=éü~œ<ý½ý1Ó»’ñ¹-Þ*À°y«ø!íé\u'ß8íx<¾ã?êÛV®ƒ‡,&)he{Š$·ßûÒïÆ½m›ì2cRœ&¼t›%T¬P‰ÛÇ>øò <äïÀ'Ü8Œ+~ÚÚîÙ§Ž¤<Wx7¢òÞJniHÞ&>¹þ­“G^\LõYð\hÊÅj(c Jhû{É[¤=y…x§óKžl[½%_>Æ""QÈ}b|ù®2_GœÏ”ftlêÈÒ©I0žÍë3}ˆU›:+ $Ø¢Õà—ÝfXiÀz^¡£C~ËÒ9~,VY©#(¦âÊ ’ töë,S·æ¬A¹åw\˜ëױʼLb}&ÈpµóÂq à­htÏsŽj—5`P‹m`¢–NP«²láêÚä¯V$±e±WÅ<³Úò¨‡»v‡“ýò©7Ò¦Ž­iµr%8èq %$æMÛ°£ôÇ(…ß Ï?Ëšt.QÅþHT¦«ƒÙÒÙ ¤ël€äô@åŸ2Ù`†÷Å>òÚ÷„™¿¯•ó×þ7_ÿç å£ÿ×ÖÕG÷‘1º»Y(žšì&²/à.²OÜ4(×@â—¼kdxÚ)'¨R™ZÁAŽ 0Xn€{’ œUgêó! Ø@Gð Æbä ŸÁ‘bž¥ne•‘󽃧(»¬’”Nx7’| úFp€÷åw[ЛÄà ÁƒÁ!èÞùjkçž§Œ“¦Õ#tf®€³ôÇÉc¥“Ð'V÷Ižeð)<]}ÊývßCò=E]|RW1H_„oʹ/õSAÈü}¾íˆ-ŠPoX#(*ò t¨ ¨3 ×à-0;ù¬s0(¢CZØXâ^Úù€·ãYÊ íÚ¶Ãÿ\ßÕ©åû ×ï_½Ä‡ØoÞÑ”½®Ïd'iÅõΠË´W>©oTâˆw}Ι½rwÈ¢€MÖ ´`7Á:e°RŽ!fW¹®CÞ=°µ¸u“‘çLÆÄ-Û¶ƒÀ—×âL6ÏLžh‡¸ú¥Äi×h2˜ó6('<…ŸéügI‘„¼›À™4­>öã„ýqu®Ò˜I‚ Sî2áÕ –çk7˜P`;m®.³?ÉÀbcS#Iv¬`½âÜÕÏ£ ßýwÜõ÷V=†_±Nß·B[™$”ÌZ@þ{öµ5„£saÞâDè-B9£¡RL&™äàìÕI¤iL]FþVŽ,•wYµk’¯ø‰¥ÀOyç È“§ÉWÔÔ£E²Ë]]è‡ÜäøWZ’„«û쾩g ²àaŒk°W:s/åsV7z´k{íÙÈz&Iúú¶²”yig^SÇv ìX•j5pð 與¶‚Rz5€see&P-I’¨­õ7l0“dxž¾–.7Ï¢' @&Õ…>ÛÃÙÇìí‰7Î0×ÕÀEqP=$‚ãî—¼ÛA~a@_^c¥­>hí"qS¸¸ïouyû>Bð;Â;Dœ°yE5U½ÜƒÑ:­Æ}­]¼8o·¬ªK±KŸ ê´WúûƒÏ˜Ð-_5+þ!½Ûn¼usÇ}d]óØ[Ï\_¦ó‡]}ÆÆLî¢*»ÜÀ·Ç¨4Dw ‚ÇŠ%ù a¼,!Åê”VÁOdn®{‚çÚíh†¤|& ó hR¸±ÈºìVâ|”_Ú"y¬A[C\ñŠ”ÇÙÙÈ[›[%?;/Nr, S$9y܈øìñI&†§2!tÆdÆÖ›Aþƒ}f #áG” dì+¼ÀDýz‰äŽ¥#ظʇgˆ³Ï•½ò¯ð!ËúZ;@,¥'9½öj71„uØn$G<>sñ$òåDʧJwÇÁ=çÚ‘NÒ™mÑÀ¦²µ3þ;GøìòmyöžºFç1“s‹9RLþ71t‰ï5§ÒºLc7r>ö(GÖšdËÜGÐY„:y`c|˜¯DÅf/à­vG¨b/§fH.`ï©h·CV}]Ÿ€×4—g]Mƒ!wŒsÛò"¾!æuútošÒ¶\*ä<ðIx>2…à²Ç*¬rÏØ8ôËë"Žº£8žöé¼NUÔPmÍcg\¸4½µ¥Ûnmeº¬Mr ¼ÇÄås“ig”Ûí!oy ˜x–9¼VBÂôŠmèÊ o_€ÏKLÁ7Ÿ<]<ÖOaW×±J }`€"ßBÛ˜ãO&gWÀ!ÆSxŒ¡¼Ì1´€ËH’V^Ít§cB•:öЦmíi°Ä‡Ê cP>Y$ò< œÀÍ8¾Š‰i{Åü6!÷û(¯sþ®Ë¤2“†åÁÈ×ï^ßRFhÏèÓ3A‰&ИÊ=úÂ@ø«ÏW·0¹g/dÇÚg zo/úŠ1Y|.À§>¢??CÆNq¦y5|VŸ“úƒUÞñaaÁ?Š"Ù¿!{g ã+)gË‘Í&é+#2ýKƒ{‘%Nëó};7˜¸U†mUNG¤R`[F2y¦ïÁ'ØS»·ôôôÑ|‚GÉG´ñ6ÒUD]šã+¹Ü’ e¤ÛnØŒß"¹Y᧤B%ý,4SÊÈp`ïôs Œ¿ôâó îZ ùT Ì_ÃQãÒ …ÙÛÝÝÝÁÌ­þ# eK޾2S`GGG»f*M¶±=³JåÖ-Û!†‘ôÀ·¿š>ý©Ïcs¾´Š-»$Ò^¿~µ q+Ìeˆ×^âûí{_bb¥“L®!tí•13•Ç•´ë£2Çsd64fÂ5‡7þVɯ_ 0?úèâoEH%ˆ‡Å4œ“Á^&£#[Ó1ªŠö’Qø×/çå3›[Ú8ÏüÕÔÒØ™q¾çåóTzC¡ïmï9D°ãÖ÷ÞÏ÷sþÊ<¿£âeÅkcS[:…"·sçî`"‘ …`ÕÐö>Ÿ«akÖ¨Ì|Ë–­?óÈcÙʬ¹µ•€Ü™ ÉS8(7œ¤Máë½:G×?ˆáý$ 466Á”+Ù¼WÆ·{÷žhs×yþ gÄm†«gëW)öŠ1ùí‚§MâÖmQ½¯€”ázŸsW‰ßSlBX>ÿü³éÆ›‘Õv€ygÁBïqŸ¬޾톨(¶B覛næ;°Î]fìä'~oó–-áÌW©µÒÑùçöݹýM×Õ!þ¦®¿÷KqĽ×è耳BQ¡µ¥5”fÛgÙ-EsñbWÚ_Ø»Ï.Û;gô¡qÕ“V˜7`÷,)ƒ­m›Ò©“Ç#;Û# ®kL› ‘Ãó·Q¯2nòVw½ù~°>èPB¼—®y´ï:ßÎ%®[9ëú4ºäfz×@ S¬idx0œŠ{ÜDÛ¶íÐJ?Jídjß¶Ù;EKÓ]átÑXTÙ”^áŸ: œÚîMÇÈÛÏÛ™ó¯Ú=×Â&Ç{rïå^»æ,È »z圆â•zà ŽIÏ ®¢Ô¤ Ñ‘¡P¼MæpG þØÑGÔ0ûx`µUzò×:2=½tæ˜çËñ~œ„²íke›xâ:•:T”{:õæ0à4ÒtÙ‚“·qteÎ'ña–Œêr À Î…2˜:mh°Þy÷=ÈzþÎ-^_ÕÝ8%f™‹ŽCϧ3x(éTQž©(ÇB”CÈvù¶:¡Ì=èˆ÷ äåÓFË$˜r7*üÓüdLå A¨«¸)ÞêlÑáå:#`Ã^:lÌúvO`Œ‘x€ c)]ŸŽŽÌ™™9«ÃÈÀH°bHº¶5«tÇ1_×SŒšáëeœyœ!åÎ3б§ƒÎ ¬ÂÆ–¯¸äXˆÙöì s]šó&¬-â„pÕx³-zó o:“£¢‰ÏtzLãȨâù•QõdUŽ5ð'v¬Yc/œL@ž¤7Öé“xœÎ‘tÿÕÛíµãGC/30Ïä½<÷»Þ >å>›þËÿúßpoæÄ ÑG¨Hß@µh8ï¸×®=Ec6¶"a;víŠ*rÛ…‹ÿ:^¢2E|frâª{¨cIþþÿÉNûn’ˆ¬¢9zäg˜à°ÃÑ@»~ƒf:‘ã7chHzidòw ð—>¥/† Üqí¶$ÃmŽ¡O>üöøÃÿ%pð¿ÿ·ÿ–ª FnfÄ?:žým;Åèx„#^]ÐÀŽAþõû€|Ú¶Ó7¿ùõpîÛ6ÝJácë}íÄ{>po$Zz©žE¯{™cy WZZ¥_€n8£±mBéoî TwŸ¤úÿO¹æ€)ãìÆ0‚Á[—ëX^‡sr6ÆžynõŸô£Œ4!CGg)û"Œ üÚ½¹¥5* ìÜd¢ÏŸúa8 ŸO£-%û­†YHt¨guäè`s\óùìÁO÷>(^J:¨WÜKy$ëßÅË«kYKZùœã8²jÔ¤ŒBÿ É32¼± IÌC謴âǵ…ÓŽÏuHú /u×dûõ ¯«dûhÅgÖidgN$¤ð=ß3ÁA‡¥NÛ_Ëtʘ$|ž±_vÙ^3“…Ù\!¶_Wçùà‡Ò˜øü±ßù]溚víÛk¿ˆ cÅ¿“Õ7áQ|g„¯¯ýÙWÂo!¶Àêzí²ZGËG6·ww`ß|w.Ú[&d ר¤b þ2†çæs|óêäÿʯku?p/sï]û÷_ùÒϼk®nÀÏ|öëð†þÖ Ìí<°ïÀ!è³4}õet'Îj†öÔ Ä}éB>ÐÞA§*ø›8SÉT ³ßïzÏÝ8M;ÁAºôŒáŸã ïõ$`©0ØíÇ©ãbZªÇÄó™,%ô)h$ˆ8/s ~ò|ù„I_&€YM(¯³¸²t‘>«åЬ2-‚â¬CÛÜàxå€9;:ãñ @Óâ ¶µÕJv²0‘Êm^&õԢÈ6ÙØóIÅÍœü^:}ýOüt 忎]y‚l*£yŠ ô@ƒ^òá\ËÊTשüp̬Ržµ1®ú¦ŒØ¤F‹N€tbQçò^ñò[wf Hò{†“o¨g#ƒ Eê!¾TŠ”‹p¦€úªk3 bž$QÐaûSy™üÇŠ²MkeB<„Êoä)ÜNÀ½˜“ÛAÝÂãn fÚ­4*›Y«zµ’¿Jƒ>·ˆ1³çXý/nÊ{‘¡,P|’¢ÃGu[³ªWðÝ,ÀuNÀðļÔ|½ðfGù]Lk^áXøÉо¢’”à‚ «Å fËÊà••#ÉÍývÍñ<ÆôYÊ9íõȸx¼cº.÷C$S6›Èì|MæS/"'„5*?±[¢‚Ÿïó¾AHõñ^ Ùe¦Ò º8Àµºª=œÕF’ÎÔ_¼?ì.puÍKÌ#å›Ü—ù/MþÕ§Z ®:¾p° ³|ÄĤmè å¬U[-pŠyú|×â:M6/Þ÷5ï»î¨ÆvÝ0(ïò¨Bî1Á4ì/NòCõiJ>:Vð)Ú³—Ë¥3[ ›@WH÷%÷ÞaÎApSé:[¯$*‰+™7A]›Ôß3G«ïz|ê°_µ›fèb×’Í´º×žR_œŽdñ, Z½Txfxa —d x„6€4âÚs0oyÅnq¯!g»‹‚X¶‘Žâ‚]AY8cd>TˆŒ9ÑBûz°Ñ65ˆm«äEöD»_Þ× Tõ géÐdˆ&“’‘GÅØ¤+8v¬¶Öct–°'Hþvê|ØÐ¥üÇ$êªjÏ#§òšÖ†·³ÉÔ$øG@Vù í©Íj§.ñjŒ. Ò³zç4{a¹{£mUäÐüA:µ ½q/“aÔ…]“°ÐÃÁOñØK]/\ÞËjÀtÄSŽZˬŵAØ-ÝW¯¦%øèè$IÔtaÞuFø1èM7Ùö‘ž%Ø»JÀ¯>]êHã'‡Ò¾›áÑ‹tûH̤ͯ:ŸIȦ|ôlƒƒ å?£bÝíôØ0¨$xÉEÂÜK›Ûî&{äÏ_J-Y¹˜Fz¹ÇÖÛ´÷§5ûæzqrùN¢û4…O`:¨*'>0ÇÈðé<’öçl5û)>ðpžg{–=/bo·l[ŸÆéD9pehå¨N¦÷ßÙžzû'Ò©#Ó$’ô¬Ì£Åü•ÁùTMW‹TpGU=C–»sìô±Þhúì|è'ùeQ¿¾¡.S>=ÙŸ6´ì?‘iÔšÈc2Æ|šëÞè¼íî`A¥<ÀކqÊÏÌÏ Œ&‰›¨_LÇf¨²ŸÅï4MÒ-ÌáÅÅØ4ãˆÍÓeÖÛ;û(_>¥Ì‚ï0Ìqv õT©DÛ6n–ð*:öuSŒuö$vç@*#á2'Ӕ‘mMä_@ƒÈçÇ6ÂAÙ+ä-ë鹄AXÍÑ*[']÷$Uܵž¡­ ÁÑŸòo¸zý]ðÊñT äQ¶É]G Äs{Å[i4 ¼½¥©Â+GGV%´mjÇÁD2ô°¹c;’Ê<çÙMÓb»ú ç7ÃÿTÔm‡fu¤íw[ÛÚq,Ž…“øüÙ7ÂÈ`àì¿ÿŠg²¤ÑU¸òÚ·þ ⾫0,ÙÇÙwTXuΘ ¼EÑ´•—г<Â[áýü\FA7sœöúý1ŽÎ5ö¬ÂÛ€3€úúƒçj¬ƒ%ÙóÐ!ýì"-±Ô'm9¸D–¯D[Ii4£#ãPئE\‡S•‡Å8 'Q¸å·¼q41?Û9)74~ÅçkJƒ¼cÞö¿ž ®ÂÞÜÖJæ~TJ_¶Ëw767§A\ÊV³¡u~07Ê«9•£êÃ:Š­èPÞ¨OH•Fs _Øk8 Wçêkå^öÚ±¬GÙç??W~„±ÜG/ï&>GÀyš`æºgæ!ýùŒ ÿ«{ç}Ó•?®}QÙ„>üÓª楱ã\Ìš×)ä˜V¬pŒj+`­ã+Œ æhÐÓŠ$Љ5ãháYtaÃ`™ã ê œÿX¿Æ¶UKÎ5ëh£ãN|âÌ¡4Í1~8â'ð’¹i(/Om±ŠbÆ:¤@?>‰3<]Å8N|# ý‰ïGËdÖ«¥asc}a€óE\îU¿¾wñâE8½˜‹è^fvëÀ4C}V{âQîGç¹3qæàÁƒð9îwíœp Çk\º|Y4ÅŒ&xH"I‰LMMMé|çyô°1œ>MÞSÐÄœ•dè3oþº48âA\:z]o?gÃiOìÝ·?°âŽÔsgO§z’1£ !Žá<âÁŠÌ±ï¾—â41Aѽ/5ê5Š=ûÕÄßÝ–"C.¦ ì–*ô2“:*hÛ@„²J™1Ö3–îºçè(‡Ò7¾úgA'ùÍ–¶nÛž.œ?ý÷@Ÿeé•—±4œÅÇ^žØÏöö-i'µ&J?}ÿ{鱇Œ3Ø Ä«‡ÍR)É£€ö›xÂ~•¸—yàkíV5O¶`×I ØjÒƒ·.BŸî}){ä5£ÁËZWq”È+¢z ¼^Ä Æ–^…M!ãjkÛêr®Wl=äߟåDÕérI*>ï¾4ööÅŸcÏö‘`З^úÉ‹cùœQè>éèjmmãì˶ô©Ï|6öG^}%ý?_þc ïMTíQ=%Ï`êùøKákððTç™Î@ñ@'»¶‚¶©—<"ø±ûÿekÖq°´‚]5øw(€U «¢é* aâõüÁpJÊA5ÑXç9sÌÛ•´4 ül›é“²÷¥-6=Çô¸L¢Ž¼LÝ Ä ?h¹X‡2¿‡­ÿu,ê ¼Ï¶:ž­7ÅÿtŽºFéÒ®ÚZ7¢í>sÚ:msk¹ *~|_š”w-ât•ŽÞá‰@IDAT…§U›þ^b]~î^¤<»JdºŠ8$¬}†áƒút0YUσ€—¥& Jœ>ÚHâäîE-"ñ ¿Õq¿ŽyòÈÀky(U òEåœVÎ&QW»H;°cû ÞB Ï¡€«ÕòáÌI¿„£f;ÉtèáÞÑ‘á}Vl]Áæ²âñ<öªàoWÀA²áÒE:±÷õõcG‚f•î|$’ ¥†ÎÅ„ïÈ·\ ½&hºßò a(ü¤»ÀQÇÚ2Ÿxá”ùœùº®;³™2».nàáî彎OžNG>óýÜ=qãßóq)';ÿžCü#M"6ÐEgDöHD'“ uŽ—{†0¼Çà­ºÍºÖ “ lˆuYqÞo.Læ9üÒ³ÉcÙFGòq°8‡W€ÃnšAZQÒ¤Rv!p Ôe/Põr/|¦²Þ³ÀeLîÿÉÊ)«É˜hŒ#¬m%í:JÕ¿ÀÇ/È?uPºï}ßgÈc¤·¨–e¼ÕetEèÔ9‹-&dªóø°H¸oÀ?õBåkò>uRé^¥¶ˆãÇ\ugzIÌAú4QY¡DgÐÆ¹I烃Ã!´©C”Õ—¨lp"™gÚQBW®Šåê‚PÔ_}–sT? }9Gp<*¿²sA•¯c$rŽc·xŸëða®ÃÀ~$40~ÎWáúýŽ÷eŽm‚ŸðsùµÝš„yçÂ9>C§GgÇ’~K^(ß±½º­:šô¯?D8N¡»øÚDAƒ"âGà ß5x#<|¶|Ð*ÌIŽÒ¹LÒWèÌÓg¨SìWÿRÿS§ðY¡/3m;ç#nDK}dœ“3™Îœ:Nêœs^ÛÀ}u~.u°[üd²”óñ>ŸãMÂÝçËí8ã~ ?d>ÃŠÂø>>#á5Ãܵ|Ì, |€ßúà€“ðãù®ËsÝS½AØ(£ÖÑvzŒ6Ö£$»x9¦<Ò9gº}¦×‹[v§*ÁÎW-àú‘ÕSôQ‰KòO÷"“¿1¯3f烿Ê/F‡9^î%õ»A_.˜u€,±n×npÐñ-^‘® FÛÉ©¡~=Á(’ž‡8†µ-cóIâŽì¬ð=ƒ9¹wñ|ðFZÖ.> wç(œBV„¾dðÁ¤gä8ã/S’Ïw–éX@êAà£À£FVR±´"?(àgy0ÙÀ«Çª_'yÃê”|#ûÎQ®ë»×‡æÚÝkž±‹”ó3±$|·œ±ì¾ø¾íýCNñ¹zŠÕ¿™ŒÓ?¯¤Ò;p‚yÉ dzÙ½¡”¿M–+ÌÇ" •áià½sc üζBÒe£¼ZJ‘޵±Fð3a âºýÛ‘gÍ×®oˆnžÒÑ(¾Ža|===iÏÞý¯ÚÕ&ì¤m@G°:Rߌ }v÷÷âoñ~%ÝØ­Ë ÔV ³Üxsº…dÐFtuÊ·#ƒÁ0ÖC1Ccˆ*c‹=„ÕÈ@/ÚVZè×@‹t}+# <3…½hÇ)…L«ùÞ ûl¢®pP_·;‘4ïžOg÷ËOå3ëëêY 9hÛ*dì3IÙ³–KË 4€F÷®~GýdC•¹±ÚM&©Û‘Ý”=³r|š#´¤}eêiqW–éê”ÛÎÒ—ÛµÖÂgMPR“÷Èœ£´àçþí\Lö’*« È›4®lØHÁ¦‰WâÞ,_ÛÁëã0Àœƒzø¹óÖ˜0S¦f là§û(OGñD/É’Õƒ´}´á•…ú+Vñ‘(¿ÊÁ7ƒâw ß(Ãíø`cj[ñþZVåíÚ‹L>Æ”•©Ò˜÷× ÇÚ¥a¹–·Ø)“xÍ‘p0;?A²8ƒm° Ü[Ù_È êWñL‘Tv+cš¨l‚–2Ãý÷Š™ÁŸÄ];› ¿ö&Óq¡ÛàÕðc>+)Õªí—%…slC¾…óçN¥öŽŽÐ±•ÆÐŒ—Û=ªçÒ̉/å®P¸DŠ “PfI*tTðdhÞóN^n^dú1 [6mÀq%³S±Ôa*ƒS ÂË—."€.`ÏZ‡¹)œ*/™q¯CA´zór|&³„γ8ï­\Í*ž Ò=¸?íØ¹'TVd©è唡7G¹þ×?Eˆ ™¢ˆÀ…±é@ñòýœ²´ŽhxˆW*gÒP]<[õ7›[2eí*])­„Ráko'0~ô•ôìsϤûïûHŒ™D <ÚµkÌw%}çorÖø³-Aw¶Ô¯U`ÎvžKåø¾?ÚR£¼jÔzÒ‡ Rm'17„¡®þ£ão€ ¢F{5 ú42ÏŒjtb”S(Ü>Kç§8aÖ7Ú-{D0yh,ZQ[‚±ïÚtð#StÞš¹Ï—‘£8sp6˜[J@jMY§ [}®Ýuyþì ›©Æñ,pÁLí9^{ö˜”É(m@wóº“LJ¡ÓÙÙ´â˜ý—/¦»?xo¬ùÁ¿ŸÚÉà.eœÕ$}I‡:èt äk<9¿šÚõè…VR˜Ùše¯ õ3› ”ˆÇÊäYŒ~·ÃôÝ>÷-ÚüÎh( B« z†éð0•ýОNÂeŸœÔ¿›F žZâµ|WÃ!Nµ‹Ës/¶Z³-–é*¥G±FÏXC§WÑ(ó¼.uòa¥FÒú´{#lòxßà¶¾:ÌJ œ9ç¨6e•YÅ÷žbN:.VÑ7üÉ[Ë™~AgðYå™ÎWƒm’5•3W±Öý×ñ!ülU'­¨ Ç7øá]1žíxùÛ€Äk†Çây>úÂq:OõÈlŽç°õìÔ£àËîÉ!9£Éý(fG9 àÿ¯?ЬcçrHy=3;ŠÁauõBõ{ÄKl§OŸŠÎVá æŸ%ѪÏçb¥2g/éÁë[ßüF$uèàצê&IŪCo‹5wƒYvp¿Ã‘¬ÎÃ{:™t"ͺ?|A'~8¸}ïéxö¸Ïq{쑇"á1ÚŽó½H>qo™wüï/~¼tÔt“(0ŒÃ_Üyê‡O¦Ÿ¼øt0̱c©Šµk󍇿`­!zyg2âŽZ‡_}…³Û²LxiÂqÝ_i¿€³ëäá|"™$ª‘Á‰ Œêÿ?#PÀÙõT…ãý*Ü„±ÆµŽ°|¾¯Q­ãj•ušý¿¬rÁ÷Y]» šÌ—­Œª!Ád˜€Ðt:2~þܹpîE×Ö°ýÿØ;Ó½®ó0ŸÙ—of¾ÙIÎ É™áN‰’lɶlÙ–dÙ’c»±ÇI“"é‚¢¿Z4(ж?û³@®hPhS¤IÚ¤IÜ,²cÇ’(k_IqIqŸœ}ß·>Ï{øÙ´âÆ–´•Ã+ gæ›{Ï=ç=ï¾àè~;7âwù¨ŽHϺwŸÂ!‹þ¬S@<Øâo¶·ó?ÛØ™\dbŽÏ™p4C"¦—ú¥ ¾æp(øN_å%ø>“JFxîkü‡Ì›¶ùâp ºdLafkE+­Y†ë¬:óýî-¨%ÍÙ2sܳb¢£CºÇ¹°få;Àá!:PËq`é0OÛ¶àeðƒSâŸNcy-à·Èð/ãldÁeèÿ‘ÏÎD»GèXÇ¡z¿0‘®t’¢”¨0Œí<àz„Nx÷Lâgòfƒü¶HÔR_OkWÖä8^Úøž k5tú?[c n ~PŽ./ožG¦i·ÑÍÝ2bOŽ€`;ø/0ƒÅ˜Ò$ìÝ#çYÚ'÷DùNh—V+L†™(Wqââ0óaª¿À³ÿœ£Ÿ¹ïò  ä‰ÁyeWWOZY†ó÷XÏx=ûÖÉtãúÕôY𪷯/d‰Ý\„AWÏîûöïo}ȱtÞÖ!uîÎSµaàÄÄ+ñWºõ¥W§š®»Ò*´ªÎä:®^¾]±NÆhá¬UõH;¨DÑv1¥¯G³t X æ€‰üQü´{’û•|ÂÁDh‚'ÀOúð™ ! ®ƒÝÀ~NS/sâXðŽ€9øÂ>¹&á ÿ{~òÓL‡|ü=—wgòŽÒ“ßsËôKž»JÅß{‰·NÕýÿ‘/Ä â‡ç –mo4ÚüÈ –•ˆÊ¼öu“ „òD_^Ù)CƒÏÝ}÷]‘ tcäZÐÈ a)–Ø TìÅOiÁÊo]”Êá­#Ô {¡ã_yç¯Çþ kÁõ  Ö¬ü ÚÒ¥êi?Bêñ¾7*E+ÙÓ¾'ó>‚ÌðGá™ùŽ0OHö¦Õk ü´r,S‚%h.åCÀBH nñ>1\tÞVW‰cñ³søRZ¨/©S©Ë)W=¦càÚuð½…*+‚ÉÊàdw’ØsÆ·Õ±ÛïâÅ‘HO Þ„§oE:P¿ñÕâ¾pR{–oº^á$ ,;”³1±®àD[[+³d¾¬K‚ð\À]òHÀR]Òq|gZ+3/_€K—žóM›`ºðç i“ V(W„—Dù‡“”&C^ò€÷ŠÃÂK½Ïñ·à:×Å å¦2Æ¿;G“ l m%´ŸƒÆnú¨äÞç˜kðü¾3ß¹@1†rDß×8z´>qÓ`ƒ¸/ŒÄ aïúƒ¦ÀQy‹I´ò"»Òˆ[^Ê «?¹1î€{cBœ÷8פþëQF®M¼ÑÆ(@1p§Þ¢N”ƒöÊvòð&ñ X‰3"¦c‘—{™ô.ï2 ÊÄ?wœ“ësŸÕCÔÃÔÜ;í×/î˜ 'l´Ä!×­}!çîÒ”AŸM¹Å{X›ì3Ñ]Š±Ô½…•Bܽ–´; pH7vˆP^*Ý»˜²Â£=†|ž@C$ ù~ç …ÌG™n/ŽdÚüê÷&*«à*iÖ­ï<1oÑštøÌ{Å=/yŠz­ûhŪW8ä17Òû"6âÐ-‹³â‘6½4ìÅEç"ýˆ#ê;yÛ¡L\Fü!Õ|š&¹ —e\Ÿ‚(ØWñ$ó:ù*9Öɪ7ª~•¼VY) èë3Â=õ3}…ÎKî$läsóiÖù2Wƒ<¥Ëäo»Sªû ƒH–Dgs¿…‘kßBʰvé¼@U%·¤9*6MÜÄ·Ñ‚­&í˜424¸oeúöˆAi÷f”³xå‰êNâtYT8ƒì“¶«kj"€h¡ö¥û.\ÅõtÅ]<…dÐÛ´i[fØ.¼v`MP’fäžë½‚|2pe2Œþ„ZÄSmZir4hgq‰µLf„ô©¦­ Ö’»Q¸›‹©T×BòÐÜ4Gy@3Ä· u8!åWê7Õø  >ƒ2ÍÈù{С{Kp×ä4; MMyæ5I´¬½ÎvÙ´‡—WØÊXWè35­ìQH`ÑF {Œ êϧ]‹ºi%ÚÑ6šM“·ÊKÔØd°Ù‡úŽˆ" TR¹g_‰çž/òî%Ö.íÔÖ’äÁûÕÇWñµÈÆ4ïÙè©×ÞH†9‡n¹ÌÜjåè}EÞíFÛz6SþRÇzÄQm+ß ¨bÞÑý} Ÿ8µ]Õ@¥/…‚¬åî‰|XIƒî™<#h…ß­T_¢z¤ÓôGåÇÄ<ž³ÊW9jev)¥};Ž?\?IÒ @Vâ¯Òμ¹ßIèƒ;sB4A~£ô‹œ¿‰izžVëìw {3Fµö6tÖÝM’'ûÙ\¬N÷s¾û:Ýð:ÛkÓ#ö³?òPö’ã6䱋´¯³àB]%ûIÕrG|µ·CK´\ŸŸÄ¯ÿÚ¨L}zSa|6\'Ql­èëh‰^]àHÕª‹MZ$á—ªú´9G›wŽ1«êKçN†¦Ù{ä·xüØ [¢H6Fcž¾µæâàÄÜ<þ!:r,£?׃·ú(ìhÆÎbíðå·/£¿±&(¤Í-ð¡†î Òq]ö7C‡eÂá!h?(‰ 3$ˆ0a'qSž*‰Ëg”-&·lƒ§ÛÀDÞ§âMÕÜë%¿w·¡­ìcŠÁx!ï•gƒZG†ü«%XÏ‘ÎPÞµ-B×Àq76>AñSwè¬+Œ-HëÎËvîsst½[˜A· yIÝ :“_Þ£Óïjk†&çv_ÛØÖç/#¸®œ0QÞ÷ø%V®9gñÚµJ£â±{$?µ Œ8¼Ž¿FÙ#_ØX'ñ‚õ—Ñ7&¸<¾» ÛzšÛªT+K§‰óXè |$~䬾@ù³2ÿÛŸg¢¥ÏüSI1ñ³Òç´ÿïþumQ "#ä-UŠf%¢B Wâø1 ÓóU¤] ^(lïZ‚ã†2Íçâ"ULBG€scó0íÛÝ l˜›xçzÿC@JpÿÝëV” ÷µô»ßÅñG¼P©’¨JŠÖ'údúã'ÿ$2{zp–p¿_*Ý*ÊÝ]ÝkEúgo¿ð·©†¾í1Å#/‰Ú÷é¹ÿþSå2L÷ÎÑ>B íP¼Güó½Ç²[90}ï}éâ…séw.Pé½3îÑ8±uî,™v>òXTm~äÁ§gžþÓtü™?KŸ~â á¤1(þ­o}£¬)=pÿaT\Ãúúë/sžÖ’DŽb|Ô`¬¦?üÃßKO|ö§Ã¼mοùͯÅë:¨YfT†>sü[iïî½ÌÃlʶȊ|ñ…gCÙïîî …Ñ³Û¿úÕß!HÓë2è/^º”ŽŠw G“ÆËÅwÎ¥çŸ6õöö…#qyµ‹sÉ_ ø<ñS_ ú_ pòò+/D¶“m6 €üÔç~:=ùGÿ+„б{\¢Jÿ‚þc޽‡ ƒ8Q/?AUþGn5§©˜…ñyø±pX=õgO¦W_y>ÚšÊct¾*•Γ(ðö¹Ó©¯_zä‘OSév9ý×ÿòŸÒ?ø‡ÿ8Zr«Ê‹t²[…ÿð£¡À6ã$¿NPçΪcát—ĵ;×O¤ï‰©±t•vš‡Þîßtj6mckÙ1¢Qo¢FI  ø]ãXåAšyûüùpfjli$»‡ žÓ‰/.Z…h¥‡øŽæð—u…qÇ`:–tO’…мJ¾%/r òÀ÷z¹Îp¸ÜzPþgå=üPƒoŸ~3xå8‰Ýa`éü°:KžªqÎyÆxìñÏáÜQ®ç€þ{Çs¿|½£wK ¥ìÝŸ¿ß]SÀVàÏ^%gÔw`‹²(£n­\˜”ä‘Ïlø›ßÅ{â—mþu¬˜©yùʵˆµp°Fª VêHD NÃ஭笚±=¢ITZU4=ŸW‡ŠH(âá&µŽ+vmÍ'§ ¡é{FQCUpœt*Þ*­:o+ .@îÛv™ëÀù€â­ŒÕÎdie”mÃ}¾‚ bF:$ÂAL42ÍØÖ(¨£JdoÿA’Àö¥Ÿû6­ Ï‡nhûf3IÏž9UÊáæ% ×0S˜ï$!Sù3FðUC<àÜ•Öt&jÌh ©¬ g žPÂùL#T˜jÔ—±VO= Þçûj1¸Ã­ LÃðÍ8¤Ìl ÒæþjÌ®s[‚`¾âÙ•K1p>òw$ÎÓ*P¡mÖ ±`«A“ú¢½:1¦Kv¹†7Ž\´ú´¢3‡¤ÙÉêîƒH¥!YLÌÌõ,c“&0£²SÅjxa€ùŽ6i—%2ot#æc` AtÀÚ4æÝCÍÞkûoáÐC÷ñÝ91ìsÞg@¿ª­Á4~„é†5aŠS ‹ÂVg™h~fo¬b]¯ØHׯ\Ž$CÜñ^°ê:(Å!«˜åZTrëxâqŽh¦+ÁP:ÿöYþ’¯ŽöVŒÕÕÔ ©Æ¸Šà40Ãiuí gj7al³æÃ×Ây‰L§U­#píцŒýðóîî®tñü³'S¼{“u|3€ ¾‰æ%ž[ÆÂ½®³9Àˆ¹ûEáÂþ _Ù„ÖÖ5…îìÛœË̺=TœòÒ@×Ùn+Oßçx4ˆoƒ·÷&âýëù/â~ÿ©©7б·F;:6-ö¢ }õÒåËé™gžb|©XAÛ-¼Ž#FX‘ïbƒNti¡Œ÷{¸ñoʦ2Gç"eYì7Õx(¤µRƼŽÄkñŒÓ%Ö-?©c=Øýá ÈGÀëà1Vq±­a¤ëì|îÛÇ#ÙJÃÚÎLEä¶¢ÎQ? >HUˆ:t\â/ëüýßýñk‘g.œ?ãŠxòEnÑÝ~¸H°Ô`û·ŸþVð=¶1Zfºwâºë`–±·Á'¤=è,æÀß<ÃÛövÊYõQƒòØæÙŒã:¿˜_~÷oâr=s—ŽÍô¿5szq˜“pT/Ù’–øÙÀ—ü*è7¹*Ë1}X8é²… ŽJéÂwå/ù–4@œ’žìö°Åx%‡º€’¶+*bþ8²t.V‚³°½p²ƒzôyš<ÕÊ›ZðEgì:ιpüoÓy„Ž$: uª{«ó¨w”•ÛÌyè×±m’Mæ7:rl_®Œ’/A Á?ä•Üû ó;*nù£4 Ødg?ë…Gm¹þ¢&ü|_Ì:ìixd(èo„Ž&l]IfLбÄ:ÔiØÂ:jqîÎEûuÇÄ>j*2q­.<&î¨gµ’<²³‹®)Œo•ôóÕßÿèz ó]]Ç€¨I òdzX?ÉMªä‹mÒïØh´u7qÆýv.â¡ gÚ¤&ÑÝ e¤þr›½}ý$ µ‡Cw™o§/õP9MÐÖY߃]èz®]¿~ƒãò;Û+“Ô7ÜuíXƒ×%>) KÁsõFñO> l}F˜†<åg!­oE½ÂyÇ¢ßýÇ{"€<ÝC!îg?èŠá^óç.ù“{ìHßçÏ·påÏ=öÞ>àÅêì tû ï+r[™ÐÀÅ•VË!ï¬Þ)Ôï„çq67vt#2Æsw=6°§·?½üí§#Ùn÷Þþôÿù?@œŠ…úçµ”ß++R×QOR'1àÆÙ[Åh2goZ9«Ë„¼)È|:æ)`„´ìüt|F\˜JK¬Å €²Ü}5©Ã1Üë`.<ìÏê£ét‚Nƒûî khB‡4`áÄÕa|Yèˆq[~ç>I÷ÊŽ<.|‘w¸>Úþz-ÃñymÏçµCO+Atq“—ÐfØ#W²#Öä2r‡ŒçøòO—•Ç7ðn`Ïd‘\µ¯ÓW(“äé!MîQ.éw4è9t} øˆ•¾Þ+Í[x#¦mÕ)IÖA68'¿qÍQ9?ŒjY~—Omn±»ŽrÀÙóƒ–üØ"H͘®;îr¯ ey§û¬Žíä7ê~¥6âv)®ëÕ“ÄõûÐL²)uuq_Å¡ ƒbü톩¾‹ 28W ß6p'¿iXE?G¦uîè€ÿµbO̱Î\-æÙÒÂ,æ/,€›Nx9rR×qM& _mðW~g`›_±¹§¨pl ü˜Ahá=ž£:ßD á®Ná~XU¯ÏVžar©6ŒÁuù—pX¹e“Ž2y™É“{ðYµ…>lr¤xëYív:Pç­2`Êœœ›pu qü ¼Œâþ°ø›—÷É›Ýq*ÚDC7•È0+¹¯]¥ƒ$ÅÎ{د l+|Ùà³Å.ò>e^ÉÎL¢0˜³þߌ\¾Á™Ù—C=² {omÝ Å,ä´b›v8UÅß ´Á–v…•´¶@µ¤­Œ¥uù¸4.ïvÎâëÕî ®ÕÕøÊ™«çW._NŒ½pßM¬"0¦žâˆÄž,oYv7ð[ª§KƒÒƒ:ªô²›ù¨fûˤ0§±û R‰+¬´Mý{È Ö'­IS®O]JÝF Éd"Ç5¨dPÜŸã˜+‘‰›CìË‹ç|^ÝÉ$o÷OYïïÂ#ó6”^àŽ„‹¹¨æD"íFm² +“‡åQò=iSý[>±œ^zùzêëmIûöµ0;2Oô¨.*²gæ×°Õ9:¥…óØ'h Ž~^S]Œ@ìô‰)tQÞµ¾¹–ìmÆ$! ¸WTXen3ìç”76qôóÕwhï™ç ©@`·§»#µá·mm¿{¿ ŽÉëM–cÉÁÔ›Ä×WÀ#_½9N—Dø»…´1v´RÉ\Ga‰ÎÒÅ.Z³Ï,¦KèAð,ùŒü^šÐ#ÍÊû ^Ûµ¦9½B0þæÒIvH°c–z º‘ŸÉûôÙÊ£ÕgLq\}Kò|uOߥѮ> ®§Œù•cˆ÷OšSÞ‰+ò|Œ{ v­Þ«}gRv96“z‹ÝÐ*«Ê–<°±×®yêet[«G†‰7¿Ý[õRmÈfŽ>3©cbf¤3äÛÒ‚ÉR¾‹±PÚ NÎ÷ |7&åH³Ò¯óùCåGßm{wylþ#ö·øL…“…òcù·t© ØðAüüñ°{8^´‡¸–kh$ùÙ®DÒÖ铯“Zý®«dÄ”>VÀÿÿve£W¥0 ‚êê܆^ÀDf: žsÊ)ö3}By ÆôýWÊ! %ä³ýš0))"ê_Þ—«W³üþ£Ýùôý‰´‰KåV&¡ T˜_'°`áÙ(2Èy²YZp&ˆ*ß]]=é¯}þ¯q.ù©0dl±££Àˬb/ïëEòo×âï 83Šd~ -c+>ÿ…Ÿ¡Ï\úêüÏô+¿òwÂy1>v3˜¢Î Z MÛÇ>AÕÊPTZì†ÈÇ'8‹ÜC|.®‹¿ÿø#Á¤þøÿ€çëSŠÙóÏ=›^¡JöK_úr:BûM[¨ÉDÏ;•~ï÷~+ýò¯ü½h§üu*Ü›0à ª_¿~%œ$$È¥"ùÛÿý7ÒÏ~ùp"7§·N¾™ŽØïíÛs±= ¯¶­º­Ümaoàù v``%¨ŠòÉ“§8ô ÞÕÎÑãÏ>•zèátäðÑPƬ„=vìƒéßøÃt×±@ÛÌÿEöa.>t0`«°bÿÑO=žžú³¯EðÒDƒ§ŸúF8Ѿò3#ÝC¾N £wÝ“þûoþzÀü¾>Àœiéùío¥ècé#>Ââg¿üKéµ×^LßüÖ“<÷ÁPÔ&1ö¯ \A­ñžÏP pOlÌ"{ãĬ¿?L«Kžî8ÕjEp$+Y:m=«Ð å#GïÜy7¿ D¹óÏû(>ÈËÒ“g"›°¥®Ñ¤Ìêîî<õLJiS~³ß»\?Ó¸=xèPt0p\xù„í`s€‘€'ÌJg—2ZÅ,œ(:tœ–ää÷ŽþÞGýRÞ97ç.ß+ÉÓ÷>b~Â1¼JãÉ/uÒÈ÷<Š¢=’9à8lk§í|W£Ú`S8ÎxÖû¼TˆJãÅÿþQaSßÃëÖû„“—++ýœ?ȘŸ•æçFaZ’®]Q¼ñsï)ãï¶¥ÒQí}~™(åg*ÆtÚ¸ÿâÄ‚å:î•):÷5TàS†çÎÎÆý­ò}y³×0“º˜'këéîáw{3(¡VTÛVÍöq:YꋜÓI¦¹;+˜Ìá}èÍü¾ÀØTG ðo°åsCZ›Ð‚z™ó ½ …¾c9ä¯ò”}Ö±V¯S’9xæQÐKO)ðgPejbœû0Ðp\ ÂôáÇ>ˆ†  ÛÒâ(Áá0J@!:'¤)/a,žaåÏV6{l¿dz ]ºæƒdTÍ7Ÿ‡]“g“…—5ºâϸçyß5ÌŒ6³Ø £ìn$AK¹íºŸÁÿ^ikkÇQ²{3ëÞjø0²Î¾:#á“%ø1aG%e#﵎"'º¼FïgÝ¿UŒ²%Œ1& œim†,×ÀžâüLÃ?ªáèÆlæX †H‡Ò€t»]†Ã‚¹i´aùhDZƺ]“›ç¬PVæYkêãTvƒÇZ7‡âN\â3èšg ro‚·MÀH'Lnag Ý¶ÈІ·Õƾ×ïVÔÖ¶„3Tü¯Fǰ]šV¡{¤qºá¤c·šù4^É; ­i?Gkhè‰#ËÌ©'fÜkPV‡ÁN$2µ ¨rÆÁŒ}ʺr0ÖgáâÑbÍ@ã Ö:‡ÅŸ´úÓñ{Ƀâ~ ë®Á£â=:-­œ²ÒXGÍ6{Ú@æú,ï’RàÀÏ©êæ5d5¨—L4Þî_7˜«A÷8:ÀSÄ;/³®y ‹}^Ãñdåã¾}ûbo5Ê;ŽÝÅ[àqÜR{¨˜wOù¹Ñ@ ÷}‡Ìðñ†¯|Î J«™u Tn[ñ”ש“]zGL&ðœ5éDÑ9)ž ÷ÌŠ»@äãhýˆ¡ϱ&?Wä)ÕÞËïÕÀÆ.ÎŽ4Á"wB~±þò”¨üâýUì‘• ë±>®`@¾¹±?øª?‡ó=7YÈyÊ“uh×`ÝWÚ~ýiÜ»˜ f|a'­EµÏ+CÝá+} Zù·Á/Ç+`§º_ò®XóÈÝàÀoàÆ0?^óŸ<—'h‹H×lŠÕ¸Ï :•Q:«¹'Î3†é”ªåÅž%Pyszò Ï0u^â˜NCñ2ô èN>¢cB~VÁÞ[!ß«À‰$ÏžÂDÜsÞòqžÆI‡sEšæo¶V—ùl}=•8—tHÊ[t€¯Û¾‘{Ml±Êº†ÊKÂÈ;ø9~±ØGy”´©£Ø ¿Agñf‘j®àýÌ5œ¬Kþæ"œãsñXȃ”Ú÷^&ëðÖÝÔÔ0’V¥Mßg°Ùv³Ê ývÌQþÚn[½O[Ñ„5Û•zLn½‡¡"užÞ´'_4áÉ®/QyDz¡ÉTòéð‰F’žÞž§ÏNÆ¡2¸´ç2Ñ€†¼{‹ïúD”™VŒ=À?<7QíCZ ž=)à 0Õ ?¬`·[‹Ž( ä'¯¾úº¤KÐŽÎ^8•šY¯cHûIê>]y"à{ô®cqDÄ$ßí{…¥N©l×’äɺsëÔ&dX ø>0·ºÝ= ‘wC'â4œ[¸ªK(k På@hIß']ŸW–³bê¾Sðéwß)\üOÞö]æ]ñDÜ.Nþ¸—c¨«š Q2wuíb}à2{°Æ1)Ó“t`ϬPÓV}™•¿W¦Kçß ÜhA&K ³Ñ›~÷7~=Hâˆs'y&èø¨2É"º °õ<ùH^£pÖ©(ýÀ{A ø³ÉjU•¶ë4Nå•ÃguÊæý‘3Hà¡òTòѱù›ÏÕÀC|W$:‚ %>(ÿðg÷ÑvæÈ¦ùÐoÑq*†³^"­š€ºspÎ~Y©é¾§µ{ì›ËÈú’÷øÌ<<ÎDõ³5xˆ2Fip®”$²›h`õ;oÓ /Þ„ ç«r]‡àžÉàßÀIÖµ,áqP„Šr`gâ©û%Î(’Ž  +3֨ȞŸñØB„ÌÕÄ+ùTÞ æÌ8ù½è<Ði ¿eGüQ~5ˆ¼–“z”‰ÂA9t _t_à'ê”&E§aæÚ'?F˜·ï ù –Ǻ·&0s üB¹¥¬ÓÁMrÅüLÌÙ¹*;™8kSnóÅz*¬ºä3çèóÙÙÎ:ùŽæõ´z =D˜ø¹´¶A(d0{”õQ«ÚÜ_‡¬_©X¢w:x±‚I]Hž-v¿ ºÊ³®q|‡¸WbǤÐyàË:اǨlŒÊ|=ø<Ï{tÓÌSgí~)„¥2JxY ¯\¶í«ã•y£8-3DÀÁV½›ÙgÝñ>y›s’Cçô>Œ‚G áâÜ”'Kõ©2‚BQÑ î­,r,Ð á“eÏôYνʆн˜·?Kã‘øÊˆ ZW¡)“.ÔAíü´²L@RØ3aï¥n翞][†ü•VLæ!¾D2"¼XéZÇúÅ­-~V>»& { °ø[öeØ ËÀ*¨ üÄæ , Ô‹Bâúä èô✾‘Ѐi[[+hD` ºsiŸ8fòrÇà†ÉYäjH»™àÛÖÞï®ê^ÂD{SQ}$èɱÇ“7„Nœ—{È£<“*Å3×RÁ³îYt—‹°_ZIí1VM;/÷È ¨ãËKÜwešt¦Îg2Œ/ ýŵk“K&öyôŠAQyÁþGÒCŸ|4ôŠ«—/¦ŸþâWHlEVŸÀ^­B/iNׯ]EOZM}ýû VÅÞª³¨ƒ¸·«øú±ŸeÐYüàK<ÉòÊît#ið xÛð\ÆNŸÁg€d°®Ž5ÏMóŒöúzºÉqVêòª%èЮYÒÕÚÊ¥BƒºŸI‚t·BoЮÿW¹TlµbØÀ?]¾¶Hª"¾Ûê»"’4–/SÄHUªvþâ²Ázü¿Ä‚X/à‹3äËÊѱ‡ºj{'61ðUç*Úµ7±ë J£ûªÇA þ'O²5»±ÖI X"m…½•ß®O¾ }ÇÏÄyù¯ûo¢o¶¶ÉpÞkõw z[—èß0èh7#õÃM!„¹Hði“ ¨‚ó¼n‘.Œ¦)‚‰ÚEì l3á¶È,B°Õâ¥ö>ùO–Sàöì3Mq¤C$©1?iÂÄåœò†HzãYåùÚ&éËÏ„e­óWI˜Ÿ´Ã tìÛ–W³\¤÷X ±ÂCç#¡¶\Úî×ÄôÝf›=Éðè5M;úÓ,úb9UÞÚ»/ÿ"~¤Væ³KÚØxt#˜œY£lè¹tåêv\!xk(=õôPzä‘”zv„e'¯-¦=»¨àwר³1Þ×T Ѽ‘cŠšà½|f acÓiw7 ‡Mòn‚ã,.a_V’ˆ질±,Ñí ŽD‘wΟ\-¶í䨻ºtæÌõ´§§%u´‘ç³ÉÂoñåLÆâ¯åÒóôö:ºÞ]ýœ9|§¦çÒåA:LÌÙjÞóØ‹À.w6¬R¶ñSf\`0IçG*áíT455G°*èŧßS‚Ï­€ & ‹Åc/™’J¨Õ¬äoÞ‰6¼:Ü nyŸJ¹Jã'>ùÊjuzîùgÓ©³§8÷| }òãÀ&û‰àBoß¾¨ÊQ‰;É9z_ÿÓ'£ù(-üØ'`ð‹áÌ)¬p]wÝu7•ãï¤ãÏ>kðŒ¾O}ú‰€Eÿ>˜­ç!Í\>˜¾ù'ÓS±]—ƹN!×wôè½µ%”Ð_Uzñ¥çaÄ0+æl•øƒœí®AØ™)xAo*—Q&uÎ \y¨äèd²Ý‘Y¼„I@º٢ǟ{&ý£_ý§7PäYÊ&·‚]£èįa7Ä{…›ï1»òc=BRÂxzñÅ㜷²…È Îvú•îý(­2f÷î3Ÿy"=ùµ?!àþj´æ¶Å{΢ÇÿldQËG<—ÔL&žkØÕ­³*; J|Å}½s½ÿ ëЙa«>¿ lŠC:;å'âT8¸Ñ½Váú‹ö\¥Äg÷Rýãù»*/}´ýÑØ›çYµÊj ‘zî+ñŽuÚU)¿_>ñÌ,Ë~T¨:OÇ—§ø½ÄŸ¿ÿø—|VÖ¡¡ ¯ä—v:Æ<ôÈqNµmÝëX¯k÷o~ýE°ûQæsû˜¥Ÿý®r)ünŸÆy¬štdðtl±¨R(M‡³é|'Æb}:ßÏ~1Waþ{Íœ•W:½BîÌɺ)†­%Ö/Ê„E´²DÎ*«t2Œ”ž1y3ž0&·?ÏÀbzöö¦*-.]8°*5p©v/1ÂiƒóHHY«î±m¯phÁ+ÜejMJVIXí>DÆ=ï28¦Sc›v˶†××Éc6}E•0Zr £ yí˜yì{£ ŒÕš™½ÍÞëÀ³Õµxh+Jƒeî¥Æá²]˜ŒÆ¾°QÆW@: ÚXm <­6ÞbÏ·€‹÷wáé>hKCÒ›†Ýø*NÛ ÁjNçáù+ïþàølàn“µN€cŽ!îI#a$×rdºzøh‚†Î/Û0º¯:vxw±Næ§£`c‡cH\ÑÞù ïÆ"÷Ó:Ð*ï]d¯­£2£•€­yµµ¹]dey]‘sgl˜X lC»Š±ræ2M{3ß©³Æà‰gŸ avm°BÔ*¯&œ\å8y·©xÓA¾9—Ûwº/Ê ƒ•Ë!£ ‘(ÅR–¹O/ã>tBH¤Å13Þ ÁqtÀ¦Hóò±î‘÷ØÚr „.ÂuFÛöÍ@§"î—®òA¢Õpté¸4àjìuì®ó»‘6ª¥f ®É¸wWâD+g|éݹo0Ïy*ËÄ/[{o|Î>,ð¹gÙ6³ÎUöÀ®¬ h^ºÎÒŠÎ×$øƒk §+ŽÎ*:0]§7 +ïsõ¾Óyú¬ðò V ¹m¦#1É}ÔH· Îs.;°j8NžçÞëØð DíÙðI¼Ì½${Dµ%-*k€K•IpA'Š´ë;ÃQ½é —˯ jË[W©ñlOu‹¾Kç 7gþØ:ƒ´!Â7¿×¹Î:Þé9­:Fëù]˜I:°­ÒrYÆ”°ª YǼt\W©g:éØjÖǼøœ×ÅüÜ{P+d¡ð>§·p¸„,©uô0Oy²-¶2ؾÛ}·7ø2Ð-®ºúLž1ho"Ÿà|11Uäñ½UP5g)jcuRÕçž—‚¿ŽQÂG€©ôìî 8)WÝ›O}æ Ž6¸ÕMòxåPc´QEª½f Yz˜„lAx_8QwïíV¼(«ñ…Õ¤îÄ“ŠÍæMŽÙvoÄIžüRÇ–^üc®ï¾¼[¸âðUŒå??÷JŸû“÷†Lu¯ýà/áré| ¹²µÁ Tú›ÀЀ=lbä"Õp+KV§Óʸª»)ó t0“oŒ €C;c®¯#®$‰Áã¡+›]‹fy¦0•Î\‹^/î£gýêRõ~iÈD‹9ÚjNLpÄIž*,ý2§óR¹Õ€îÔˆgUãøè{‡³ú÷ys{cØþ¦œ]º‰|,‡/ÇÑKòÞ*þ0“Hð© ]X'¶³ƒßàô,ãË _×Àä¡Md û£fË™¯áwwOýÚgÊùÝ"ú±ÁVaP…¬²ý´ëS·ñþMxˆ.ƒÊBs Tƒ-ƒ[:¿ ®Ë§½¸QUÁ1S¬}^×ĺÅɹY>2yª.ä}šRoßn¿$ ò.éRüfZGéÙ÷(Oäm[ó[.Qg¯ ˆòÛs»å¤9yœs]&INYZ€ïV°`Õ|Æ9ÙØê×ѧ Bʯ­µ8AY>Æ~ªë· ?äeê×V\œšœ yäQVQ+g æzŽ1Œ¿©÷X®ìTב¶\£Õö9é…0o?ҹɑÀ­ÓÑï±7FfÂe²“tkRèÜUÄèïæ:¨>U·š$À`PAÙ12rƒÁSÚµ£Ú_¦õ`œmA…y+Â<ÓYp _åU“i’sšMú©gðë‡æ;Ë"á(Û]Ú*Êjçh¨ä­BÅÅÆ sõ ÷àâ…·£ÂT^,í(œf°#¤»æD¡00Ù¸ˆ^"-N[÷µ½#“Ï€« }&nÌPa§¯­h'EÚÖÂ[œ'ðhàMȆ±·ùH’HL@ÿšYñè 嬲K«âYq\66O{Üet)µuø)ZjߺAÕ*h^êþi_Ù¶V][Ú˜aB®Ã×ãÌjÎ$nmk&øZÉÊáS‡#Üç— r¶D`R»Uz§M§k¥Ç!Iïœ;KbfÖÄ—ñÑÑ•ÄÅú.åzƒ8g—,iKÙ =©®"¯0@"Þ€üº>„—ôk°Q¨É„†û¬ÿ#ø?G`¸P×_¢í*—ì2å§s‘?Ê'ýYÛ(*}/ºSøRx7¯Œyxsö®}V:VGòþüÁÏÔCLh‘ÏŠk¾ÃqC{zŸ¿óáG?ŠVú~õ¥ç"©ÛÄ© 7h°­ºÂòþ2ݬVÕTe»OÚq¾«]À$Ê?ò©ô‘~"öɽw.?𧼉[gIÖ²ø§ŽÊYÍ‹à¤>ß鱩èîÛè@Tw{>ð2sš&‰cxpŸjN²î&à¶û þ¬Þž]àwC´Ú¶µ·ãêG°Æ€e9x:G…»ººÇIIÜ&o‘-€k³à³A÷œd§ᑾÕÕÉÃÇ^H‹Ð—GK4À#LhbÙ[êGnë*cIۡÕÃÓPš×¶°gÁs“J6Á{Ñl þä>Ë·ÔuÕ÷—}ꬑ<΂uñŒ6†²Bù¡>m‚ôáýÈ1Þ½®¬iw˜˜Ísëè찉̅·ºóºÂ~dŸ‰]ÚÓØ­&—hÕÆÑÇaò–¶šEtr.Ë@IDAT¶Å÷åÕµ i×`Ü=[ÜŽ î½>i>d>ëWÏ@bãú´AHz1Ù8'mß³M sƒsÄ«8úijl&Í3V]ëžTݰz%yƒ*ûžþCð4’?I8hÃo·¹:7pª^&l°ù9ª©‰DlÙJtMá]ldŸkãS‹KÄd9÷×P nbùÉ37ÒÖ!º¢ÂL‘Ü,ðå\„_N夈é;Êp>x±2u¶T§ö"I&TÊ8"la¹@•}=GÁçëèÈ¡C<_LS¹+IÖ…—9ÿºæÎ®=8‰½ŽŸ¥ÙdörYýŒJr}] ØÍÛv:3y„~ö¦þnâßŇà±èÐ~‡f°Ù–WÙ0º3·Ö(¹é—˜¥à4ËOgšçൡ`Fe–÷ÝB¿Ë€¬ô{ûmÏéIýèÇ`(v¼3ÆZK}}ýéç¾üóTzÿf×ÇÿG¢©;7~ò•Nƒ½_£Z¼Ÿgî½÷1gñݬóøÂøéêîÁ-£ZüÁfÚ…²V<Þ@àœvíì‰@²­ÚGF†8sä£0µÚd ½«{w´ Ò1gûž¾Þ~œ“0²¶>ƒ1`b†4§#Îv¬øà‡ÈÞ:•ö Ü×.GŽ0žmkÐQÁðµ¯{úÒ/ýÒߌµª« RõÝ <ãÀà@ºðôSJ:`–8yËHHh댿ét™§-ÛžÞ=é䉴·¿˜>ûø¨ ¾;ÚÌŸxóœ<ƒÚÚó>ÿà«¿‹ñÖ‘>ÿù/2FKì|PCÃý{ä‘Ï„RfK-çCûmö ¦í^ºï8>ÿ¹Ï§çž=žž½1Õ„?ò÷[M—æ2YBcqs“,U®ááá¨ðÐR!(áHüñÎ?ï;€æ¼ €JH⇎ŒÛ÷÷öŸ¿ßBu©¬4b`wvv€—ùŸÕ` yÆ}a@3ˆ¼ÀÏuPæ¿9gõ—sùÞÛç]â?þèÎQ>›+õFÒŸgÍh4 ‡Pyÿ»/×yûœÞý÷ö÷Pæà?ÅÂY¾ãØV™ ÛÌkÕ‘0¢¸WãÝ÷¤ÒH2è«‚îßÌŠWnÈcMzÒùd’°p¬±o¶dô\®! M«T½Oô!¡ÈvZð/½ü5Z£Oæ¤, ›‚ì:÷Ä •fÙ*²Þo{t 4åDnM¨Ñ•Ûê©\:_çêXIÈ3×Zc³Å!FŠ·°°ÛN9J÷ ”¥Ä ƒ¬¼Oçƒ-ç Öc̪ДҀ³âXç¯pTÞ¨ š‘,Þxž±™ÝÐ ö Ku5ˆ§'2¸Õ3Ø*Ã:²ã޶_ž´%ôܳ9 Oƒ¼}ÈyöððP†:[ Ü(‡ €˜tÐ ??xèp8«Å ÷e ƒFüf±Pߟ&`Àº„·cˆƒJ\°-Öªìç ÀÀr½Ð1¾¶žl …žñ2 w QÞås:g²¯£ãµ¨UÇXÄ¢ào¶Ý6xê9·ÊÜõ‚† "üîØÄçý;Þg5¤-¾ØRöÏ6à4s·"L¼ kaô ë;ÿ iøT{íü½×9 rßÉ 1_f͘dÌkðK§TËÉÓÀލRaŒltö]gžAêhÆ{léìÞ cVp ‹-â¥:·²ÃŠõ¸_¬Ûl}×åe»F–Ïšñ¾J¦vü†æš+*tRäµÚZÎjçfdùƒÿdœI>Œ“ÇDØù=rKÄZì&Æ­°^ouÄ5q>×ô;58ïïÛ·õT¼ß`³{· ÍÉŸgÁ[/+LÙ <gp¸Àó¥a°½Ë"³œ{"ˆ mß{ßhÑy%žë&xäy3O¤Y“!jXÓ*ÎÒÀÖ­ÞzøÕÉÌ£Œýñìå¨na>j†aüò>êGŽMúðƒ´Ò¼˜NŸ>¼2ý‹oÂÌó™mÑ-Ðø¬ˆ~2zs”=%yºµR!dÐå¦ÇGp` ³>×#©ză÷-â—‰7ꉷŽRï§ü\è½Vb;oß/Ÿ 욤°“¶†óßV ‰Ç:›k~ˆG¹{Aý-ð¸ºVÏ’›ž·’æ—<¡Ù±Î¥-;ÜälLu,×kP±tÙYœ“g¹?V}|0+Þ.á(g½÷ïÀ|§œÎÃVÔWPá°Ÿ01d•÷‰¶O·óü¶ ½”býòƒ-טŸ÷JÃ&„ÓçÛ˜Ó±cÇÐ]ßdlè§<ãì€ÊÕ,›Ð…ðŸd Æ \A;wh™‡L qÎp d¬Ã„û€}®”‘“^ÀÖÄÚHTàE¬tbõUŒËý:[DlþÛîI°>+®Ëçt´(\χ£š;t*ËO\„ô,ÿ×1©ƒ\½Ø®&â€8»JbO2‰ñææá<«ü(èÜc/|ÈgfJktNå@´@Õ5÷Lt÷\=^g¥URø;âò%ñÆgk›HcÎÞ[ybRK sòÜd×%ÞÔÎ3°ÙÜÌܘ¿Évýɼ‚'$ã"Ç­pZÄ‘ÞÖÑFpÀ0€°ãT>’ 6õöï#ÐØ‘F‡ÒÏ|åLèbÏzzö"g– ÄŒ¥ƒ‡*¼ÄWJ ÝÿÀ‡8:à,É]Áß!˜#oЩYÿæk´£PGÚbÎÊ;¾«8cµcíRá.d.Ê>’È$Ÿ«ß¢²cgN<V<   DçöZ§œbñ]aµG3º¥‰'Õ8>Õs._ IXÜ£+Øðdñh+]åÌi»:ôtïš}óÍ×cì#Gï·ÐO ŸhŸŠÞ¤ƒK™.ƒqþŽ-ÏÐÙ-?Ð>V~©·(¿LÎVÑy¬~!ORIO‘ÜÀ&†ì6Ò]éRÞ‰3ʾ°Í¡Ã^xŸ€|×% ¨—Ê{üË9A Œiµwm}‘=݃“q:ÍLއlÑ®©ƒÆpVëLõLNù“Ahñwsåâè~¥‹ÀÄ88Mç#ø*ÐÆäù“û •Ë÷!«òƒ-ØÒ­óÞ¼ÏÏÕ± 7‚/ÆÌ °p0øV¥ÞÞ~ô<æ…¾¸¹µ#u÷€FžË+;vÂ×å÷•rÎ.Ê]½ó¨Õµ¬ÞÙF¾eº7HØŸ›D§nz)x¨óܪUçæ\í\"þùœ»P¾ËÝ{gÈá#üÌÜÕ b_ùXÝR,pÏ••ž±¶Frêì°òŸ›àeÒ›2GThÀë­óÖ pß]›<Š*pèR¼²ê+'ÑøåX„éÐeÁÖª®¬¾#óØ›*\@*Ö#ð%Êu×¢ï ì•s«ù´°–¨Œ§.dŒ‰0“È匼M˜LªQŸŽq¶ƒ÷{ƒGØ’_;„ `Цz† 4P[±¼H8Qiº MYÕýä@>Uò´ÕY=û\]FãûÉ9¦þç/öÁK®ebò6u4ÜEiƒ¯óçÏ¥ña*ƒwÐ5Eý Ü—¿ÏËYáNUå€ ò%å¨85‡ÍÒÛ»›gÎwG© iqŒ³±»»»Ò4!¤Ö°Sõ;4’ n'ÛÏËK°»W&(‰ pGÖU™vv¶26Éw졲fG'¼­Š@!¸%oP7‚ab°&æ^^¯<÷Èí¡ðyÈߨ_÷K«ïAÙëÑêÏhùðPe Cqr¥&µÊƒ%)e¼Ò¯,#‘‰õÄ ¶IfŸJ«ÛU©¥£\kH“øàM†˜Ÿ')€9îëë‹öí;;v¥UжV† mö’ªõHÆÂ ´NÁÜÆþ!ü[MÐ7<õèáNtÀÁ46Gç)F‚Çi¹¾³„~óp#¡àCŸîa]éõstWäW樷•Æó»Ý"¼Ð'ª|°@ÛPùÙÙAÒÏy0ƒ¦sÐ]Í¥IY|ÙÎà“ŸŠ«ÊŠõrd庶iŽE™LhÞ,~6}…,$tƒ²md5új=ûÕŒnç1‹&c çTYIhçuº{ì’sñx+“Un»»•n§2Ää?A—‚âöÀÅ-8~‚`pg)1ÂÈæ«ýT:*tr©4Ü~yŸÎtÏ„ÕÙÝÛÛ•4AÅÞ®yÂÈJñáÁk(ieÅÖÄ¥ûnÓ# ¢Î‘ªm0YC÷öKEA#l7™.×®]Mç/œM‡ CPAý]|†•Ãh޾Ã)€ão·æäÏ*NV&šíØB»·{ï»/Z¬/á ¹4s!æÝÕÕzûúc:xe:+½Tä NØêÝ- ?Âùá2\C„YdD”V¡ß³wo$£’Â÷;·>ÆWñŸÆ)ýä“ÙÞ£éÒk \´¶íÅëå—_à|ò‡¢õ»ŠW‚#,8“Q^&yÇÁÃØ3\ f'fÚúƒvô¶YéÞNÇî¾+ÚåËçù»{3´4’r G¦çvùùí—psOt„>|ò, 65m¢HÀ™‡„­÷|èãéü¹s@mmmƒÑZE™‘Ñ„IÀÀŒäPTø¶ÿW‡pøÎow~x?B@§R^Ó`pvb©Àüp—NK £hs´J›ÇîÀ3i0Æ_ÃY<껜ª„‹%žöýéG¿ë{9ä>N~2;4`JëÐ`/‘Eé³Ûßë„Fßë¥P“aÜ¢qÇþòåy{Žê\„·)ïÓ© ñ!ñK‡Œ²@%Yd€Ö ¹Æ˜4lÛôÁAºŒ±êÀ݃C#$)íåxxd„ççqRÔÓ®j:ÚŒ·ÃW/¾s!:D­Þ˜Æ`7¿ëWi¨8~ <3*Ç·Êo‹qUðÍÍL€wꈫ­&xƒRJÝe(ùÂR¦Ù¨:à jðtu,„F¼®ì¶Äš…±Ú`žÂܤ´ø §­Šç™£öíZI¶/ë\6ˆÚA%ºYà:I: #ß±wo/ÝN}ýûƒ6:¡¹ž{6ýÚ¿û·iwoo$nw[\;/÷**oíe8· ¥mTŸþÌgã|è—_ü6Î…¥˜¿ žó.®ÉßêÙ?rÂv˜XmXÅßtØZ…o‹_Ÿqí ±l‹Gèzœå¾ø%ôVðý\ýóüöoE‚–z¥4´Í³^¸ÄaiF¹ëßÅ+ˆÕÙF©S.°¨Ì3¡ùæ´´ïÀþÐM2}çÂ9 õ†Ù×Á ÷._Ð*c8÷% ôö]]éoýÝ¿GÀô|úWÿò_¤Cû ¨²&õÎuøù*pA*¦õüÐüŠ´ÉwƒÉ&;Át¡ÃuJù¼Ki}™¹WrŽ$¨CgëUç•.Å{Ér 1AºÓé PþnÒâÐ}öì9aàýåün»X/ý‡JXáÀÕ‡¥wq_>gR~æóVœ;Ïõï0xó†ünGZ‚Ôª$gÑó—¸Oœu¾ry@®Ê`oW:›MÙ±£›d˜²4:Î3ðÕø•AçØbò–2áú/ôL™Ê<¬Iï&…Ó—E™¤°D²Ü:r=‚üÀlGx;¨d‰Á.Ÿ™Á¡­cÍ* òô‰ñqäËpœÁÞN‚À‰'éèµcÁÎQmeëö…p¨]ºx)wâŠ*ÒÙ8{Ömµòb ÈVqû:40õºzYMçN¿2Â.gÕÈ_ƒvÊÈQÇñ\MõKg¨|°¢lƒ€“ÌzÙVŠ<´»DTZòŽÌtµÄb&ƒxž’•ÿáèDŸ1iA]kj†äuÞ猵¯Û@½r;’pø‹q“8®è?ÆÓþ6p²ˆ1¥W;¨¨‡Y¡µA"€tVÙîyäÐ!Ϫc°Qñ%4©Içx$iò\à±v»éˆ¿±ÖGSó ùàVJz’Nèè$(Ž 3­,rŸ î+C¢ãÌ­÷ØáÂ$PQ ô~fšÈxãWâh58)Ž´íÒ‚.ÜpÈ7âì_¤BÜdOuçÀ?¸Uåá â4ÿ7çƒTÇCÚ¤Ó€z’xæ=s8ÓM2ª&Ø®ž®^màÑy,Áo}‘Æ–Ãc .Ë3¬¬‹Î/ÁŸÐ­Ñ <ž'ï°|ÆÞ™Äe5¼¼¶®¶9æ­¾¢SÛ}̉“ê)A¨§2oöS¥Î¹Ê8òv«#Ý“(Õ/£uíÍ Î‘–gz.¯I«Á?Å'àž;07Ñ– 4iR–òÇ ¹ö[Ö„EîRàš´=Ý+¦E,÷2‰~  Ê”›¼Ç†±Ü 9>ãüù4ä…I®ÞtÆ»;hÛ+Îkgл#tùggvÛB]Q~ÑÖ¾²Ýãl¬˜6‘5ds)uê2 g7ÏQ%*Þ™”¾®Œ `º&0“I÷l6:ǘ*/ØmÛbÜœ"нÎgë$ê€ ctÐá¡JžÇ¹âÛØé¿Û6ð4± ¢ XÂkÄ%¹™²G»P~"½ê«“ïÍM²økM:'õaÔÉïÁñVþ"l _„8ç~Àà+ÅåK&ölaÛš(‰®Û ±åµ ¹KðÀ¡±‰44çL„ÎàôVC˧¬@çõÌ•õ0w†š©¶nn+¦qІF§{ˆÆ<8Ž ^[FRR#mvvàχÿ˜ fâ¿z£|L:sÞ¶¨¯¬" NBŒIžÂ²É²e©ˆM³sñ#TnS9ßÓI¢ :ÃÍ¡H˜Ù$ÀÞÞ½{¦;M^çø":&m®.‚“° Ü/-Cñè÷vCZ'¡³˜7šRïžæ4}Š£j¡s;¬Õ°o$aMÓ¢¿©@"À Ç'‘,1FKïJ’–ñÕ5 ç.0¿›è©´mo/r¦k©^§•°%Q{¨cgv™ÇN¡ßÌÓ)ù¬m¨SWLÍðøfxÚΞ¾ÔÑÕKÇŽ4tñï¬âdTË.‚¯©ªGoY݆—áÒVâ md­øŠu=ÏÓ¶vÀ¼~by"dÅ2•û› ‰ò×À0b#MAþÏž9ó]ú–mKîñ"8¼o¹~}$’IŠMõá«Ú  ûG]ùþ ôd Äz²Ž~@09ÛSø,¤¾BçOÖéD'G2X8 ß•‡™´·,žøWéŒ/mVm9u« €1›åˆWÒŠ¹é‹Ðß$öB {R…Œ²0@{S]o‘=³J_ÝHöåÒ†|^|7ÁÐ@¸‰>­-Lª#™¼g2ãg´÷Ââû¥Oþ†Ö|!p¢t|ù¶ú°:EAÙÅóÕÈ[õØH `­ÍøŒIé'©(ÇçÄܶ©Â××Ù€LUÖé*À˜ã“£iü¹›ÑU¸™˜žz±º¬Ún\2s$h’.yçºïqC"ðŠlaá»ðEºUøGÈ8‘™œ:sŠ H{84²KÏø]ƒJày°¯½þjúÅ_üåP¥û¼Çñ²Ž6d·¼ñÆ«éS´çåhü¯±¤R§bz×Ý÷¤ßÿýßM5T#>t”û‚¦cÞ>ãØ2†»KÏ=÷lðÞÞ~˜‘ÿ|ß æþÜ·ŸÅÐM}½}Tt§F€Û ›«W/ã(™"X¿7ÞíºmM¬ƒ!;³#±ªJFOvíï^Î!CsVµÃRƒ Çš+œô ELçtâWÒÏÿõ_eæìÙ·B°Èôξ}ž5nSyñ 0ÐOpÅÁ@ËF€A“½{{HRH]÷ÁçùG0V¾NqÑê€ÃáCûH½Í~q&*ÁóN-fk»&ƒ'gO¿3X_}ùÅ8k^Ø;Véòç‰ ÎÆÄQY±Š\CDgli}~×¹q%R¦m5aœ£ŒßIøÀoy¼üÙcdØssT«á<ð*bvR½ÁXÜ+—oðÝ`ŽÕTN" žêˆ^­Ã‘Ëx-’šlçÜRS 瑼ÃLnÃu´Ñt^iˆjÈgg±Aäs:C·1,Ì¢Ön[`Ûš…Ól]f…§[NÊ £Ê©ŽS5ïסaÕ¦N&+­°××Y ²Z oó~•Þmœ;:äÿ&è_ÓÔ§ƒ—ÆAA^å€-¼Ãá7•"ÊÏMÚaóQ8Ç*0^­¨Áp3«[Ãz'ã4¬aà7˾5Ðrk''÷Ç HòçÞîu¹—S8^{íµX¿Nú7^{ãs³°ºbNÎÙ@\®0ÌqK‡»²Nxh èè G30O4à5–•ÑŸ!×X»ÏqÌq6ùëâ]Ò¯{A d\Ýšíðu|Üö™ì„1ð?Í9a&]­isa£1TC€™¹#/ ðDE·ûþ9'­:õ¬ppÜŸ8³—ýÎ+dï:f1ˆÕtÒÔÔù…Ï— Iò/«àå/Εkñ=}×YØŽÓ5ô´Æ [$Û½Á$e†øÎñ?<^ÀÔ[vÞpæa±§î‘<7Ë ƒ—ì5/Š*\ðUZÒ1eõO9¸jÀeuš ññ;Ú+Ê 81†x,m;¦ís둻οˆŽ®Ì^¦5µIKV¨™°bijt€¯oò°É\ò¾ €j;YuøñQd$ÁÙø‚Ïšä•áÈkf¯˜NœÝÒˆ{'0@£œ6øµšðâLJé6vƒ®V’L“ŒÜî_„ÖBŽ‚·n¯|ëÀ¡#±~“àlQ|ùâ…H »ëî»YzYº÷÷Çï&ðyþøÌÁköxvÆÊ4ƒ…9(çnÊ+Ù'+m öëè3a  >ÍFÄÞ¹ïîU´yGîzºÓMœ·H›!îYÈ2öw™`¾ö™rÃP>ú Ç"0ñLÛJo½s‰õCŸj¦å¤UêÈc m{ïžØyÃVͶÈ^¥ E¸oÏ‘/%ªÈ;ÆÇ'¢ Úƒ¨ä Xßml+eÛ¿Š: HX­#~̉ òao°]þbãú»k ™ÎúÔ¡”åî§zЏý^ôL¦ñ¼Ü_åš¶öÄè0²†£ÌîAW_.¿ÃQmÜP¤‚нXAàÒŠz’uG÷t÷Òn®Ký¤*¯é²QdËràW+¼¹غŸÊŽà¡ŒÉkƒ6M^)•ƒ:MµÝå¡ÒŽ|MåÏõ/ÆÇM B‡c~ÚÐ8"¿ÊsáµMWšéÙ¥Ðe¡ô[¼Öv¬8A©àr|“œo+Õk‹ò¦ Ì« `ïÀÑÜʸê1Á;˜ixªÞaòeð]gÏÿÙqê{}2 óç·ÌÙ ” "uèÏžY ‚¿áÆX“2+{7 l1¾•¶óèÊuéB]«Ú…„ƒßùü ã­¯Y) ?ä?e±Å:"›uœÓ*¬B&ón>b«aƒA-8‹CÑmÿ¸ˆñ¾E*Ð<8ôOpßÎW-¬}‹ª]Û€WùN‚¢&¸hSø¨IRêéÊ íByb#[vc‰€1ïp|u€ íË´Ÿ­Å[/ÏwQê}Í$[XmX ,ä­ 8¿ Zý¨ $QØý‹}PÎÆðIÉ#NÁ&¥º[–¿Ò‹óQnÔ“P`‹yqÉýð+tHöanÄþE"´oWíá¢Å²š¿¯­tÁ& ™ÏK…_Ö¾E‚½Þ«¯©ÈÚù˜d»xŽ~æî<ÛÁ§"üß@˜niÛÀ¤ÁgÛ¯ #MÌ)§Ê0žaÏí<Ó@u{-ݯ j­,so……òÌîBâ¨x4 Ô3—6ö×Ö÷Ê…ÀUæ>¿D2ªa¥5çí3Š·”×oÌË9ˆG3ðWáãÄ—];Ú°‡Ðá« Ù nð[fCòB9G ’Œp·â½†9«ÿ[ÑkPÎîžyl>K]b½Ú¹RQN%›çñ ÂÉä7ñZZr¤Ëyø§2Ì@»HcହÙ ¾y€ø¤`$õr?—>‹à–]8ùz`ïËÀ§]áãQ É­FW3qg‘“üûª]QU‰ÞÀ½Žï3&×0ó¦Ò'?óBþǾã´)ãØ"hh–¤¤ñVÈI€i¹a[pÿ&xo%ªëWç«Â†­äSN$¨V°nñC;AÙäœ ¸kÃGÒcºn«.ÝWù—¶²IÅâàÌëcê•™”Qeà´|fƒ/å•6uU¥[e½²Qž¬}píÊž÷`ΛØëý¡'QøSƒ¾‰þV_/=ÓqÙçqê9@!웑¡t<üÈ:a*^þàK¨çk ÿè*8]M@ò—®Ô‰”GsœÍ|“Öü&ƉÕØeêˆ`B—:Œþ(*ÀÍZ`nÑØ6ö~]C ëvœÏ=Ž<ÆŽ3HggšŠªæÔº£ú…fð‡ ¹†Na’Çúêà•¿[Kq‰²ÒÕ§šIŒhioã^;ð¨ß‘pÀ¹Ö¢³ôV¨Š®®B'F—' òÕ!Ô}07Á[Ãb¬°àÁC‹À“ ç|FœçÍ>zö·¾y¼ЮÊc!G‰£mKÂÀc[l­®hbz²¾Ãòµbs;‰´Tž£êªGw¨=ÉúCî¬Aï•éâà•ÐÓ‘meàµÉvÉj…·45,¦ótkª³Ó…Ç=ÌE—… £ø®¥%y¥x,«W—Á§#á ¸/­”c/ÑõÒè:Xg™³/Y>&|US$cÛ‡ØÉì3è‡Sìu‰EÕU ؘí”øltrƒæ*Ð,“€UOW;ð(OçßåÝèûàÃèÉŸ´;W7_ ðÞÊyé7¦çÓîÞ ]X"¨OðµPW–ö6¦=;ámب¬Ã¢±µõMTÌ£‡âóº|æpà(äL1µL`w]ƒ´F^Ú™BŸ‘¶\k •Þõ­ Aó2*ÊéüR¨Õ÷X7ä:2B:®‹ì‡Gµ]½r >B¢>KöøÈÍ´i¥9úŠG²ØÆvuhÀ/$^ÉMåÅÎK¾#þ™ä”í`™›Å…`62Ä`¾>Víf“´å êQ>ƒHrèð[Ù^J‘÷Zô²f'ÉŸêjËÛ-9G·àX¹ ßEט…¶´­ü»I%ºâx®x¾*`¤îUnX„¢ ëÜLøò¨7e•‰yåÀYût…/õgy±z—6ˆkV|à˜2W½]úl"ÉNý}F wκRèÕø1rWéš!ã9uõÿ€AM´ ñÃÛ€ñ8<Êcoœw|ñ—¸¼%ƒ+~¸óÏü•€€U]VëÜPa¹ýR™’¡ø¹çë³êè<‚ožx3=úȧ‚ÈDP,ÿçû%îCºß8ùfºxñZ˜?¯„•½yF“Fe__?ލbzíÕ— ߤÒt7Ì )Âå{­ö6û>ª•F €?þ4•7Soÿ¾pöÜ®ihøŸBõøño¥¯|å—Â0 F3<}êdzáùç2P"¶zÓѬÃS'u_ÿ~˜VÎR”á¸Jî¾§´¶R +;Êbå1ßÒœý® ÷Çp^®©™V<žÿíï*$±¿ø¥Ÿ¦:âWiUÒŸ¿òâ³éÊ¥w¨2h¦òiá…€£•³‚}hd c&&Çb<+$ß¹t1ª ¥8´È¦Æ ?ŽãçÜÛçQ:ŠQE¡ãå Îß¼­ÆC ‡éêÔ¤Jâ㯧'_§2ÿ~Îò;ʾ޶Tœ>s"õ÷î£ðÌéS$ ì µÌܵêh´MýSO?•>JÛT“Þxó5Î]ÿDÑ’ÃÁû ž¹fܶØÊŽÛ[ce^ü°Þùá¯8äMe8ªJ4ø“ŽÎ˯nçk: ¿ß¥‚¸„!©BG·Îºä=Ž/¿‹@?ÃŽQ m™ˆrˆ1£Ñ©c\¥×êSƒ“Þ«ñc Á¬Ð’c`€¡†¡÷T²u²Á9Ä:î厥²ZƒÓZCË–£: TBwíèˆw.Á‹ÍÞ"æ§§“Ì÷){<6³0q·a$ç6l:Î46­^¯#ÃÒÀV’£PÂ"C™wX™cˆC(À:4”= (ú¾ƒ²ÞïÙXÙ€ÓA¡£F§A\ƒ¶¸V®X-¯A‚ ŒK;4 Lá)M\RéEg…—b +­T·u8¬cÆjÙì@Èû‹C ‡ŒÊ¹ 3=ˆÃYdU°­–t€i„ÍÍŒcüYeLÛÀÏÎŽÇ^ÌqÆãôç ’-¼¯¯g_sÚè`¬©“*ÞyŒdùm81 8|q]¿6Î ÏXµµ]0^Ê ^8ÃÜ'åW8’˜ž|[…9‚–>þEF/óöXÀ(Œ& %ñ#órƒÀ‹çSyT¶cÔÒê;KñP|ÜÓÓEU"g;â\Ô(·òG#HZèþ¹gÓ: M-â$`O¬2oª&ÓF£ŠŠŠ5œüûèê¨ôÑ çg:uØŠ':suÔlÓæ® ç Žý¨2‘çˆNZ1Ò&`Àð·Š‚ý$ÁL‡ï°שQcB—ÁNÏ™Œ` ø¡ÁàxdLGᬠÌ«Á«A·"n`$UƒD:g|#‹lài$éÖ‘°Ë‹‰ržÙ—Ïcï½õŽhù©Ïhˆ•ÆsŸùÖŸ.X1ùÆk¯sL/°ÊŽuñµz¶Å¶ú‡F²|Ãs«u&‰ÛÀCÚá„”î¤×mp«™3ÿ®\¹ ÒõYô‰›T°HJM:¸¤QùŽfg-^Ô²Ç@ðšÀ„:ø¬êÓÐ3 ‰?&.y–øm÷¢o½­æ ŒètÒiè\"ê hâlË‹/À/8”äÐ7O¼‘^!¡ÐŸ®_£’ú|T0¹— lÙ(︖z÷õÇþ¾9òZtƨö¬>ßÉ¢M.+‘ïE‚ kþ‘´  š´C‡°ÇÔ“Ã@fòmÈ.Ú¶ñõð¤pŠ2ïªEq›õƒKÜ÷È€DZÿx8ðÏàˆúÙ¡#G 3äëâ8"%, ®ã˜p>nô)žÉgcžìƒ{hb&·óIâÿ1€8,,úúûÀ=Z'ò¼´{Å&á7&˜(âxÒ½I/ìƒu^ëÔQ¤ ñ2¹†WÅ¥õ sC^dUŽ3]|®Ì‡˜xàüåÝüæ(€AÇ„kâ7æa²DNtFêŸÂ]Þ¡L°‚H=WÖ™@@‘~ji}iUy¦£Ð£>Lâ“•íÒ¼Š"x Nsp¥À:„…WÐ Ÿ›èä\„„ë×¹¾î+£åGÎ[^ArÖ݈` ¦A¥`›r×ËÈß8™w¸ÑAêÞ®,@ÿL@'Œç”;»Žñ)ëÊmåg ¬£ ÇŽ÷›p¥bÐÃ.BG´{éñ:7n ÅøB§±ë>pÿaïiõõ_ä=¶!·-lî–²{Ïž4þùn;#\…˜¬ÒÎ1=&Õè/Ékñ$’\7N`»Ï„s ˜¨ï4QÕ=§Á½km! Ü`té2ªÏ‘ßvµX^ãÆrö/Ó±ÂúÇ–V§P'†ŠxWö{ÈCìþaÕ±º&t¼"~y.ECT;&XÝçüÑø.n9yR0;ºØˆHìˆúhT"¼šª=ï‡3/÷Ø eN8y™ Þ«¶Y‚<òúúS~¤UÖ·¶ÂQ̈8î> 6¡Y<°Mo=v¸]Z¤ ô0+ÿk¨ÀtßóÓÐó2ð ˆcoüÜc œ{K‹g‘³—ÜßF‡ p¨šµ‰¬€.Ö®bâÂü8ü‹{Õó<Ý@®ô¤lö¨7+ìj`âñ ÃTUcOÆ»rÅ»÷—óFâyì-ëÏ›"C·T†b?xlƒ•ð⃟«–Ë+sTŠZÀÒ€ aÀT¾4lÕת½­ÐÕìaÀDú³‹BNœmdüõkÃÂT»GÚˆ@>;È—Nùÿ±/òH“?ì:#ž˜¼âœã8 «ÁmïW®®y\¤šÕ@¸ú¸{Ë–E‚m• ‘_šÐ£ýlÛßœ\ow£"G\tDÕ~>fÎÜeí€Ø¬Ø3p”ßÕñ"Ö½äŠ[Þ'dû¯%ðžÏ]vL4Ó?yú:4:‚ÝsL/R%òzW‡“— D0©½ÿèvÙ5æ ŽÊ?"HλÔM`ob “"5fóCüãT½ø®.ÑÚnWh›=5E$n´Ë‘ËÚ!ŽØG5Ñ!4•àÁ‡Õ뀷zç2pœ¡ó›FƒÿH?¶|ŽVùw9ô#þKk¾Sܱݿ4»…N5ÇóžîÜ\«ô¨NlËðq|Kи‰îñäö¼ ¢mÎ{´c—€oNÓ6CÏßåƒ&7cÂæ÷Œ¢,So*‡öL” LÇzil"…vîÔÄ8¶o–î›üV^¡î!>®ÀóµÛ¥)u Q[˜§"›G¿‰Jc’€L^·"Þ„[žoãƒVþµã ßÓ½;üRã¼kD ÿS§¸>j˜—´(n,ZAÅöâú´fç}5”oP•>M¥9ì›$t$øœ´i÷¤ÞÞþÔÕÓÃ~âA‡“w釒f< Ýã:´Öç-d£ c~”÷T¦öÝGзMÄ×w`bÁ牗ROß¡¶Ž^†§‘pBÄFôɹÅ9B.Ág;a ðÝl pkc“3ë• Õe©wo;ïßJ¯¿5LR=ø„<ÚX-O³àë›O*ËÄôöܳÓW‰)@f4Xkl©Kãó›in{^Œjmß…(wi˜cXTjí"Ñ=I€~j¹'uîîŸ8Þ W^ÜL¥ îZw’$2xWSÿB_ŸU®ÑLñ•X (S_µ8Îýðg µò.ÁÊÂöÍöýc³ëi„sϵ[íôbâW‰/hÓø³|¼Ž3Ü=Z­¡ })q4Gûcr…¾ õ;íau¨Íð›Jwe½¼HYßÀúê±EôÇmÁû¤E>æ-PÉ;ô·©JcÚÕÒ]$—Ø[\“®í¦=§í'÷K™]GãŽ]Ý¡‘ÆˆÔø„‡¾HHËÁÃÐÉ™€°‰ðƒYà¥LÒ&—&MV›&)&w„Ë3N<¼ƒÍµSNÁÄ5ìÀ²ó7ýmâ)ƒ¯0YÌÄy×V<Ñ7² MÙ1gmMùaÂ~Ú÷«—è»T¾ÚIˤ ¶wŽƒƒ7Àß:\¶§‰±!:ˆÞ€¨’Ÿ"ÝpØ©;×ü•„€„i9;As ôv@¨øû%“²z;ÎDï}ø³÷@–f×}ßíœsÎazòÌîlÆ.°i‰@,Á$–eUѦHYfYV‰–KE[,ÚT4eJªR (B¢HQ$%‘ ("ØÅlœ “S÷t÷txsnÿ~盌Fär‚$¬0˜íî÷¾÷}÷ž{î ÿ.U,_&˜}ñâyªoÆæUX¨ÀüÜÍÜN‹Ÿ?ñ?”þà÷¿ÀÙ.Ýáèú¹™uFºPˆ*ÚžîžàÛŽMGÍ—JWƒ)²iɔר|ê©O@ñ«ÿú—ÓþØŸ%€ÜÏqŽQÖï=þèé ŸÿÝôüóϤÇÿ`|÷ܹ·Ògû³œµ;’>õ©ÿ‹s1ŽJnEVJæ³ü½l‰ëý}ùŒ[Þü#~Üþ¿Ÿù}¿8çŠçx»ü=­¬ÛóÏpŸ¡UáŸúáA!ÿÝôêé×R?4îëÄiih™Gñ^£•ÉH:uêÖ­0e4]¸x.î×É{xdB“qö¹•1ƒC‡ƒ¶¶»>ŠpÝOŽ„49à,tùà‡?F²Ã‘úÇß•~ù_ýBúÞïý!Α¿7è)|îÜ™ôÄcL÷SïÚ}þóŸKÏ>÷LÆZÉ“2‚é—/_¦[ÀÉtüä]AßÑëÃÐüzµ®JÕJö‹/òÌÃñÌêj”ë«"Že¿åå¯;¯ït ¸u|ò{ð¿ezÜ>G÷Í­/e‡ï|;þ­È6¶šË—ŸÝþ}lM_ÊeÁw+:²,ñ,›Ñ€¶‰B|9 ÓUrWs7¥òÌÌS“ÍM-´¶9ßÝÝŽŒ ×jðäõ~~Çö@.LD"”€¡íu–}­öõ8‰™Ü4•5|ðã4ô`’ w.— HÃÛo[ ´*ÓÑV;«uœÐM*dbœè=þFevdÕÁNfœÛ2€€ ”³>5juèócpì:òÿ>Ç@„×éÈòœœ*d–Ai×C0Ϭt¯5{ÙÀÐ:À¯m¦):ñ{îØ2Ž’:PAíâ›ÁžmÓeª®6g1øq–<ùÝ}³\÷9[ °vžŒwŽZyîë|¬æžDLÍWº'²ù<@¿p8S ÷—#2`Ä /{«EÊ¢£€E¼a€R»ÊÀ°« ëØXÖçæ5 8®•gçàõ¶{Ì—Õ8L’µb­Cžh×9}Á¹,X*d²*quu‘„Ã7ã»ýƒƒ¡ó3ÈÆ pzîüÙtö­ì/ìé±™ -¼ !€£’ž :Ê#ÃØ1W¯¤ßü·ÿ&žáš8B @>ÖÚõ•^ÒÔ{p 01yîãæÞ›®%übE‰ÕöE¶fP؈Qö ÓùârìõÎZ^£Ó2!*»n®‡Õ:Îe$HüÛ_û×1žf*^çþä~_üÿ޼{¥ÊýÍHBoŒ¦¿ÿ÷þßôÏ>ýóhÛ%øãk`ˆ$OøðÍ×_ã/‘°5³‰98±{Ù‘\ev= àÙ³g’¢Ò,ªãkÆ)Ý8®Ø3ùtx fs25,Mæ,Pé}L8^ái¢œ2OÿE¹bÀ#æ,/#/œ®roŸ¹Yá›Í‚é{G¶w¿ÛuCÚì ƒ"ñª§Ê+턪Q:ºº¹§÷e,ìñîÞ~tïJ:óÆë¸½²À³·ÑETºÔ5·¹‘#Ñ× ‘ÁZ[6€mõÏÐÁÃçTÏPÝÝÑÑÐÂyëÜG°âPè±U ø-ê¹|‡-¿wŠêp“ç\su¸>ŸUxŒW?A ÐÊ[´8D0d°ºØý.0æ÷\ƒyV¶è ÈêÚ¡@Ý:77¨ï¨Æò\Ï{î¾;õ–™Ò±±¨Z¿rùò``³ _ÆEÎ $zçOB#å•ànœQÌïKа‚Ÿ®…²½†9æön¤ëããÐ[í(¿²ÒÌ¿.lŠjõ |RO"É Vö j[®Ü¶;>f$ëqîºIZçÎ_ŒûH›˜˜[€l†„g»ÀÐoÔöئÌÑjUå{IÑTTæÕ»î?­ (5Lr-HÆÙ\·Âx%‚S‡$æLtN°Òׄ.í”Jæ¨Ý˜÷I•i±WY“|?äpìU¹7Ø9ø8þxÇÿ‰Ý»fUî'; ˜L½rîõ8mÑîy«VIò™I?òçc1€ÝÜäùYgƒ$UÕu8c˜D˜éÑ«€ÈØlñ¨Ç 9Ã^ „Ç«ûÔ-ÀÙ˜!óØÆawDR Ÿ»ÕÛ&Lp¯aÿ€L««Ýk~®½RIbìežçsÜ£v¨É& ™Š•µÑŽÐ¶V^što0ݵ5 @~6Ю\à f³$AŸ¥|óY«H>¾Ï<ØòRUŒ&Í1¼ì¹¼/¯É“βdæÂ”%‘ÓÖÜäÏ¡kìXcûw0À[k°š›œ,ÿ+Ûý×ÚÜÏ8±Ÿ‚FìE÷ž Äîeuà ÏsÞòŽ•ë•%Øoȃ×&>›ŒVÌÏò ]Dœp<Êjçíg…YòŽº×€Ì.ûJ~ˆDZæT‚lGR ãñ']cx8KÌS[ùhnàå°;¾L&(¨PDŽG§ö¿4›˜š‰}jŠ*öfñ¼ ¨Ì/PÆd²zTù‰+Ü_ƒ¯DÍö˜Ï‘vú<ÅÈ©m]!sò¬j;KDKžé5òŸ‰<¡™»ÏÓ¶RÚ3 öWëãÈ÷Yb)ÏCÇÈG&S¨çYË–®¾tï£#í~CWº•EÞÏe¶1tS×Ì]Šä¶u‚® Aý=Șf¦cLÊ,«² ”µP¦nÓöR¿½£×-—‰—T‘8ìñ.®‡þæ$­Åm_†¸CW9±Ñ䬃2nƒ„†“¥X_iÁ ¬,•ë²së÷wáU[­#kµ¿µ©¶ð¿ÔmL'(S—›pfRb$V¢#½Ÿ2ÑïÄ3™k=­Á›ª¬ãú&jt äBñaÿ*£ä'ÖÜõ©‚—휧?m‹x†3¶c›'tDa–ìªd¢’¾‹zR[P,Ç=£M¯O"ïYÅë:F’«z‹û{}=×ïò]õ€úXêþÞÝ{áþ§ß:ôõ8Ñõ2qª8²î¾Ô2E1lí[õæ<÷qîú“‘(ËZ¹oaÛðïµy˜J?ö’üËÎ/+‹v£²s‰±È=…o§Ï“#¿Hà'b#ìµòŠl¼îÍM*Æ×v³È©šÊ¶ÔÚÑUNjܳ¡}QJ!ç’÷ô¦Iº ÉßõÈ¢ª*)öЙȳJ*ÈÕÿŬË"zdsÛšo££W7LÔÚM}ƒ$ °Ï.\BVqôá4‰8¿+3s©µ_1#«{šJRM¿Ç$Ò¹‘íã¹µ4»bŽšèL²Çþê^V~ã×V—¤öN鿍^°È¢±ÂÎX_C>”U ³˜¸dö à/ ×±å<â ¯·;‡ìš§Íe‚›º¬Äùäö(~÷"ã^ÚŸù]»Ã„#“™ì£_oL¢–î2k­ ŠŽ*ð_™òJ/)_ÊñÊÀÖ´…µñ¢Ã Ä20l…µþŸ×e:Ç=ÀšX±ŽüE1mõ3à'`ÑFTF-•Û©A»¼Š=XLWyɤ¦môÔ:AùƆÖÔÔÖ<þ$6ˆ¾¤¸…|íq4âµ,°nà\•éÆCÆÇ®‡^Å®píw¡ÿG%”à˜`WâXkæ Ïׂ±¶s,õk£¬/2‡äŽ=ýMhh ѯß³2Hn·‘Æir”Iv+$EégØeÌ*~¤Eð¼KLÊíîîHc$¦ŒÑ­yva=MÃoÞU;ĸh@*Ç~±ãŸû× |ÇR@)ï<ßNß÷Ÿ×èÈ«<5@š›ÒûÞ÷8­7Ÿ¦7 Š€\§Ðõ¥b5`Owoz`õe*ª5r| ò &hëtúOGçÍÀ‹†Yž˜=”]·`ÓAúð“a£o¤g¾òtúèG¿;îàPlrî¡¡ó<™¾òìÓ8 ³!Àó3¿‘>÷•çÒ?ü›=}×G¾;€õ²Á4â;7‚pv˜Ç­¯wllÝò¥[¿#]txº»ûÂxšÜª+dxñwJA_ UPãgöo¤¿ÿ37‚ÏûØ'9JO>ù1ν‡ ºgê-§£GN¤1ð™Tƒ¼üòW ²,ƹœGŸŒs;:ºQ ¯¥—_x†5*#ya0\»ëzÏ=÷ÇOÇyß}!& Òç~÷3Оs†Y3+±lùi«@׍ƒ§žú¾ôå§?9ó‚üI”Ámø_Š ‘}èIÆVÇÜÊâÚk×®"¬×#QÃ@ÑWžþÏyƒ¶•µ$èáæ:ø|Ü× ÜNàŠ›{æöϾãþ†î•hÕCgÀÅsx•9¾ûKÌ÷muê>UÖĹÑ\¨ATQ"Ä™¸T?é*O•‰:»x»-F5äm»nçnòK¹muІ¸A>3lí,ÒAV¦`‚@DS£mL©ÚâêññÑ5r‚S»d<{œ†Õ]a¨a€ ˜ LèpàtTLùR×4œ×è´¾yšÉW‚‘û:Ëœq.hJºDà|ç+ÎXä^Ê'ÇUH5A¬yf„¾ãÞ:‡º±”Ã70¥x숀 ¡A0ì³¶_¸bìÐBúi´KW {ëE€Q$1ÎqëpÀ<—vm–3¾"ìðÌÛ-«Ç Æpï=.!—Öƒ<ÇŒW-Vœk!°º˜ïDÈ—û ÆdX*÷Š`­þ²Š„ª&ænbŸN–çë f:Nù2öš}ûy–Uë‹ÐzÌ‘žò¤´^8„-âoyDýïþöÚØ£èâUèZC»­Ë¥3䋤 9Ö{Ió|vÿQœÎákW ^ÁW¼tuÜ pt­$d çȾ˃s^‡ÿÅŠÒ÷~ß—úúÒWŸŽs|_Š@Šç @ïã¡ÙR÷ƒüƒºci~ö·~½O†´ë÷1Á„çòZÚ |úÏŽGÑ¡ÆV̶Ko¢‚pšã~í×~àk*œ_[upÍö½×ZÏmÒÖÂ3V~mÀsÅÒ5øÌcY¹˜x„DÏÁÐÓª÷2ò'ä3!BN:èØ›µn"Ïw}ôã©­½#=2AEýŸùñÿþdÈ¿_ùW¿ PÙõ‹$Èï‚—µû¤Ò•}ðº²Ôµyö¹gàkÎ$è.o T–2œý¬Ãu´Çj P‰›Pzö­×c.qÞ;Ï‘+#Y‡õ¬WvÍ!§—¥ëÐÞ £÷Ý„Þ ö¨ª`™UqQ1Åg~÷Ðᇣ:ÍäšYã¨Nâ¾åÜ£þÖ‰–§ä»ø{‰ÍÏÿåx&É2€Á(:ö¾ï,ǶÔ>ŸÂ•'^zñ«©€ 8àC0šPÁo~täô~¥%Y$׈|Œý+J#¨¸Â\"èÀ쫟üaPv©²åüó¦ý àwœ&X­§ òy1lÁMçë<ê^:oÇ`,¾‹ç”?òØÒ‘£Çá]+_KSo__T Ë úÚ Ñûà ç¦Õßs/ùy,”Û¿³ýå± Y°|9÷Q%²Pú F­¡ÏmùYA;Hõf úÃ€× ¥®Î6Žõ:Ní¡‹¼p}øjêÀ/]Eÿ~ö3¿]€.yÞ&¶‰-í’¢ vO®æâ¯ª'[‘ jm½jÀ»ýðátõÒE’ ip§ ã˜MîÓN’ç6¹·à¡rB¿Ë ¤öÇëëË3‚ÙU!ŸdŽ]‚‚´ê˜=æ§^kC¯4•/\KƒÇ T.ïT×Þq/”ÒvU½/X_ŒNWî„íH[]MÛ[À‹ÇcŸ”’àn°m„ ¤Ûk¨üʯ…ÏR6*[´+LÚÉö‡rÀìˆçáßþ{ûŸ»ùá³ì0;ðÊú¶wõ†¼ŽJÙ­=[ž£¹á#ï«lnjn¬îŒCoß@<ÊÄõ|$ˆ¾…M¤.T©Cø3a×ð”•Ƕ¡s´~å¾e›íJÙó\´¤uóZ´à¼<Ó’·‚_6©d[¦ÂM;Æj¡ h®ý©®S§ª·]gõŽU«ž+^Kuª`ª{_) Üĵ2PyÉ3Ø'îm ïc ê™%ýµu ¢™¸Á‚nQAÎ8M&7Hg%8¨FÈ-umè<è… ~ÜçLnS †Ù \àº9_‹´E °EbG¿µÓ¤”=û#GI¬"p¶C‘tÒuEWUV4ü?xb5pÌQ0'4soÄZó·>–:Nÿ1t“óAî¹òz£ÚÉÅ&š¡g”Xù/ lÓnG yÙ/¸¯ìˆ¢>²Ó‡º-ÓT-C+“¢<Ó\,A¹¶3‰ù«ëK%oleGÀ*O;»z/W–ºþú˜v ôÞò]ضÌ)’º‘/22yáŸÎÏ€©ÇèØ©­­#æ¯ÜBó„Lò^òœ˜Ta)]6hkžH…ÞsSœyÞÜzx[`291›ª›ðA¬¦Sk%8üO‡ºäw-g”·7Ùµ‘#†ª;RÃz :×#Ýì‹]ŠA*µ»étr”ìMýôj·{‘÷W7Zylgœê*«’ë(¢¨Ž½¦ü0pZŒ½×WA*äs¹ªˆ)<™eØŒØìZŠ“œ—>¿Hç[lªjâ7=…óiqýjšŸæ°³…Ü{s‹DÔñÔÑ{„.t½,œC—Ò5`‹¢ÎÌvoëê2åq“´Ôê+mKm 2ÜãvÚ¢ƒÂ*²Gû¯ ;9‚Òð±‰lümB¸:\;ÉÎ"QÜšŸ] R¾ÈÓUè^}·:›L„ç3Ù¢.¥&ÆÉÇv¢rýytŒË.…ò‹v¬8%"$a9ªœ¶ú[^(äƒÆdbBßÙà8’iº{èÏØ•@=ζà{$¤-‚g’|QÄýìº&ö£>#³Jܽ¼Ÿ»ŸÔÊZ¿gW‰ÉIœÂväoƒâ&dTÓ-dÛrňÛÛØ’Üsäœ=:A˜´í>[çšÊªzäüNº<,¦Á#˜›Ï [ŽqjóD'E÷²3ä§÷R½äñ·ðO¹§ø—8ìëgþ$ÇL˜×ÿ3ÙX{g {Ì®ÈÒ{Wz—QÝ~S•Æ3îüç¾ã( @È; ·O^¥êg*ÌÌ`ÉŒ![[>üð£!@ÿ0Ä}ŠÀßLƒƒCPµêGãA!å+ÿ\¿ëstÞ5ŽÃѸ©ÌÃ1`|b>_!öQ@Î×^{-*l=ï÷òã÷§à…Y_ï{ïãqFÞ—þà‹é7ßHÿà§ÿŸôÄû?FªãòÞ TÙp2SfÀ;òoýK èv:9×[_V¡;žûȾü«ŸúÛ89¿H5ù+T´¥é1*êMVÐ |¬=yòîÔ¸óÕŸh˜ÄQØMO}ÏD ýÊŠ¬ÍRw7UìÜ»®¦9*¦l ¿H ê¾ûBY}=+SúYeŽÏB–ò™½dr"ô»œTl*ÿ'@õ^Öþ+Ï~)YM&PÐ`ó÷39Â5P‰iìÚžQàLÒ!˜?tèpŒßçÉ·ÓåVš¼›ÏÏ+ÏŸïæ¹Üû· ´‡QÊ,ƒÞfÏ`Ã}m€ò<§|ñŸûQcU™g IÃÈ÷"ØE; ;[»GÀÅ8j IÊ*±d€—¿UÎÜg§.œz$BºBû+ƒ¨u`fgVØãƒQá`çchÁ\A8Û–yþ¥™Äf—;;†F뫹‚{Ô™B|G9çõ‘Õ.PÁœœ£Õl^g  áÍ{YDgYè"è ne¼2ßÊsƒ,ÓŽUÃ^'Ñsó4ÞÕ%& àL à‚ëÕKêÏJ „ÏTËÚÛæž5`ÜV?plsÓSéÄÝw‡¬¾t†o¤éáKëô_¸N‹[ÆÚÔÙ—Š§Ò.†lSc+ÎS})ŒÏ„1OðU€zlßM2ž•µ§î»/Ž51@gàHym ­Á¡!”uYò“iß:ÉÓŒGzK;ýœD_‚µ$cè\(£ ª9tPo¶Ks¬›û8 •| ã¿:—ê"iäZëˆÈgÒkNçD^H2¹À l¬9ŽP´¥f¼¶¨¯ B‡Ø~62ùqì÷ rGTøK m{• #Á ÇgQñ]i*¿Ð /xygÐ…Ìõ¹ÌUg"ÆÈë4øž<`ޫމŸ¯âàÚ‚1v®³Åý€¬“:S p'œ«w²­N¶R¥T'[ÅñY‰n¨.À(¡Ci;RNé¢Ãnv´vÑ:€–<^^©Î¨4ÈζfM¢²…Ê©FÀÞ}À?BÒÑ\¾>°çY”%86$E ëÈÝ8=îƒx.<Ã8œ·º]G[§U"h[iõϲ¤Œ ;È&ß,rΙ …Nt9c(‡Gµ8A+ö“{×ýmÒ‡ÿ‚è&aš´25:"· OpŽ£LRpÛ {™s·X¼X7ù6â÷l<&ú㜌ö=ר—‰ˆVÔ[õéÞ²]ŸU……Œ_¾õ>Nœî Íw?õ=¬aeúÅOÿ|$ŸüéûŸ8Îæh$#¸'^~ñ¶8­óÖÉã«$º&ÀþÃÿãh©ƒ÷=÷ÄÞ¿réBzýôëéý 1ÚÓÿÚ¯ür:ÐO"#twI#åžGAyM§Š¶£jcØPªš ŽÁ£h½Æ|*ƒaײ·6Y4i­-¶ ::ÛÓSŸødè÷ÇëØÑ¿þ«¿Â>è[Ø`ŸÕ‘:ÌàòÒ1®ûBW÷Š{ÎìsHÑšßuÞ•—£×¯§¿õ3?‹èO¿ðó?—¾øùß!‘ŠŠäš2ÃI™¼à™‹‚kVl#sLBq¯_º:¥IZ&- ôõŸú¬-Öœ™q³J™ŸAƒV$¸ÿØDAƒRZýŠLNTÄ^àsï0e½ ƒg«¿LŠ ¹Àg1&eôtxFiÈ Ah®ÝÝÍŽÿðüPé«Ü4˜e‡Z¨þâwƒv¥ N`3øŽA¹ÐOÈ!墕 ì}ílårA I¹ô .HIZênð8ÿú™]Ï 0¹1ýCaNîu÷{d½ëw "*ö¸·Á)ƒIû<ÏÊé»àf5ƒß×8 Ÿ_8{†ß›©R'Ã|Ý+Ú÷Ò¸«b d·Ç¾¬±;rM5‚åèNå òó­7NG Ý`³ S¶á–Gå7éæ?¿#ú/èŸx:8|)ž©Îiº™Ì×ÖŸ@Wi­ ŸŽuë<šÂõè‚ã'OEKG×ú~ðO‘ˆÐÉó6àÛátùâ™Ð…À­,·5xooØHÎ9ttv€‘`²0kiåŒ@ž{ì”RÏ܇mY Ššìl»W«rR[ûYPÞ¢jѤ+ƒö«{€‹´…§©ø*z ]Ĩê´úÑ@c–¤œ÷™Ij"0z RšÛÈÍâ1‹¶ÅV¬§µ³²Ey¹h`Úùi‡åùN0ÓÏÐ’9éK»/ ˜(‡\÷N$1¸'yO»Î÷ÿø|Ç:º¶ž”mœa^Op|*C“;Ö°Iì†ö|é~1aÀ½ì.#IÂŽÒzš™„b ÿç©Wa—8·M€ÚMô t.b-ÔÃðµòy‡ªMe ÌÂ=ÕÂèG6ƒúJPÚª¼}Ž`ñùž  µú^žCÚ¥9u ÝGG”=ÒF›ÒÖå&"ÊËÚ ë3t:BÆU!ë æVÁûVoZ-,¯FÀ ¹éÑ'ãßeÈWå¶Ø„üî8Ù¡ŒUPš{;jŸ/;Ψ¨gVÚ ò½ÕÒQ]ÎwÕü"¾9†5ÀuÊwé¶ Lf`Åàƒ÷3YÆuÚ¥E¹ëk"–D‹@›ãdvîÞ¢”vºË´ÝÙã_æ_Èö£­¯U²ÚU 6+Ó0<#Ѥ†vÐîWÏ0ï?ÐÞ$€ Ïk×jØÀ€AnN¾—_˜CqS°VŒ8w,¤›‰,ò²Éùb!òšöã7ð¿æ¸´»ÕpM½GÁG;ðØ¥Ïà¡{Lš R»î„ Bê´sô”ó¤VO¨'ëöç þËJÙÍàÄÀ©Øç!_åA×îfRÌ áE£ëk5jØOœ…í¾‚%±'µo3¹fw ƒ¤eeò±±á)÷ »‘çˆUO¡+àK×D»5ìgÏ,îÁ0¨e½úËd;“” —.\â{\Ïœöh#­®0@­¿`LE Y#kMQñìÙ´¾ä×Xùi‹ëÐZɠͣü_[sŸèÿ­EB {[¿åˆµÍ‚®ÜžÉ« HƧto‹v²ó5–‘kî‘V’º»:8‚çrÈò|×mðr® J»_¤©¶’÷Õ‡P†8ƒ?«ØùAWíj*MxÑΉŽ+È÷Á4“_—–”¿$1a·–AëðEG²ËV»ÚøV_›€œçÑ"÷ƒÞ€ÉÖÈöu‚à&ñÂsÚAŽIËJ_F^qÿ+· iAv…Äæ¥ÿiÅpÇ3`ÑÜòMói1ÎxÜVsCô˜!zËÊÊO|ÿŸŽ<7¡Ñ®CC££ÇŽ]<&°ÄñÑëñ=eIð8keÖXN {Ó Ë -ã01ß5t¹ÿß‘¾RæÑSš˜Ä'ifÝ ¢3g· ún²†õ¼Wƒj‘ë§Ï‰vì'Ûê­`Ÿ."ß–ºÕ"“Ô™Êk¨L4²ã›‰y¶“6ÁÖÈŸ‰¤ ~Ðn2`îäKå¼sðŸ|lìþ^dv/ó§ÊT¹¿¼Óï4iÒàZq%û!dm¤é*¸Ë~]“)_Ëxçç ÈÁkâ$&íîó}Ï¥ÏÚ¯#ûy¶˜±ö«Í\kå ÿjñšhõ¬Íª¿UAe½GšÅ1z|^N²A–4È^†÷”wbI&/)äÄLô¯ †×PMo÷u’« =¥­ZXd’Û†µ—§ü}äå}–0NBv7Á;t„ŽÊ’d§û|%ºL {™·‰Võè+m)íÊž®®Xƒ©éÙÔÕË1zaÃ!Û§Ç 订3ÑÞ~G*Þ¥SÛ"]~ iUénj®ïM%G˜ÝD¬ïN¤ÁŽ `í<çÞý¾ÍÑ%7¦¯q­þ7X hÊ¿Jî)Íö82£¬¸Ž–õ†¬bƒÎ³wÁçXø­&µ·6Q1Œßÿ•ƒq”cW죓Ä,ÞXE—W–¤6Úµ¯ó¬ñë7Hú©&P‹˜‘_à)l—õRàæRmq’ã<›£t55Uà×pÜÅñ éÈ!Š"‹zÙ+`e`JãgSC­G5§ñUñŸØöÁ‡‘œ‘&9C~vv>tXÆ22ÕÄS±7ýT»ÌÙÕËAâÚ—ú`kðjɈ͠µ{öfM¨~ž¾ß׿T~«Sä- (+j`lÈX®µK¥üà=<ÂB,pAÌ(»•„ À³2{GŸ©üã§ø—AøŠÇÎølu·òº»®»ÃbƒÚlY¸Qݤ§M¼Abš]"µ' äÃ.â”Êñ±uß.ü§­¨=c"ø2üvcœîsàGàdå,’ˆèµòhØ”ðx1Iž¨‡ð{z9ÒúÎâû5#;Ëè0bÑQÄÏÐ÷«0ŠaÂV%ÁÓYXˆ¡Š~étÓöØç˜&ÈØÑÈOñ¿C‡†ø}‹®ÁãA/;é,€ÇåfH d¶´6¤ƒ‡{ÓÀn¼‡íÆ|´:î¼îPà;–ogH„ 4áuëuþ®Úø© øÃ ¯ÑIðŒÙF X_^ë+¿üÏÞ ŒÝÌ‚uÙ3¾nèøž’ÙI<ð`ÜÏ{Þ:Æü}|F èÞÞÁôüä_!?€Ñ’OÞËYiˆÄw⿾‘ýÿó[ùó¢Ñ­Ïˆ C £€ö—'Óý™óY1º9;0ßnOÚK§÷­#(v`pç¢3=úè¢j]GOÐï&¹1ðêHxx<{ •ÞÞ¬ DJ_áÔaŒ¢*ÝŒ<[é™]«êØb] ™ÊÞŠ¥Gy,Œh—f”£ç÷x¿üšæ×§© >:8ƒOH:çéá]‰[_ÿñ_·~òîø]:Jƒ;øÝuÊÓäÝ1ƒ;£üv¥@žüiÖ£v€Å· XYêž2h­$Hªr-‚Ê”êê5‚Vú¬°åìÐÁ!2ª¯§åÉʼn•±: ‚ˆÉêI_ÎÇóª=—ÍÊB«“\WlÛ"ïѲ žÍ@cÁdôv3Œ—YÖÛþO@ÉÊqéWß²ÂÃvÙ¼ÒNlŠ37!€ëáyêV¦”à©ñ Ý_²p€(-­>Ôйt 29¦iÀJy=[¯ììw[aÊϱ¨\ÛL ÿÊåË|Ÿ–wããèÛÖÐ?~m¾}6ãQ'ÅÚ2<¤Óǽ+*ÒéÓ¯BãÑÐý‚›³Óœ_ͽ­ps?zæä :_ôöõÇO[nO²× +/ÔéÞ_¾Ÿ¶‡l¥‘í._yé«Ò B× X$€Y#ŠÚ"èèÞ\ç½°EJ8מõsÿ¸Ÿöq¼­Œ0•N®·­Üá}ÙNîÂùsé 83KÉqZ€æbß„|&!·yn1€š€¯#Þ&¹=4bÿÕb3l¡w¥µ-Ý´3ò¤° ; Ãã±–òË çW2–79zÊ€ç±'COz¾ä¡CS'˜e·Ç2y〤Ý^4q+°\c¦¢Øc<Ó°<6ਕÇòž2Ú*tý<ƒ¾JÃ[ê„ íõ µô™ žëäœGŸÇÎb\‡Ãg±¥:ëóÝÂð¹ª<‡{œõ“¼l’‹Á¦!æ"?8Ç?JPÜýdPR10áý"¨ÉÕ]ˆœÐ·òÔGhs¸¯”éÊ#ÁAß«0c¾vì²Ý¾­՟νºÒÄ*} ÏÙD÷qƒ Ï¡÷s÷¥ÁZ“zL$jgšhr€À¢v˜ç¶pÛ¦Û=«l6ç­“öêwë±BÜÀŠÕºÒZ©¼T?)Ó¾îò®ûæ¼¼Nþšžº‘&ÆF¹U/ÝF @³ÿ}øCÛ/dÏ1¨gí;‹s¹4ïÏæn°O²Š|+O8f@=I'5 éÐCq&¥|9|õR»v@/atœA÷¼‰`þϹCaÆEâ‹RF“ò¨D¹£œ‡Nž]k€c‘Ž®™k•u")Dô–—´ YÅ%‡‰Y"¦¶§à½ÉL®%×7hEŽè£[éVZN5fèMÁWÆ„l°C‘<Ý?t4*//y%ø:’)±¯\ë}°\0tä¹E<}¨l’¿b®Ê3øNZ_2Äßwjq³$"Éa“@û8ÚIÀ¶-–V'ÍcÏîlpvlSOìEí %žkdzßYaÖLpssmfÛ Ç£‘¤qlÅ&É (MÂtºWËY{» j¿æl‹Ìs•!³91ê?éɼ˜ŸÉò¨IvbK¥ì®€ô†¶Ønü®m&Ïföí¸±ÍÝÚ_Êoí=×еà[ÑÝggŒýá¹Ñ‹äs ¦,›_ Ðr½Z[I6eÜvFT™§O Ì1YØ€š´µryï:Vcs*ów6ítaâUA$ªÍ#?”±ŽÕêÕ,à1~íV÷‹`÷„º! w- x–sm"ˆ^DÀ}ƒN6L“'õ ù€ê|¼ìþ«cŽVºm漏ŠöKVÎä—pŒ>¥:z:Gmämu5É\è1å¨BtºiÓx~ñÂͤ‹Bhàœý|Ÿyî±Î&½ºx®™šVÐ~­€ÀQš˜×É›òU3²L ´ë\Ç2ã# » ÀxT“ÁGýªúÑÿÊ7“ZZáy]¿÷vw¤Ññ1îiòD–Äj"©Ç†y_yÔÎ)ú“+ÃL*òè-Z>sz´S1t0©BÚ‡Ý È(§J q%÷±ÒR»Áõ7èbà‹<¶ÍM?Çy¨³}Ùß`Gþ~¾gR‰ ëŒU1TUÓýÄêÜ-è"ÿ)S pé+h¤W_T 816 [òª6’6ˆ<¡ÜW¯ásÍåHÚbœžë¾/FE;ß÷Ü`ùIû͵0(åç& ,˳Ôë ˆÖ‚ý±†Êý ý&ÿùž~µ•“î£ð#Yû¼^ÐÿpLCµ‘ʰ‰õ+Õ]î_}|EŒ4†‘ apZ,S?Áï¶R ì?Çx†¤»‹AJaX ™ZÍ\Ûé2£/k‚wÌGzA·Ržç~Wö˜œm¼ú¹‚±Ã/žëEë$/×â¿,o¡ài­ «Ç§¶:³˜}>:>ÁøHÇÎs_åé =lÜ$í,É;«âõ‡VÚÖb/ú±UUMÈ5ðV8šùáwê§Ô³w•q‘¬Éš+´ïÅAúSwO7ë®?ƒÍÔßÙ¾Sg qq,|©n¨Á60п†-ÑLRM-k½žF¿ô9üÝSLpÚ£0(»…|Ùäå"ôL)ë]Pʱãg&S_‹]VèAæ¥ÚO‡•'®} É宫ûȵÐçʘS1a}ó&lJ÷¤Iú(ú…Ê\“µ¯=ÓÛ »üh¬@½]„Ï£ÌÞf¯U²VÊQ銊²$ZíùR’M–™!ñFžÓ·iAÞiwär3È üTÖs‚d‰––|S!¿œ‡zÊÎLD¯Óvò¤¬¹´•FÆ)bYÞ'8¼‚Œ*N$ÞØ }}Ãã{8{œ.ƒÅå¬MSÖI yµ½½œVÍ¡÷H@EoÓ~—¹VQ9nÀ¶²Š®i´OßDŽoâÏå ‰NÍT¯›¬¡ÝR^N—DZtÏÎ#³ A¶k“¶/rdQ ûOãyá{)GKùó—çÓÔk´Sž¼çA׎¥ñÑaöËNš½”ö–.ÑÑÌ^Î!ŸI̼Œm_U¶’NmK¹uŽïð¨4Ï–ç}~i¬ª>þJìÕ2ÖÕÍ­.QV™p.Ÿ›4¾eðÜ ^ê6åµG ªó«àqõÎ.ü!¯¨ÌŠY[1._ò2Ä£«MŒ#è¼GQ‡äy-Žqäù®C-×­š–^ÚAv¹Ðn‘' T{¿(l@f;f»å¬¯Ò‚±îß”kÅì/­­íhùP,ÄNŒúÉÚ&Çj»#jöÚÛæx(v® ïúáS²å9eúÁÇéÄu =ûÌWÒ•ë7}µÒkÉà96];ÑC1ä9ŒÇÞÛâx‹öÔ;tþ¦бœºûP/šP²€}zù³ˆ½“OP1ÒóÒµy¤¹+ìæ¾aBèhp?0“¦Æ–HdTv´Ãûë\c2ýöfCêi¯ÇïnFZ¹_â¼væÃJßyýפ€ÛHö¸óúö¤ÀÛ9Ç*·û<>cÝ”î¾Þîz?×ÑË¿n¿Ö¿}¦×(\½¯ÿnyN Ô£=ƵY`^Ã-ÏÿÒ<—îíc½õïü5ÎÉ€Pwo_Tlû·F¿Ž’óÎ_—ÿé=º{ú2Ú11¯½•–aL!ˆ´QQp“–·ÞƒǽòÒÙ󼇒6.7iíw4Bl§(¶cÙ|–ÅuŒÃ±øÒ¹ôÓË×®ûúõqÿùFeÂ7z}þ9ÿ9~J‡k¡@©Î†Ê?Òÿçxæ{~gQ $|ÖÞÞ €>ÎkÀ_œÂj'Ae ÷º²×½he€{T@Õ—{;ª`4„àÓ ˆæl!ŒñÐÊržw¼ b[!$.`$ØæYš‚©Ê e‡†%·âoÒ0ùÛ ùçœ&—Ýj¡Z«48Çun†³Ïo:„s€wŠq³Ž}¶ãÞ±Ԗ¬p×1|P’7W©F6à©Qnv¨s5x" Qï Íä7hhEœgýdûÃãÑ–h¶Ñ-'Z_×É7>œ¦©¼#Ë=7r5­ÌÝHd÷ZM½»N…]=™è8ž3¹ h`e2UüYµß>gÒÒæ0¼šª‚0n‘Ó5öU8á®OCS \Iú“?:Äq(—Ó—¿ð9œ³ítæ,þ8(v,Q·;ó²¦"º÷>ŒæÖà×OŸšÈð'ìZ`+t3Lmiiƶ„–:¨ä˜Íå‚V°”ÆòŒN¾trm V˜‰/_YÁb5…Ï0Ë]BGÅÊc ……Ó8Κ4tm”‹žWè=LÌ0Éj|ܶafû4±núµèòÛ<ÏÀ™"P›#0j{ÛmÎŒR/Yå¡QoÅ”À™ü`¼*]T:VEÙq`% dÜÂS:ÄEÐZ]'`¦Ch¦î"ô±½µ-­Ühˆó· Ó&ün˜™ÉV)mUÁ«YEÁÕ×v9ð>‚ã‚{òÒs‘oq_€lß…7£ÂÍ€5÷ŸÁ5[HºG¥‘μ`p + |m`UÀÍó××w!'Y讣fVº É¥³oDpÊóaU¨®“­þ¢e&ãYaœo¶Ðó3Ixe~ç·~pq‘Dº{Øg€UŒÁàt´‘O–VHvŽÝÇŽ‹µ˜š ðOÞ–¯#@Á}—kêK§N¤««“@ÛËé s2@)ßh¯…]ÇZél³(høÌ¨œàýr£-ÖK§×qx×pLy†ÁC_ò‚¼ãúNr~ :875]kyiƒ`C"ŒÏ€½ï®îžôË¿øé¸Goìï_âïl ‹/„cËy×L: ŒšÜb`å·>óïÒà´Û;LÑ‘8VC ·µ«;}ñ‹_H3t~èîîL³BYI˜y[¨,@^BÆÎ‘Í®Ão`My'øOþ\£Šˆ  ‘å•|Ÿÿ#c1\?Fû‡ù"d°qV¯çq ìB›Ï~Öãy ^Àì³S'"3y/*ÿ\·Ý;îEùNû%³á¹¯)cóÓá¹/B\smíúÿQÐqphˆ@V;óÈ`ÜÝÏ=N‚KÃtœ‘¨Å|åÃN’,ϼ~:hR¾¶p­ý®ëºÃÞqÌ’¯ìX`ãÓ2OÈþRö]€\ëOï ìuŠ{3ÀTÖ]¦Ü 5e[ͬm."Œ5Ú7© þq|µy7Ö(Õ£VOàcP j¤Ý"€oØ$ñùü?Çîw p _„Åó¾Ñõ…ë+ÊØ£ÈL«,ÕW‚®ûêVHÏ®¢·ŽÄ9«”³ðŒ:ïä}„\¯¥KÕ©{îM/½ð|胞Þtù™ÐGžsî…!"ÁΖ‚&•íB×îžž÷£#×"1Á`Ž£úÓ½»pâ®»CÇÔ“råò¥Øc T¿ δ–iÜÍùçõ<ñËÕTU©7vh=ïzEò´ð§ûIàþÏ’³²„iy*«øU (Ó¨Zå]7X7÷`#:Q~ÍQñ~ ½gÁƒGz$á#ÁÐJÀ£–öî´L Ý*ôêZ‚ÂÉV!–ptM©Ì[¾óùVaúÝZ®S¯:¶àdµþ"öÃà!äî²e,}ð»>ÛFeþ›1u…Gi©3 ðÚö{œªu^üd®ò“ûÕ ;n$Qùåñcž¹ sÏ(g°Gä[¯÷ù§Ž²Å¢zË{(C¶v» O;èA÷Œºs…½ÖÚÚ œ%»;'ƒ?Vº§â8ž'JWiïšq«à-i£¼5@¨_i2¤þ›¼g5Y$ÔÄê¼ý|®÷âöt7Ũ®kŒçj i¿ăÿµµ3¬2R'xäPY?ìMUVêIƒrô—Ç”\:ûü×IÀFwöô§“§ˆµ»1F"X…IlDlÑtuß(û]wƒQê +̤½ £{¯ÊxBØu+ijšîHÛ9D‚ëå^-eÏšT&/ ð¸ç£O¬í I xÍûÐ&5´‘È`G]X1$,DE3É[¦\5€\mHŠ|G–£+½^»×äÍ2Æíß+Ø3ì墲X=[€ürî¨ç+>ÝŸVŠÆ.¬ƒkm@g–gK³¶˜‡ÁKomRnBbãÕtáZ. ÑÖ»ûF/§©ËWÓÀÉØ™$¥>;^ç(°žƒ–Ic·ÃRF^ÍeÏ‘SöáYý —ÅÊßJ‚SÊB[írälÈTu«A%•…\zi 2y*€m†Ofk­*ßj7fs[NϽzÑÇ¥¡žfÖ¼(»œÝß©%t cŽD.¾°>K›`|§&ìbƒ¶ÒËd`(ÜWî%ƒ˜&®TP=¸Ë:.8ØÇWÐXç:œÕ$ؽËkˆ|¶Á¹ñ{$‹A)Úßâÿ°ïMZ° ´]“,˜FÈŒtÙ*úmžjUµº²Ýó´›ÚCnØF\zšü"i³MNbC0îjì½Rpm·œÿDP8 ?T0V˜žÇžäÒнl€m{sŸö÷3A7¾k¥òLP];;9Cg2»…e‰5Ú\L`1QvD‚‹/ŸI6 nâ Í´ nÇ2Èh“ì&aaÈÖ¶ K“|o'ªFµ+œ?f1A ‚–è½D+a;øõ¶òÍ$ Çi•ß³’Ôj~RM4ð¸«}º÷ W#a>ò½©©ÙH42p¬Ý)= ~6q¶æ• /¦×¯f>²¼sס.*);ƒÖÚ+tS¨Å†Rä¦si =SG€®˜ägýŽlf©¨»”‘4̳”ÊNíÏ«/#–ÍE|4KTWG®®z4…zÉ`£­³ÁNÙË,CèåÇÖlæcL…|¬½ˆþä[Œoï{ñ\£î4xnRî4Ácg>È{öm6.lht!œ2W{cbj{p+bªi±ïõ1'hŽ×±ù]ƒ›‘øC_&Pð˜Xé±Y¶N‡ «+·‘Ó±ßy¶@IDAT&ÿéÓ诙xb`fav:{ëôÍ£XJñSHäXìçóøºçÞ<ÍØiŒìÛ€Gl“lµ¶6 -¿É+ ^WÏZ‘-O©·Õ]+£‹1~CÿQ­-F²ž~g#6Æ"¼´€³hÀdÍðv9{ky0==A>ß½žà ò97 :žq^C`L½)mvvßè;nÇúì17’m8†ì‰_O<öhjïéf~ø”øõÚßÛøž+ü­¼ÕͱwÄ ¶}ÃW®Ä}õå´k]Çëš™ÀÉb¼ÇÆgñÑü^]MÛë¡~l#0\x´££ëÛìuyÉ@´|~}ßœ6êUe¶ì†á”ºì9麶µvL[u!W’½y„>’rAûL/‡¾´E$>ó?[Í+³¥ƒIêGåšk&Þmkw}ê>ôX.}v»Yù=í¬ð-'ä‹õu¬ê=ÛÞëkJS7HÈ3ž©ßãçî;pÝ?aKãƒ]åÜg»ÿXy~ñâ%ŽŒéˆNhÚk‘´L"‘‰¤vå˜ß©ImØ!œ¨žÆréúÌ$ %$evC™½4rþ:ó‡GÁ– Ô5I¹{ðHT®Ï¬®$øÞ݇O–ÃÑ!þàÛìû",øc,¡W™_<`[ï"[ð¤²-B¯KËó´W_LÍûøòŒu|dk™±Wã3#H‹ðˆ7N¥ó#{©Á€*úkÛãØfrAƒUŠ7 9c}~«.]»¾žÔ’!MŽÑ‘Á3ÔO€‚º4·Yo±ÑÜŒYý¨Ü¾v-ÖU_Ó`²/‹{Ä:|†8Ü0X¥‰vv*gmøjÈmePf“ê—ÀøUk|O@¡²¢›œRÖÍöþ›t ¯+ìý­4‹6³¾«º×c{`Q^bŸÊ_t9 ÚÑžen‚–ã³í{5Éùâ‡eæ½gVð_f~Èkxs¹²Ÿ8d#ï /l®/’šC.4’¨„N¦¥ZŽgY´ÁÓá¿=ðž{BŒNÌ­Žd©?ùáôÉ’ÚØò¢ºJÛK¤Œp?e ±ÇgoWmìYæa’JæéËls4ÇÉuµEðÉ𛬯û¥YP‹i2F$ØÑ@yx+{À"!“õL^{² ‰kºF$¶d"Ø*kº¾”¥¢\Õf1ñN]þú¯øRðÝy½;) ²z'¯wz÷úã®õs·?ßîZ%Ò9²N™ÎÿÛ]ÿNæñ_êšü85,0ûbì¶ÿôV8 Ê©|4|åïÜüÛëüçgÒ_òg?ùÌ÷÷ÂXæ§×ùR”×­¯¸ÆÎ­¯ÛŸég·¾'Èpû{ñÆmÿ¹mT·}úŸþù^ÿŸÞá[ùŽœ–¨$¡åëW# Ø %ÊóVz|+Ÿzç^ßÐñÃÜKÈìÜ£À xéiixµÒÂ÷ÝÚEá `¬é\xÅ5ÎÃ1«Ϫ%my¼ÀÔY¥ž]TaX(¤óg |è ÌX§ÙÌ1:üȶs Ê#«)76 èS1ŽAhpß ±-k÷ït„tê#ÈÏZH F5’=. ˆF5Àé<G[Ü/ŸÙmûEA™6¶vÓiãñd¿æâýý}tη^ˆ’“”uÓ7F£ÅðàfÎî'pÅCÌßÜõÀb+³Z¨z0“ÓsSׯ„¥S[ŒaY¡£ŠóWG¥¢- ðÒ¡A@âùÔÉ3ªV¦Æ†ÓÄø¹È¤æ„UÆÅÙÈ9‡m¹Ö¸m›3[ø*¿<š H@¤¾utõ¼¸z”àp Œë¦Jó2~/F0YcXçúé/ÿA:|è(Ù­Ç‚þòÆÁC‡ÔU¦g Aºìü*éÕI+ZWÏÎRuÔºwÒvËjUƒê «=#QM‡SºKËtènÏÛ–¶çå‚kÊ:¢hIˆ#\® ”GñçƒGb¼—˜?嫵uŸ‚ôž‡ ¾›& /O*W‹?¥…í8u–ÔL:ŽQ,o2[¢Ú%E笄9™Aî5‚´:C‘ Á¾ðœÀjª¡è3 ­¼õLOÏç"8 ¨ƒ ÈjT!c«eŸX-µÍtt¶à~~¯©¤G„³Ê k)NHR W!íÅÙl€Ú8&tjÀ™‘6êY×cƒ=éóñmˆuâôØæ•ï˜-íx·ûUK%Ú6V? `Ù’ÜÀ°mÈbmX?×Áñ~¶ 3¡ÅýR ø¡€Z±FÅFN௠‚`”ëSWÕ‘ú᱕ùT.­ àE³ü¹%4‡.üb¶ýõ«Ûkéø¡¡ØOTh3ÀL”ò äbœVÎé$Óf )¬³™gäUA‡ €ÀrÁut¬ëk¶ˆ÷]`ä=ƒtÎÛzwÀg>§ƒvw€ÛëK©‘õ7@ây­:ä‹&8œKùÁào%A€»N éÊ8«Ó—~ç×qªé\ÑÜÉu1¹Wu]Sj!±¤੺¼:¤t鯦/þ›ŽCN"ã`G¹:‘NÝWjèpÚêâ§ò6 œƒF`€Ùº²^ÊÇkCËÂւ¢< ¨ËçîQ}¾Â=nv@ÁÙ‘§:éò»ÀO%€ú©“Çâ>Væ .ZÁ¥]g›s÷„@´ü"í=ºº’*(Æ]ÊÓ.²‚q(_|^¬ —öáHó~=ÏÔtcª#N;ÏÈ«¡£ºI`8€+x N†Ì YQ¢œ(bN¶Af&! ·s^ÎÓë³¶Ý ¿Æö™§@_<år!w6èÆôÀ¡•èŒ%@½{ïƒP£«úÁ—Ïr¯ šÉwt>:t$~†-Ì ³¯[mcƒê¾üL™!? ¬|°;„ü+íÛ:»yN:;KôŒ/CžoíèÁÖ Z‘ÖÐHÛbÖ£þŒ±p_héçsµ«”]Ѫ‘k¼¿÷È‚qÙø”{þæØLJ(ƒç‡Hš¹1þ¾ôÂsϰ •.ÉW9h/±íÐXO[qôºk Ÿ,¤2ß#KLòvïi¿lqv=h<ÝÁz¢ëÈv޲¹–Ê> V„„ýf’•^€xA@ù··»‹ÏvøÞ²Œ=…nŒ€ª›ŒW=:mdt,x»€ºz× êA÷‡sRß8gçnRd•A~7‰ÀÏmîx Äø,×CÚ¹_þ¸WFãŒvõØ%Ïöú{´X%÷õŒNïç>òß>0ƒÀVÔ¯`gq&h²÷À‘ã1æiè'ÈØÝÓÈ{ïà)C;íË3¯¿B"‰I™T]‘Øe5¢k«¼Ù@w ³ †h0²†î¶¢¨ÚÀ·܇E´‘µA¬ˆÓ®í²ˆ,5('Ví Æor]:+'Æ`0Á´·×¸n— ,苺&0áu;qŒ€º§†ýí yæÕs¯ñllå+Üç:Tòìè8D²€vò4‰b 3ìQÚáBKå¬AímçëÜMÉôd– i°ç‹5²ƒŽþ¼ÝìÚQHÔ˜•Ãg€8´^…fgâ¼lç$˜4«¯b;àRå94[ yC[ÍcÈ´gv•6›<£]¥ ¾´²…o:ûÄ&êÎhEÌú(÷ôÇí¸GSA&Ľ´é2¢ýªÎÒÏò¨šedlK;E%$°4_$d%¿ìì4î4!qae.½öÊóاtj˜ã˜€…i‚“CÌÍäö'þ±v†þw ÷ÙÞ&Æ3L*U6³Èɰû«I ¶×£¯e+|¨/UÁ3ü©/a¢î•Ÿ|±¾ø $x« úû{ƒÝ'eèjí“ØËjLR`Ìè‚=ô¾ YòA9>cµ&-¬šäο­‘ ¾‡NÚ£ËÅüTè8Ç®¬4¹ÓN¡Lø}<Ö"ü-è3„&¬hÓh‹ÅøIW& iWÛqãÈñCø9Ûé¹K¬ ~#ŸÛaàåÚ:º‘àì2É»úkEq¡hÀNÓx,e~vŠõ™O3µ$ “4¥¿Œ ¼CßÏÄ2r¼ymr•¾µ×Í#›ö°mä+;Ì©¿×ö­d»¨ »—Áû#ÊÌ¡þ¾ÔÕÚȳ2™b¾R÷ùbùCæ(7M ^e-µåL–Qäò”Y[ü-vP©œ"H=æ‘Fá«ñ}y+ö"úÞ¤þ,ð»Ì*÷7Po‚Ì8:;0#è‰ü|Ñ$¡ ô¸¼gR”Uò2raÑ ‰ðeد`ÍéÂ…«ÈG*âYYrÛ üù¯dá^& ]¿6ÁذmŒ~gŽîoö›º½¿·ƒ¹¤×^¿ ?)Kf'E¢'߲˖2V½¡žtÏdIÜü„ŽL’y°¾ð¼ûEùe×?Qw»ÓÄäëŒâ¼uçu‡w(ðî €Fû;})dl³ón|9Oÿçëíæìgï亸è¸_ÜADTÌP4´þ°×Ûé[qývoç÷Tr%fpOá8.a >óå/¥ãÇOD寷óØïŒíÝA¼<ÐQ³Å 4@‹0®e6ãü@Þ«* í(?uJé­‰öëÛZ…VxØ6NKlðW£1‚‚8í¨~G`HPU)4; Äýf ¦ZÍ®¶I@Ò–ïÒd{¼¯ñåÙTÁ:ºá¬càê Xe„ßÊÜ[§^çOÀVàÃÀ¨ÎäíD5Ö ŒÛÙ¹>r˜1[M°AåhwYš¦¨4ÂÉÖ@¶¬Àò¡ƒc?Ž\I¹‰1ª‘•Ê8ææùC³c#i97…|¥Ò àrPÉÎV^º@k³æ6ª²»ä¾ARÌÄÈ5ª<'pjið™öi‡†1Qé¤ m+ªhŒƒ ˆdvïá®\>ÿÆj%NÒqÀÚ.NCC³t&<³TùaUEÀÛk—/8?†¼àå~AÖÇ'Åöc¹èFðñO|2}úŸý|jg¼‚ðv È17[Õš‘çm×Ç–¹G…Îõá8 d öêÙ)@Ù/Pm;×бŽVâSãc±6òŒþ=ß›:zz"ÁÁu~2‚4Ë–ãÈôrds bµÇ`²ÒìÌ z€ªY€û‹o½žîºï¡tôÄI* s©§yMB˜üc²—•J›«Íq­A«ÎÎƒÈæ‘¨ÒØwK+AeíNåƒã²[Ç vÊz_&äy­rEzËy¾”®™ßÑvX âÑÊ{u’|$ßû™k¯Œµeª`ý&6‡þŠúFzm°^&ÁEÕ=×ûŒÌŸ1hEb߉Üì×Þ‘/}Ž Ž}|ø:3% ø66µ¦ÖNô+6`Z/ImÎgâzÛF0ÙgX1\KàÔŽ/çÞ¢#ûÇG¿ÛÀàúkž=×£jõ¾ûï=dZ½02|Zn§z:1PçÌÄ}ö…:Ðd [mÛÎÓ.?ÚdÝTuöÜM‚èÚuÐÀD«y‚¬/¾üm^û¢]0Ò`«42qËêuéc²€/÷‹‰qÒWÀZ;Рœ{΀mT:ò»‰!ýŠ º.î¥üúÅnûŸ¹Ý_Œo…Ž-î•EŽõÉÍpˆ´G/8®ø§¬\_eŸ­§Føñà!m™õôú+/„]"ýÝ/›$h–•ˆsºIgßx-:•Û"Õ5æál§´C[ëH„߬Všg­¦±K ¢j/dç“gÕCÚ‘®A%ùÄ55.?H{+¡¬ò\a}‹IŽ;tô8G¦´S†õR‡fËØ‹èƒMMM$ß¡‹Ô¶TÞg¸5†¬ðE2ÆWIŒ¼ë®S$†´1nt(߳»ڤ>€iÏ?®¬ÂŽ`J£cW ž±Ï–ÐÝVj™HÄ #`'@͸™¸zW×õqc9~ÁhéæùÙ2–833šÎÍ­¦·Þ:CkâÎôÿÛ_L¿ï±ôð{ =þ¼.=ò¾'àÑ«é¯þåŸLGzÛTŸ ›ÒÎËdQǪ­Á þ0iKZ þÊ7Y`Ž ü]£­î–¤•É…D.*FÕAò¦ú×jz;–”°gÝ'òïºz o’¢>`%ˆ„j%œvªö†r¯ŒÈ€ $…¾Rfò?ç`Ëu;ti»¾gàožä‚×_})YíëÇÿÜŸOßÿƒ?Gßx>¬¶ówÏÀ»é·éÖséÊ é˜H­þíVäúK熡Ê<®1‡ÙgóǖH8ªÁ±óŽ>_)­ ÕÓEÚ¹V/"·ëLè61ÌŽþØÛò·Gй•yv÷ñûaÃ1_ml[R_Ãxö•7Ò#?œ~âÿþ¹ô=¼"Ï(‡®]»š^|þ¹ô«ÿòÓøkt08|$ìý³5’—þƒ¯ŽvZàÃû¡•_îkèiðCù§¼Ó¾2!ܔȨÂgÜv$˜Àgêêî¯iÊö(sÜX‡þìg×v—ñ¬+°;m»k{{yÓ‚P>Ž@É Ê¦l?ë-Vm¿ÙK†Û]³²RüèºÉÂK'»eÌsßÁLÜ›”bø'ÎB‡Wg8;–Z‘aqÄþ7ºa;¿äϼ0/\ú5¶"7ù Ãý«<︃}ë3Ô%î]«(M¬Ñö7ùÍW_L‡NœŠ}wíêUüäciÿU9¯OíÙ»£rÝ‘‹d«¶±[á%b+„¶Œ„çâ_‘ ¡-ª JÀEú&^ÅÚq9º¸±gÄX ¢»¶vYľÃpNÄ{ºÁ ØOQ¡ŽÍ§¡Í8KRÁsùÖ³íMnPÏÔÔØš»Œ$ŒZZ:O¦bì÷7/\♼m¾Vð º ;WÌGßJ9+¯™€³ÇµAå9ùÜÏwèR¦ ã‘\—a,êuñ»; õtÐM¡†õA¶¹ŽÙ‘EêF«mM|Ê]°…x¯¥¹{‘ Â~Ò—™çœîm’´Ó'ð©MH-u~Ø&]kŸ*÷å]Òøb¶dߨ´R&øL_̽¡^ŠDŒWGñUxœ$ xM[Ä£æ|Ù=Où£RÁ¹îÍÕÒÞW`Œ×.zÝt0gva<’µZÑÑGHçÐÓóó3ø mØEMÈÒN’Ø®3¦µÀ}\Ϫr›Á£ì:a°àÁ§ú_óM©§:ïƒõâÛ5ÕX…®%Èó†zps‹¹™ñÔæÔØÞ•†/¼Ê"À3T¯C””# ¾ºFΆ¿ÎÞ¨oÎ’sLúnj(O3È·úº4ÛÛÇ1$c–ÀàñõÈÃ>_´sMG÷ñYE1I``2eÈÜ"’ÍÄ}¦çvÒäbYê¢È£µ£+ü }y±¾YçærÈEŽKàHÈ:åDçw“²úº«Ò‘AlØu’œöà»a€R0üÐÁêr»°LONdz ¦— Ÿ‰¹^39ºÂÆ`öàK婉Av’§Å 3+KúW.¸®t=M9?ƒN+-&9ŒqmÄ®EgÚÕe†ÄŽuð“ì’6 Õ®Eò}$ "÷üAbe43Ñ@ûUÆrþú^úÌb=&´¬­‘dÄsä7ù .‡R$ö`o»Ÿ÷¶ØsðoroåŒ ¥¬3øãÂÙÀ¿œ«ò×ñ*ÅX••þs®Î¿†£ûº›Ñosà=ký0™Æ¦gÃwUveZ@#i,¶ã3õ v9ÊhíXýyíñKçQŒ­hÆ–ú493{±Z,ûP|Ñ[u»ã2)Æî2â#Ò¡\SYdB´ ¾0ËÐÓ}}Çá±ú°A•1ê‹èPéf»óºC;øo“*æï„—FÑ7óŠïñÝ TÊä¶NŸ/o©2ûfïýÍŒçÝþŒž 0m"2¿‡†bŒq J5ÂorÞít¹3þo=t8̈׹68ªcÞÐÐ|¶ŠÓ¡Ã£m`<Ú·ã@Ù Áª4«°u–ü®Îÿøø(eY|n…°­5ö%Ì$Ãн˜Þ`ßLn’ï ’üÃø,+˪Î2À‰–lh:ïc³F]¯F`ÍìÚîîž·m;F –Y9tçFãVÇÛŠß*e6oE#ž«ÔÓs€ÏªFr“ž!WsÐÉ5àj ÌÖ–GNÞ÷¹xž úHjîèMç_ûjš8-äÜÅH×\[œåì¿™p¼» @oL^9w#lØÃé4Õ+5¶ê#«»c`ý À£7 xÏçÈnhî!jp:2?q¨Ú9wÕ³í._¾€ÝÂYjdâs}{WGjjëŠÌS‡¨qd=«kçubг§i©×RTFǵ[fˆh€7â»–õÈÄYÌ[÷¥g¿„ócŒíîÈØ×È™š‰k`Õ–„ÝTµXýaVik%à «Lz¸Á¯¶°>xä8Îî$N\Ê ÈØ¢Ò :øá+éè%0nƹƵç_™9ïš1®J ³=®-Ò yÆqÿƒÈÊ´@¬ ºMÓ’N0E§9*xàA+!¬¢±µ™Õ·¾œ“ëûü‹/¦×NŸŽ X[q š„Æ5ÎÇ*$¼£–·­s³ý¸<ó¹–ñZé°S¢+¯nu¡™ç‚ ž)%õ»ÞÔ¼zå Ž2`*@—A—F¿ÊyÁA[iʳ~y‹}âu&žDU;sqÏ JðسBÍýWÓc J'Øêo[Ð[©#¯ð®7ÇØï¹·Ô/»0ê޲Á÷ ÐYM Ð#®£dÛÐ=>³ú5‚€8_ô¿ŽÏÔ_ʰ¸Þ},¿E%@€ã)$ˆwz‚øÓÃÙdžf3ë´ êHäó ®2Ìl|ŒÍÜã1ö+ßSíÏgÎ[GTyæ¸<môk8æ|ß)ÛR1€IçÊßV@ù}Gƒè€=ïXyQDЦ¼„꺽L†¹®¾äߨL…~. 4îp˘3O‰ †@øð%•*òš€Ê.I7$hÀÛ„+/«!x6‘–8ºµ³—s ûÑT ²ÿè&m€e¶JüÂçþ}$5­’”uôØÉtâÔ½Qi;hç:DyâýNÃô >üñ§Ò±“ñµ+Zš[cHr÷¤ºàø©ûà/@(äŸa×̬VªÔ³Jb—Övè´‚$`/h/Ð,}mñòžp­ÂfvÍ}ÀÍW¶ÿøƒ{È(ã£×ã˜*÷ —ÆÛ^*Éc¾¼O)»/BAÁ'5õÍð/ÝJ°S&ÆG ;æPÙi¦‘¹ÉÑF–1ƱTÈÖuÖϱÉëccרïTüaɃ&. þ)‡¦hµZqœ#à+÷³ò¤óxOV%G°Ý{xï®®.ö:­wù×ÕÕAxE™8îLT´ ˜åø üœ8q"Æh•ŸeK^¸.7nLÆú8“uL‰ïÓ—~ñÓÿ4­_NGd¯dºÑ¶Öîa4…+ûÊÊ;«)šäo*ý±­þ ,/®Eq#]†`í«++$³Æ&TiûCs+/mO«-`À[yS„>ŸšKÏ¿òfzüñ'Âxõ¥çÑQõÌ‹ó 9Îfš`çƒ$,<ù‘ÆZØëµW^J_þâH}ë$< ƒìfaK÷ŠH.¨&¡Ä6Ñ«ø®êe¨ L®á}dX)Ï5Påùïú"VÂÉŽ_ùd"·t-)5é[~Ò†2 ¬¥Î6°;59›ž{æÙ”ãœø¿ô“ÿ'cüX€öqŽ7ÔSÞG21>Òƒ¿7>v4ýÍŸúk陯<›N"›kª±í!»AC;oiË黩£•«š·hÉœU6Zµ‡Î\”ivE1ÁÙ@‹]«Îž»@ph3ô§òœA#ï8/t;C¢‹öDZ´œ{Øû8ak$X©m¨î©ð—™ï A-ùRùÍ]b^;ØÅb§³Y|Ê+ËjJROÿë÷„ÑOŠàk¡-h²” ´…oŽ·4OˤŠztŠ6À&¾m AƒçU•tB@+wÕ{QbÚÈvR56¨v©þ¶²$xmz½þÊ‹aÇõô‚o´¿D6¹Êî<†±Ø Áûà—AOƒ†ÊÓMä³³SwÊöFrÙ–\vRVo– ìäb)’Óàq Á1‚»ÎájäpäNçÀ¾;ÇŽcÃlÙ’"[•’Hî²láöém§÷Þîóy^ŽÂ8²¬ÜœWZîìÞÿûþÊó{Ê÷id†7耗^Ѓ]g*}iOÈsx³"hŽú:s· »¶×*ZÚižû,t n@m:dß 9 -©ç·6Ö¥‡<‹.;•îáØWG“.”»µ·¬â,48Ì@äbJ£EpÏpN™Ã²$®ˆƒ¬ËQŽÎkÀEÔ¹ ˆp~;ØBÒ²g0Ÿ5”[9Ç+›g‚=ÕVFé$Wh»:éÊ }êé®­t©Î¦rìgcçc¿8uñ*ƒ{îáø^f=l-Ôv¨-‚eÔÏfø2H|Ãç@Ÿs3àì§PU†pož³µ˜úï^¤RÆfà"²¤‚"‚ѱ¦vÑ2Ë¡õÁ8»´7Y Ú¤ äÉö8D~ ÎVÚn1ÇqôŠË·ï Û nSžq”n¬#SȦ__!þ22n VZñTÀã±òËkSÕª(ÒzÐÀ…u*ËUV2WîŸJ»+S©«£=µwtCh »´T¡½ÎâföèѹUih^ç9I<_[NœÂ  áá4FpB%:Í"¥äñØÆz$32<€-ŒÝÎm6ioùê~J×VÍÓ!ív=­rçýf{àY5pw ·‘£ ˜\2Wšµ tž•œw¾Ûê9|³Y-½#¡ø—Ø÷ü,͈i ñeÏ‹õÚ‘ÚNyÐÿ.Ù÷üÈgŒ™÷‹¨WGeKhÂ*,{8‡¸„ë`Û¼û:‰nß<ÍÍ­(¹;d”÷~‰ˆÌ­tíòk©å®V#j‰(ÎÁ¡tèð12ãèaxà ak¸{;ãWÞà¹5é¾Gz'úPºüÒ×S¥öG¦¤4ý1ªЧègnáE‰ÖCé.Žwû>u*œ.GN`¨¢$z @™ž8¾Ú»(ƒ†Â:2Ô‡A³@N÷ÑÓ1G3@7-9…r¾/0 ï0XxQÖ{¾v5dYo¿",êå:r²À÷P2lžå¼}—ß²|,gQ^ñŠû4--Ÿeˆã€‰hk&À>ÏÙ/ð £Ö¹0žp‚:pgÌ_á05t­|™odéP®Î¡lóŸ|ö}›³µ€•|'sìú{¶&žëúØZAÚŽ?ìŸó”7åœäbàгFöÊi÷@êZé\òež+éÚp|fgðÆ½–Õa”Ë3¥ÍÎ4š\ÃÜ54ÓD¾&(l‡Žתðr~y–(|éÖètæÈ=ž äeÎ’éÓ‚Ôø~3§‹û¡¡ìþŒ„×EÁ[€Âù®_0›Ãý³ ¤Æ¸Ž ×(쇤)8àÞðCœ¹À|³/§[¥uŸ¿Åy(b/5Ò}>}8YÄàÃiL€ßëâÒ‰O¸r„ç:?"ö]w¯]gŸ¹Ã¸|oì?¼Ðù ò w•š8Á¿]Cé$@=Æh:ù$—²)‚KŽ<„Æy“ÆØøÈ¬œ™šM—.¾ãð9ì²M¼Í ²_qàÉOÜ{ÇågfïeŽJ€)~gv§º²¼tš÷~ÏYqŲ|”cg^9”~L_üÓ/=+S­†!à¬ã¼ÞîÕd(šñ»Ï¢_+{^ƒ£Lpzœ eo{gw¼Ë ËM¶ˆ€¯•`õô’Åüf8]|ß`_OzúùwÁÿžL/þÙ(ÞÄ ±][Dzå8>ü“?årFiÊî#Gã\@¿þêË<›¬Þã¤ÔÔJ9tœ‘Ê‚ƒX)³\¯ä³:†AnÊu ×ÃlZ{ê:1 f‰—û-O×i@Ÿ{¶¤ci\}@Úöžïváwò¯#GOÄwâ|½íþïð¡·y ¼œýTNT2§Z¯L ÷m­rø8eÀ9®Ï ú‚ù³äßÛ82¢¥ ãt|:cl?bÏ?uŸR@1ƒÌì·žƒ02Ћã“ßó¹ôcIcŸ¯Ãw‘à+[àHá[8ˆtº§£è ÈZ+N4ÔÕF•u÷Íu6`£€ulm(xý™Ò¤|f–µüÅòÄ5µgXÎc5h×}Rw3ërf2 šS¶š©žŸ»{ûŽHh‹ýU6çåR‚ñ¸W®ƒûÕ%ØwQ^àwþÎ,eÏ¥²Í}s}äsž©l׿ÛNîmNv¦­Zí3»åÌÇŒËE²ÐçØK€z¦Ô#m»#o vÀ;稒ÔÓ?Ùaç"从7×ie1« ãrzj"Öǵ7‹XrÀ4¯ µÕ’F±Í`[8‘àçȨžƒóR@UzSnjÇù]õaõ%WÄ1šAl–«%7s¹ß2ÂKØÑ Ü#}Ï¢#¯òÛå³g:7Œ!3°«÷î@êèj€«èìô>iJÞ%àjpÇÛ·Ña›"‹Þ¶ ÊSK…»öCèºÓ”¦7°5hšÓ‘¶µó·ÄÊ 0I¾ƒ®€LS(:Î¥ûåÏÛÈtÛ_ló,ËÍËW-Ùm_O³â*×õŸþzzþ'Þ íá çÞxãWJãÊLy¸û½øÝï{ú??ó¿§W_»–ÎÝG†.¶ˆ:‘NnϽ}uj+ÔqÇY#õ¶btäÅÙ¨^k_³h'ÆÇÃAYEõÏ©NÉúz]z³ž:6-_k°æûÑÓ?Šã`.=÷üóàžL_øÓ? ½Æ ÃCÃéÔÊO3nK=à£ãðÑóbîç¼öê«´ z4x½4oIi{[ÅEˆ Óî1 Z'©Nö­m9á˜Ó)¯“ÎLx×Ê K™êp‡,UǽG hè…¬™khðãÆý•o]Nÿü¿ÿ¡ÛÉô¯ÿå¿^n@§AÀ^:r*Y%Ø®eާÒÉÓgÓk¯|›yÏÒ¶‰¹dÁg%ܯ–Y ~€@WªЕÎ]éܽpŽÏêô/GîKÛÒ®Áb%èH%Å´(À™Ð€o»€uäŠüG¾Y–Ì¡d*œç¿ôþaúë¿ð7  ;Íýïxèáó›L²®Bv*sÜ»æ–3I_ÿÒçÙèçh'rÒLAõí±6 È¿œö3òYÖÝ˵W.ËgäïÒUXtfœøæ86øTûÔÞ®:Ý3§'™Ž¨öŽu¿fÈ–ýÊüzª'Pú_üwÿSzæ¹8+Ð:çLœû#ϘǶӱ׽9r$ýê?ùoÒßýÄϦ/å¥ôÌ3ç±w²Ê(ðXØ qwÍJ,A¬Ó&ûÏã¹»ªjåæ#v©$²4/"¸fr2²‡‡°6€úBv¬#¿Y`V3)+ëˆ@÷T~¡Þ*]Ì:…çâH/ž‹‚WîMÈ9TÇ·º{§>¸ÂY[àþå{é¹ûhúÀÇ~:Ýí¹›n_¿’¶âL¡’[«iÿŸá»Vy()[¥U óC74hy•¬M[Àɧ·7 ‡Î ¾³ª‘Ì Šm = Igòèut'[æhÿô¬ìƒÚ嶨³ç¹øÆ,2cdp t_u6xÄñ3'ªÄù ýëdÖ‘_Êø×*S%º[ 6å2øÄF-ï Í¾È­âÔ„<ÎÃ6¾ôíë8­@„&m8`€ßNZÀy¿ÕZGůËTvÄ µ{­†"¿S/‘^ý¾6»¶šçÅóå™Õ¡lŠÁa¶B¨­±Mt­Ž#xÀóÄÉ´'”¯S¼‘ÏZ¯´ \°}¯€ÕAµ) hdI‰n<Ì1(Û´íwx¿¦­²`§6$Çï3ùxЉ•œŸI2ÿ'°/Q‚îVQea|x„³»Œ~3Ÿ£ý%8A+дzCÔŠ‹IA&Ï0Ç>è¹§¿™t\§=l@x\ŒÕìxõHmkÇÅ$ø–ú¹új”êš ÌS-‹¹2Y¹¶e^Øú¨ü¡šÚÒyxßðð kSH°îɧÚçÎ=zÊA“¦îhªÎs6uuu´7¡luÖ}×MÛ™:»ºIZ)½v$À Ê*w+öW¯Á VyfáPÐÙâ¸}·Ùb‚ÉöÅ8PY× Á­Ïýѳ£xâÞ =:œfÜÌLâP¾ʪ%Q4d-+®ÃYP¢ãðQ€¥Æpò¶ÐKéôƒ§2ÄéÕóæ%¨dËŠ]½t%»ÿßcÜ(ßgÏ=Dôp#JövzôÂSalZê«Ò²(ÞòQ@•ÛæÖCq_ßÝëaØ« ›¥]A¤%ú¥åá€ê¢wªýÜ@º"º´ñ@{:ØÜŽ1·½Ó9Î3 #_ælOrûÌkd-j†€%èužŸH3jT‡†ådÝ ]•`×h0]@¹eÊ œ°?á`³4¡2nÛ {· ‘Ý$ènV…À<Ëõ½~í*†7eÝp ›©8Î3TBì[8V¡7{–ÍLN…²í³vpÄõ÷öò.¢_1Z4t–12F‚‡qÎrŽ …a!¬(›áà<¤¯QŒ繃SAZÙ¥3CpRJÂ0c)Î[¥Ê8A&±Žqð]C_ˆ@ŒŽ&:¢§«ÙWÐA7FÂ,àSdµ²ö‚å:ó«  À·ÙP–ÿÖ¸Â;½N>Ê9öÍ|ògÏ« ¬‹-Plù.M3]t¶ÄÔøžFJ%`¢`b ¢Ñ×™Ï\ )VADiNÛP: g¶†ÏÑÁèï4Ú4ŽÌ¶Î²`2Ç)Ö%{ü–³“û<á¨d^î½sÉJzé¤× ×x6ò9+?) ÆkùÀ†ûÁø<¯K¬µÎ÷úúèú@dØW> Èú¬fÆl šï²®þ‡?š†Ò®½á Pp}4níKj¿Ë xÎæMÖº FLjü;`Ò 8Úqt®‰à±Ïs]-ƒ[ˆ%SÓˆr ƒ3pm ’>£f,ºN € –™Ô)éÙ“—NÓBa@Ý·Ò£ó÷ ð!êFªK#qv…4u°ÛßR'£ŽK. ~Js±L§ð玄¿¥µØT>üâÅ\KTž–1¸u”G«ƒjËf÷ ˜ºï:$óÞ¾8.?`› ô‘åép$ISöœsÁE,]èç:pÜWÇ‘ùߟcÎtFßãÂûNÏ‹gV@À•B ²pìf)FßKöQ‡°÷KC–œ•—z†Ý—À÷û“´%=J£®™Yõ 2æ)¨¦ƒÏ9[ܪ%®ë¤¾yh‰Ï’Ö5”›ÎÓ,çåÙ÷>éÜwKëÒ}üÔslIs"l‡â|¿AÒÎïw µÈ:uÌvuŽöìU'4£˜ƒï±EÎ ºpn·¤lmM'ÎÜÀµ%ž8I6*ÿñÜ@IDATÐå‰û¹¤tùÒÅô~ãÊÑ+/½˜›x^%à}óú5d@#ÕCNF[“SgïÇiÔÎlåà*û~ìäi2¢‰´*°*Œ²RÇùN[?WPŸ¾„ÓųæZ+oäiþN0søÙÛ¯½KÊù±ëáå{ýçßÈ>{û‡ÏñþïuïÞ»¤S¿¼—÷ZþÝ1ôÇþU’a#_Ñ!#½i÷`á=fê+3=:ØÍ¦õ9‚p5 ãl; ÜËxß3pLýÀŒ4×Âs >åúè|’ÞÕy ÚšŸžLƒwoÄ{-§>]é´´cu¯ÌN—Þåጅ¾«st=÷Ò]8ÅØ{ùŠ}bÍDÖÉfIñt-[ûx¨£ƒÓ‚‰ý×Ö³Ê?»Ÿ>ßó­ãE¦Û¹¸âÊçb6bì5sÛ»âÌs×÷Ú“½{ýÛ±ë^ÄI¼0;™êqä‡ÌGXAo:™Qfíg²„NÕŽeãÚ›7i‹õ Jûµõ¼ZMi’ræçÞç¿yE*àŒZ®ÒuäÁëgqÞìõë´ç¦N¶yœ²Ê³ÓåC›ðOþ”:,œ»|[gð4`±rÆ*ë8œkÙ»‚)× ÊRÏ(f_ªYw[ äB'–_¯$ðÊ}Ä´ÿpeT2"ˆŠ³`©Lƒ<Q†Áë`f4¼›ìê ²éÐe¤g)ÆF†â|ê –L þ‰ ô¶Xdt%3ž€¬y¿ë-oSf3‘àÅQÖš}W^ª¿Xâ}’l¨Û½ÃéÓ¿öOÒ‡?ú± Y÷×=w‡ÃÁ _”gÆYôÙðuõ9yM#™?øÃÏPNF*ÁŒ¬Ÿº¯:–Î~u–fW¡s+(èTæ(—tœ <‡NŠÓ¡ŽuÕÉÂáòýó8 ¨Ý 3ÍtÌô Œ¦™…¬ä´™Îò¯—^úVz×O¼G’ lj6‹ú´ãv>Ê÷ž;·cÜO>ó<™“Û©çÖuJ/7ó{Pì·2H½×>Ñf¸ê<3]@y'Pè¤Ìÿot-ǘ“+õ”ºV_)ÐoF_wf9óV[Qv)f°¾ðÕ‹éãŸüdú¿ðÉЩuüŸ{ð!ô¦ú(1<~ðèùǰ=xº„“z$=÷Üóᄾ}ëúþ¡ÔKP‰e“ëhi„t }^GU-9Ž]"iäûžCÒ²ëÎ1è!ìξ:ÿ,:é¡öœUÈøÑÑÃGB癢=–´ë:¨“MN/¥/ëµôË¿òéô©¿ý÷p2нÕþ8~‚RÁÈ#m·®îôÀj ™/ŸQ~Σk(SoÐw¹œ>Áîµ:µ4x¾¯ìœÁ>i$ ¡[Ô lÏ¥ë>C¥3ʳžçÐ=A:Ÿwˆ‰¹²Î¤7¦FôûÙ?ŒJ,bÈ3ÕI¤-Ï׊šÁp¡„ÀBæ{¶•1ŒÁêV%ÌÓ€*d?ïÒÆ–vfá‹fúËÍ”µ„º7Ìh®#\~1 ²÷´¼[ÝÍà`ñ&×Ož©î<ÏÆx &ÇÒ]††FFÓÔàpºÃß›ìõù§ŸIÏ~ð£éæ_CÂŒÌÝ9ΖòÎgyfÃÞ`Í=:àÕ­<£Œ³ÿ®òEùïWÇf8×~f¯yöÜLñ²Ð7gãsÖ šÔ¡©­áxu,ë€unuÈØ¿ö7>ÏÜmx®îã«/ `/ù¾gÔ1¸ìj‘ííûùÙ}ÐÖ’V<ïm´@«¢Yè‰ß§´ò^Ï­½±¿üÅ/мT:Ô7('—*~±^êò3ù®Ž<3jÍÞt,ò$K¶ëk% F= š0{uàžv­[àm3ðþ‚öûîÜÁ6ÅG™q[Ñ,AÓìåìä8ÁT›!Èk…õž êz‹e«ü½I0ÿÊÒ<޳©p0y{eð°<Ùêc3SЭw ÓvYc]³*XÒØ 4E…øÁ´5 õ|`&pƒ©ø÷ö½òPg²Ï­Ä6!þ'¿Ž6ƒ 3»Q[ÐÀ®rÞÕ¹ŠX÷_ygП¼ÁŠ(ÒœÛéZê_£ez§-¬ã^j?Äù€öÕ¹Lb¤K×ÞõÖvW¿³ê‚gE}B:QÖ©ÛYAÑJŽQg½t¬œ7 T‡¿z {ˆÊ"èH3PèÝžF8?%ixšÒí8Ñ÷ZmÈδÅÇF'¶¹Ã3æY×k19ì¦æ—ùjëÒV‚ý“慎¨[Y}Å3 ð³À&Å–—¿xv凡鳗V ±’Mü× ˜JÈqé¤}W›§;ÄJK&Y"¿.2Œ„ ex |Åý¨¤ÿº^lû%ß5S~)òntkžÍÀúïÖæñïit#y¼ß¡ó@G–Ap%ïÔæÖv½sóVº{ëVœ)t•A’?F‡ÁÚÆX£YÎ$eû— L@NmÓ.rÜG¼shx8dó2º¯v€ú½­ t\«ó„^H–½öv‡A®ë6çÚ=3Î5´R!A1b%Æ©oðûFôÑЙ:Àv«ÏJ}=$ÝøaÀö$¹³t«k`I_oÌ»££30F[µ(çÜgƒ€LØ¿öW`öWà¶ › ܈x0$”ÑwÖR¨ü8¦m¬ŠgoßÈ®4‚TÅÙ~I–­R̲EÑjsû×þ üˆ+ IJҰƤ†¦™·R—F­8`—Î (‡*šgÎ>€ò‹QŒbêsTÚt64jÚGENÐH @#vïY|‚‘YÊ#šó[—YéYMí(é”ѥʟïD™ÄxWù½ðÌs€ÉË(p·q’¶ œYãE®–?C©:\FQòZx–Fv= ŠYÒö4 [$£Ó ™2"rë‰èÇÐëÇi]J/7ËÁe}þÉgc}úïÞÂÉÐÎYùÞ#O>gÑ…k ìù£?•zzï¦?þÃßEÑ®K'î{ñÌÓç°"=ó»9»ë©ïö›©óp¼’¨Vþg°ÂЍŽm3–ÇG‡ 8Á¸íï„Ó«‚(sž?~o†ù—¦W®¡x¶RŠ37½yùJ:rŠ>€”¨;ÄZÖ2¯ˆ0Ÿ[˜d qýÙ´p¾ñêÅôž|#ˆÌ5~ßÈzà‘ …Z‡òò‚Æ BŽAr³&_yéÛé+Ÿÿ£t˜ÞrRŠ%ËaPÂß–Ã*(Ø|º‰±A&&Á–èŒ<Övw—¬:€mM-hÚÚ\£G|å: =ÙÖã÷¦pÔt3÷.§KYö˜@^1«JJ¤a8¢D¦ :àåƒîMõÚT‡ò.½±Ù˜z{³eÖ:§uîõ÷÷c fÎíŠÚú´Œo”¿:Ã[Z›ˆ°¦''ó×Ym™j 1 ißc™«M*'T—Õ ¸ä÷fwS’£©ñPS€/fªk ZUT¶„ãH°DG÷Ï PªÀYÉ–`€á”aEuJ8o ~Á4{T‹AÙ&‹d¬iäa¾ °WJÖreò²H{@†m7ŒªÖs`ÅŒÇ(è.°döƒe©5î-ÿ¬áj6¨…†ŽgTãHàkK¢ãŒ îèx×éÂb½”ñÒÊkKD¯ã‚öÞvµÑOwÐÍ«çË&û­S\§¯Näk·î¼íîìÇŠòÒÈ>ñ_ž±v€X3?6=pc_¢Ê@"†9°÷–ÿv¿œ ÎѲ1Vª 8Y¢qg]Z bþÎÑòú&n”â ó™æ30^å÷ÜG\‰ê[V¼ñ^÷G:—f‚g‹À¼åÑôVf±ÿV:¶Ç{ÐUд¿F® –(,Ä鋼13Ñ–Ð,Å¯Ý Ø¶†Œê»gÅ9ê`Ê–Z=ì>ä‚¥üŽ¥á²2úÜŸ;çĹ2ÓÉŠA^••ðPæÃÙ׉kÉd%œ ß7ˆféëØ;Þ¥Îå;u¤‡nÉ3³}õ„¾}·þò‘Ô ÍµÜ¾N<{gÁƒ«i’¹«+æò.qy„²Îþ­sð“¥åE**ü$ãHéu*¤©Ž$°˜·?5÷ 1Ú Èë8¢¶ì`û9ó‚Ûp`XÚy[MVî¸÷+dzë<0Í G'ò—÷K³:y ²ƒpòÃKàö”4€ÎQæ–âåÁ‘E+¸©Ãd=,éà›PA)Nx§™Æ–øììjgp€ÄèÜ:‡Ô÷œ˜cÜÄQbvu!dß1<ŒÞ‹îc%"eý"ëbö¢gSYbÿm&›í„ã³¼Cþ)lßàÌio…¥ÊfðÊY2ÚnÝèMÿð—%½Ÿáz®ãÌñ|i8d4üV:×SÈÙçW±vÊŸ~ä'Ó@__úÌÿñoRåëqH/þÏ«RÞÍ:su.£_Î’µ)ÿñ9~®ã©yVì[ÝËr2¾ttBGÏqèÔu."¸`€ª#Øeoe)Ÿ¹ïû¥#Æ`*Ÿ—z—rÉy»¾ÎÉ=íìê «Ãºàoê|6ŸZ«#¸Dþ.PVÙûÙ௜]À“ð`·N³j­ÊÄ,(ÉŠãy S©‡püBç²WŒ›½rÌÙÅ Jðo­cÀӴɤìžž‚à*ÐíÙΊçÀêF³¦Ò´Y¨ÒË0€¾ÅtJxväM–œ·¬4çÿpwWdê´7ˆÐ3¨lu};]»y'=úÈ#é…w¿'‚¢µÄ:Î=ôHœ=×x ¹%boaƒŒ$Lå’2éÖÍéþŒy]|ùK©þ½ïC[­]‡×,|Ͳ]m™®­´PMjph”Œm*mY(rìE>[„žƒ béRƘOe—šJ€ù½Ù§AxÌkyiRÿƒ©±ýÿó¿JO=ó\ÐC¼Í¼Ýó¢Ð:›çƒ³Å^xùoß×ÙÙ•þÍoüfzñë_K_ø½ßJïúÀ{"àÀó‘«žÎ3ìOnk![ÊX™ÄL{¿kßvé>‡€ÛLÀ’8³¥ðÂzôCu mW«ÃäæœÃi^Oû®~*YÇÙ5Øw‰½0ØÍyEwˆ iøJð1hmŽ@”›oÞN“ð¿û«Ž¦sO<›žBÑ®6¸Y2:ŽíyúØkOCØX½7®‡žx¸»;½~é"cÛ €NÛ0?²ÍDW‰JS¬ƒv\ úŽE×eb‚^÷ÐN•ºt…‹›~˜1•† ÙâžÌqi"6%É?‘?h¿¹Ö¸n¨¬p&Dä V­p¼—_-}éO?ÇO¬³·µ­• %Kðõ.uÇHà܇SÏ 03(Î÷(§Úƒ[½7lÊ„‹«è`k¬kýGÄ`ØA^kéÊõÛ©žìЩ‰É4pçU:ƒ¾wVÑ¡ÑQÖi¡œÔ.7˜b™ŒøÙ™ ÈZy—ñŒ,ÿQ§umÕ{ñÏÚ(#tL:žUx®™ÁåØWù @;1ŸñæsF=¦â3dØÃ÷¢ Ïa“cmå_,BØÄõس5õ¤ZÃmÔ©Ë87Ûlê d ê¼™ýGÐå[;à>xvŸº¨6®ååQŒÍ:î`o{v•VNS1˜ÌD³x}¶~bf¬;>iUÝ RCG&ÐWÊÏt¦«;»ó!»Á9:àÿVí‘ߪïLƒ{˜LÐH0˜ºÞ$Òí-m|e']zùbzêéó©Û{¯õ™ú´ë‘G…¨Ñ¥ÔRÎ\@ÀÃÔÏ;̳¨ðWˆm^CE,0¥¹eÆGe±eèvq‘Êà-]˜÷ÞîKe ÝTã9˜ +WÓê4ÍЗb»»TŽ(¯Oåu耹ãQ~}}ënEê\ÂÁŒ£˜íusLBPu]óîÍO-5Ð!;ÓøìeX¨rx[‡9ø ÕF™[IÕ}¨„±m•X‰½²)¯Üfïoï(8à?…żÁsv!4ù¼eÇ_í*AŸTBbí V6vtv²T—L¨Pa¡àЧôdÀ“¼Î šç,ÈÿÄ´! ˆ³ •ëŸUj FõSÏ‘Á /*;x€Æ}*,/H%Ї•hòs±¹×j››÷B?žUéÒ¾XŠA…ÛЖ²_ZQ´âô%mÊ´A QØ€>W©È`¥çÚó}o|ŠùBëèýðm¡sz¯ô¸Í<´Wyëâ8s8sÜÀ¿¥<Æ ãQwóŒèéG~—¢šº‚7É+=‹ŒWYì…©ÆÉ:Êò!f”Ñ}=wB?Ùw ³]û×þ ì¯À;sd†*0É’±2m„Í;íúQg¬@Ó8²¿Ë]˽ÑA2 î=œj–¾qûv‡‰ÈV¹Ü¿öWàǹAÜÝ=cßg«˜I‡~¦BŽlèÔ3~ÀH#P‡€¶—Š¥†]1Ƽʘ߸ÒH°Ü ÔJo-Fàà@_ìf”gJ-jŸF}Á5ŒTà<ÐÀžAI3bqb|4Ýzó²z¤êe8±ì¯.€c`ñN„«¯½”Îcd̹>ö×IaÀ$å-v-k„ªYf“›dÄ» ¯½Æ>Y¶u5ºÞ½èHMT ¸ðø…TU(ì:h ®bH¡“s:œ/—f/é}ÕØÍ¿ÿtÜoé[wî¤/~î?^(–§Ó'O¦}0«¹7²Œprë54Ü_ uf¦l`„±GEÈè—…ñ:5óÙ4ÙÏpàX“ööèL¡Û·ÒIJ×7’ýñò7_L£ƒ}•òýgž}Ž’¼Ý˜™ƒK0SZ0“Mð}‹(_Ae/ rÏàVMØFòjZíÀßkHû;A³ oܸ‘>Ûs—ï`d±7°§@kRôK'x•N_ž³„Q½Íz껯®A”’×ÁÎ"”ãwÁCt˜sÏæ6Èzüjú»¿ø‰tŒ¬Îêï°W´Y +âë_ÿP!mÎ_HÏ>ÿU=FÒ—¾ðùtåòéïüÒ?ˆÞÓÿþw~;}íßþFz/¥uêo²§:2vÑóá¿î_¦YFG0ss.9¹dø{èÆlë*ΕgEÀÌ?FÕg¥ß P`FåÓ,‚Ÿ½ »MΙ%õà }[NNþ<¯¢Ü=HÀqP ø7 H{ŒØ:’¿ògNpÑcŠ_cN{W+Ž8uð ‘G ÆHÐyÛ¡Î4D A7üt '\ÿÐpŒ%‡,±2èÊ3¯wèåíÙŸŠ³'è  ñ$œ¦ OôåûÊqoñ3øòYõ:ÈÌ`Ú!»£„ù Z&RPP׬3ç/Ša_¹<2\Ö|zšife+¥”È.¨ 3€ö©œ,JÏV? .Ä©dïY+uLÒæÃ '圔/8g†±Y-žQ³ d[ ‹£x”UU¼0æÎá´iíàܱ–Œ6 ­ž[DæÀ•Ë—" Ùþã·o½²À¥Àõèðpð?³à[š›ÂÁhæÌµ7^'£h0uCÃÊþW¯Dye÷T¾ØÅ~HËêûÊ 3sm›bÐÐ`_”Ií<|$Àù½Îq×^¹/ð¢óU‡´%ÄÍüÑÑêžéÔ >Çw¼šäa‘ýÜŸ=ŸÊk/ÿp&H?üÎw¼ýbkâ3å·´ªÜøAíï÷½ðx¹¾ÓÌsŸ/ˆëï•}^Þoõ/ƒXÔ•\ƒšºÿi.ðÆrty³µ‡‡â¾è‡Ž^’Ã8½ÜsõŸ/ÏõòoA6u#Ÿ½x:Ne‹ úæá³‚‡ê)ò^37Æ +Ò¡n€P¾»ÈÒ™¦ntãêëéÊÅo¤önœð-Ole£nÔÒÖ0†.%PÉ{Í^ÓQ-²Pgå†ϳ•TØ;ãš®³êmOº^:»Õu¤—……iö…aÞÍD‘uœ%ùò_ÜĘùþŸ½[Âíâ€1èÄg»þ‘ÙÌò vâ, c‚"Ó”ùùk¿ÃáüimmM_øÜçÀ)AL žãÈÖg8ßYXÀ¨Ø½Õr¸›çdŽ…„óÏ Ëet:Û¤ø?ž=é6xP2¾@oë() Ñi_te‹g] ¸göŠzü»ÇÏ·[ŠÚ¹9ež/ù…ăèd–Ò'N³OW°gçŲÆî{ŽdéMš²gí|ÊL¬r>סzæî.ï²_ʳrí½ÉÞ°féj‹tÔGe¿ÇxbÍÚëï{=½ïƒH¿ð7?…Ý]@CÊê·.÷N}Ê}’nÜ”¹½_ð\‡3í²¥u ORÞ´Gr8øyN8àÕò!ÖÁlém³úÔ}Vpt,¦†$`4`°¼L™àº«×Í/.‡í±ÆüìÙî×0<’õî Úf²ñÊöÕ Ð'ø~__Tb0ÀE¢Æ.Qþ?̸?ñÉO¥Ï"§?òÓïågHð.l ž¡3Tçh5NñuxÀ4ÆéeÎ2ÂóéÙ1Sºg™dϼ:¥m#´ t©·+ÿ¦G§Òí©ôÿùÿNgåHGgWjÇIlöyWW7¶H]¬¯¼‹€Æ3^¥SÜàU‚Ïz#ùØÏ¥åã'ÓÝ+ôæ>u$x¦Á6(¸å¹¶×¼%;©ºUÊÞ˜¬ƒ[Yiž=l DÑÉnæ¹tVÀ™Ù<7MÖ:ë ÝhõßKƒcÓéÿþ§ÓÉ“§âÌÈSåÕÊBš¡!eÆððPèo– 7È@{òÈ‘£ØR÷¥î®Ãa |á󟧚Õljk®g-©†ìÔé–CvAqXŠ9gat3ËÏË[»oêÒVÞ ê"Õ45àˆÖQ9Órp©gh÷x¶¥‡izL¿AÖû§õ×"àýð9{´î<³+û!¾‡ £¬ö#XŒsþì ïŠê/¿ÿÛ¿… 6tëó¥mÌ8ÛÚ˜¼–ñ[͈-äÚ¡.rú‡‚$×ÓÓ‡Óªˆ “J²†çàõ{sǨ<Uk ׂÛt$)—¢BÏ©£Äû.üBÝYý}‹s>JÆk'?|ÿý©þwٻÌaûڵЅ ¬¾qíjºƒ}3‚ìŸBVø™Á–V¨S'pÌøw} ºÎ ¾œõz`¾òor ³¼·2Ælcƒ‡V9×S«¶ù²"íÄÐç­VLÅm)ƒ1”ÿÚdòSß©Ý^‰ŽgެrÆ&ŒãØ ÿò{ÇTãà,YG›Ø}vÝ ÙûjðϺrQ»«œªoòƒA?]GNÄý:—ƒ Ùèl—÷öü»ÿí=™cþ‹¦ügÐ1^›Z[a[‡ X‡¶ötØ•ð÷@¼Â—:Kš˜>C{nk½*dAùÁºàýòYåÑ<Kõ–Zž©¾zè˜Aÿ®_œO Üœg ó.[„(ëwXùÕ¹Œ±¬‚4è`3|‡€þ<ž!ïÝÞ ôŽ"xœô¼C9¡å9øòxà.ÄÈ\tëBèY§dèSÜg™ÊÑT®|ñì©GèÐS–Ç¿ ÿˆ}Q¿v6Yf ë,´:†÷{aÓ1àêIÁÛy.S >c0¯b;Ò­ßÔŽ‘ûýRþ¬®”®<@¿BS}0@GçûºX­08oSÐ]{Ggà-î]½z=uèfûƒ«—o¤Ó÷ŸE÷EÆÂãBwd½a™HC]ÌM‡Èön.ÝL£ )§¸!uur8ÿƒÃK©±«-m¯MáPUa›’è»al¤±¹­t¬µŒyPéŒdõ90ÎQ}¾§¦WÉ*ï »'gvÚd?Á™©(˜ÓJLt>@`N~¾•}ÎA?ç $  ¶¡( -4¤æÊ™tæDJwáù+“!^RÞ<{•ríÝ]ì?¼u˜žé¦M•Àg ð/†tÞ¡ñ &Ó«—.GEEuWùâÚSɯ œûZ]ˆ¬xí.ƒžWVÅ+x´â~z¹À(Ûå›Ê?“´Wg‘«Vȱ/=? 3±Qù{ ýk›{ °gC‡G×Þµš%з¤ñlèTG„Ń:3Ç9Ÿïª³Ac5ïåBë¹;Æpc´š€öqèÌK«Y;@ BP¹¸ÊÙ£Ëc ž7ÛbjSå‚ÕxÖœƒ|Ûã‚Öϰ}`ÎʗСGƒ+µ÷·g×6ùIr<³AÚ.èYž WÉ;Ô'€ÛbÍ#ðº*`ßý½ØÝ꼬y DXжÑðÏ*6Ç(:€­0ššIfa **À÷xÅÖúbºýæ«©±¥s>*H<úÄóŒÓ62Ëà’¯Àר°‡?£¤´2ôõ»}·3õDí_û+°¿û+ðŽ\•$•4™°?ï_?Ü ¨H”Õ¨A:‰Tˆ‘søøqÀš9À¶!Œ£Š(ÉÖD扙= î¯÷·Øûßú¾V N34&­…¡‰¦ÁªÎ¨ñ¥'H¢1 r&¹±aÉ4ËWâA™T)5»ÖK0O€uzj<>š9—ÂÆšÄ©]…‚Ö»Ž"­Ó¼ @É(ÉÆƒ­(ÓEé‡#ÚqçëgÓ—^”ï` NÑc”­m\4ƒýþGžÀµ·6ÊFN×ác’d`H jÕü¤Ù&(¤(›(“–N/Ä©©S@ãÿêë/3Þ@² @×x–ŽL2gŸt`:O{ uvLK ,Ì‘c§#šS×ì&Kœéä³ÄS § ¥Ù>.¢ÙŽ–Rãž;W¯±<–^üÜŸ¤ äfÆÎ1{Š«Ì ‚$hAÇO;åÛ—ÈäÓÀd®‚]–1/Áœ¥¬i/€†À«‡Š±ýãŽà±¤ùÝ;·1`Ù ¤Ë—q˜p¯€§Š¸Ù­›8`Írn‹ –Ð[d>”›®Å g¬ .Í­ Õáø¶§Õ¶F> ¼sô9fWU“i½'8oYZy›’`¡´!˜¼Îïj¯±oöUŠ`,h)Œ_²½ô4ÛC8úBB‚ãØ¶iç`5J|Æ7‰¤ÝÅ)EùN寲™ù–>rô(™Qdê1'³„¢ª†t5ÆZ ´h[‚ÚÚ¬¼2ÖcÃxgovŠ-¹K櫆.û'¢AbôºÆ‹Óè,d|‚.žbª8Íìƒë(("*øÀk©+ÇŒk³ç}§cÐ‘Éæ…Q&HmÅû?øá*±´¦Àß©S§ø&Ù ¿í±Og ¹üú«â¨ÑÚÒ–~ò§~>0øw—RÝd½¼4bÙÎÏîã†/ÈWtŠ;OAlõŒ(ׯï=ËÙyŒ…1:vÿ”ÙÕæŽ]ÑïaLoõ½®åËZìrÎó‹=/|Qgk °Çl1¬™+¼ÁÞÏžÿ•‰ú±tB£y8¾æ‰¶þÀϾ7=õÔ3´8_Ké`O'zäüyì+utvA‡Œ;´<=øà9@±ŠôÀ¹s‘‰¥#Óê÷=p.æ:‡>ü‘¥K¯|‹ñðJÇ ¿“&v¡!4k'á¤PÔ~ݘõ³ï‹ CÞç÷êd>‚ñÁ¿9‡Ò±e$2ñt ̰Œ–>ôáàdnçQ_€ ò¢¾ÞÞô­_ €Àˆ~³‹æ³™›U8„œOd 07÷)è‚53a|‹£‡Ñ­¤ „Ò¼ã] ˆÉ2u•ÌS€}ú^]±ƒï<ÐSFXÿåÃØ'^êN^Þ«N lL³ €gÈϵŸtZŸcîêÊ!uŒëù¾sõs×åPGX25E†S×Ú;ŒóRÌïÖ ø2Ë÷†ûà·ÏälJ:”º¸_ÇVG÷ñplxîG8ŸnÈÌT‹’¯dÇu¢kX…Ãqú;e˜ª°?XTÏ”gZ}BY%ˆ¬œQ§R.ú{÷LžáîVGðwfyçÂ{ÕÂñü—­'Ïðrý½EP篭Z¬ÒàgêAVJ0 Á—¡—(ÍùVnçÐ]=´¼È¶+yÁ[ïƒç~ó_KÛ€~ê©0ø ý2ËÈ®¨£ÿ/`. YD•Kéü›§m ï*f=‹ †À©£cg€žž¿ü{ZC³)«æ£ÝÚ!À@cPã0Ð˳pÒ±áaî „vkkè²fDYÉÇñ®°æ<Öxꮕ{¡³ÕŠ58аûáÊ—¤­ÆS\i#ò˜séžÜ›ÀY˽òõuË‹œhy^ˆë:ët¼ØÏ\=AÝÎÖL7d¢ûà¥ÓÊ=.«ªKå ¥é _y9<ó@ñ)¼b¿â§ì?î¹W!çQºð í]ñï:ØÔ”yøáÔß'uµ7ÃÛ³ö@òˆúû2‘Ÿ:žt5¼¸Ž€"ètºäâüÔ¡.Ó®Ph:ŸxÿÖ‘£.¡3f}µ¢JSkgœ=ÇbÁæf>m–žˆ>Útˆ³1ØßOpÛhTŒyïû>Ⱦ/§+¬ª_ØÊ ›`•'ž|&ý&ôY ëËùjv>ÂiŠ~¼‹s2dò©ÝXý˜%Œu4ÀRt^¸±NœDg† ð[>YGöêÚ.­¦®éÿ!ù¹¶–Xõ;VÊÐŽRçö2@ÌËyÇÅ:HŸ: çæ&£|º´ø¿Þ¼†>.%Úzb*x:ˆÎgZ>ëÓø¼dõ—x–ú1U7Ø›Í5Î ÎNm *lᨶâ“ÿÎ͵’ÌVºžwö¾³éIô%¦‚8~íÈkW/¿U!Áà>.8¤®"ŸtâÊgjØkmÈft]³Æ öƒ¶k«uÚ3L÷çÈòâ,6 •£øžºÇFèÜ8l–°÷´ŽJK¬6çdzêÚ01öx5džiË€’œË±IŸ{þ]á$ÓÙ¡¼ù^—t¸GïÚeÌS]]éyt¤7_ûjTîÑžñ½Õ•é´ó¼oá”, Û3ìlhÇŒMζ±PÏTŽº«Ã÷ÞU¸ÐQ~øz¬ÙþŒƒ=>:Ã1Ã>,•ìÞëTAØ6É^ÏÔ=Eyå¿÷~-èiýüæµkÔ P…ïhc#ÉÇ‚®¯_y•jwœ·Dàà=z´7POÕ™Jص5Õõ©¥ë$sO×`¯2ž¥ñ!ªø€ ¬½² ­²^5í¬ñ2®8Õ5R¥k‚ö|ªÓÊqi'{'ë¼›æG/¯œ+OóÛè”ñŒÚŠfpp4½úÊkè- è¥è¿Èë ˜äR„í2:8Äz/±¾&*È×ÒÂ8•ù87L.ø‡K•Ñ"m_àãÚoàýXik»ÐªtÒTì/ûÏv퉯ÝÀ§É¹5*^”Rý}{_`Ò*´¤>œ‹ÍcÕ÷\`H½g¥‡Y±ÐK L¼+ä-ó„²=å^íRq¤™‚™“6UÐó‘Vò¯Zn`ï qÐ÷‘¤bk É^¹Ã_!û ôC‡¢þ¢Í­äÑ©o’ÏÝÖàåCõÀ<î•^å]Ò˜rÑgz¾¥om^硌ÉõûÜ#þfžô®=RB¯õø;¯¹µž4M;Å[èÝ´¨·Ú AèÁ55Y%ƒXê›»¢¤{qI:q%PQ.8co`´åèþKð«•ÚÆÐ?Q9÷¯ýØ_ýxç®@—wîô,3Wî†Ñ‰Ò0Ž œÔÖYÀ½Ñ½*óýñþ|¨£ë-eôÇòêý‡ì¯À_¹áØô.ý .3*ü[#EðÔ2Mö†jmïŠßkp Ô |x ³8±}†Î\ Kº.m+°l‰8ii\Ùlñe”ø¦ºŒð¬üŸFr=ÈßýÁ¦³>œ®¾ñÎ~‡²gä°†áý?¸ºªöpÄ× ²ü”ß73¼ù@cK”Y_ ï½žÍº²Ì²ýJ5Š4+PúG]•N3¾¤BñuŽ*Í :ßrü4µ´]{0Mòì6¶PnpkttGJ†b¤ÍòÎÞ[7¢ÜÑÌô½Tˆh†ýßøÖ×Ó‰ Ϧ£çF9Ö§Ü›†MÎU"¿5€ÇFÉ4ŒõªkhpKç©JöÌÔ©iy%î‹ý1² ,'ö¹?ø}²šZÒ…§Ÿ¥7ýýl_¿þf¨*Ïþ1ò]…zzÃŒuËpZF} ðОì8Å,a( A±Nä°Ne›”‹‘€-p«Á q¤C>‡ò·¹å”Úf¬ÎÏ€AÝ( ‹ñaÉ¿u#¤1FŠ¡M s P‹­m xõó-­µ„±¿7‹Ùq™=%U€IÅ:h"ÛŠýÜb>f¥”† Ž‘éBŽF·ï hÈ N™]£ÕiIg :r“™nF›ýåûqLˆ!RTÐe@0ô¯qöÖ-sZˆ‹H䀦ò 4~ œÐ¡©!ã[ßpÀða,³f´ñFžëj¦š=ôtЬI¾È˜*¡;D Ð%2/t&ë44q_YÌÈÔìðß‚m:<4by8¿ç´»ˆmüÉÅ1¯C+¢º1Œ½t¢›9ÕÙÕM™qZ ðü¶öŽ(!ûäSO§÷¾ïñlë¦ÒO8˜˜_ YÊÔŸ3ÔÚÞ=íµ²K°›ù1ç‚ÉîATºq5c<º:Ix57 æç»[è; Z£·(n7Ë—Fð7Þo¿§Ñ+L•/ù} Köòý:±[qêöܹ•6ÉôËÅ #X¢ÞcF™<Á3%ª£ÍñYvÔ=ö¾ñ: ´÷_ÿÓ_çÙì?[†×¾Ÿ?÷_}<~'Xê%8 SȽ؟MwläN:y¤›YgNþ¬40c€Bž8îNMM@'d.2¶ˆBgìÜ{&=™ÙS]áÏ8…G¤uMl×/šÒin‰ÓÈ~älx¹÷±^œIù¨Ÿ9Fùägá+Tpà.d »¼QÝ¥£³+èÃ6éÔé3±÷ÓÓSa„+¤3gÏà*õ3×~` €h‘sÆyÚ àó“÷ÝÏù s‰ [×#£ Î!7xÜS刴-8· Ýns–ó˜{tîzPk%˜ +cL$ÖŒ2•ðO(ÈàçŠ(ikÖ~A/Ê-¶ B«fU %0蚤µHe Çëº@ÆAV ò=©u¦o8]a°z¨­)u0€@‡e@‰mάٰ–'•Nâu@Ø›µœóV¯Ò¡>ð¢<÷Þ·–KëËë“_éDÛ!ë§ïz¬·2Îâ@ühºsëfé#À.Ÿ,cèÚ¬ä-xAeÙŸù8“àO~>>nÀ=Bë©B?0SÓÌü¹©æa»5û0·6ղ΂äŒ=¤˜à_3à­œãù2H·‚se†³j"•ßé8óÁ¨Eè˜ò8 òF§Ïã\Îqµw"ýʯÿ“ÏëLsïªÉæÞa¼ž»Þâý‹KT+âŒi‡ÈÚ9ÛÊÚ=¨ù¾ú¤vöff@ìµ²( ÔuŸ¤Wy²ïó̹×ò—eì Çé™sOüŽï¶íˆY«!Ó˜°\6Ë[ ÒãEÜ—9üP0ù.3bìò mœà[|Ïsæ>ÛÖ«šÀTK›ËÛä ®í†úgʨh ƒ#~þªx×:gQ™-Ÿ :ã­ž_u×!*¥isÙÎB>–É)Z\ ñ3­RÐw 7øÄvXrNŒ¤KßÞM§ÈD¯$øm‰ .[AE9næCÚyº5¶Ciö­ÔR·Dòþ´T€2øÒ\ð×åéqæ¹€£\g»8 ööbÿѺ©ºƒ Vžu ƒgÔl÷‰´‹â}ð‚Oq2—UÖ¤œ›Ã·‘È *>ZÆÝÒðyìKÞ.8 í + >’^çf&R]|>Ç–`r•®^¸µbF6-ÅæÉ-_Š ôÉ%œêdž« ©·ZÙÂã9:8’¾öå¯ÁÛi-¨O(­®šÇm¯‚¥¨çJWÄÈA´¬­çÅ€ˆm²äej}Z]''=‡ý2±Aç¸iäÊwiÚókàŠR–Z€itµªª Gš¢]…m¼Öy—ôç>ÚlR^²¿wYË\äw~ðÎ-{&N’“›ÉBirƒ1Á דWP¶Íä šV6È/ |Bçà^åÙ6k»©_n‚ååc{·‚ˆã äÁ<¤Su0Ϧ¼Ó1‰m8G3tTϱú™XŸ‚©‚jÂkP›gÖ3å=êvž›°á=o¬«6<ä~¥­L°7´¾AÐÖæ6Õ5 ªÐc­ê£ýn‚=_ÿ²uËÎA'eà}`:gÏ=†mnÎke»Í¦6løU)UO FðL'²óÒ‰ïØ,y*PmßS£õ_zñká0÷dhhüNÐ'ËL K®T˜‚r¿ÊyÖiTÉû&ƒ%äÞõóŒp?KŽYv\'“€†jsõFm8y;»»cÞý½wq$=ßàýD‡zŸ=ÕÍ†Ô hm;Y=Žßyf¤à¥Q^{ìÌÙpêô³½5ö1oime‰2#^gŒNÀuŒ‡0qù<3RÌ'ÖÌ ¨@IDATÓÅÚ÷•SÂU£u…lmw×YG“§ƒ:l4œ*P4¨Í¶óÒ‘m–yeܯM4øME|. ŒïÑžtÈÆðŒhœ6ö@ÔP6 bŠ€nËã ¾ ôá,ÃØ*'{ɽÜÁè7K  d_ Ißm¯Û0âùݺF|d;sϳ|½Q½F†kÀitiH£N\~-½JÆGi Ym”TË38€qk€Ø‡Ûµtœ>Ë,‹BhX[ȬßU;AŸ¹¹™‚T–½+ ÷˜kà‹ü½€€»Ÿ8£¬uz”‘…¦ão:8prù=‹Ì]°M@Àf¿ϵ¢@uð@Þá½ÒBV¼Å õ=ððɧ²L"_¦Z@:8÷ <ÀR_;éø‰a¸0‹¡ê}^‚äöø³Äâ{–]6ŒÓõ”Ìh•žü~‘¬½j¥tžY®§™-¬-*äÙ[€ÑÒ…‹F'ë™Öfkò{ùÂÆŽ¾ëœ4ºy4#´œùµåùhw`ÐÀ=Çç WW)/uÝ5ì-mYª|Ñýôìè„òyÎÏó§a-h³³ HÊ}A“¥hX€Åmy!˜§.àó::OÇ»|†çʈy…h¡àx²òq™Q™ÅyÔ8–™:F2=÷û8?ŒÝþmî¹ë\Âï½¢¯³{Íÿ|÷âR;¾ÍÍì3ïsnáØa.¯{ÌWXǸ^­%ÿ>~ìX¬Qà,†&Nž:YW÷ÁGX#³=ÜÛèY ]š‰ï|Çpð™m&¿ÒycÀ†Ï·Ì¥%£¬cqôî…cp¿¥k†Ì©†~Ø;ç#¸ -»—›tàþ1NÎω˜Uî9ÓaãÞr“q§'¯ßâ{ò/Õ"úÙñ³ç¿ç¤çÂŒ«4Xz¯¼¼:8ƒ}ÜKÇgF¬•[¢¤$g šç„_ïÓÃr…çâ ¨ü^ç…Kdk˜i[‰lõüæç¹Oǹ'»¹Áܺ´†ðx´d³4˜Í®®JÛ#ðQî­Äº²´΄ŽÎNdÉ@û½tá¹wñý¯^åü–Sw~ȹâ¹8äG:ðಘ؆B:4ëÌgnÓ3OˆÜêf.Û+Óþ·#då5Œ›1 øT±˜3Íú!Wº79kî‘}lÙ6ö‚~Œ³Ò±Þf,DSöCçA5fc;^[¼¤âçúYFG–ntH ðÿ8.iý‡¹“<¿¥s#?Pñ™X(â2k«l;€{ö.Ï»€°A}>Àà•ƒMm”½lM=wâ¹Òº—Uz)û_QY‹l¯åv8òB¯Œ@µ]*ÚXÒ}fz‚ŒÂ~€Ë¾  ǧQ§Þò_]®íPWœéV½A@ç·?¿€?éHQ†0&©3Ø Q½ÎÏupzfò‘}f©'ùo+©ß© ºW,ÐÞtÿÊ¿]G[ˆ4¡[--³×ȺbJ<&f¨9>Á|—E°°;Ì!Ž`Æ] =v<­ÌO¤õ…±1‚¡ ðûeèkл¨²Šl~€gÁß‚6ô¬92øÑÉ+Bg“»VÖYN§}_u6 *™ÓéÀÿ¬(a›ŽŽÖîá£>s&M‘¡Öû¥/¥Et“yhÁý2“ÈvG…ÈC³FÕ­ä¬ê«QÚ~d@œ¥×¥s†úÑ.e] â\âìë´t=ªèõé9R”à`Ö1¬ƒR½R=tž’§®—²RÞ — :ÕñáÐ¥4€ÆÇï‘—ÜgoñèÚ ×i‚õ¼tË…¥iƒ)•1LýÅ+h•gºo?Wòg×öCñ{Ï*W|ŸerTœ‡Os¦Xk³ÎÖX§BÈeðc¸Dì’t—c€a<ÃÌB{±f2@Ù•‡m`pœNÔ m+ŒèáÜý¾kÓ{—L±‘Э%ÕâÒötþ‰§#øöÍ멳³‡bI:O)÷Ñ»¯2îÃÈå…ÁÅ:Ü$û¥.°ŒmKo㪘=>Nå')ûUàHõ¬) ¥uéË,.ϲÏò»^ÍØê:£k]}}úäßú¥| ǽ6¯‚³öècO¤Çž|&œ¿¬[×T–è­þòèãO¤/þÁgR3Y®Ê€Ð`Ÿòe« ìºÎìë }ÀËÑGטË<¼Y'”v×<Ž3«Ï¸>òç‰fÌÌ“5—†-ãêuìè øQyÜ£<öw=wîPâËéØÉ³Á;äYV®ÑöУçÖ¬÷«+00Ž^`PÇŸ¹ZH=XG3û ½«èpÈçÌéÐÒ.ÑN‘æ=cÊj×@ágC+Ã``[ФÃÁ>¸„[Äï#P“{Î?ö8UÒÚboœ‹t+ÝÇÏoãco§méÀËýŒ‹û´Õä?žìJ²A;w§»×Ãèñ‰¡wpø"c5ÀB§ϯ1bƒÒåmÛ»³Œ©%*L “mê9–—Y’ȶD«‹Ë©£«ƒžçG©L2š®LÓ׸±=5@‹Œl£¨»#ú‡‰ %þþ}:õÄ$dè‡ ¶Ö‰ëÁ œ ³Ü=+»ÐÅ|G·åÐ!ú2ß`œpøY6åÌaé™daCÏ·?®r¦=ÊñjÈ7=ë]­®•„,[\¤uHÕ!ì–²4›OÕ!ÖÁµ0ƒu›õÿICy8°8ܶU³E…™Ég Æ9|äg÷òaƒ ‘ÆÀ ‡U$Ô9· Ã¸˜ÛÆ:ŽõìÎrö‹8ô¦îe0¨²8l¡ìßó¿ß¡ î²êˆú¤sÔÉë™ÜeΚiñ‚^¶bÿ[#×.¯iäl‘-,Ïã,ªûºvÚ¥–жu“AÔ½#І²…V­\ÙÕ ¦€¼µºÙ4Ü?<ɹ3à:=ØÛ[‡¹Uà=ŒËŒ}u’Ì —Ùzòf[ƒñºà9a³Asž-Úøj—-sÎ º5°QÝÀ€ +){ÕeTí=_Úvÿ $˜EvT2íÇ']ËË%yuÚ thßYVJà4¨4ÆM+ûm€»ø”6X¿“'97u)+*XmGýƱ|a€z_vÎü>kÏÿ|îüö¶d -4LR]œ§ª:K zp×™©"øZîg´´¨ jd¨7xîÚM(¾¶N¸%È-WlÿÚ_ýØ_ýØ_iD*µ­­­8~h0¢„´Ý4™½:ᆧ³gï¥@ÐëíFÚôòý/ï¯À¸*£ÒŸŽ…‘ÉE G.à¬Ù-Ö{Ʀ´ªá–•0E=ÔPd­pµgzeUV K€ã ¯Æ„Ê"úŠÿR”2Ç ¼ßh6£Ú1è 3# -;œÝsd€˜qá;6ÛËóå÷Œà´\¸×sPÆM-máÔ2²ÕR½7‹8õtf ~ù=Ë‚VžIP•W3ífq^¡ÙÔÒ ˆPY@fëYZth°?u=FÔ¿+M|6zÙ!•”~óúÕt G¶Aý}=a4ÜÿÀCÌC‚?öL¶ÿ¥€žeå5L][3íkþðcpW’1s'ŽO`ÐL,çb¶ïá#Çè??À@Cœ#öŠÓø6ãõéÎÍrö‚Ìf(ØŸÚ²Šþl)`ûJ«€›¥…1J4¶Ý{³%Ì4Ý?OX·ñs^d>Ù¿½çÿQþ£GÐþ%‚k(—¬SkuešÚÅ Þ"VC@ãR c £Ã¨å†¤'—\Ì̶Ÿålª£ šÆ¤Z–%Ö˜}ôÙb¿ÍÆ޳ŒMÀu+³(̆ÞÄÌ ð:Œtܳn^ÎC‡¿ÆšÿÞÆàÐa-(à3Ë5è#ó €AãÑ€í|!P{ž CDœïJwŽ`æ‚Mkº Érì~î¾ÊÀÚZÖ•Ãý­Sö˜~¦œ%#¶}¾Ž3ÂIª-¸®1^£ºz6Ãγ! ¹†þ’ç ŽxeÀ„{©ˆÍÜ4àÄŸ˜4Á™ùãÙoïèÚ p…{dö®=GùwþÍû|þžìÒ€ó¬›52 j/T¯q[0„¥ËuÀ߯óÜñ Hù³@]”ä9оÏÏh¼¤-öν)xqã „)d}|†tášoò;çªAà>ÏðÌËCÌ4ps§cÝñ Ú)æ÷fy ô›ý !]\"pNÿ6În™õf\Ë/¥ +A¸þ΄— Îqƨ¡ƒ=`λgºYºî{TÂ`ì£,Á(ggë–eu&±&<9ë‰ëiˆâ½–;|XоÀŒRažfÙRi%V˜µ1óAúÐ !°é^K-®¯Ž`ß%Ò±)ð$Í6À«,ùê¥Kéýø@ДŽe€—ël/JÿæiTÎʸJöYö=9uÜÀǪ9Ë:¨œŸí*ÜB³¿'i-aГsÑà`q,òy³›4„Ê5a:œo³ôØŸuÎm!Ÿ H@Jñöz¸ö{Ò™g*ÆÃïm%!åSjTáö6tÅ{âuþ øxÎvx¶@Ÿïrý(,y+ð%¨1_™§ÝÆÈxÚfŽóðÓ:2G7ëé«HVÄ.Nº5EõS!—t ȇ˜üОÜÒMVÆ. ’r*œ Ò6•]ˆd§˜‹Î Ÿ‘Kö[]ôõÜŽ¹ vì‘3!!—f(éêú2EÎ4ä¹ ~ɼ‰¤QçäÐÌò„…GÉE.t‡ŒÁMV›ÑQ3<;ö=Î?g´Ëçÿ\¾[zÌJ³âä…>üÌ?–Ï÷ﱑ¡=õäIk~&8™—˜ë|øc {%’Yï¬7©4•õãtÝÔ›¬ž¢É3ƒÂ1=‹S£ ^j-jà?Gi_#M ¼½ñêKð…²ÈDâ-È,å§ý+©`€,qmuº†F³`9K; h“)Ä8èix¨óXЗò¶ ô(ƒ|$îUÞél‘Ÿ«3P^"ß3s\~žW”“ÈhR÷›"»¯¶¨œ¬R¯£ºyðþ¸[Úíê\;|&¿:ÔÙÎ?þDzé[ßÄ•Bàte%Ç¢^¬N 0@õvõbÛ‡u„n-í«sÛæH;D'öÝ«d‹â˜Ï!C_žé:…³‡9(C+«-óOF-NÝr‚PÆÆ&Ëbj«àùÒ&üÔuÙF¶Ëó¥'K»®üêâ^ö öÙf~Æ:"µ!ú бU“zˆÚ£ØÒ? ò_{AþóøOÑjç4öÁÀ¼÷ ¥ùžy_ðŽØ+žïåzä0ƒ@7vÖÒvƒôd_eíž‚vwÈî&ûÕ=3ˆDZÈ™2§’Ïs ^4?AGÐÁk/“éÿøÓ/°Î¡ËZìtxP__ØÇxȤCeT…˜„Ïe™°×ß$ЋìÂD;ÜÐx¯çQN. ¿´`P@ã›'€k™ °ZxÙ"ñ«œ)iêÀ›™£ÕÁlQ„~£~±E™b/ƒ5½ Ú’}‡v•­ð;õ"u¼ ÞïåÏ^¶œY‚/0-â±¾êÌè)ë¡;ʃ<‡:št©OBHè/Ê_ê8œÙËudsïí;d Ï“YžÒéÓ'RWG{8¸ÃÑ­,¬°D¶¥Á+êW8Àú{{qŽ›…Z‰Ã½=¨«K#“!Öx‰u©Åþ\EÇ™ÀN:tæt8xB÷‡×l¢£,Á‹”èµêºí  ¬ë>~"ôÔBdÆkÙ’ì;î@EÎå&:™Aíêã:¦¥ eÃ*|rë¹×FÓ†ÚÙ!X`ÄéÉ™¨$6N6ý VMµØŒ¥(Ö«7<µ1zž!þà÷~'ýõÿb²ª'¨û°áX• öò®È'€‹}+ƒT]ig9/«‡¨ËnÔ¬ý=½l`žç?»Ü׌–ßúà¿ø+ÎŒw±c#ƒ©1×@b+‘ +âh]‡oâ˜-F†4Â7ÂŽ4ø’ÿ¹¾™*AÖð;½GžSH ¬Á¬VW“ÇK¿î{º·ú™t9ðJ厲U;4 ÄÒ6“wZÍÈÖCŠGô­ÝÈ´¤rõVéPzV_UñpI¯p_ö-ÓÉÖy¾Áœr¤I0KÏÙ*´™éfzþ’ú ºoÐ÷©§«c¸Ç‘ô€ÜÃç3ôÞe`ÓæF0bàW‰ò†÷´¡Mëœ|†6ºgÒõr­œ¤6ƒÌç/ö—ʬ_Øzî=–ÀËØãm*Z¹òeçªÍ¤a%/+$̬ÑÔÙ« „')¬xâ¥îb5ù‘¶šëd ¸‰P¶WpÝkù~Á1YACž¹H@ËŸ}ñÏÓ#ž yãY÷û[ÊÆh¢qz¢Ï®¤¶­ep ’¨ •íuèÛ2/Ÿ`ÔMó Á¼jò ::ÆØá+yd³G”Á/*¡ò:¡d›+Í,Õ€1ˆP‚~D¼V¸B öÖ¨,ÍÑŸüè±c©ùôñ¬š4•[€îC°aiëT_ÛA4ÕØ/…Ÿ®ã€›ùÙ{ÓM¯ë¾óÖ¾ïk×^]ÝÍÞ››HJ)RÖš6eI¶’ñ’ÅqÌÀˆù0˜2c ‚Á 0A;cÁcÉKì‘DË’EIÜÅ약Wum]Õµví{Õü~çé—ÑÈRDF†üå}Èêªzë}ŸçÞsÏ9÷œÿYnUZÞi ž£YöLÜA'´7oޤŸ9 3÷6tŸUû-tp¯¹=¿˜® Ý@°{ ½> ô™åQ2Y‚„úAISŽüO;j‘£BÖàw±9m¾ó>Þò¡¯¨¼+wC¨{LŠ®I<Ó½VŸÓ.v(”·Ü¯•¯"öp±(“8„ ,…á÷¢ÏtÑ+›~–/vftöt³›„6©~‹Ï–?7°ÍÅ©âhTF²€òÇiðÏ/AGÕÔV·ZmÜ¥—s®@/y¶™ÛŠû¦6ÁîÅ5ðy:Ã$<Ÿ' ¯ñL¡ƒtÌ%³‡#QÙ‹„öù“÷uõÐ…æŽ-¡kb_c#~z;’ô¹§h<æÇ£Z7‘ÇϸF/¢‹æéÞ9 6¸LâÇLOD’syÅH„Î'E"+sÑ(àØ1¬ºü•§@žy ä)§À4 4 t¬lacæ›Á/ƒrp€ (LO’5|úôÄO„Aƹ»bþÊSào€ãñ°¢Ž…–çOcÞñ£æl˜´ñÿ°üø- €.«|#¸XÞO —VŒÂn€‡*y_Îx60Ö &"¡mä5ÚMð¾@½m ÐúYï«seàÜÀªÀÌ@ŒÎqUFÓ€J71î9j©r°*Èêš [:¶ÐVÝ ƒ€‰ñÑtóÚv&q”¤¥¼ƒŠoåx 0¢…^ßzú+éÜk/‡c«£oÖû<Îøõë×iÕ|ä0÷˜°Ú¬÷W^~‰³¢n¤O>ñdso¾ö*mÆprã rºôîùÔEuXVaÀ9v7o¦ƒ´q3ø¯SgõŸ£ÎÁìÌLè 7×deu1î!ð%0ÚÓÛð¼ ~jÓEž?޲¸•Ÿß{ñùh7]^aë;À_ª#¬”±ªÉ6ûVËša›0ü¼aÜû|£VœÝ»†ó .K,LY»iË7ù'\JÐ^ÙìzƒæŽwÅsÂ7?·uX ¦ìgšÏ–ó7L:®ÎMýiÀÑŠ&ÏØö<» ¸`3 p€çô+Ôù#8½KE2çC7ê<ÏŠïí=tè=óɤƒV|ëÐxfVQÎ9¯̶R•$ehDõãQkKǦ&ÿ (EpR9)Ð!ƒ&@)¼'«]°ùò* $ÿ;A°¤3Û ¸:×p@ŵj9žÕH€B`ÀÊ<ÁHÁ ï¡Ó/ø9÷|.|Á”l,ðt™å(‡µ¨´Ük*]ä ¬îªolN£ccqŸœ|I3Ø™¹fTʼnfN¼<çÛjŽõhÓÎó Žû9ƒG®¿-o·q¨u D2øÿ¿$/ä‚]V¥•´0èm@Ùj?3=y }ÔŠ>ÉônðïÍ­}î»sW!ø»¼ìwÁ}ï§^/fm—&~ êȇòšï±‹€³ë,X`àɳ½³¼*oú~AA‡ð/›¬£Ïò¨ þòmÂêóƒ~܇–ÁÔåE+W P{OuO- %ÑÖ6A^7ˆb• †c7¸©þµE§ H º5Áy¢tÈ€O¬/¶¯ H¾6Ç~0>6¨[Ï~Òî¯Lsì9]½ýй)Ö^¾@ØèX ,FoX›%Ád`«b±Í||Ýùš%ØÓÖÖ(HÀÞ<|âTºúîÎ:=>úø'C ³‡¶vÐNùÄ @AZ$2?o· éÊð:`\È£t“·¨æâ5õó‰PžÉkÒ“Éž?*èe‚`º´ÛÚ")„ûV {n§û½ùp Rp• Ó=‹ ÐÇÀ”IC®G´Œe}2Š¥ŽŸÝ§å%_wÌ›—6Šd/eÀqÉ'îMê<ÁÊSúÃ5uÌžs®Lx9zƒ½¾îûÓW 4„¼…ž0‰ VøÃyÛÀCß+=;áï± 9æÙ3Ôù¶¨5ÎÊœýÐr‘=Ú(·{MF?s¶X^צ̳S‹„4}v”Õƒíûºƒ÷ÕÞÇy;7ÎWýâ>é^j°¤®Þóéîئ½ò~¯Üš t r |»?jOÌcÏi÷Ôroõ¼G²¨µgØ&Ør>?—€`pûÜé·SYÁFTiÛ‰Ævš­$,•$29Åûè•ÿJ›n 5Ri>°¿>H_Jëå¼Õ`G Û æaA&+Y•®¬bŞṺÓc£´áïâs€»»Ó÷Ïþ9k8–Šyî+/<› YÏÞ㧨 ªL#ϧfìÏÏÿú?‰öš»ÝΞ=k&MìîgìÖб°7RlpÝ€[2SOeXqÉ2¶ÚAª†`¯6• èß© ÏN+Õ‚ÐèGu÷”W ä*Gì˜!øîq0$ýT˜ÁÛî]âÝÿÝk¤·6ñ«¯~/=øÐGRÿÀ@Ørò„ük²—÷HíFõ¢Ý[UùS}‘]ê0èÆêóå!îHðBß{82ÁÉ€ctU¶¯E—†gŸð™W}_Tl{ãÊØƒ­l”OíVà”/÷žì~ÛÀóaìQ’{ÙÿÝkLh71äí7_KöG@ÒêGÓÿô¿ü¯q¹þ…t³ÓOAÕú$pE °86‘¢­WßXºÿ6ëX ßÔÔAp®ž±X‡¦ÒI[s]êÞ‡ÉÑÜ„ùDEYŒ6›CzÚn/<ûíôŽÌé;˜¬>zü>Î ÓÉh3?ɾrßý¤}è}y³—ä eÝ„eŸaG¦ ‚ø®Af#ïÐ@ç-CgóÑjëX+求mÙHò†~E–ÊàíøP±žòØ0ÙÞÆ€¾ò©ù Á”f´AµE›Ø'VÐWCT_.ð³ë¼oW‰ñœW{ùb:{î|*Ö6QÐóê°]tè4-ÇW7Ì^¾„öÅÆàköø1|Þöt™Ž †M,”‡½“ÝG|m‡D‹Œ§ôéX4hh}ï9:“ í~ öxƒõ&2ÉLúfò«<#ôaŠè€£_¿j0š}¨kdl<ÝûÐÃðÓ±x¶<&O›ø>ë]kX?ýºþ‡XÓFüÃÓÐ ¾ yìöÜ$ºÌªg|]’?õw§óð~w–>îÿ£þ‘GÔ•îëåTmÖ–5¨©†³4Ô6׎2p Œ¤Oªÿk ±•¯9xFËU=d­ý1ÃÚOÍá+’ Ìš(/VopŒÉÒ6þ‰?þs¿®­å=Ü?öldS[©mBC3Ǖıjt °ú›?/ø]?E[×ÀºÔ@ž¤£¾LµÑ¡îËVü›˜ªmægìå|שêWF”;å_š˜¤I¯Êsª0 -–ÖØƒ“¤PwV³—y)OV¶ë_;Fï'þQsÂFÀ¨ÉFvì2@ÏØ¿ú Ì\ ß ¾{‡r"Vê0÷Î,¹WÚš¬€0Љ¼¨Gå?»÷™ü¢-Å™ûøÏDrIÂV{s´@; ¯ê;º:còóå+×R‰'í]Ý`º½tWãsKA?éÿúç‘Ç?E'øÄý†áA[’$XËíí¶teævj*Oú*#®³­¯cßµöb3HÍTpëKll™(ÉQe+úÐÝ$šdÒ„>mC7ÐA‡DUy øê( 3$m z%êÙóç7 ÆÖ¡Ëw÷ð‹éІ€ °8SŸviáNf%ØÐ*<¸†ÝÊß”†Ë:y„ GÚ £—.^O/¾ø 2j8-Ì À:r(ôÔ 6ìÁëÑáJ½¥Ï+5lk š/®cb<ÄÚË3‘lÍw×QÛ–ƒÒ‹ ±£ óYߣüikÊ_úõòÒ.G.RÁ/‘Ãä¹Êà.ÿªÕ+)jð™Ò˜;p0x!ü`4“­ÞÅ·|Ö:>²ßÝŸÂÿäžÊã2ùF~7¡%ãðî¼—„Nô¯{¢vK ¾²Â'Ù·»ãû,IËÊF9óÚ%Q&áëH|Aö7i¥Ÿ½†NáÆÞ]¬.ÝsÝ”;îéñÑ ×ôG:"†ÌzŠ©9´âGÆ~ÌíøkM` ÚøÚæ ,ö22Ö¸—².Þ°Ab›/0íÊ ñ˜)>+8Kc#vr LÙ]ALÏ ¥¶‘Ûuö·¬3)㌧çÿÉS O<òÈSàPÀ­Ë½ßÍÍÊ»+—/§§¿úwBªÐ ÎÝÄÑÎZ– Fš]n—`„†FBþÊSà§M E'/º`d HØQÇO)»¾?µ,ï\þ(eU¯€¢N¦‘­Û4 mC.ˆ£‘fÅ“AiÁ9ï`ÆHÏoŠ@)·òd°HÀËó§°` ÷…!«#n[våe‡Ÿ§ |ëxÛîéÕž‹³1§¨Z¿ôÎ[iàè‰0ø@õàþs8ÜÜ›¿{†»gcßûÀƒ\ÆåÂÈÅà†ŽK§r’sÖ<£©·?A¾ôñ§>¨K%çjÜ[­ÒÀ©sl»â³gN§ûü0-SûÓðéó_øå,h‡ƒqêÞ{ÃÁ3`|d4ª|"©€I§ãæøx:Ð*•£Ò 4•ÒÒ×*}Áî­óiEEww_:útšæœ"@\iƒœJÕ±:;¶p3Xü𣠋¦øÔ!©Ä¶ÚR³ÝjÀ¨ÈfþVÕ_«2‚a¼®ó+m  ºþVjT«Â¼¯•àº-:¾f 9Ú°Ò[“åNUw‚ ~Þ áÊB*Šqý› u)÷«ä÷ãG§£'Ž„“³€¥øHð•Âm ´,»™¦qª›0 r ¢úXŸ§ <³Gž€vÍæÍ&6áÀL_? #n°H= 4‚³™ÄuÀw2'Ê¿ ãSÝD ÀÀžŽ…կʎϕ²* ­½V±-ĸ†Êðm3-°&øàÑøaܧ(ÎÏ4€æ8ÌèöÉÅ@€PþŒŠÁfÁO2ŽY'Õg»™‘,¿(“¥m9mU„.(c…žÃó²*Ã9)ÃÎÅ+káI«O-b1ãÕ¿þò¨ã§*-æ5«Ž½oêq®å)ۃʋ^:A ÖGPÆ ä´ÒÚ*†*À:VËIs«( € 6!›ÎÉLüªÐ1‚C¶ÕÏ*Dœ³mò!-Ñ8òŽÅ¤Ž*ÖlZ•SÕ hàøu¾Ú¼§òà|¤ë­›cQY ßÊ#kÐܵ˜3häkÙ®û˳ø]$ÛiÁç ZICXmÂÌu³ü l§jðÑŠ­ÛT-ËSò ãÁ9'1fpI'×1ê\›øÁò­™Œ"qÄy`€€‚naò„WÞ¼4dUx£’ˆŸ­qí YÏòS^ä·Æ¦ì Iî\ð‡Ù*ßÿZð‚ë:l• óÍ­¬Vx^'‰ ´™ îo•î0Uk-èÑ**˜­ÔÚö˜tŸW7y?V?È3Ê«¼èï¬`@}¤™|cðL¸ðB`dkÕ2¡Íµ¹¿©“¸·üà=Õ±êNóžçm`Að¾€×\?ƒ'cc“éì;oF¢CT 1€#ÇŽÐ ª8@9ÃøøÀÁTCÀ®ž}«ïÞûüûÞ¯§W®^‰àÖmôO)ë'8'»·Ãy¡Œs Ù "çé^©  Ê’`bÖÈ© Zù´ ˜>99ž†¯ñù öÈV’¿šI6»ûâ IF¯¿ö gD?„\>R寨î.æ.0¾½ èŽlÙ†Òê¥PWè&ƒ(òˆûÎÀ]Ckðªºö ¡«g¨P€ÎòY:Xþ6¡NýUÌÑ €®Ð6g¿*OL  [™„zªª†±¢ ËÝÛs|Ib¬@»2ï•ÉÔ‘?ÅËç)ËØÙÚfÈ.åQ€M;ÉÄ!×Mý©Ü9G‡n‹NáÔÙA¾K/«¼­4¶e®¼¨®ÑF†_9¿^žôþò@$›@_Èñ˜4hàÛ.¶ò¶r¤ÿ¤¿Føà-ª¡Û;;ái“²²/AFÇåçwØC¬Ä‘ºêI“»Ñ··9fÇd:}ƒFvßqß׿°kŽ y“$·ÑÎѽ6‚õÌßq¾ßKšj{ú [®j?Õ‚œ#ØwcêEìA}ر“$B.ÿú_~-­' ‚gl#«ȳ ‹Ú}ÈUnßøêÿ›ÞùîwÒG>õh¬á‰ào÷Ϥl¢«QûÅgÕ9Ú ´ü§ëÅðõÁÐE´sµ+RCSYt„XgkËdH$Pç…3qùP=§Í¥‡É^–H(h«lH÷ÞÿÑ<]8w&ŽŽ†íqíêåô¯~ç·Ó¿øí߉.,ÚÈŽ]½cu¬v„-–M¬ÐvµC’ãXör­ ˆ¨g Eò?g¶Š6#û´ºÄZ×qôQ;«Ïxµ¯gc}a’àÀü ¯iw¡{Å5 Ὠ¼¼£r‰§&QŽŽ§?ùò¦/þÇÿ}C¢â‹*J=]Vjߎ¤ 7 Œ?ywŒÝ³Kõ=ÖV8‡9Ù%pàmÝ7}ÿÄþ¢b•š²áÑÔÜ?ëßh«Dp@¬ß½}·y—°í €ï%¿y)?Ë$×á;Íc ¥žûnT®]y75`TàXdÜø4úEÇŽÇÞš‡¶&Ãþ)!x Ùyšvžû&¶SÞM 1˜¢-bG,«Ýæ¨úw~õHŠá™ GÝ3³Ž9Ð8\˜óµÂP½àl¼§—k¬~ÖN0¨ù§ô‡éO~ÿߣ³ÖSï¡»IR&ÈOP4ŽØÓìæ3F€Ôdc}ÓÅ‹—ÂærORŸº¶ j´5øIùÕöÕÏh@6¬t¾qõ²ÂÙ¸¶¬Çv¾Ê®ˆn‚µ·Õ‘&ÂfiÇÕ¸üѱK¬«—/%+á½®_¹’j¡…GY´¡S[HŦ½B@úñ"èl2À¥w/¤¾þýæZÓ½?Ç’©ÇÕiê1(í‘_îaú ê^+3épÃ|j±ãšZN°wãërÍSkUºçËm&MY•ìžRDîɾ—k¡ïx› íI×ÏŽ´¡þÔg>åáë×Óa‚âØ¿þåÿ''¼ãèqøn'M3ç|,ü~“ ÷t»~5Ç^0Ñüü…óé"÷'ô‘LÎMßOÿI~õË„cilkõ *¥}3]LÌðü`+L‹¸ÏV9ô·šûÙJý‚=eÈqàÈÜ{ **øÓ b7>÷Oþ|íÔ﮹:Ãd|ñù¤»§ä‰5|ú‰3;LéšL¿ººbG9.tI5{—>Nt‘Ð\±öÙïëßê ÎUg/6¸«=ìXô¥L>ª +‚-Ä—8¿^û\?Ä×M¤±À KN_GŽñ  £Ý  dÍ/€Éþ×P“%^Ø•ÊdË"øeÈžŠë¯M¸½-ÞFT‰q:’ÊîØ°{лÞk¨G.Yíi×ÈÎò‹þ–÷ÐÞ7 \ßÊdYí‚J÷<äÛJYçKKð/б&Øj[ûš2n2¯— ¦~nŸõÁaƒ…LšõDodƒ [º\‰y¸†u$»x ÃA\;wh›l{æ†ô`œ`U§ ã–:ýPG"‰]y¼Ô»Våª ´¿ôÏÕ¯»Ø¨Ê9¤‰õQzŒv‹¾Ì¢­Û»‹¸W=Ï5"ë =æ¥lpÑyHƒàb'ÚNvWšFÆ<î¢×ÌS¨q}p0 ìßÏç6Ò7Ÿþ ðŸÒácw1hoCŒ ·ûX){íä*GË̯§æ²åÔR¿•ëµÁLŠ¡W];º@=]JòʾÙNº‹ä‘éiìŽ=ˆóÂiã¾³±Lëvèÿp×6’ý°+ö‘Üÿ1«ZŽüÙY»ŸnÀëôÅXŸáUl^ vn¥®VôÆ6ÉWœq>ºÀ±}»ðÉGåè)yo“9»”£#œóþúÛéòÕkècK¨PGŸšô©tõò`šÇÞe¯ Û_?—µ—7=¢ÔD"×];ÀÄxe¢’ý8‚ؼ®úƒµ‹®\Hkl7á…Ä#m9ÖW½ãÞïN¦üK×"‚è¥ëÙÞ£>2±X™ Ûy—ǽ‡~¤þµVn`Xð˜{3^_𦶅‰ÔúS<‰1e¸ Câ·l.êˆjîe²šö®Q`GvË3A]_ZÿM|Ìõu~æ“$£ÿÏY/‚ßÊÙgÅtröwüíäLægQ8¢lxϹå"Ž/À‹ð±|°ûx|Œä%î·íj9~ÖÀû™dmÛy†{øãÒ³STœ²ƒ¼ì²™Øyoch”k»^ؾ?ówÙhñ^QMÇ… t· &Ó»<ŠÔÿè‡ÌÒ 1Êÿ“§@žy ä)§Àû¤€¥»<×СôÊ+/¤š ÃÍ@ŽÀð†½Ùƒ‡q\º+£êÀÖf‚_:£ù+OŸ6tƒºr.3¶ ¢`u†ñ¨#ê{2Îþá£Ó¹Ð1ËÀPœ *Á$ÛPôõï:]‚6Ï5F#ÓÉï^„t¢W(›* „jdÚrSƒ±½³;*:—è ‹ª ÄÑ^‹ñ N ÐÎ\Çy‘L|+“› hßý‘j‚ïm8uáðÎ<Ñ Œ¾@ØÓƒ{7½ñú+بì¶²À—I0]QIâyîžÿ¸†Ã/XÒ{ð 㤵)Fû)Z³ÛFÑg H?úØ'0.+£ÕÞÁÃG㹞g®aÚ»€ª‰†tæ·qýý NK)Š£¨U8JGÑ€:÷p¶Š=WÉÖc¾V´ºÒÖÀº“-²,×q.<wnzÓŠ³]Ž^!¸~öL¤àX%ˆ”UiZ)bEå&k/ˆ‡sBPÇ{•—ó\re›Å¥•Pkœf΂‘‚û:œfëÈ :¯á¸ L¸>& DP ^*d®òm88Ôà‰*xx/AÝ"Œü6VRi]sÒ§oN…ƒfûg×[gG¢¾ç¼äOéQ†ƒbŒA Ú¥)ôòy‚ :o:vò˜gµãó8cûiåÁÊ'æ¨ Ç9ɲá6pLš ;´Ðù³)ÓÝÃ8¹3·r SM€ZYU:”Oºpˆv¨À”¿ V: «7¤»—s0ˆF€F%Ç ´À4 x‡ ^‘ð"ªú8¹s_yM Óqú¹öNƒŽÎ­þp–Žå×Òä[ÀmÑZ;‚çð±Wê|wRE|ÙTA7Öð¥œÊÛ ¼çÁèc7 ƒZÈå3ñM‘Û\¯ÀyÊaÏwŽj;ži¥dSKÁ ª(‘{+„¢õ/íŽ ÒÛYâÃýxºyã Ÿ¡Ek ª“ÊJ8ç–Äåߎ ÒÑ/”E@`”É8g@ >dò󶮢ÂJþ1C`K \i}c1h$mÒˆ™ø.t!—~åÜ{ƒ@ˆÀø@Î&ôͪOlÏX‘ì<ϵÏÉœëúã.×Õ$£\‚Ðw¿óm>²ºnan:ÖÓê+AaÂ]ør‡ pu€ÉX¶æÝÙ!ðï¨Õ-.‚À¾g§;Þd¾‚õ·lmp¡t½XÈÜÿlaî9|V­EçF¡õ½ê"u•—<Uâyê5ßãzƒ8ÓÐ܉OããФPßÀ¼r¸®"õüpªÞ×C÷E:e‹JnÏ'¼{ÏöͧϳÜ}‚{÷´¢&Z27Ç`ÖçEõë\q–©U   Ú–««çg§RÝGX|¼£bѪ˜êzZ"ü´ËÊ2\M+áu&€Š@’•u•´WÝC÷Aå^iPHÞ7‰ÅäÏvo¡‹JÏþýŠŽò,ÎÀÜ›™!!mKðÔ¤!’°‚W¤šá/ÖÌÀA_eÓ@ÕfîÉ‚=vtñyòk–ÌÀ9±ðÌ3I³ 0u€cSÇý4/eÑ„ õ²c÷wÁ´h%‹¬)/C×®¢“I2NŦ2‰4|f °ÕÊT×ÍJ]AHƒ>ÒC¾¬5èWï°Äìñ´uf¯ëÙC¥2ºAÙsT'×p/ƒçžçl«dy$’ IÈ»L辇>Š-ÕÕ‰«u«´Û$éOþ†LÒ†°ÚÖ1HKçb@IËd5ï‰N FÛÊŠD×Q¿˜Ï …Àa+Õéî/ò’<;žØÕ^\h%¡¦½ºšDY¬š©†ÏÝ—íHR€LE5 4^#šZ[N÷ž³?çôªvX$ê HJ<7‚aÌ9ºqÿ2@Ú*€é`@Ü@ŽÖ{,džœå ÿ*ƒ¶nµcf!v>“bÜ»¤§—¶‘ëdòÖWþó§ý¯þ·tâøÑÔ &lD÷õ{; í¬ƒšüj¡kA)¼­[(nY9.Ä?Юˆ`eŸu)$èàeE{'xÍ@-²c;äDB‡6·ëüŠß=&[ÇìóêV#ÚŸÆ?ðx¦á¡Áô[¿ùß§#‡¢KÕ½÷?”݃›IcíÓÓo½Aº,|ã?¸ÃÃÑL¡>ÉɧI”ÇÕÉêS5§{³{‡¾‚gc+·¡\× ŽÃbݼ¬r+@.LrP B\È"l6™¿3GmpeÚD }€º`Üÿ‘ÇÐ;&Opÿpª¶yýÔÝ÷E2ÝôÔdÚ?€oD–—É)‘üÆØ‚‡Ù«¼§cÔ/æLæ=ìä‚ô0¶LK[K$)ê£IS«î¤/šÞrÿÊd‰ÉGdY=å¾kÜÛÙØk9~çMxm#=ý_LÿÜ/FòÙéñÑ8~ç⥋16;¾hªåí›&ôîßùÙ§§U’¨NŸ šÂCoÓeÈ5à9 åÍ5*%åc×rÝëûLÝB¶»ûºcmWX-sfr%ÇzÈÓuuØÔQn¯‚uG£²öÒÙséA¤'‚‘]]ýt¤9 ×÷<øPì¡î¯ìC×Þ¢Ú-?ÌòþŽÎ®tìž{ÒÛð^'ûë‰û?„-s(Ž QF¾û¯¦6ö!ùÎ…v+áÈ禫©¦ïÄÿÎÐÏ­t`â}3óK$ROÅ,|0xízt‹ªÁîè38C—$ò6#÷'ƒ^ʸ>ø8­¨•çÝ.fe¡»k-½M€w& – ‹®áÔÄXìEa÷ã_™Ä'_*'~^ùuoËí=~ÿqWn¯’_ZHö« úûEŽýgÞi@÷šÈor@cS;v~9úפéèÞª_¤ÌØñ£µ‘µD¶Æ'ðñáŒÐ3Øx#ÝÝ—ëê‘Mt!CÇv×ÇQ÷`#b/éØu$‚Áð½]bvõŠj`‘eùß„’Œx²þ/ôsO«@–ꨚõµä¡ž2Ñ\=àßÕÁÕ%OÖ„×á7eGY Ï>e0{‘£\{¦ :<Ãê| |ëÐ5ŒÝϹ¯6µ6Å]²‚ÅáÞv‘1‰8:Ý¡çíÞeH¢5yR_'ZcsiÀï,Wæ‹H³rÆaà1t ª*t¥HeS{Žh½46:ÊX´oÀ1ò¾üÒ‹q¿Hªàùø®ÍÕåÑÿP ɤ y_{gtøF`K½½ý$*4F”‰âTo¿s.Bï¾çX$1žMi‡Œð ›ew?~“¤ÁmËVY’µwFÓæ*vÅ¥5Ø2& U“<¢Óí ãﮯÐõæF:žd³ú¶ÔÛQ‘: ~B§"ìlU·×9WÏÓ©%mOÁ{tðaß)*Äî[çØÂ­º4·ªïORJ¼ýRÀÜô Mè8ö2!g" ¦ú޽è~ܶ¯\oó2c݈5W/¸×ìê£@£Øwü‰ûi/ºgX`ãb·—vûv_pßÑÎ÷ýò®2R´çëw>Ïïêåu›õ“ÿ\{e^>·#¼jÒc¬;?è³Èã¼ ŽgÃDê«þfbÜìùƒïŽ&ƒoô ù™g(ÿ&ëéwfz#ÛÃ}§se1YÈ#ê´‡´c |ónäÕ$m’º½“õ3Œ%kWÔóÜBö2i ‡|w^ôÒ v,¡£ ãPþÕµÞD_Q¾ô®EØÚ·k«Ðc¾`0dËq¸Ïǽöè’¯še‚éú:612kÅ{tÿÓ@39i2ð¯íPZЬA/1 ii‚D=zÐ}z~î<„}çÄòWžy ä)§@ž„np±É²AÛJÑ Ží^{ú&ŽŸˆ¿ÝÓßød‡ÃÈ­Á±˜À>g gæß›§ÀOB̤0¼tæ3£ÈjK8iþ˜÷œ•ý$s9ãNÜÇÏd ¥¿ œ…sÍÏÅ~üŽd„üUv€hY‚ÅžkÔAe`ú‚‚ÊŸ„¶°Õ!õuÇ[#sôä)€æ©ŒÇsÌ-ZÎ gA]ÁýÂÔÄ=>ùägxËnzþÙïNÝŒ³7;5z °Ä³<ßü¸¯˜žŽ¶túvÂa@IDATå/¥c÷ÝŸ~ùW-ÎM\ÆÉj£Jl?¾Áê\•Ä~¶‚¥šñè DßGF¼-Ú €ÙBL;G ǵûŒà˜Ù™B€@°Ú`üÆ•¿˜³Ñ"–±[iÖÝÝÇØ÷ÒÓ_ûs2•/¥I*j›šqŽŽD‹´Vž'­°ï¡ÕUÐëÒå+Ȥð§bÀºMÌ$×ø's–ïá,àÔ8ö u–M Uwes'žï:¤%´¸\'H¤£kÀ@@Ac>ã×Zgð™{…“…Á/è©`0ׇ›< QP¸ïõ³f7ë`]ºóÊ©èÊ3g.EÐ×d‹éYα#øâÙÝ…8¦ò‰€ ÀúÞž¦ž«K •ÁÔ0Ÿ-˜#%ÙV €z \™Õ€k±$˜/ïD%6c´ªc 'c €§DUgM'SÇHçŽXo8ÜYv2à Â:ÁHƒ}| ÿFpçN'±ÚËËÊŽ`¶]œ·ý‚"V•,qÁ+Ú]£jD«Š}ÿÉYV3î$Jã¹é̱ñ}òt¶L(±•´€„­À«>3øãØ™L†ðxéžKÌ2ÀbÐcÞs]ü7äî Ú–PùÝÁ§xsT Èß&p[ô ¼ÊHÁ\yÜÊÏWø¤ Ù,-7åÍvZ9®´if]öqŽç*¼UKE`sÿ4wåR€4Ìc‹$Êžñ±ÔñÙϧ¦î.Î'<÷_&PvGÿ9ç ƒsè#[\–PRߪÊ Ø˜ SI…ŠºÑªA«†—k;à™­ ä©»ä;`¬²? ܯÃÛ»ð”{A¢mx>μ„Ïä§΂ßbü‚@ê*ÛxQe;_ÇA'ƒ=ã£#A—5žmËáò6ÛÔnQí§Î U2:ÄäÇî}Ôõt6Ôñl ²Ê8­:÷ ×+/ؼŸê}÷"eÏÏzæµó‘ç¼ >zY‘gÝ÷ü4® Êä%k?j§Ù•H<ÉŽy—6âî³è½žþý±êAkÊçðè Ü!ø­"=òÈÏ„mäÞfµŽ;äÁiì—ñ±±MPó¼YƒK‚ò‚жDvÞ»½gVÑÆï>Û£gìÐcÛQ×¹£§qÓŠþk"8èxß ¶KOÁ:„¶à»\0Ù׊fuÆÁ[«Øµ%´M¶«¢]p.k#œ%Ö8ïõÁ.t1R±Ø€ª-ö®1X}õò»éê¥s±Xán`J›Ï}OÛ©¤Š#X.ù}¾½ÉÞKŽ–ôÎÔöìNçceÚ"ûƒIÛÛ€²Ví°¨Ú–ËØ³WéJá½ífcÀÀî‚ø#‡ºlV±WÀÀ¶æ¿ÍÞªr-gýZ;»Ò4´r‚[èËVÖà÷÷ߥÙë·RMÝ‹î½'Õ5ÙR“6ÃwŽ ]m½³o¿™ÎRw»o)ãè$õiXÜÐùü6Ðóvr²±ÕtîÓ@éìØ ¼¿–1NÄÖ½Epb‡ýŸêcµA’uôx£ùuÁ~e[[€cQd»±¥1õ¢CÓ¡ST ÷§«W/§?ýÒ>L[Ö[è‰Ìî“ÎÚÒ\¾²5®If¹–ïÊ‚›ºöÍ{´C‚ß»äÁVÜó$n±ÕoÐ]ž¶›‹[úÊêyЧÅ\ØIâÞÅ´¾ÕöÐfË@tö>ã=Ο;›¾ü¥/¥#‡ïŠ=ëyª·ÿð‹ÿ‰Ví¯ÄF®_`ÛðèDè¼ÜÀš‘)Ÿùîù³$!DàÑý—‡Ïâsäí‹HÊ„íÔÚµ¼ù¡e2z‘™—¶®Iò˜´rßgÑYÚ:!Ümñ|G­ÿFå%ÏÑV¼v-]ºônøOO~æ³ÈFqzù…gC—8n±„6ZÝ/¢ôÆw5ôë¤²Û ²A“唥=øÄý.º4ðY¨´ßæYÅ$5h£”ˆÓRºÃƒ|:ùŸöþóáî€îfaøËDìwhå%ÿ666§#tÿ"¬Ð¥åxUpüÔÝÌíJúÖŸÿ:|-’ˆzzû‚Ï]Ïø&ðãÞ|óÔ ßÌÎ̤Áë×¢#ËIÞAèsýò9ÚFßä'í/ά-ä3ÊŠG“€åi“¬¢÷Â’¸‹ñ÷±@{tFX&¡„ &¹1—Ê8šAûQ}eÀÓöñU¬w#‰X%Ì­À_V-ëš—§±«—ÒûˆÁñìm|6;ýÚ«i ¹^b&¡×ÐÀê× ÕŸûî·ÃÞ·k’t1A® ?©œà½{cWG Ao幘gy„4ÕÖTÇ­­(&É£~ªÃwq}•“/!¹´_Õ &ÕštYE ´~kÂöi$ب\ÜcpLZ5±V¹hjíˆ$KÀ³Àèf·}ßÿZž³¥ ¢CðѬLÖO1ÑÓD'åÈâÎê;[ OÝšŒ@Z1 £ú޶°º›FFÇ"‘À÷XimB¡6©t(¥:´ ˆtø¼aÛcëIw×úåÒÍýJ^™pÎ5<×ÇWLÌ·Ú]}ÀðàŸL»/¬ÒÕª‘ }%Ï‹î+ÜĶͥ$$˜ô£ôG›tžY^‰ÿÁúÆóäe„quÇ {üFü _7(éÀÔÕ¬µ˜€ÝìŒuõ¡MJêàüì°]Ø÷”ASu8YIq”˜˜ˆûÔ2{ŒÂi•³AÖH"àwùÀ.$¾Æ!ï;ì“aðÞX#ö çT޲o_23¼ÕÆ|F:Ö!7ÚúòMz6±ã“xdº‡$Žeª¼íxÑHÒªã²Ëèþ¾~:ŒÒšŸ¤€10÷Cq£iFžîõtèðÁÔ;ÐŽ #Hn»;YÒ(‘¤øff›ªÛ¾”¯âÕTU€Ÿé,{Ão³ÿ“ຉNØ¢ÚÿöràÆ5µÐµ,`Žùº6"AÌŽíÙ/# []ܔֿèô m—èÀ@’ê6‰øØðŠt±í÷.I&{*zäme}‡®'#t­z; L^0IY¼ÄŽÚ€“cãS™pèÞ"Õ«H¸¿Ýå9øÄ­~‰¼ä¼õcÜ#P¬"çKsÖÞ-Ä-Æ5UÆåµ‚7SðÇBîï·X;,ø@½n0Øgon¢ÛùÝöì­8–-:q¸ÿz<˜{±^_†ÇhWdþÐ&.c?·Mºt*Ÿg0vqï4hv<Ÿ÷ÿ°¡xŽtÌ”ÊÐîãÚó¥%ð>2OeezÇpü@9gÝG’1<ïø£«C̃}^Èt†}Gšu]¦;º3ŽKgå}q"åÆã=bA›B¸WÊ×®éIvdà™lé£Ã=>ô*÷W_º·«Çœ»¸Wø|^›nl„=}àYçvt±Û‡²²¯«‡{‚­ÐÕŒ1|ŸÕÇóòWžy ä)§@ž€fÎjÈ?ú±Ç9Ïëà-FƒšYŒU j›"#Û^hù+OŸ6Þã:Œ7«ùA ÌÏU“¼Ÿ1i„Æ…á¦A¨S‹›‘J`Ï¿æ ]~Ƚ5^‹Ê€Ì1£­+Î;âAï­#fð¢ ÃOp=ÉŽaΰ »@°Î¥NcÿÛ˜ÓÚ“¹p3Àmð/gt; Ch u«Š¬æ±ýÚêRÖ~R `ù\Çñ´òܳn‘ߟ}òIÎ-*:48±bÞªÁ]e+[¬¿…³¼@›EÛ[z†¡Þ‚6ã?úXÐÙ Í}œ8s{ž*+ ä¾võ Ý*‡“àß `õôôÅ¸Ç bðŸÏ[äs“ãéØÉ»#اN¸2èÞ¿ÿ@Þ£##œ%w”g̬ŒGզ΢ãÑxÖ¨˜Œj‚b²Ú›­²w¾ž»ç= Z^ûÌ ²Ó#1£\{µ1øbõ”tÕ‘±¢4;_ضò®cÖ:žÇ¬‡•.€œnb…N¬­TuB ø,tü'HB˜Ð9¥mya7u?ðP€Ô¶[œÅé%"ƒ Ï?8V¦dN`¤ ±Î¹¡ÏßáKºéª¯Eå9S~Ç5§5˜@ÛUá8áLÛ®ËÊuå#«¨†7á9¦¼WdU óðwÃýÈög.žWZ ð‚kŽ‘²¦ó(—¸+ ·ÁìeÀr4“'ü}Š í>Îlœ•wÌ0)Ú+OËÌM ]™*GÜcœÎ UN:¥€K+ÌS¯T@ºR‡¾×mu­÷˜·Ÿ_'¨ÒCôÝo?C`çZ‚žˆ@º‘—3P?&Å<;—óÌ€Óìu_ó}:~®§ç¡y†¢W€·¶`ãoUUˆ´¦K·¥¸ÕTÐ'Îà¹`zÀ× 9¿DÀYež~÷µBæ >áMœ×Íû=7W§RçZå¡óìZym9OÖØ6…V­Øêlnu.í¨'À²œ¦i·¶Åß¶¡ñâÜŒ“ÈdòÝóçÒ[¯<›î¹çTÚîlyN,í i59Å9Þ×.¾Í9«€þðþ:]­t‡F&“r*|šhñ7BåäJêëïçwZ\£#Â@×IdY%à(í•£RÀ'Ý{A3g¢¾r=åI¿KCi$µåÉX7ÀÛ.Ö"‹ó$ãq‹XWÛ¬X}àÿ®G$fÀ¯vih`[àùKd«oz_î$…*‘ß ~1CÀéCèFm;]àpÝs·ñä~ÿkß];ôËÿý{¿›®¡+ÿîßûÕty^}ö;ð=φæ{¬Ük*ïTÁß®‚‰÷!ë¾6tíZÌÙ÷EpÙP4 &=nÏÌÆeÈ:Ìà€:× êÙYÎ…~ËKð•µU®'û€`ÃãnìH{í©¡€vŠCBë»]xb ù«ëêM]èèye–q<ÿ­oƒ§ÑEpüÑO=ɺZY{ðz.ÖÁÊOÖM›QùˆäË2V’“¨I°h&Ó vĸ9{y< ²çØNݶõ¯½üU$ïDç´/ÉYSi“3g0=ÿÒÄ$6ø&ž€(ªL ’ÒÊK2RYcV7 R!×GÐó ôÎ:CxV«IêÇrxE0I0r‰ðY' öXZVVÕqF­mCisZUÉB–2fʹ˵Uðr?W×ü´®Ü“|fvÌ42Ξ†® ¾N²„ày;‰VZ6b€›]š ;ÀÎ>Ú!ƒƒW„Õ˜%8pð®#Èõvš áý ¼öâó$ßîçìúΘ¢Aom)é&ŽÙqÆêGA9[ÈšÌ#0Vˆ^ñ,ú¨Bbý ,”b¹šXÈòd¶c5€®u¿ÕF±%´I&£ø~Šv­Qæçà›]5&©z¤ƒ<¢ž7@e÷ õ"FÁû^’lýÁ ZÖ¶ðËvþVó86+á êyþ*dŠgVtàÁªºtÑ^m¢»—ÚH™pQfm‹{íú`è²îÎ}ìI´¶mª$ñÊdÉiì>ºxðœ-ä²:õöõ=<A^–óƒ®îãŒÅ$—j:ãø¼6œÖ®nÖ‚6ÚФ»o€„…þ ß'¨F}þÅ—ÒoýËB[ôH :›ÚH í¡µþŸ! VÙî•aÄ—ºÚ}}$€÷æ'{;4_b½<ËU=5Càm›‹0Õ45Óú{„DíceË#AX²¸<‡YÐÛ9t*Å^²#Ò£OüRÚø/ x%MЖº}ã±cîÃKÌy—@Ø©»ïF†I`à†¾¾Â؆IÐNuÝ‚öÐ@}í¥N³ò³¥§+^s^ÚDði%Á@eê8ƒ}Ú÷ÊÿçÏ ýáÞµå‹Ñ±ÙÎç} iøú;vÏ_&¹ÍN¿ù?þóôØãOøoÒYêÔ½÷¥{îû{gé*ÕIEñƒÐwu^ »}^>'@àQkrA;ƒ4&±b&`wy<À:û²_&$a•@#;eÀ#Øwk¬UTåòym”yZûy62ðíºÑã¾_!ô¬déáo^Úµ¥ ,x®ê믽–†Ñ úmTúðÃ!ú%ê\mö¨êÆÇøºâ]F®]]BŸ¢ª{÷Õ/0™©#h¹½ í±ÿ Öf€½ËMàŸqß IUµ¹¹)Àqüèlýµ!#ØŒ¼i%8âèãÈY_ [,›FüëµÝûÄ'ÓÓß|&öPuŸòî{¿ògšªšö‘Ôð*vEkºŸ=~àÀ]—xï¿L¡E‚•¬¿‰Û2 {tVµŸÝ§ Ð=ÀŸ=Jž\Af´‘ÜÇ|–zÅÎ6&ËììÐ1Š÷H—¨ztü\&,åÆ//š\æ½Ô­¿úOÿYzð¡DP]ª?ØCr«ß{)ýç?ùr$´˜X9rãZúµôO#Yt“¤1»Œhç;õ@ºI²TÖ š « fÄÑR¦ 2OƒÑËøÓS3t¡ûöÇÂüLNgÎ^ÀÞ/MwŸ:‰m¾/’ìŠcBzcs#¼^ÝJl¹<9v3ôVؼÌ]ÞPöM\›Kc7†Òßß½@‚Uw:ö/']|û­ÔÞÓ:ûö;+O/}ëiäõï%ú<¤WÑùSØ®oŸ};äÜý@ŠI»Hä„wµ!ämåÝÀjPØ}ÐÄ¥Z Mðý~X™­Å®k X«NAlYg’5YÛõ5ªøÑmUØún[[t!¶íÛß5ÄZùkåßõIÄ”w“Þ®¥›ÉðÚK(l"üèÓÐÔÓeqù…—ù›:Uõ~®l¯ –G÷ÓU ú´ó¨‚¸|#ß*—Úi•´—Ž`›:ÏG Gïf¶®û™Aòuº³]º:Ⱦk‹q[[sD¯0Ì‚“$€B7×ÑîULËc9êIØi¨# åÇê6GÝ–*±ÔE‘<À=õQ ”—"ãŽÅ¹8vùȽ²‰€°•ÚÒjÓEa±ö-ˆÍƒÌó\1 ýNƒš¶¥6àn¢¯>1áóüªuoÜŸâ\h>_†¯¬°7qϰÐEˆ-g…Ë7â³DŠMS:êëø%-ä1ç¡í¯ 3‰W$sÀ?>K¹õÞ%e¿…?ç^‰Ï"ÿ5r¤ˆ³Òrck.l}~ÄÔ½âÈÌÜœkÉœXO“M®ÓîŸb¿ã1“#êØ÷.âßZ``÷ÀÃØr3»aò“²a'¡wÞ:Cdziº öÐ%¨½ê¸™“­;˜¸2 ùð çvMZ… c ¾QíÖkÊYër‚èÛ³Øëú)CÀV[×CRàVÖý.ì8—{;MÇç¶wIrÞ)$)c!-sŒÒnq3ëJ2.Á{mûÂBô%ë ®Q^×HÚµ³Þ˜ô•+×IX¾ŽÝ4 6Ï­ÔA·,“ WW6"x^PHð´³lbŽ{d¶VËÕÓÐÜ`¬ûYs“Gì”'Á‹—‚v…”mï:&ÛîwqL‹Ÿ AÑ~0èkñ†þ |Á=YþL¦ù?ó\ƒÂú-üŽŽ‰¤`žáZëó¾>z6±°…àS–´£LF÷ñòu F{\|Þñ¢áØw óyõ›r É+bY¼%[Käˆá).üvó&£{œCÇ¿8~é¢/ÖÖRÀÂXMª[]C†}/6Žv«|{Ï2¡?Ïço㯷´4„Þ´8@ùá&$T,Æû|¯Ïqœê!ééÞ-ö¥ïë¸=VD¿Å#Ê|³óQ6Mè5xžé/öUäsÝ¢àç܃cfèL+Òµ…=&*“9dý— »ù+O<òÈSàS ç š%lÐËjŽ ðƒƒ7hÙÙÉÆf›*Ú£ˆ˜µ6(u‹ó›56rú~hþy ü„Èñ­`¨@ªÓåÇè`²éd@† Vß»Ÿ¯ ´k,æ@·ÜT¢6ŽèÍÑ‘t•)J+rUhvzðó^tvt 3'-«ô-)Á™æ=ÎÉ/ÇÏÅÈÜÃ`5ˆj{J³9u l/¥cnu°F¶™Õ&Ìc,*³:žŒÌŒË _h2UƒÝ¶i ¥ª—¦fÎ }óuÎ4½™ÿä§£U³ €•ƒ¶ÞtiKÃ7DšbÜ:¾žï}=Îù ¡•4üRÀ[¯)ôÇ[o¼*âvZkÙºŽLaKƒù¶æ¿ÁýM2Ðù=qòîôú+/SEˆÜÖÅ|¨Näg[6¶Êl› ¢Ä:íÂt0L7A6qYpP©ÔÄiÕY¸MP)œÖ^g¬ 0´† R `FfÄs^cÔÒѳE¿•O…8,‚‚¢fµZEl«¡9«,]3UÏH>Ð@KÓtðìx+²F¯‡ƒ`0ßÌÙ%‚[Êvt°2§¨0`‹{šLáš±ôá`ùì=@iêzÕÚºMG~´:*²‘qXuH*ý³·åú.ç€\“ßœ—À‡ß FáŒDÐ>ÒÅÕãKŸ·Š t ¡H§jji÷‡³¤CUÆÙÃV{zOy2²„åmh.pb»\ifþVáó™sTW¬åœYûŒó2BþaµR 4êélM7FoÆ3m1­ãã…lÍRâš àÌô 4 `¸ʨ?ÏÌÌ¥>˜î¹÷Þ ƒò–“ïø™{øÞ $6/1*Þ[ëÌ„ßIƒ=!@P' Œà/©p°Ë¼¢Ãë4ªh× ŸyoðÎþíÃ)_[ìˆõ%Ã_Ô‡ª_øLN¬U|žSíç¬Î, °¥ƒï¹¯edÝ[õ¼Ã¹‚V÷›¢|¨KÆ•៥jº£û Õ¡Ó´îë€/éxøX蔊J€Æ ›6ÑV‚ zé Ûš³†våEä=˜/“!¤ÎìgAõ•É5€yÒµžVˆK•¥i§†ûàí¿Y{h Ž[‡wl'nÕ¥ëdu·•ÎÒ–ðÁ ~=moã”[ñßÎ0tò¥‰g׫ssU®¹€…÷ú‘²²òç~á3‘$ewftc\<»XýÂ{¢ú„¹Ô€VU6¥—ß8¶©{èáGÒ«ß}–¤™ÞÚ„9 mijå{×u—ª‹¨´‡ïM¸¨¦ý_t™CßöõöŸÞ¸Aÿdª¢º{is> Dq“©#qç$4Ôuvb°£È`CŒ€ƒIVw~šN&ea®Ks_¿%yð$UOT!¯!+ÒöÖåËT®¥âÊÚtsb4]¿p&ÝMûùÏ…ìXÕÔÕËÙéu3ò×™ÕÃÈŠú¹¨Ð¶èÈ ü¾K[´ë4ZÎk¼1µAOe5’VàÙ‘« Ö GâÆÏôlm«Ý/”uïc;GAÁØ£yÍ ¾^ø¸J°Å³Ï]ç=ª‹~]au$õ/€VÒk+dAK ËJ3õmuùª˜H%PƒþǦ°n‡…œ·W]݈þEç´:6×®Çà|NÏdZƒùòyÇì{¾ÿROþWùîûßüßðsè1æöƒWNÏÉÃþ<…ý.à§ U¡Ûk2wîᥢð|Ÿ=zdžÄˆ¡ëWi÷Ëñ2­#Ð5ËìÏÖ¤†ž¾¾NÚíÃ}÷7ØZÇýº¸JAB“µµL¦sVÝLÖ3ˆmâÎøØ(º=ž*å^‚À Ø\´ì L›Ðç³ô?¼L´ÂÒã¬ø6hä¼ Àdz¹v˜ ¨°ãŸ5A »¶ÃÚq?H¿ö»tt_¶ÓŒû¨_Ú/õ$íDÒ1º‘‡3øg+ÊJxG›Ïý(ªã`õœGkÈŸèühk޽QE²PtàýîƒîÉY°ƒd(dÜ@Bñ®•~ÈÀ¶U…ê»Ù”’¨¢^2@»%syÃäí¢ÉÙµ´ÿ~*ÍŽÄ:?íeë©§~d´GãìgçèÙ»êaq~¾gš5¾IÇ¡êÞþHúŠÄAžçw»5³W3³¿ÙÅÀd7kè®29±Ì™KÌ Ûtö©zÆN1ìDáÛvTR/8km×=Öߤ4yÈ*´¦ö®ÔÙ{ˆµ£K:Ð6ôü¥/rlÀAЀ»´²¡kÂð ‚³G©ÞKÏüÕ_F°ùà¡Céþûà9Ùý´ÓdT÷i¤ÎPníÔá>/ö ªÂ ,»ùYþ˜¶áGõ¬‘ù™[Ø $£t-¯l$p€­åú†8b“r_REBîx˵¹Ö¨üÜ/~!ŽHS{=@ôî{ï;fÞvz'õ©r«g†soRW™ÔI¢ð;ÛPðã.$õ£vYÀðŒÙ฾Â-$ú „qØ[ïtB"YÁ@ª¶Ú6äH)3°gÀÀ=KY1çeÐÚ½´ùÔßy2ìtíÙß")® ß öÌÔ’úúö¤¯ü`B`øÜS}kÇ;=óWßLËs£<çH¶¯£/ $Ç™´tl¬ÄÖÅžåøƒü]Ú«'ä½Ú1æ;ëmv¥­ìi%¬ ²„ݹF‚K–àLfõoe,VcÚaÂäMƒ»Ú¤ÿýFzâçž :û “ILú…ê/„ÝòGðØwXgxo—½È q!ã3ÉPyGÌyŒÜeðpŸ`ûò!M¤«LB°šT)?Bçe*w³ SØÞºËqñݤÕ&ôàc¦¦ý«ß׿Ÿ¨}éëO-ýÛóoÒ1“Âì¶ÒÔÔ@Ëýƒì—e$ \ 9W_>~É(#é{ßýü_@Ym½=‚{ð V2sA!rUô-T7 ¿¯~œò}ùêÕôêË/¤»ðw9Bâ )­MaT¡GLæ¸9>G¡x« FZ^ÆyËï¼ñºƒJvöè&ìÕýè©öŽ®HT¾ëž{Ó’ë¬b?N‚wÛÇ™‘`ÑÀž£/dp?v«Áx1%É‹94Ññg¤¡ûÃ.Ïeuð_ðqüè:p™¥S©û#IêUÈH rVJûp‡–hQ؃:º˜à­X˜˜#{ÖnîrMžkaÔOîìé‹sÒ[Y/÷ ×¾e_7ï)KÏ¿…-IÂãª&aÇJ~“±<ÚÆ.&£ÉEïçò¹¹«ÙsËå1÷Çe„Õ¬ËüiâQk¼ŒÜºŸ]ÐOõ ¶˜ÇÃG±å²Ç¦èãÏÓÉÈc_ X™hê^f"@$mÁë[Ø—v¦±ãÐÁÞöT_ÃQ8øæ¶íŒÖÄyØ&!làߨ½dy((Ôd% ìQ­Š¾S~ƒßyݽ@^g*ñPÞëÎ/‡—D·|YeDÉ7’®ìˆ‡‡$ÚÊ|ÑC®Šñד´âºyTJèþÐq¾‡=Ïñ¨aÐ$Ñ;ou°zaûP›ÓŸý¼Xƒ-¸ tÆÑ<Ï}—H_¶nè~òÐám±˜8­¯n—$yÁýFfšš¢cÝòLx)gO_ »@§0WæÄmèLBýë:ì°—øÙ^|ô&ö`“ó¯‘=F%{{ØSt鑿ƒ$ ÝF‡¶’<ÛöÒÖÙƒ%Aî&|Rû™,NÞw?ï¡‹ öHGÇ„0Niê¾»gâ=û²¼]TDbvÞæ^%K¤&ó¢›=º ó<ƼMrQðgÿ[‡cï,« à ­LØ•†ŽÍîV|"ή.'©ÃäùÁýÑ®z+$Ý&™x’ĵ!pꛃãqe%$ cê ù¤‰áqþ6Ék ’˜L£<`_ ×*äf*óÅ`”“²Ôõmè÷ÓY;'@OýXmE1¶eŽ›‘§Ô%2’ï:€ò7Ó[Äü›û³¼e⡾¦ß|¾‰1&Còð|Ž hÚ,[b¬ã2ê|½¿:^ß[ŸÜû)—ê*ƒÛÑU…ŸMæpÿ÷¸?dÁŠûƒx <_[S‘Šñ«:8®Ä×õ—•¥(ðaïóYÚAâ7Ò…q¿lo“ÇJèx(_ØIÌ¢ æäú©£Ý÷ ¢û>Þº'·6Ax_~1)D»ò †®_ ÙÕ.ƒ1ùtþÊS O<òÈSà' €›4pÃÀMMÀqdlŒ³æêSÙÍž¨333Æ¢¶WþÊSào…]^ëï÷Ò`–wuV4²4ƒùÛŸý¬…æ³?û^¿ëDè¼l­éÙfa"QK[a„ò~H[?kð)˸çùÜOÅëÊåKqϵŠÐ`B%÷9~éÀxéüyÜÂ[¯½DÖ|+€èª!®â€Ÿ ³‹vœ~Þ*ñ›#é>#øUW¥çΜŽ6c7p°Ì|^|öÞ Ë8Ìåiðêå ë"¹gÜùyƒílc;C8=}}€“]áxX}¢í¼ Ò 2Ù–ÕêôÈà…ζÅöè£Çñ̲¨VéîíãïV=/øb`ÜçÞ çP°Ñ`£¶t.ǑߴÒ)Ь’@'^ÃB…£m»FÓ‚IM+:¸謭ŸmR‹ Èž¼“‹L’/ä%Ûq»®ê7ƒÃoÕ¬ ‹Ñ`@0kûXí~›³¢7ˆqMÂ1²ê+/Vë„•ò7Gg0TYg2s Ì'øð†÷ è' gRR‰ZŠÓHy@*ëmpÚñ fèðè(••níð< w’­ƒ ¶¾ÊœÇ’Èx¦: ˆáý I*Vk«ž'@£¬ˆÖ™òw3« \7ç)xh7å[[Oå2ŽŸíðØùX éÌó4é®#Ÿ¯Ðˆg7p~¬àc5½k1Í, –”ã°Váz„€4€ ^Ø¿u‘‹VYïfôÀm*Ì•QÛ¹Ud¨ž] Jauç_hµ<}3½{îlúÈ£z5¥'Ÿúl:vìU…ÑŽ Ù©çü]ù·0AàÄÊáQŸŽœLO<ÈÙκs>'À¨•¤MÍ|Ž*M¾öut¦fæ-ÐØ_ØÞpâg 2ÎjÎÕ®Ÿ§e¯ÁÖM_Xž.ô¬”•ƒšÊÊ#gäÄ`úì"Î4I/ 8á:É&„ˆT¢ x§S~' Clp^9 ^e­MÑq.b~ˆC<Ïêsu`ETïðæ½ƒº.FÄbûTž©üä²ôÑ#Ç@48áÚ ÒzU³võT÷- mKøL= …åUiŠŠJ[¯ò^Ÿç8C—ø3ôr ¬´¬ƒàªQ¹g ¼^CRJ…A(*?¬¦_ƒN,‚)Í­êœì¼íUö™Mö>“wöà©êê v?³²MÀÔ.-«€õÊ€gÙW_¹ÁÒƒTb£o­µ=)H…ìOÅÈ•Á»˜xd„Á#Í'Sowg¥Ê¬þ0õŒ5âýÒÁ±ä0•PñarG¯ ÚzÔˆm« [SKS€öeÐâ‰Ï}!*lÝW®¼÷°U4?´,•–†7Ô d1?£‡ \¹ŸZ¸o˜ `R–etw@‹ÑÝTÀ+ÛÞ¤œ{^¥*¯¶©]°~&‰ °€nZ˜T· §m-^Nn¹ß@_`Žê‚+væè;p ‚ìL3{B0C^ $ > Úç®<ñ õÍßäû4òÙ¹=+g/ð´?“+ ÈÛòñûü‘S§ÒÜͱ6›¸63…~†OÔÛÎÓØ3CƒÈ?I´Ö¬E—]8óFêĶPW¨[å+ë”=u§‰‚î£ñÚDíâ^á8¬¸s ” ÷d© hl@i…ç;uo$êlB_+ËH¤Ë΄¦·¾{àœ¼f5®‰pêy«†ÖLðÕþ¼ô8í,V£¹/d4yb ÜqäÖ"÷ýÇ­KÐÝ#z-p¸… ï,o‘0ÐõñQ`ì„ÂùT}æ8¦ ô„mïêŠ ˆkeŶ+J m´$…8Ò¤ž}ÚãV6D¬upï÷XSN½­]Ðß*"ª°=܇v‘3«†ƒÑ‘T:^ÁÇJZÌ{/eÑkll,ö@ÁâÐÉÚLð‡ 7û:;ß{MÙÏ&´WÕ'?óÉO IOãÐØs•‘-ÆnBŒ­©kà$ ŒÅsÉ­>³zÑà¡’lâWgW'v+ÝTìžÂœÊÊkÐpþw.©€b,úŸ’ÔíîX_¡· Z©g\7ˆÑn4ÁBššP!Xë19/œKgßy+ÝœH¿øK_Hÿó¿øíÔM«À1;UÌÓ–¿9ðbì•cƒCéþ“3[ žóœT÷ÂJÖ Ó7Ηà& bá*kî&&š #°l`DÚD•&zÆ}É$3ç(­äíW_x3Ux{è2çcå¬ :Ïd¹Žî£ŽÓ¹û»~²ª4ÑÄÂáC©–®"[¼& ”»x:IÛ‚À¸sDf±Ãûº;ð#êB¾–jؾV>5ˆzÖ€W¯—Fb¦sËtš] ä Ì£ðkî:|$xLZy©]ŽÃ¹gÉÇ´èÎxL^u›Ç,¬¬ C<’3áéh›Oð ¢3ƒ_|G¦ æEð~ˆ–ÒØ2Ú‘ße{‘ãõãÙÞ˜óŸLTÖ²K *¥îØÝØ|^µ]·ÕèÊ…~Ó÷Þó¶rð¡‡Óúwÿš½`{NT0£«ôE”½¨Þf½<ÿ\»ecM?Ç:ÇOP‘»„o¤ns/õæ§ø'ž!Ðr rxy¾v.Ç`¯z½¯¯=·í…mš²÷âO±GÓ{Ó3£=·Û@ÍûÕ\ŽjX\(¤ IšI”4,ZÒó^÷£‰d (Oî*ü,ßÄÅwu˜>Ò£ý XZIf´ªf/š¥sëçñ;Vzv÷;DLÀãÅš[Hè€ÅÔ ÿ…o³Ûÿ¨ß{iœÿõ%ŠI®P¦LÖ¾t^îkYœk¡ ðö°;ø~çêJ“2«Í}‰}Šgonà ‚y¸'ÌÍaËÀïþçú8‡( @N”?ÏíJÈûœ].´“l¿6WËï«° Ϫoí$`aJ9ëZöéí±bÒÍd÷öa|EϽß»€$q×ù’ÇÅÜc¯V®xn1zD»Ø„ 騮TwˆM8g×-ÙÄÖ²âõªIÒ å)§À €ÊM!Ï_y ä)ðÃ) Ñ©Q¦7Gäô;§S@âMZÏ?ÖY5Küú•Ù4=9A 7v?—¿òøiS@¾ѰˆÕ˜ÿ WŽw5P8ÃÄX{ßü†ãÿWq<^V%é„jàEE8ŽŒYéq^:«:‚¸Ê\öLÏ^¦‚ZÀBçH`Yy´Uû÷_É:Ü]]i³Šg©âjà|c[¤ú~«;tj}þÌÔ$†qaTKØïî¹/äÝÝ‹çi¹Hë;€ÇlTB/Î ¤‚ kt`çêå ,ïë`œ) ]‹óÐ&©V÷9gO¿´­Þ,<›Î /A>«Ù· ÌH·UXÚošJ8+‚uˆ^|öÛé•çžØ¿/@ξóL:ýúë©gàŸ]!¸K0m+W¬€åÞ:¡:‚:*V},ؘ°Âbà²à‹×-B³¶Qdrï…cCû<+@ÃÄÙZ:°Qá_ŽSo𕆶\4(c«¿âÊæ@u¿@@À4k`2Á]ý‘I¼Íú<-æoëð¨NS • W/ž'Xy$íkiNÓ;“Œ=ËþÝ%œµ)+ŠÆ‚åÁ/ Á6³:e>ƒX,À‰áH¤[´ÕÕA_!;z`–!Рƒ ”ŽYdï³xf7X4/ˆŠsl½,sP ·ŽgmÐÝ g%*=Øwl)‰¯mìz 0b±ÂfD+¼ð”mxs&§í¥‡GGqôk ßŠçë€_ {V~*r>·nl›µæòÌÅ@ (×çÏcr«WÊ| ¸  Oß"»ž1õíGÌ1Ü–·.\·;¹3g³ÚM M¹•wÔ êKÁh/[ÇWò÷JøhPw–³Àª¡«N´à¾ ŸŽe5ÏßÄÉrÖiµ=tò œã°èßÜXàyð½ȾLU‰þ¢AñE@#ÏxeÐT°óLîï}*Œk<Ó{`¦¾µ1Í-mf <"èÖLµra­ì;*ÐaÐÉ *y×du`œò¢œ(/:“‚ð0pè²9:)ª—pníh`åé=÷Ü <®ûá£GÓ»GO¦ó¯<Çù¹aTEx–¬a@Ëêg«Utb‘=Ã>ø`ìµí¦@Û€CE¬mϳݬ»Ðt #²á–Öp°£’˜e¶c¦·M6Üc¤<:.Q¹Ê¹sÕCžazïý÷§>ú1x5Ô‡êZ×: ¾dë ýÿ¨¿l ke‡AD+e'±ƒ &yÕT4ÀâYYmð_y¬Ä^ZSIr™|(°þëÿø7Ò3_ûÚ‚SY‰lñ‰˜2)ƒK rè/ªà¡Z}…· ZȈÃîïî"@^m+óJI¶Böêhµ¿ùð)ûŸü«Û$#°'š#h!/À¸X`»úhËÏû+©¸Ù†6Ìî‡Ö5Í­ÁÓ°]*ãˆ-ÞÛû¡“ùÝÐÐNó3L´a2‰L¾ À†Öðu€­;[€‰ÌÉj´R*Ðúw©ž³Jeƒv‹%TrP¥n‚Ko¼’>õK¿’:;; Þ,È1HâJÓ‡7“r9W~èêíšgGÂVŽËK™wÜGäIiãå/\4øæ™ñ(·àï>O›Ã€‘û’{·v”Ôq½º+=Âq_¿÷ÿ=Ïw΀åŒÍÏŒ°ÉÞŒÍ4—Jk‘º”,/Û IðYðÒdC’5-+£´]£…1“°M·6‡÷TVÛ LMŽcM“EGã¬P­jûö¿ú˯ñ:IȨ|>A°\"èºvåjÈmúÚWÿ,ÝÿЇ9’ãW"Q@¾4HhÀ$£|Fž¶Ë‚!TqF0÷´½·n$S²×`Ë/Ê‚•·&³ ¯,rþàîc&v¡Ð ììÙeØ.þÍýRÚÇaÕÒW¹õÒ2)JšÆ>ÁØ\όػY;((¢Vý+Ú}&Ç qÌFV…™×` ’…Œ»÷+§--±ÛÙ¬î³ so¯ k$Ë@Ǩ°DÇ™$)ÿ`ËÈ‚üŸÁÎSÓSÁ7Vˆ›à*í¤‰_™¼»‡1îákòœ÷d}Èœ|]ÛWoÕjYu{ÐÌ Ì6re‚S!2lÒ!Cç³Ø²<¯«k_ÜíŠÑD%¡ö­m©]ígFv±2j—%ízí;çå¥m$cP‘ó„ø›c÷+^çÿ,¯ì±† 9{¿G />A2ý”/A{÷&yØÈ…òë¹ç{#ç`‚Š.p“à|…>Ý€œWŒ ÿÀnKŒ_¹¶ã•—‰ªê|Çâ—‰¹~×&uÏýú_|-ýŸÿû¿÷ž8q,º™ìa@T›Í%£…ž7ùËà˜wVøUÉþW ºgzŒ4 DÐþd-¶±SLz2RG hyx-|¦(*UãÆëQV¸/,‘üÂ<ôÇñNÚKK|Vâ˜$ÿX¹[ÓÛ—N=øp$‚Ê7vɵÒwŒ0ùX;)ÒÜ*{Ç”ñ‡] 2=7?EP?`q1(¶†í\Äû °K!zèé7>:Áþ´…‰- ¬U¡§–©¬·#T ¯ik¸° Ä3µZöu¦¹™™tc}ÃQ/a0þ2ÿ(ÇV¥ËS¹Dú‰±‘°ŸœÃ•KBVZ ®›è¥}á¾h°Î/ù1îÅóõŸ‚ñÊù'ÉÇ&b×t[_Ïlù7øÚá“Ä]ev~28èeÂÕ û”ëoknn5’œ¸ãPŽÜ{Õ_&#Ì“8¡RKPÓýoúí¢3ÔØÉVROS\ˆ t´Hç¾~N®îSHaéòÂ|õû|M‰÷Ài&ž¤‡ëY'Ææ°ù΂ò¬Œ]?ï³F»i/u€6Y¦W•oÁŽÅÿg/ßï­Ô[&LñŸ¯,dÁm}2½üM½ïgö8ã[Zålé§üÚ¹!äÖ=L¿‡9´Ë„É¡ÐÕžís£SÏ‘¦úbKø÷K+tiá}3”Ù?YÛ®<&4ïÑñ g I &ö‹#ÍÍL…§ÎÖ/·â\,A^ ¼ù/Foh.à‹Í£Ë…¿ã± ÊžÇ ÎÍL’üÂq(üþüs/§óg/²oìK}}ì?úé$“çþí¾‡ÂdIäÒ!ìøËD|Ïç^§{ˆG <¯ç¸õ®öæ&ø…•áóó èªÈ‘)ƒá³3·IÖg,s|ͦ }–×<.o“jñ6t´dN&£LߘÀZÀïnHÝÕ=Ag¥É2ïqŒV™ϰ1Tx ùÇ>››ŸŠcLZ›3 ËâyC›Î¹ì؉ ?Ž=Ä£-¼—xвx…by =¢¯"mÕ‡Ê|$üÂÒÚ ~Æ× \ëk·kƒÄ¾!÷ñ¿k¤ŒË÷vÝ„ü"ŸÃŸF‹)üÐÞñ>Mò\“`ìÎ¥ ™,ááñ?Òz³ÄP8û8ïq-Mh×·ÃÒ´ËÆöÍÀyÄŽ`Çø›|€4poÖнÀ„Ʀ?&?i‹ë÷»·jûi×inÁGwص/µV±LbÑfR›Y!ïjøpÚzJN[ÞIUnLΕÖqT »F¾g ;ØÐëÈ—r+ݼ¿v ˆÏdøš‹ n ïlç˜d˜Y¼9å)§À_§€Â”¿òÈSàGS ç8i0˜YjÕX í»&h³5C»œ#'ïÆˆMg9 { € f7þœaÿ£ïžÿKž³_5彨nÒQÂñ5 Ï÷{åî¡‘¦™(Oÿ¤—ÀUl<:B:>'÷¥‘©A®Ãä<ï´Ž @àrœ.èJÏÏüõ‹{ñbU5íÝ.«dã\QŒGÄ2òGÅ 4k¼÷и¥¡5€ ƒ&²ÄuD~3Ÿ@IDATv Ú†ð,•è·ç'S5çž«¸‡ƒí9A:IfÇ °´éov¯AÁ ð·ß|öYCѪU ÜL|Ï—t»çîi&8cuÎĹ¥cá[ErchˆñÛ†r9ø^%@`…N@u1´/¡ª­™ªëè²ÄÙÒDÀO§Ü®fÛWâðêhËgVSËC>ÖÊËjÎÿ5è» ]ë/d´ž‰Ç‚G øíò³4 ™ÌâxÜ€»™£¼íAÁTAìÍ'­<sU¦#1ÆSŸêÈ«s @Ø1§‡¿Ÿ&®ƒ|ªŽô3vŒ¨…¬Þ8|ìXA*àû€VA°:Al3žeÀ•.nÔÓÛLH§h7::t=lvqðMª¤q_¨dük$l"O¢ËÊëÑ»8>c”ÊÖ½8K¶‘jÔ:‚T—¼úâË´ÿìLýïì¢úª»?€à™[ã´R¤ù[Ù¾ cbËr€Nu¬[%ú}$o|ús7*ˆ‡©ôr]ëhÕiE¹íBuÄgJÑ[åì…‹´Ž¤ŠÌ*csŒìF´ €5>|`üAh ˆ`ìÚ›èÕ¬—ç´ 4nò!iè9¢Ê½ô2@>Û9@fåKà,FÏ®nëé‹6àS£²ºK^<ݧ6Y;Ûúg•*„b¼ò̃Î&Y…ur´ê'ÚµòyƒW3CìFSrÛ¼+=ÚÙÞo²‰@©-Ÿ´-\1åp‡J´Ã²Tt>ú È@˜‘ëiÐÑýÛ½Ìß À¹fWVIbðC Èyÿ¤—tõ>w¼Üs °Ä³‡6’üŸ³C”eõ£zïü¹3©f÷·"æ³³lëÚÙPOOO Ë„ÊLñÿ±÷ž1ºžg~ß=½÷ÞËéçV‰MT/[,ï"Nœ›q ’OA‚äC¾%A$ì8‰h•µ¼ÖÚZ5R"Å"REŠä©sÚôÞ{/ùý®çŒ–’¸kÒXÀX`rÎ̼ó¾Ïs—ë¾Úÿ*¥T?Q·1`Ãg >ë¼ €œqø,3 ®óþòRP¡Ë= 2ðüO¢nd@¼FþÐÚÖÁþ/§}t€Bx à¹Og ÂìïçsTaÝÜ#y»A‰êKî‰Næe>«d‘æM̦—®|Þá:¸§‡¼Àõrò}ÿîÏëŠ}ã½¼ßÌR[šdú# 0À‡·é=q&î756Y•{¬C%YžtH/Ò€€¯ÎÇdÒ.Y·0t†­ÀGäÒ0ä2IÇû0Ya+€8fŽ ®6ª÷•›…žÐ‘–ä'Á^˜˜éD”&Ô{ì%Y€ìjoHYöâ¿OÛÅè©íºÈkNæÑòÒ‹éÛú­È½rå²ÅZr69[Ò•nï#ßWבïì¯!cxE¸ôÇ/¾àÊÞ“ðvÏÑú»À™6¹%­W 2ãÜsÓÂqÏ”QÊ3uwƒÞÆ'ÆáçV‡À¡­.IÉÓ­JbÕy©Á[Ëðjæc°›ü~úC:BoÒ ófîò=u¿œt6&Ïû½ægé?{»/Z7¨cd--h(óŠahpù36UµT†ð¸—™Ì‚ pN7®½ÏØ›¯Ú»¨€^è­%:ù%•,ÀÕµ7K¾¬¾&xQÌ•íêˆÜ8Öœø|öšçÈ˹»^«Ø*ƒýÁÇhå Þ ªVqfå#îŸt Íäwÿîw3Ø<Çž«cäÃý¼ò#b±ü» º¿K¿ ˜ü¬Âc ~TF—YIF§´à: ÌJk¶n’Z1ÀªKüùÁó­1Ì~ÏźK›öÖ¶\ûÿýýOé©gŸ Ý@Ü35=5žîܼÎâlRùë -±^å³èÈ]éí·~J@Ìgƒî\ûrÀðò ù•=»€•¦ÌêÍV@›§þÓå¤aÀ¹räm# róÍ‚YäF”Î…––±QŠ+ËÒÃW¦zTè~ò²qÛðŒÃìæ x{ß½~@¼Vè€þÅ€^UyÕì[ÆkB ][>z0¹´²½ñŒ²A×êä¬)ïQF¸uîüÙµ2³Y:Úæ…=ÀeâîATpî£k ¾ –®¼ì¼&&f°}hÕÐHµd£|ÚŠò[…˜¯î/ÝK#êÃÙ3•ÿYëϱúCGW/Õ§cÌ]½'Ð{hO dJÞSùj€ƒpãò†Nè_zI4Þ„\1€‚hŒT„ìT~XÂÛà’2À3ubPJ°½ Ñ¥·òµí›Ùµ¡‚g33óiŠÀüXoÆaU3å§òó3æÞÒ\­o §Øl•Š hˆ qY§¶ªÕéy­vßêH|0Ÿç›9ë‘eîGÞ»w®›´(m:Fù’ïS§“ø‘½„cÀ[¥K³T§Í^Ý„O¸n|y&×Ù¾Jæê=3žd0˜²ùË=¼Ô?•9¥´ùz‹@|÷FºRΩ0no­Åç¾Ú_\=‚ `Иm2”ÛÚ|%œ}5!Cã³Ú&ò7åwyjDgÒÿ°É9Q§³Ò´dEׯ¶fÒž 2xõvKc+ß—ák«÷›…;OUùÑ ñ«Àëå= úƒ#æ|ª[0ÆyøÁ,%ªH‚hmÃo¿¶%‰ƱIçf$L7¯ãB·²šÀ{lUšsϧ^‚cLj°jךeºõ ©°Žp`”j³ð®ZZ~œb>ØÇœ-u…ñÑÑ´Œ­Ñ@‰Õ¬ ¦\[XXaYol"i¦® žÀ| \\D¯T9ç}³Â N0Hù8‰,TöɧµOËð¹ˆí˜¤­ÁÑÐ)+±d%ÊÖMäÖŸ3Ù̳”³Ž?/ýgÚjSÈk”ý)êÇ‘ŸH“ÚTÒ‡/  >D‰4©¬ô×× /+ªÓp~på…oÃ{úeàšŸ ½Ú5ÈÄ÷sã8ûò2å“4,r/­dàg ÂÊ/ƒ¦¡X7\¾%ïT×÷ý™®ÇÞËW¹ŸÕxô ©çÑ¢®¸p™ypf0?ú¿Á5¬ûd°£ú‹óÓŸ˜‡h€£s³Å–}˵HèBÛðÊ(†o;6+Üz` ™Kð­Î›ùB™-÷P_dð>çÏKq.´CÔé úή¬Ã&z‘çQÚ²ý“d-¯`ѡ彼ðRדqg•#ð¡@ˆ#ýÁj};Z£8Z£ø„+Ѭ…ª ™J3-7×Wæ)W[€ù½[·Òݾë¡(:ä?ᣎÞ~´­+p¨lª¼©jp桇¶÷1žtøy•-ï‘)ºÐâǸÇo¾}/uªÜÞß×¼ïásü®B«¡ á¥¢?qªS×ìÇpøðž_~FC˲…í]ÇRzõ•ôÆO^LéıÖ¥³sòÓ(ÄôfÄPnni0LǵŽgËYnõ…ÙþjÞÏÌKŽš®Á¨#ÔÀÑK åT¤ë¥#IãDÃaIÀÏ @šõ«ñi)i ÆeœÚ¾?œL(êÎÏ¿ßÅáv‡l=¥þ,³õÔg>ϳ„‹÷kÈH¯L.=B¾“8DßGùÝÃ`#£ªïÆõ(™köÀ÷GõdÜÃÀ7˰’ñÏLÓó˜ï–eW©ÖHÍ×YÔh¶¯F‚%çâù8v?ÐøQö‹…FÂ(ÍŽÌÆ 0µˆ2±8ÊÈ«ª(ÄêÂ4ÑÐߎ ƒy20¬0ŒÃ° #­ù±Ç#³ìĩӑ±®ÃWcÎr¢fÍéì_#»«±½}¨µµßì2_ƒwï¥'?ý)>_KÉ1KøYŒ2‹8(¤‡üBùÚÌÑZÈm²N'˫߿›Êq:Nܺ{iÛÝ j«ì`I/ÚÜC  ƒq«ˆ­Xr¡‹1d6D “88u–aŒ œç?0ܰIè#VË|0bX³}54kš:¡²¬WÉ2Æð©¤ÿÜŽ(Ïh1Æ•å÷×1úttTÍßÜÒ’&Ø‹Mœ){;6n8tlX »§‹Qã Ç:ßaî››ËQj\Ǿ‹8äÿ䛜.\x#¼1ݸ~]û+@{…^{ï=2^ÛØ²×¯Fµ@Œ rͶ~/ä ‡ ÷Õ[HŠ{.ýh<:(Ìþ§álĵ×Fo—a,®ã4Ô£¤£¤”Ò#8špøæ@çk¢QR‘=0Óª>’FU—Ž`\{uþFÆ€Rϱ©³§7]ç-=E»M^mP¢¶¤‡ÎÎRú%jœ ë œÃ©à95˜Eºñú0ßñwj3ò]]c¬4ñÐÃsöÍ´ðü`x2gA­.§É;¿€&Ñx‚ ì/{N©À2œ®FŒoýo mó^ô\Ø”4‹Âà³ñ gÈà^â¿Ý®B'%¬ß.÷Ôé´ÌYß +A:îm“íÏÔYU‚Álm<ÉìÅB²¸ (pœGA_×Ò/¯X£¾›7Òc?‰¬Ðq&…dW¬ï °Îg£I ¦B³gÎOÏ?ÿ¹4öÖ?O§˜A»ºn¾Ï5ð''Õ8޾ïæ>E ôÀ·aà|*(Rg¢ŽlìA s?È¥jÎ*÷k…sº¸­êì™%‹³­«;MÏ.áØ„îq¬ÁÚpn"šq*ÕÆ¹÷:JŸ >& tô˜¥H/c®{-?æeaœ=éfÅlSx¨` Îäeô8É\3:i ×cz÷õWR-ýõ>ÔÔQž},óátÁgöÃ>™ò^KOJg:¿le`Oiá^ê½x.ö‡œp –-®§,xU½ÑÙËUœy:9ÿhl¬­Ä™6{«ºžÊÌ£gÚóܽõ™ö7gÁ(áKhdŒÏ– ÂqmÖÒw8ûòÕ ø»g@0Þ ó蛼·‘÷™Ñ_~"x`à›÷Ó)èÚª¯¸)›œCЯ‡“ºš^Ð|Vçµ` rÌòÞk¹V®ùáy?\ÿöËþ‘/Eñ¼©+éÈ ¹Ê|tÎ×7µÁÿWRK‡kcÖŽgz‹šA^Ç™éêî @"Œu4³¿Œ:ç–ߦ¬ª™_‘y ½ þUæl3Ø ³ÂÑ«¼ƒ?©³)‡Ô)‘¤SUª Þ­ÄÒYkÀ¦Ù{ž«ˆ€7¿ùÒ é…¾Î=®Pi"£G3 öM?øÞ÷bî?{ãôÇÿï?ŽŸÏÂÿο@äiÀΫ©©ð„¿ _&ø\é®…½ÉÑá¹OoàÙ¹45OIçå5èy WCu k"û; ùR½„ùìð&u‰|€ð(½ ±Äõ溴£8·Ö!ÓìíXjë%`9ÿAï½ýó456Æß)é Ôùk?ÔG$@¥õÌ—_x%½úÊ+1Ÿ¯}íw‚÷à±»èrCý÷SßÝþø['÷U'´ˆgK@E_À»BÀ–©—ÖÔ›ÕM¬Þ ¿b›‚fÙÛÊr³¿ÒaM›|r‹3¡~ÚÒÞœþ·ÿõNÉ/®ÿïÿü] n"ä‡g÷ŸûÛñ·Ãz¢:vê,óY‹²â‹ð¡×~üãXE3Ê»t:×) Ïa@Ž]Ç7L07O)Ü5t6H×3ùîûÔÁ-aíW”?…¾óóÐÉÔ}/·p¿uF·4Õ§ïüÙŸ¦?çËÖ —¾2ÝùÕÀËÕ™_úы黺íÈʶÎNôÚgÒ±cDZA$äÌÊGîÝ£z ×Nu2^†<”åpb†µ™ÐÓù<Ûª(ê]Ê ù‰Á¹|"ªŸ"‡ã09õÞþàwè”_\Ï&ÀOƒC¾ÿÝïDe‹gžûLðißuB. pž”éìQk[T]°Â’;x‰ºã›oü4øùý{wÐÍücŒü·¯ÌCÞä°ŽE€[¢l±K ãÂbô "m{°¾ˆ­‚ü…7ÌåÃ9§iºþj¼–©VÛ‚V<mŒý»ÿâÛȰátå‘ÇÓIlŒÌN)ØœJÿäO¾‘¾ü; íy5@Ë3åí¬RÆ· ]éYZÁß²Eu´Í HÅ8¿´ ”ÛÕðÊr@\Ì 4¹DP Á‰U5 Ø7µÐé"ã#Ì•uåÆêZÊ8u€Ú:Ào|Òë,gaý#€cæ íYU)ìMé‹ëlÅ3ÁVƒµnÝø%Ï/Ckjrím©›€éWߋտñ MÐ+¼ny–jˆ· |éŒ~éuõV´} ?÷S¶NMa•q¦÷¹ Ö%8Ü~ë¬ÏÈè8O'û™ ˆsÈéuØF÷±^ø¤ "´5ò3º‘`,¶À2þ–eΠ6‹eëµ­ 0 ÚóÆ„nU)ÛÝXUOšÜÕ.0ÐŒpyÔkn°—úÀ­Z#mYåGù´Pƒ½MÅ)ÎŽú‡ 1…΂&äòIƒüBÖò`´%”5°+¨Î yû­Å÷J«%œ+Û˜Õ®ŽáÅr@+ð,é’÷ɳòqØø“ùðAÐoä=Â< &Ï.ø~È}¥k T4@Æq--kï`ÇÄRHU9ƒöô;™^SÛ­ßÄø4ýç—cò3¤@ZgOŠJø‰³² ÛRði¬ºU÷±Øú/2—OÌ£ “Ôs‘ &©fm@1¼ž«­[ ˆïyT§ò¬FP"¿|ë|ôkJ#ÊªÍ e¶0ñLzø±'¸™¦äØÕñ1@ŠÊpÈK+s*#8©‡Ä† ÁúwBŒàêÚv:ÕÔ¸Q’*jëÓQáÓôíal–j·µ†¹™:ˆòðJÜ¿uàm“ý§—-†tó,Âe@Ô¥GÅÕÑE ÄÜtš%à¡:kíèÅéCÔ.t¨3¯7²àë{ã§,]»zèä݈_¤'Ö}ì¶Ö–а/®Æ)ndÆk$ ¸ ÀÑCðƒÙ<@@}KSd­®¬Œp¦pÜ@hÊBŽÆÆÑŒ‘Ð:ôM%ÛŽê¸ù–Ô`ĘÓ×Ñ%ïp4­álìì&h¡:ú'ô÷§wÞ{'ÿyö‰Ì' _³Ú ò .À!°T¥cÃØ>ç¸ñ>ö£÷¬š%i•…}Œn·}ÊþÌG°ü£¯EÔ9cBT²vŒ‘¹Ä C\/Àù›ÿ0€Å^çTøƒþ¡uÖ,œ*ü%é=œñ"î±J_LAz ç|Æaf3ðµö-å#qTp6 °0Øü‹”¹ r  c—gÉSünf’N2ä”퇬ìÃü(x'kŸå}žcÁPy’WÉîW€qZM•צùY@œI.{*xßÕÔžt$o­ÒÚ€ç-ã ÔfÀAÆ[5äLèm&0Ï(¢ý—ÈpƒÍÒ V] 4DÔ8·—N4Þ)Ž0çcÉZüj¿vb­ÐgxÆSdÊ” Ò”Ã4KOG»üEGŽü:¿}õŤç~ÿ멵˲ì[Ì‘õ‚g5µöhh€{¾¶<ÇšÓSà«« Е¼>Ö“VçÒ“O?K©ë޹_ùúß ) i‹›ïus S2Ò$]Ÿf2›¦¦&F hå9€£ÏÖ¼0ç•ýÏî.€t•eEÒÛ™ †Î.‚ÌÚÚÓŽº|ª†ÔÈß)…?3=Æ\-åI «YŒcYG˜`A1ˆçÚŠ f‹Hò%3M”iÅ8=kÒWà¯e••í¾ç7Ïšsk¢Ï¬DæÙûðßyð_yéæCq&| Ǥ<o´/ ã~ZÉF h'—2Úðô*²ÖäÒ¯ú…Y¨çîµÁPSè8í?{òìùÔ€\°Ò„ó”öL,ï#›Ê §5ôõ³Œ|žcî›ëñC3Ͳ :ßãYôuϳå-³LW2AàÇs³³ÐIyÈ[ƒ†\;ÏjºG9¡g(3<ëþ¹/íøäyUÚ›ÚK =è’óçzËÃä}®µc žÇ§õ¯ìïÞåã]ñNî#Ï’.âY‚pÀË×›²À ¾™Õiæ,0”•M-CžgÎ|y2kÂÞä³23|·¨ø dìA՜džºÊp¾ 2—@‡Õ”ª¶Š‡:¥1­ÄSŸÓ{ ø”Í:e=ÀCxvªgÉ]¯ë×Þúë9ÞͺœØ—nÜÔþûõ«–“–zh™5_^ÁyoЙ_‡W3:YÈ£/sæ¥÷w]Î@ ƒÝï&5Xm XðwÛàÀÖ¸F&ªÕ"J2ãÅàÍŽw_/v,3ï2£ˆy0*uX[ià Êõ±‰ÐuÞVWf{ÿÆk/ðVMû”ñáQä'~œ×®žcd¤ã¢2ãýîwÿüp*ñ½¹¶8;ÕKP_!Îy€+Æã~E`‚Žså-ëÁib­•8dϨttà`G_XBï*(Ði›˜j[è¼¶®Ž÷=ž}V™×áyÐÞ~æ‰s±ÃÓéÿü‡ÿàׯä/=]æõá|^x›I?þÑ"{=«=‘RGK=@uû´Â'p ³Î1^ÎÅ2Z‡³|/Ï€²Â½Zâœéx¯8T_RÎèä—džQ)ã8fÎêLÎmƒ2Í€˜ÿ½øÃÆWÜü7þyüò1dúNzý­·ÓÏùú¨Ë¹°œ”W®sŒ=FGt³â•ú°ÀdbÖ?ÓqЯøŒ™ô‚úKeÜ$6¡l³Gµçê^ ¾9nm»dõ{o|@*Í?ø?þ÷ÖÇ~­£™±e½u8,Ï,@#¸bFÈv@€ÚÓ¸æ:ù ¨­Ëô Ç-™à{y±oÈåÓÙ ƒ ‰ŒUWO™IßäÌ~óßH5t•)C'Ÿ¤:#níhJg:ý—¤“'O³‹Ò;o½ ßl[ÍsçþÊgË©`T\Lk¥ÅUª|ÜK½œƒž&dž8Ãðnîgð…ïwÝ7i·$ˆ!Ï´¢•-£¢;¶ÂàÀPzùÇ?&çÑó¶XFVhS›mʉH·®ÝHýÓi[Ê~·f~Ê›=C²TÁg/¤ƒw Ÿ̲µ…€ò/’¹Eð£íÀÎ?t1=ñä§bo~åïÊ+u†Ÿ¾öJ‹êz‚ñòc+Wô)]oì9y›€œ½©K°˸Oú‹`œe¡Ñ­× ±qøŒ°–v/)T•ú¹ CöW9ÀÀ -BЍF`À³ÏÒÖàÃ1/«T$îÙSþÜEÖVcoÐ~‰÷*Íž4ãÜéòy¥kkÃ"¹N[oཇ— 9u%——Cò‡½½™cò\%Ï‹Aóê6Ã;y–u£‚ökeo{okT5mÞœ£ú2[PÙ€&0iº†€„ÀòRʶÛÊ«¢¬6ôÑêŠ, n€»­`dö¨4¶Ã˜XVξ²œŸy˜gÚïÚfâzX…ê3(-‚ª± ÞRguo¥;[ýy}g# vÀ12à»C€Ð½Ô¾Ø Þ¨¬–?B+ì…[WÀØÝwíƒ(ÜŒƒK~g`¾eís¨Ö©üWÇòK€ÐI¨‡ $}dØgÜBþ ¬Ô2!ûCÒ)….ipË-Â_Õïx]櫛½9Ž­Ê½å!YPæòÝŒ9ÙÆ`~+B”TÍb©0Õè>5=$ÑÚÒˆ­MVºòLÙÄÚk§¹ÿû¸æ¡[yáöXÓb€å:슚¬hnnãm¬/ò‘è§gx¼¿sÿV{y4‚j1Úʪ•[;øKæ œÍÍx4ó±-è½þ[è»á'ª¦šL:ÕO?…MÏ 0œêd臈ÎE0‰Á+›èµE´ÄËî´ãÜx)— ö€õS¦æõlWÐJF}X{n¹!­²ë“aâØ³ Œmx5ÈJ:G€€I Òƒ€oÈ}Þ£Q¿õì:×:ã Òk ìRyÄ{û9×X¿€2b¿J1¼f‹àÝu@ge“¶¿´.½8viX=Õ½gà 8‚®9ƒê.EL3—~óòH<εüH9b`ŸÒ.ìù‰ûÆø<«‚º·—ÁúðîÁŽã‚N̸¯â|ZyA¿¡ç7äºLC->,δ´\ÌúKÃ[ÛïJÇœ#xè ŸS¾iCIw ¨UVñ[ÉPD‚f­bi;‡¢ÂÚðÿ-R9a}®«$ø^íD“ 9åmËX]ê×àY{ì…¾åY¢óóæÆk¢ϲ§Vr2˜¯ÔVU±Ð?݃“gN£'ͳ¾È…X‰£ŽVàh>rà°®£ëhŽVàÃ+ ^£% $èrÎ?}íe2OÈXŰ(ÐSðØs°ÈK%ñèTÅRýó¯y>¶1ù¡q†LÅ3+ݦA«R(¢ao¹C3‰>î•9”>¾”ÑHÑUùwü:UœÍ\TÙ ‡Ut¿=?«2oÙ¼×^ü.Ž›ÌyŒˆqK§ZÞ{‹~ß'0|Í8{) ܽÎr'µ ¹™Äå87ºq¨T£áªânÖŒÑùkf¶wBjdkèO‘aýÆk?IW(Ÿ©aÏ«iœó:ˆ$Íz¸}c "£Z»ô¶„§F£G'q ¤óÎ/§ç¿òõôÎóñ‘Ñw‡¨~±77EéËåTÓÙž,íÞPeù©Ò œ1j×ÞC™Œgî×Ö}c5ÌMJã‘͘Ÿ7š ¶æÒH?=ã[O`„ä§žžn z˳•§.Ökæ?OC÷ï§€ñ]z]®X·t…¡qó]Jh–Û3¶ûÔ²À(ÍÔœ,ßí#È`9-ŒO¢œÛKžHúÍ|JS‘iyŸ½`ýÉÂÈY›Hƒ89Ëqˆà@92ÿЕGq ÑÓ“ð“#Ãdç¹»© õäôÎEJuçϧ…Éá4ôîOÒPÃÙtüáGSGwO”ú\¡éóÌÌxºIï…YiDToáZems&*¡³\ #3¤ÞyûíôÓ¿ŸþîüŸ§çìõeŽ ÓŒ°UAsÐË”NkjïNõlè<eeÍd—™Q÷Ë·~ÎyÁ!‰¡Ë2CÇ”~ä -ÐgY¿Ý—:qaì RîBúF:<3:ñ*j)5Ž£Ã×4Hí¹'¡óA‡W †Ze]uššÄÁI®ñ¨!¹—OùÀÚ†tþü…8»¨Ž¹í-2šžyöÙZÚè?¸v=Mô°ÅXZɆŒóg$-Î/ŽÁïÒ£Ž –dÕém6 &.¼^ábr¾Ç,ÏÏ:F±Æ£QÜŽÛì’Zֈ̷&Ü뜣\² 8z)wp™{à\2Ú~“g¡öƒ®¦û pü0è!‡gn®R*Ѷã¡çÓ‰³—ÂЖÈOäo@È+ ¶ùËxäáë~×)ãyógïãåÏ~É“*-SžSHvÌúh*ØՉ`zEu@-µrq¼¹v:¢g!?ëÄØ×é„S@'ì*ët ó>[F¹ö5³•Y¿î}ß}>kê—YV"ã‰uÖ¹ @Ê^™y¢£ÀçÕ”–H23Þ—ýúÂ}Ìæ4Š=&zŽŽ³vw:cm¸£ä óôGA1[jXJÑ5Ìúßyþ¦“ï5ÇÈ|ÓéÆšää³@TÓP˜êɼ™Ÿ¤?1ŸÓq«SUšnììe*ôy®,¡‡=%¡ã6øTr–ºÑ[Æs§¿-9ŸS_oEð¨;ƒwàsf‚3¿u2¥:»{"xFÞ™•jNéo¾ÎÏTs€7™‰ÒK‹ýïttŽ ¦Wø=2[§Üê§JDKdhzn¥7K Þ¾q-Îøó_ú*óÔ1cfŒ õ>¼m%½÷ó7ÒqªƒL¹ƒSñ³TèhRƒlb4æ.e¥ªÍ¬­ G>š5Vq~WS}äâSÏ¥‡©`¥²Ñ,iéËèÐ{ž½üXôBº€w)-7pIÁÛpŒàöX[^O§ÏŸâgyë‘ úš5XRŠ£*ß`H81¡§"xS²Èr’f“çíS~[pºkljýu-rå•_ƒ Œz@NÂø·)ósì/Ž~$êà…‰èˆ[$›Ììi3éÃyûÀ¹)mmÅXd­³>ž7Ï•d÷/»tA^ ë\À–üì&žÂaxYNSy’üÛìå%ªÈË,ƒ® :ô"­«¿›nÉÙ HN©§w7õ?UÌz3»grb,ži†/Aq€8£Ã#A[ºðrŽìÎ)¿•§® å•òÌÞxŸº‹úÖr±®Ž*8Œ×Êê…œ9éÅÀÏàØèHêîé%P+ë•®¾”éMð>rm¼—ã·ºKÈ‹kãö™þM>Î}x…Eƒ:´y ²ð3Þ›˜ÌÇù'&ÿæóÎO±Ù/ò]ßÕ¶ÌÍNǼÌ>›Š÷vv÷Â(ý´‘ñ˜±·ÙàþH{3A“‚Õ€Pî‘:VVÒ˜À$— "ƒ·T4T!=¢Õ‰}5-£«#^¾sÒ)c–ä…ê"ÒŽ²Çßq·„Ø‚œ²ò €×?òAí?”©p4héíJªýȘL8¡-›nV¦r&õdÉ¥kAyÈÍhÈ6<ÑÒê:㥶gé«ê˜¬ÈaJ³ðŠË'ïSW°<«@‡ò/£1¤zë,ÅUòí£· Œ¦¾k×à Yƒ"ø˜6k9uÅÈ'²?}åUÎA~ºøÈcíœ"ûh•àœ_R ¦.]8{<ôºÖJ ßV,ž' Î4ôÁÙv ƒG2^y…g×ö-ÎYÚ±d®B%ªåC:•OfêåäS6>úrK3\’ê Ê{³Á¬(RAË› è³õôòYÆ„žÍ³×úÊ©=G¥´ÏÙ¢mÊNâf²¿¥‡2ÖO°Æ C€Z§l3Wå²ü[ùä‡Cž}ó»2I9œ‹S^g¹ó³tý ó°‡ë· c0§ *€îÇ!y`©Z[G ¾4׎qµJ^³Þ;ÐN)zk)è=£+)3_]•¦ç)mÓ¢¾ßbÜ–n¬)Ýñvÿ£Îx¢gsõ8L¸ò<¦ó_ B]Í!÷Ã2öê ÒŠûbz9)í½ dÁgÔk?2ûñ+p¶ÓíAú®’ÝŒNâ»íF T=w²Žgì¤;ÃðRÖÖ@Ëèªó˜e¨Œ9Þ^ò¼t‰; ¢ ÒC¯èJÞËÌò þ&9I3sèúKœ&l-ç“óTZÅì™'T`;²ƒµ7£½1ªS´5W‘Á[‚>LÙþUÛæßï pÌ—®<Œ^‡1‚œÌÃîs/_ûñ·Ò·þÉŸH‚©öÙyæ,ÙàcT@è -Íyt„þôÚËß%¥Ÿy T P¤`嘌׺Ž3² å©ÌK}½^584@eÙïÇàsÈZt 2D×VÖÒ:¥¾wÙGyY :Ž—½ì•Ùêü›VÈAw®,<ÌIwïÝI?¢ÅÄÏÞ|ƒvï`³NÆgÿù³ö§‡?þêû™³'ÓÀí;”ÿ¥2V#˯GOU.zV3°Í³+`š^ð3Îjòjm ?×ÊÒ .•©¨e€¥û#øï8++©†<ÔÓfÙTÎä©-ÇìŠlß{wîòZèIoÒ¤[kV°û!xäÕNFoÙ@'pjìKçw 'eA¹Ê°`ÐLÆoB1‰Ï~¬âÃÙ;Õ]# Û¤@@+»”,ÕB 7å÷"¼Ñ±hgå ó—.xX‡½A)}ª Ü¹s‹Ï-È Böpã#쎉^­–ÉËàÝ!{©"RIe³Ç¥#ÃŽ¬Ä°ŽµIÕ³UöÜŒmù›:‡à^!AŒy k©;«7h PsÒòÅÒØ®Ç.cÜg?‹ y§¶ƒ²F› êB.pŒ Ö\ΩçP~$— æÏ ½eàŠàô Ó'`Åc³Õí×må«÷ä¨SÐsÉàÙªz¡s—çíS5M½KSÞî cÈ£¤;çìÙq\±pœÏ´2Æc®ï$¸÷Uзr•:…¼"ì.æo@Œ•×V±ùVVleЉ èê!p>èúÈùH¬½U 4áæèÐÒÚÁÖç.\@6Sþ[ÃÞ.Tx¦¦mn°•:™Ÿóµ õhb{›rþc‹«Ýe}hïTRM`Wg:ÕM¹õ|ªîá«°RƒÙÆO<©ì2P{YÁ3¤?ÛPm¦î†lG—e>>ÇÊž#+´RUd—§¾[÷¨¸5su•á…ÈÖÊV¬—ÿ¡8Öƒ22}N}ÔuóüZõÐv„úx¢‡¤-vƒ³-8Q.–˜ËÊ“d·#/œ_Êl,Ì ki'tüÞÏz[סa@O|‡GÆ-Ñõ••Ò‘Õ7w•ýQq€õwœêQ~ÞIUWÉx£ƒ2ð+?5TQq¬†3˜C-:Ö>¾ ƒá¥½Bô»A/´:\Ø#0[ÙÔW¶à¥*xëjS5kë¸Váÿ³ô·ÏãŒeoèç<Óu´ÂRSS]Òš‘^ƒ¾tÏÙ’n2_‚É)€îì›”žçè:t´ƒ r]åpÎ>ú ÅÌÉ2ô-Ø»®µûn0§‹§8ÐYŠ£ëhþ²5]G+p´¿½*h:Ó,Qfv•Žëßþ£pî½óî/ÒØØO¢œ ÙI»”éë¢d´Ž—,³äèdýöнò7a¤\•-AN…*k:c5^t:^ª¼Aå¿úáð/¿þ]å9Œä_ù¯üM#Åó§ÁêíUìótÀªj¨ rãߺ@eÞ4ˆG‡È}ÿÝ·){u"@»*€ø–¶Žp`JÛ‡ØR¦ ‘°J¤rtK{Y uyq–¿id5ã 0¿³³€¼£¬!ÊüÚïTEÕÌL•ñ‰Ñ±-q)¸79FöÔâ\8Ù5²—q5·v’éÛNùJDOŽrNv×öSæŒ=Pù×ð1x@…VÀ¶ç}€qŠ ¾Ï¬ŒŽ’íB–0QS#ן~£òÌ2à2éö­›á”oë9å½,Wˆï@o:¢hUú§†GRñ¥£pÕPHI9A6¨R@Îû·o’!ñÎuJ©b¬º7»Ë–²{û§/£à7¥æšÊ´>zƒy3œYãC³ifl ‡/€ŽMiÈñkˆUÙÃC·D0´€LŸÒü4…!¸Šq~þòXíÔ¸O}×Þ #qŽlÏ÷Þ~“õ%±¬0Ðw3uW䤞'Joõµ¼¼ÆØšROÕÉÔÏßFïá(£Ï_e”®¦ß:ƒ= ÆŠú þ>”Nž?¯œå‰4‚ƒêSO†ÒzÅdàã@à9Ç€ú¹8²Ì0/ÁÑR =˜åoéÕÖ¶vhŽ^̯þ°àJG³~§.?BâÕ4|ïŽÜ´ }5ž›é³ƒsIೃß}ØàKc1/aœ=½”ìŒÓ‘Z‚!­a$é¼ éꢧ.ÆyßÝû­8!ÝX¥{¶Áñœ¸Îe€…f~=­3äo}ý÷¡ŽœôU.^z(¡3gÏRf¸‘Œõ;Ñcú+¿ó»8»è3Ç^ ˆ˜ñ¨£ü84¸3a‘^Íeô]³Ôös7#@ç±²BÏ'γÁí«jy1³¦Šqè˜í`T|÷jíhK3ЃƤÞ=¾4FWæöÒ:À›ÙJe‚*8<«žïúÃë 0ƒ©`Vš5+¿ðh£篎áú`WB_æ?ò,ÁÏ0œýçc\ò?ïázJû¿yùRWàLóÞ®ÕÎΠ?½á3˜1qïÆ{d©ÛsuÐaÏž­màdÐÍúilëPÛ•8°\Ö PœÈö]è@Ð@ãÖ1ètðŒÉotúÈÄëñ7>Ÿ‡Ã"ç‡Wù)öv:(jˆÒ€:œ“`’—{ª3Ç/¯pPÄ=u\Èfy´êñw˪B»üN.žë¾œÒ{XªWGœ_f¨ëH6ˆPĬvA*A'u=ËtzÞ²}ÀñŽÜ’¸ÿ¡‹ýëÿÛ;Âøßì/¥‚{®AŒ‹3â>Ì!Í–‘^ºzŽ1rÛÍÐî€×ºu¢«ëà•·¿®f»d_³ôv= @²_ªÙ‡DV ?;n ß̶•®œ ¤sÔÅë/V|ÙC†øak.ÇážlÂ7oXœP ‚ÙàK¾äÚXºv›¾µ°öõÍ!# iB¸tù2Õ,î„þ”ƒ£u¹°°¸–N¤éšµç ÒZÑB¾ZȦ± -.Ùµ ó8æm†•lÛ0e2Ƚöw÷Þ9¡‡l1ƒa”ËÃã3œYªs°ÊæÕ-²‹ÉˆÒ+ ‘}8¡sĉҳ<7«4‘e­ Ðlm²Zœÿ îméÞÙE‚U˜7˺¸rÅlbÇi•å’¼Kú5¸Ã@“¦+=Q±ƒ}’'˜™ïœ 00ÀÆö9òK„ ÏÊæaF–s›žGoÄ¡î%è¨þ¿'@[]%Ø÷DÉYÆæ3¥ Êþwų6¹§€Óô/è^­mœ7*ˆÀÿ-‘lŸjy±û.w],­Üð>ËðŽm‚À…à‚1NAŸ%­^£lÌ*"`Ò =A…Ùx)ଶû¾:ì7 ‘],—ù+"óž}4cy‡,PKáæâÐ>üŒ2c‘Òα×|>2v…kå¹…I±Êxù/Á!Üˬ}°€·¥X+×]ÝÛ5¨­#8†÷î±ïê(fé¹Ç.¢À´ú›{¹¼ XÈFö1=ëÉ2öü(ë•U 8›ýÜ@ Ç8=c;²eÞ'ïEö2=Ñÿ›lsW„!áï 6¹ïY#° ´ ‹õáEuVÇ]HPŽVžãÁy¬§Aþ=Ÿà8¼·73ø#¦ÅaSñ>å«úªãÄwm 8qMÊKòS=繌5¬W‡\ßZu+гöjî½´)Ý›‘ï8#(…q{>s8ŒÊHï½C)³ý=GŽ)*°w®Í.ôce"«Ax¾kñ“í¤™•Öç>ŒÏ±e:!ŸaÌþMºóì©óGeƱ‰ŸË½PNzÎýâ–¨>—¢ïípÎô_ZAÌq””P »1ªˆÅ®ýs´G+p´G+p´ŸtfE‚Ÿyþs¡ØÜ¹Ó@œ)ï’ÍWŠAw¬·A´†"QΰPæÕ$®£ø´è‚¡ÀY^ά17I8ÁìDñóTn-©#Pg™_‡W‡— ™K¦<¾úßUì2×jöš (‡- pA!³qüÒˆìÀh€Õ€FÃû‹ñ“ÏòÙfÑUŒ\~âi’PRms;ãŸL7®¾Ÿ¾ú{;Às•j3¡-³­SE;êfºTP–ɬìÎîîP~ßûÙ‹©äüCQ~άq{[¦\cеäèÄ l¤½Nm×áÚÝÛYè½VÇqº›…¦c«÷øé(ù%˜¾@Égbû”«[NTDeoUjÁ±}õ—¿ÀQÿ>s²„^'ýô^O>ùL8Ñ«Û{Œ3Þ œÒͱW;8ìò0ŒZ&ÃÒþP•8kKÙÃÙÉ1 j@UCcc£I»½@où†0ÒpVoQ’‹õ7Ø`dh;2™ìÛ‰ÅBÆØ}·ÚÀ¶D & †öjZ 3Ý##Þèüú3›:Cp@-—Öêãi0ÛìøÀNÍVJ7»Ÿ·É`_ÄS€ÕRX]o¼úrdÏÏá´d¯hoN SƒD¼¯¦ÛWëÓ…+¥¾ÞIo¿ôÃÔÞl <úLÓúÎÍ÷Éð"›àhyn*Ö¯«¡­«=@ë]Œµ"z<÷œ¿€ƒ`pÍL›JÀàG¿öï<•¦ ²NÌ,=t˜ ÷÷§ûwîâ£\%ÌŒÀ7û¹²²&5´¶é8‚¶M²V²4ê"J¼2õÍôÅa°µ2Ÿ>û¥Gqví¤÷ß¼gLPXCHú=À!kYy3±Nï¡3 CÀ[ƒm{cÀ®3?u:JW‹q>ÔR.pjŒÞ§8-íUX…µBà‚%‹›éÉ\  #l‘rže´4&Íp40EG ¥gÍxiok×{È›¡Cq}v¾…#kp ?îs²ýDšœ›IM–ӭÀ!¨ ‡„Ùñ–y´ƒ´­£Ø’m:sKq".R꿜 2öÙ K¤Véðà}5œƒ5 KË·Í‘  3/Çhtžï¾xŸ=£ÜÝ]KhQŠÜeŸK(£¶£c~l.µÖÑ_™ò:°,Göa¾ä\>É¥<÷ó¾Ç‡?¯“X~ämó1èçém8»L6 kÚÙ{ GSaº}Ý  ºpºËºä­:¾¶8syÌÛ^tÅÅüŒU¿É{ÛqR>sžcO´m²ŠkY«Mæ²È—Á.:]òq”è‘]8<á5E:@‹Î4ƒaä?^=0“Œ3ŸCž©ó]g´ó³ad…ÿ$sÎÔé\fäå~NÀ¤Ùß,@JžçÛî{nFþ:õL¨ ‡¤™ˆV ˜"8æáà-o¿þú™“SX=ßspÐmm͇£†öFäçn,˜0WÇmÆk:>2˜&î߃–é'Ëèx7ýõ7Þ¡”~ej¸Oöå/-«856LIõÞàG"ÅyÃñ"hi°£ÀkÕBŠÊn P0,“1f ™U·¶±šœL'Ï=¯_‹öfÍ6·¶½/Ïö³‡5qùÞ&€k¥CR:¼àj)ðnøÿöš²ü¹™ŠKœÕT© µìžNtiÊ@%`[[Óûo½’vjÈjœ…׆–2‡‹=Ã3«×É0룔ü`”ˆv?/õWå³­Dœs{w7ç[sÇé„6Ó¡¿ÿ^¹;”zOžÂ” ºp9ÓËu÷>Îiý@G§.Kó4#`ä\/–7„ªÿäÃkÙëöÈV"[žA9rÞ× G¡úú,3ÔÕŸzz{¹åÁ9~y†uúº/‚3ìYMÂŒ.Ó–V7û\Çe8™Yçëg‡cü8ßý¼4 ˜àÚ»®…ß•Ë:Ç•ûÈOÏ–™3fþ5ù3Ïõó^ì"Nž»Û›÷Sk{{ÜÏuô\ ííÑ?^'? žesÔÍöp:³Í^ð\‡0À؃wÌÏÉ] §·eúuâsqÁrf¡‰mžíXŠIàEö—þ·áÝÎË õ6Þ–îM̦g?{–‹c”}¾ŸfB¹ BNÊ)À´åÔÐ_—Μ=­07èÌ9ËKtî.R¾9Yïr™‹÷ß¶½ ã‰ò¥<$œéL"*1q®sáöÈÌÍ¿/ ·ÒKYú0 Û{ØFFÇnœ)þ,%f)oAÃNƒƒƒió–¥Xi߃,ÆßçÕ2öÎËE1S×Rôf&{ž]ù‚Yn¸ÊÅry–Õ(ä¾îµÎbõÝJøW1_ò þ€³7û®~á8•c€ÙÅÐ÷”îHÔÍÖгdê„‚ãfqšñVÎwuð>3>Co[FQ…LdS)ëâ3Š)oõíí?¤Ç®CÙÌPƒ |Î!¥ãˆöäÊ3—@‘hÜ ‰åI šã¼7Ëšçc {WVY_2½ŠÕkg9ºšºPðbö¯ ¯„ NÀtôRäó. hÒ`Çæ: ›™<^m_^u5×`Kà×s©z à€ p^;Û¯ÎÚüÔD«ƒp9j<0‹½qŸ-A»F»Žƒ8ûVJ `³¥Iÿ6ãÄu­0`?‹ØOum‘åËï@Â=å)Žßrü‚‚3~¡;4c‰m@ ’‰2¹ŒÜiJÇÛ¹ÖQÆ2°Ö `õY£`ˆúM¦‡t¦iK(Ú”Ö´Aw÷ …YÀ=UBCluÀvÇù60qàö=Ú­Ì!{°Óxæ>Úžs×wuÝJ?{ÀïNß«M­ÈÞr2ïÍt-bÞ_žë¾ Ì{Hƒü,øãR·¶Í–ç¹” @µØ•ÓØ7S³ËéÔé“‘IhÛ$ÈGã^òh?£N ½]Xåù„‘F;:šC6(¿º žµ½ŽÕ6T¥ËúÇk?¤dF©ç<€ÆiÀ›¶©¼º¹\ÅõT5Ô–˜ÍÖù-ï!`nòAonPºsÛ¤FÝ+ËÇ—•#;°›zhëÕDõ«uYÚ[0uƒunÃ66ûײì}7®¦.‚ˆâ2B¶vÞW>m…Á+åræ7 ýòɽñ=Ž¿„¿ÉgüÝ1¹g¿º—Ûþ±/žÍ=\`?_Eû§%ÖÎqB[ê•ìÏæD@p¼@}i;Ï #Žu{Ï`ÊIñ¤3I  @®BOôß-  x+=Ý×KRkCej@3€ òÌ^ù‚Ä®/ÂÏÔÆQ‹}±îG 6žA.3S´(al&í0,*ý4‡®¨Þ¢n°h.ÈÌGYSxàö&•"¤ùÊ4fü”sÖ–•W &ÚJÅ —Ý\>OÌ+0x³8Ê Ëi¬¥Ml°ˆ}Í Ú°â†gAÞÆÍX/,UîíZسºñš6’ò“CsÎE§ß\Ê2cÔ–¦XkíW+uÊ:þ™yÊ’Äå:…,GŽY>]0R=%ztó³còýIÁLX1«ÍP ½ÎÏFeö/€¶|*f¸ï!_µ½rø¬¥°7 0çÓúˆ¼_Óòû*Ap[ðù³áê×~Þ³k…θÏpÿö)—ºÎ[7fy~îjè)ò³Ú5méÿBlwmSIL}E&Ïõ,ûšë¡|Ïg?rr²öQÆYå}ÁïOôgm•aie¥ü™#¯DµÈ så½=qbªÑž#é8 ]y˜»½Ïøˆfž±ÐAÐ K°' ÓÆWïqZšª¡É­4FОúò~µ‚ºøP£nÐ~¶«€?é÷ðR‡²Ò†À²çÀ,rù085cÍt ïiP„º¯Á,òsý…–CºÂB4p@é­ýÄ&ÀóÿlLÊ?Ïæq–´ðå±§<ÓϹ‘(ÃÚxæ•s¼•ñp/ôHíaDÌ·‡ºüQÐz ÝJˆ ~xE`s·ÅÏ-F¾ªohÏÑzqlÝ›µt‘q.]ƒJÊ´‡®îçðy¹f&˜—(k€²ÅϢ߸7êLÒ‹Š¢Jkê „s8Cop*òõÏé*ú¨•8sóàOÌG?ÓZ~ªÆQ™ýs´G+p´G+p´Ÿp¸\dYD½ßuVŽÐËXpBGÂγc'ާ3gΉ:Î$‚Kåáè:Z¿+©Ü–›-Ã`£ä.4n†°Jò`Ù:=ZO»`¯Î`#¿uœHëAóLSgŠL88xÝÈåCýÐ>\ßÌNתë8¼‡aµXƒU4˜u‡BéÙúË¿«ìjÀ¬ ÍÁÅ–2 1'õ{(²5´<×f.Õ’A™ºOœAaÞ,İ%«p×rŽf„÷ž¾ß-a§ñ\G_sçæÜ-±k&¸y#Ã16Áˆ§Ÿûldeë°¾J‰½[ïÿœqmfï6”58:ç€ð³:),Óîüjd\3A·*Ãå%Ë’N“©uçÄ\ôénO½½½é¹Ï}‘Ò£ÃôÓžL·‡îâí:q–R¿5´šè ¥lôÝÛénßá\Ý£|ýì4ëµçQ÷É’ u‰ÒçÇzºe+wèÝ^ÊÚ²m½'›(KËš8ö†ú$a˜Žß#›ªµà‚žcöÞÍ)®Jíç ÅÞ¬Ìã§Ï¤þ{wÓ ÷ï¦ å¶€’~ÈÆÞÅ1šzvàfÚ]£äZe]zè©Ï®ßlñêÍ¥4=|ça>™¡×±_|ÈXdÔe8ñò÷¶Rß»oaÚ«°4ÿôsôx';š,ÂQ«ó­ŽRÈÇOŸO¿ü9=äqx”R^[[nuèö œ¬8¶GFFR=Ë.<ú8 ­8VÒ•gèÓ‡cc…‹ÅTU[øy5½ÿÆO0q¢c„¯QfRãiŽ¬çŸ½ùëY›NÁÿ­Jð0ci°«f?Ú»{q"QOÇŽsÇɶ/¥,=ÓË«ÂpHØ| YÇÁÂÚ´UBùå2"1îÈ|z`hÏ“‰UŽUOøu ¹Eö=÷t/%1öØ{&ÎQ¢¾4ÞÒд†œÙ3–ÌD²°øçΜ"æDÈ4š=úXº{ë&ôG¯Ã*0´ä ­AŽ -DÌÀÌ!²¾”hlKþ²ÞÒ|AîdЬŽƒÌ^ÂL^ ³¬¡ƒCŽWR®3T‡ È.%,™×—Pž Ðu N@a…ùïnáǰÜ,l‘‡H7k{«éÔ_ pE§×â•Gñd`ßâŒÆ=šÊšN¥‚ú¹A#^Á{â'~æû¡h>d%‡¼ïð»oõç_]‡oôþðá_5F;pž}òùôƒoüC¨„²h'Ï¥±[ïºaþÿÓï§–c—pvgQô:%tT(’Ct|”ÛãµBø–Ž„]øŸeòæ°1û¯gjÝÄXæþŒ€’æåtþêÌôl–à$Ø&Å ï›”Ô ¯&hA§ñòú"ô—e~x1Ïø'®tl„óƒ‰:7õ¿>^: utº®­ÎŒ4ìYNpÃäÎsy€o)g¶®± ~³碣³›^kgÓ¿ýM‚ÌÁÉȺ˜)ÐÒÞY‚yeI;ümlx$u_ì…oR*ó:|V;l”Ã+‹y–Ù´Ðãs_|@œ¾rKÐ"åõêêÉúÂq}íƒ_tr.*_X ´ þ%ð*%üüg?M×®¿ŸN"¹w(Å®S¦ÓÞæÒ—€öó_ût>ÊÏÿ ÎòP€·–æpfŒsoš»Nâ諈`2ï>Û~ :wlç!(-“»IÐU#u‚äVéPv5¶´¥Û7¯Â_·sÉs sÇ}ÕÁÞÂß ÐQ©Œ ÙÏq> >û/>øG™}œ¡ û0Ê/*)5(}ÖÑ™Ïû]ÿ3]NoüäÇñ³²4sDx!“ͪ g2ë#(ñQ—k§ž1‹SÞ>å¹Ðcc+ÁQ¼vûæµtéáÇ)ÕÛÄ:sžp â  @[ÁÃ×ÌR6£ÕA5℞œM#ñ¸&×/”ÌÍþfÁIè–„¸O?ÝËAÏsÓã©çÄ)Ö &œü‚\‡úcõ|xéÌöCWgѹ؆.`Ÿ„÷÷¼¨_™‰ï3å­:¼#—±n“õŽp÷^,-|¢‹{˜És€ ð¿Xdná}t·p¶ÌÚd ‡åz•Vá1c3zŸCËf® &׳ú­7˜Á<‚Îò8A +É?\‹ —& p9ÓÙx–ì°üxs±*JÐÈDb]¢Ô,òJ™UL%þ6ꈮKUå‰,èúihé G²z†ÎOüóéνáXšÚªüÔÙF¥žc)c«l‘1:˜mF\K[;Y€dîR†±¼C§µ¡~™ÉG¡1ò­Ð¿vi«“Þßáðáà´d¶gMw‡Jf;–ÌšÉÑ 2‚À˜ÖæäÁ%ÛéÞÐXš$ˆO>#] Z»×îJ5º½<×2Ó9´ÐÈ‚S3-^ìÞuʆƒ–àDH‡µã¹¬k§Ó6üãŒC PšqlŽÑ3ì:.Àóss§Ñ› pD»®Ò±™±V#1@É+ÊØ"÷tµ{\ àuŸŸeÓ±lì™ _.÷—"™Õq²ò´Ø È@e[Ù8Çp”ƒÁ 8ANAHî¿òWþüüïtŒ_çµÙ…yT3àyêP¶ûð²Üpd®37(<¸fàí p És]Ái×Yº²·mÈS̤t=Æb ‘íMìã½M°ke%ò±°ˆÀäâóðÛê¬AGÒ°c´0Ü/ÆSˆ.êFd##à&s0›]E9mè*3!ãLfë<™ßÝ+çiEÇYH †ã2›QÚžÁ;¸¯%‡Ítˉ Ç[Eà™s—¬P  &íZ‰eieƒ5gߨ3ϦA‚m¶T0ÈE»'Ó)Sâ½òè:ʬ«ÓXeB@_^¢,âÑàp}w_^ðõ(æÃ}Í”Íþ]·ÞbûÒNOŒPk0)„R6¡«IWÃÒže§Hf>ò—Ù‚¾3;ÛÛés xêyà|:ny»ë ¿ûšü-‡ñxÊ«S[I½ “ƒÙx¼§z”ã“^½‡¶tñsFÜí“ýãG |Ž QxX16·v™ë¶„mZÅÙ÷LðVTœ6’ÁyCƒýØÞƒ4T‡n îë½¢\54 ^/Ÿó²Òˆz£mGr8+ ]õ#çª)HèçäY~Æyí1&m]f=K•Œ‹Â[È5Ö<mÔVÐfáOðQþf« ìq/ƒ=º{Ú¼±U¹¿6š<Ï3#qyŽ|–ki 1évÐÎl[ç±n”HÞ^ôW…=­º'eèRJõ­[ ‚{{€„ÎÛssxN£’Lx…œbl‚À„Ú„ì6p?Æ92èC^/°_\ˆ¬à&~”%ñ˽Èx#ÏdÜêIÒ¨Aß¼× qꎂ‚Ò‡€¯òq“Jn·Ÿqœ^Yp5ûf:8_ ‹´VP.±æÊ:°´mì_Az@¹¶–ö»ÜŸ-öÂqÄ5ËþëƒÐ_`ð‘ëÁfÐŒ~:AüÍTÀäºi·È«JXǘ¿çZpæY?«áYÑŠwÚ>ꑞ;y¶6†ôé‰Í᜛lá{êëmû…=oPôýÞÛ o/ŸÕVr©2h›²C=0 Ëk¬.BkcÀ_ AZfH;'ù³²F¹ÁL-„X÷gÿ”A¬)+öƒ† $±]r–1kskö»¯œ¶åÎÕJô½P "è{9õêÃç¹GÊ–Ú+äÄ:s^ 8’)±Ÿ Ìð'ý;[õH³êYëÖ€×m£aÅÓvw­h¢YìÃ:eÚ¥Ð#Ø‹¬mFÐ0t“p×ùŒúÅ4›«î¢›ƒùñð®]Æ/ Dà kçØ=×Ûè {ØÏqñ×ÄÖhЙ4s(7Ö¨„ôà]Ù{þ=Z£8Z£8ZO² $/\ý÷…-Éc99œ~(*×*™uFs³3ñ^…àÑu´“V@ƒNeX~LeX‹No•{#7ÈØ;ÙháÌB©S96»L@YET¼Z«†ŠcIÏ„?ž ?§a/Xâ3ÍfV¹ö °œ÷¢ûÅÏ~Æ÷{~>~Q+D™ÔÈÑd¹Of¿tu¶§!Ê«Åsê,Ç~é‘O…²n&Ž%3½8¿ûo£4›5ÇkDÄb€¶µ4¤gžy†^ÛôÎFI­#’^ bmm…2½'2£’,%#xèU,ˆ©ÃCÒ¹ `0Ÿ½p‘þœ´ƒèrô”‡ŸŠ±¶utGä4}níYý]Œ0_žŸ ÜRNºrù\ª©o0z‚{×¥qx0˜vôÑ,:w†y=ñO?ý,rYÿZõu\TZÆ9(㚯-‘TMÉëÖtöÜÉTK¿^ çúÞœ"îcEd9ëX¼w“¿=†Ø»Z[”üo$ õÞ“g?ªèÛ A[{;™žôA%kÁ²ö‹dœëx¿òøÓìó~,R>³áJà@)}ÅpÄi°ó¼“=€Ÿ”ßë»}?•ádÉÃ5Eæúñãt%€«Ï|éw_ºÃIq™Ã°87•j¾Ö’ŠþðßMÝÁh¸ë8·È&‚)Á˜ä18‡!ŠA+¯Ò!tÈ{tÇ»ãC¼‡³,oÌ>âìù¾xÝÿ ÛO]H—>ÿ·É¶+ÇÑ1–žkÿ½tòô@Žòô÷þÓÿ>½ûú Ì€W{ÕгÎr×° \‡@àÍ‘ÿåTGØ"ëÌì–Ý5 ðǧžý|*¿ÍÔÓŒ#È})ÈÓ‰¡³#˜ >Káʳ‹È.°:HcsW*Ç)›ÐØØšrKÛàCó¡»õëW6g_3³ç/æ«¡Î4yžüWgPö¯wþ^|øln%+ŠsK’GZÁ¹µ4·BI#½Cºq&œ:{‘ÒãmèZ9ãs¿óéõW_I‹óÓð²([YYƒó;×Jð{Î@Y]Mêm¬$ ˆ€ö²ª–8)&è§.À†Ÿv`Ím” Æä†7¶ÃϪjÈÁqàwŸç•ËìÕ¿âå:ÄÛdJ€ÿgå%Ý[ËÑ Tå›\çïgÌœ/ƒÿ¦UÜò\y~6p.úžt†©Éqt>³ÑÈêD!÷-+©€?E53»ulF_jöÏ502£X‡ª_1TÐ7Ù=•NŒDnè ?yêr¹:Ý»u+=tþ!½{éƒëWÑ?–"otr!ýÑýzTWzóõWYë± /³ 0mki$H³‰Lú–à‹³Ó” Yi0б£P³æ¶ì§ì«­iÌØ\ÄÙ…í…¾`!Hl‡²,«NC¹ieÀnY„:®I‹3Ÿçý÷GY3ë,PÆÞ¯:ä›)!Šù™í5€Þ´85–îÝB;KÉú |^í¦™§·Ä‰ÅB_8Á<-¹m u÷Ýy»‹sâøVòÈÜ„¶¢„/Z¾÷ªñæì»`³A<{ŒI4—’àži36}¯gLcG·Y’•È’ŽÎî8‡wïßçÜΣ#»V£@þRooc0–ò*‚FÃZö©Ä`@•ñV¹pŒÒ¬ó3@!dkîÏ¡òšiÿ䃶*à—_¯¸¸ÿk¨3ж)䪼Ǡ 3Ò¥Aù¶lH>VØ.£M®Í¦œUÖê~÷3ò‘z‚×=o¾rP™ÉµíÕý Htß'¼ñgY¸þúÉ/ÈÿÜ&ôóuÖG;e ýÅójV¬Ì)G=W ºbŒ¶¡`+ x\ (J]+Ÿ²ìÓñx²ìX‚ä NCå —p~ p]«ÐõÊo¬4 ;÷ußå?¶°"Oáïym w °|þ¶E0¹À¢¶¬û½¿Ã,kר˜HK\G¶,A¹e¼¨ã¸" Äûqî ÷qRòBrAôÜ[«2¬#‡Ô#ä<³æyÒ)‚ù ÊL8PΪO[Ìêaö¹>¹û†é‚\?˜ƒÝ. î«g óäÕòTy¿û©,VÛX#°Õà3¾C§ä>úC˜0ã…rƒ­,y/Ð[}³Éû bÍl:x3÷‘Äå϶â1pÛ¿ùÁJnòÊskˉh÷€œU~ª núšsÓ_" ,o—géóuñ<ƒ,³·½ëèC³`e»üÝßwòHŽ¢@ªµäX{î5Ï™€4rÛñÞìsÎBQ`_@;|Õ'áö&†ÊµÌÿÒŠ6Ç|=áÚ®gð»ö`ô$gM¬Lá~™€ÓÜCÿuÚÜÌ1Ê÷<×Ò‹— °qÍÕßbŒÏyG%!ÖËÀ e¿>:ì>ü‚Šð}ù÷`!¯ êÉvßÙG—nßàqÆÍNgËÂ9`Þê&~>‚dðe0Ÿ#XìÚ¢÷_ú 3»½¡ûæýœ·6»k¿IÀÜ:¼a ]çþè6Qyž©ìW2Ȳ–.ú/#p•1²ýŒúågǤ-!/ŽJè—p^ô&fߘkgZ=ÏlzëéMõ9Ê÷7§ïs~ -^÷Œd~V_B>±/ž§²€ü̧½)>Ç= d$Γ±Àï…Å’rߣëhŽVàhŽVàhþ•V *‰š®]»š¾õÍ?gªJ¥U¥ÃYg×þþ|ºöþ;ô”½c8ëlG×Ñ ükZL%ÿxWY68D¦_{{YöúÀ­ €Ý(ÕžO„öPR Ó)¬’g–ôá¥ÑaY9³¶â#"T¥Ï×qxŽ è}í=UX½²3‡²Îó4’½¦~ÄYâ>*¢ö“^ǘœ e!ޏË>|>2-ƒwÿÎt‚ß5g¢—R(Ú9i 2³Zq  ŒÜ¿†œ,bœ §NŸIç.‘±ˆãh ÇÅ J³Ž‡••¹TCÖÎÀùÎ, Ù0…PÐ 0zˆm¦Üwš©™fÛ€¥:õ0B*ìCË©¼H v -ÖÈò×Ð<ÞX ×yÞFª¦DV Z]cMªyâ©´xæ¥ÎqžÌôãq#z”Ì·rÞ'>O¶.÷> +=qó1љ³W뽋ËÒµE¹”ß[Ì,¨L5k…39”žÖÐ^[˜#² xav‰þÍ8dOÒ @lz0íÐooß`œå4=r£=?u7áÞXNKãÈNö÷‘ˆ#Ãû©'‰ €™™™´ÒÑ‚‡Rdn: Šá›Å«dfÌcà´«,ÂX)¥L. |M~Kê¦w=}-'ªQf4¿™h^D˜¨a ´KéÀwŒ“õÞkgL+ © ‡úúZæ¼5»µ}µt$iê´²\ÛÎìUÖô l¤ÜæÖ ÛèÞòœžmÅb{÷änÌœÛ?襧zBöY´ÌÙdö)£$õØêdœ—õ…IÊoϤ‚=Wy­˜3:áð,`äèˆíèh ã ¤4=óÈYÖ&Qê¾Ç”evWS'ýÚŸ½@Ÿu@VË`ÖÔ”¦mçq~f}ê¥ý‹§zÒg9Íß ù&{òÅÇÎaèá¬\L7ßœ¡ü÷rúþëÿg>AÓdœON¤;oÿ ;÷pÚâÌÿwÿÞ—SÎJ?ÙÝdÌC™Á˜•PÔ0nk.5Uì§ÿèïÿáà´¯[/}kî|vUu}8©5870,mtYîpd€}€8 œËKK»Ç5$)áVFydŒgû§kÄv”èén©bnm¬­‘Ó8ˆp*6pÆt¨—Òÿ–ÞrSC}áüÉÃq^HϫjɆ!Ða¯ô.ô´“ƒˆ½õ?Z:$ XKBâ‰Ç!¬S§„Íût³ïð<˱G Lî•Kù:kfQè81+i‡@ÕöÙõ{]eQúÜžá¯ûi~º>U5”Á¾N+ƒgŸz$?ÓàÚ&ûj€ÏàÖí»d/ÏÌ¥ŽçéQÙUŠàymü\[ûçxg\àg[D™/ÏÓWý³dncÈïв ®žà•Ú&€ {SVB«ñœÇUè|’³…“µzZ]x"Õ¶ŸÊÖ‚õ CLj+ÃYp®ê3:;å¹þæºÅÅïY˜»gI‡Ž3½ðB3X çß×/¦ód|댲]EI9Y&ÜÆÕ«TÔ°$ž­*þàïü]œ#饾MP*µ­]ŠFÖ騄 YL&Bfí½Gvxϱ3)‡Êµ þf„/tv­ Ê1Å K€Yc…×SuC{úêßúCœ£‹Ð4r ÇóÄÈ@O cc§»áËU¥uÌ›ý‡Vr‹+S]Uaš›>Y lÃYZH…„uøæ,{RAF[!ήN2ÔŸú­(hQÌ8€&¬€bFkïÙlX]?p&ò5Bž±Æ–v}C×éÓìGVÎÐà®SÎEFÀÆÚ|zöó_ Œwë²ÁWdttusNá[òÎÎ.Ïmm£G|GO¢V8 u‹ÈZwÏtŒ=úÄÓÁÇáÞ:å«2¹¹TÌêüòu·ýð=~wì¿}ùyÎçH ÓÌÂC9¯ãÒÏø]Ùq8&[/dô§C’ ú0«Å€…@Ž2ò[é5ZÌÜÔ³Ÿkǽ̮ô¬*?În ˆëýôÓŸ\&ˆ•½4Ê`³±] AKuŽeèv§¾S1PÏJ;udVךշÀÑß«¸½¹‡oô»s4Ë_§_àÆ,Á,­=!Çç)ïIi bA= ©§Çñ^Ù›ešé3ïéöxEÆ¿kë­o' ìÁlfÌ rë0,+§ÏyÎsžsÎÿ,–µb€yu*Ï^A.”&Àôæi™é"gW‰=ðGžH·oó=²^ÉJd½Õ_[:Ò'ÿâÇb_éÐÖ†¤ ºóœ›ÎÉS¶p™x÷­àG¿h¹œ£.ØÖ½{~)sŒRÚ•=¤3Þ3ÉŒ.÷‡mŽû <t—Ë‘±+=ØÃ'Ì?* pSuRˆ@V_wV£.ö ¢ì¦;wÆÓôÕ58;,«<<:ƹ2%·hìC/¬´àzZe‚!r­tAñ`Ü9tb³Õ¯ÌÆeÏta,)#+€ÜÊç¼zïß‚ÖÊ2Ñ…Ìz @z×w®'2†' „zý•WÓ•‡¯Q©îÄÚÒ ‚0^½s'ú)÷ _IO<÷Þ:2Øê×!Ï­ †Ø“¥4<25séÛßz  b%Û›Œ»Àù¢“=J°³¬X¥Lðìè$ƒ{°k†ÖUP 4¸p^:ì•afç±óïÜØmrÆlš(;>u'=üÄsé©'žŠ>Ê_ùêWÐ=ÆÓä îÃÐ É Ë6Ò“ÒåǨ¼Å¹þW.]IS““é~ïä9t_¾>ڸߔGeô0³ ƒ4ªˆà‘®£¾Y]7ÛL11ìÈ:ç7grÃÊXö©Î$%càû®©|ëúMÞ x‚]ƒôðñd;­ ‚nm…µ8{'Ýšáø—¼ºr!l eÃ6AÆÊ{œkΪ¯9J+hg»/Èn‡n©£Ì¬BoeT‡òE}¾•à?ζ üA?jìÍÁ¬"À£ºR; }ª¬¿å³ß!HO›VPJÀ|=îÞøtãÆ”§¥òN½ËîKðß €étj/·Sùi~ò:àI>µu„ž$¿›ééghèØîØ’xøjFNËßα móa&»öÃg‘í½útblü¸¼¸ ¡çÊË4çÚÍ ¹È|àå —°•(±<ø¾£‘ß! ‹iÕ»?•!¬éé«1È~úÆãç÷n:Á1V3¶e3Ùÿ›Û0µ‰üôŒ¸ò\t_0îÚû¾À˜•³îÝ»­PL°‚˜vªû͹tcYõl[*µ=³Ýsf9[Aà4`È*p¸¸›xì&¼·Ë:kk²dØ3Û€å´Zá¬YëØ‘Ðǵ\§REŽñ´#³ °䮲ž{»îd3|ì¬ÜÍÚxpOÖÂÒíÎÕê ò¾Uv È]³^Õٔћx–lZ‰ÙPC/×>6A Œ}®þà4PD I}Òà.õòcô}Ÿo¢ÚçÖ:z5vÿÓäØFo÷ü 6Þð¶òFVõ"³ûÕSBs'ÏÛ…µµÕÓ0ö§¯Y‚ó}>Ÿ².Ž{‘ñg™×T!¼Ï×ú“ŠÜËñyΜ‚Áê@>c]00MŽO^eJ?äŒ µò¯g¸<ម¼öºê¡gÁ.ðºówÜAwnáK½F™¥ÛǦÎU2[I;I?—ã·z‹UL,½<ÃmS ù¥-®®çšË/>SólçÆN %#iÂ.ã§ûõK7€ö›6Ž4½Ï•g¯3 œQàŒg8£À¿8ˆ8¼z)kû¾gŸ¥Ôàt8°ÃaÅxw‚ÒÏ”v<4sÄÞƒþìuFŸNgùÝ Ué_ÿ’¿½Và\ƒFgë*¥ªUôtЛ¹¤§q¥ò¥rnßÜã‚}ˆªá¤ÖAmù5R‰ØD/:¥5tÐè˜óy‚r§ãôïøÇ05ý]£ûtŬ ²xþ1ƇY*yŒîk†™\ò§F¾†¸Ù]òé1`ªtkÆP1‚y§ƒ¥W›¹ëÞȧ:ô0€ÁžÚ;û”k%Ã")Œâ}DX‘ÝË3‹ÐìÃHûŒãhP=‡fà”{#RV­™Y‘q°±:‹‘D45³]#YG‹øÛôœœŸ…È&h#xÀì+ÄmÖl“5ïèë‚0*íyÕÐeæ #`Á“–gx€l¬:íLOúè‘=Ö›KBPz¯2{§— ŽKƒÝi—ïHR<×;sTW™|޲¤¥‘Û ‘é­g á ?ûôÃ8 ÞÙ&:©£\Cüx±Ø!íuöîÉF'à!Á–·Õd4àÆ=ßD–±Î·M€2Y ¹ŽÑðÆ­wo’åBæਙ?:XZ)ØÒR\™"¸ç0‘êÜŠ±“­h˜¯¬Þ£ÿ;å:çE»Rn0 ‚Aø¶Ç­eÌŠMȲ޷WfàAö`ºcˆh}œ§Ê<â˜Û¼oA Y¶¨46“ÓïH'Á?÷;Ä ¾o±ÙÚ8c½Í>ß ^iFfHùTd¼î¾siwçÎÞ†ÉÚ¾÷zjËo¦AœÀùÆ:çN1Ñønªõn¶¥5q&¹ß— ÷þrÚ”Ò9\£ìCG®X¹ú3Õäÿà‘¿Ê‚¬«Î”­eJíÓ{Ÿý×Òy¾:!§àÃ5²–àIåØÈè92ÒÔéíÊ¾ß ˆi}hPZ/¥¡ÛZp4á÷¨€`fšN§pr3³Çy–nU䂎O×¢»—êcímÌ"ûc½üžçârßÌ("XSÏS›8‘…ÞÏ÷¼‡ÿÓ‰èë€>‚˜<§àµï½˜s0ËÁÉÜ"¯ùò¾þóÔv£zîSg7Æ/*€ìýãžÝxŸ9$‘¹¬›@p8z¸Ç¿Ba]Ém={˜‹¬2 h KBû d9†ÕEügæGÀ Áç( 4ózÏ5[¾èÌLDÕ«þe™v˜ÛCð?KµgAOÃVã,>}F/` YÊF« ꌬRfT{Æ{ bÕ óÜ‚ÿ0©ÿþ–®®¥™2òÓéKú8¶à Æ¡ã4ªÁW€T÷z¢ÙÙã·n†ü¹}ëz.öö„¦Ó§?úÈ.òG‡p•µ(7óÙŒLÛ^èH7«5$ÀØÎÿ&ÖÊÒ¸+K2&³¯ìƒ)(àzªD¨«Z_Z\¡#2=¡³òqøé§žIÿËŸ`-¨0À\|î•H熇ÓK_ÿZúÿþ¿…«uÒc'Î¥"à³üSŽz[Æ”Ða †“ïm c¶d´ó€¦­dÒKÛ«=D6mozùůr>¢pöÝ|çõô·ûï¤OýµßŒÀ õ Ïaå¾<áßêDf_ãô_þçÿI𛺳™Üèl6³Ò=-nU÷˜AYê ʃT÷·Ñ'Ÿ4÷ìÔæ0cÏàN«ÑäÐ#¶ÉÀo%kó™çÚ><š¾øÂçÓ7>ó‡ál>&sX]Nõ/~ôÓG>ú—Ü30$ 8!‹€ ÷˜g©üþÿ÷ÿ5]ùKÁŠ‚É:ÃËT›²¥Ð>ü'Ÿ:Öfdº`a޽â¹ï~‘WV¨ð#ÿV<:ÕÑ bØBvìB_æËßžçþÅ¥'ÞKÕ"t¶Ïî³éÿø_þ'nèüew‡¾þÿÖ•žÿ™ŸË2uáU€¾@Å®´ ”733Sé[ßü†Ããùî÷ý,(ˆõ¶J‰¼~Äó òUvtv´m+ å«6É&óR÷¶÷­€ˆ¶ˆ²3ôsl y)€ 2˜ßÿÁ_H×z8Ú>üá¿øç\Φvy ›¯ç_z<}â7žçnèðfßÍî-@IDATÀÿ†¨hq*cÆÇï0ª|´œ2£Õu6Pä[ µ³=‚” ê”ö¥fMì…ÁAd[Ö²§nV´²Üýº¬t¯ZÎ÷ˆŒgçéfór/[Æüê_þµñ•y_úÂçcýÛ‘™T£†iè¤_þÔûB -h·À>”¿ºðá¼ðÿqúÝðw©du™u¥º| ¸b«ˆ¥[Ñ¿×`*@† än¢ —VØX6ðIâ5¡ËµR!Š P–@.zú¶qþº¦îa¦ÍÒó}}Ô å+ÏùvvŠàIö­rH=sfò^úãÏü2 Þ€Š8%¾wëömìÖN*Ü|8ö_ÿ`zàÁ«èÀT²¬ÝF&¾ùú«éö­wÓë¯1þ¶ØË AVƒ¯<‘Úóö.Î18õÇ*O¬˜¡OÀs¾»¨N@íû€rêð¶“Ïm3ÓÅ™¥¼Po6hЀõä‹— ™õõ¯}-øùêUÙy®kèO_§?Oχxóô?\“]uúÆ¿ÝO“w½!Ó Àý•»Ÿ3‰3 ]Eš¨§7‹=08¿ÛBCƒý4»1禌š°°)tWߨ@+³ç ¼3À¯‰ªkûè‘gÕÜ*X!‚ñû„3ÎìéФÜ;üOžVþ¸><>ž¯|µ¹g¦ú‰rÃ3K >?gÐ(J;ÉF’÷ŽàFv'Úg¬•gàНkipV½‹ÊEð—ú©s’÷ÝÃtMÐÃÀüÈŒ…ˆXÞûûÞÈllzä‚矴<ÍŠlT§uÒÝû)/³3Ö€®Ö¤ö¬¬· ŸòNÝ&2n¡SÐŒ÷… T¯ÙãžúÊгÝXßQ>°&ÊqϨuÎϱ¨XÁ»·²¹d: lò{´ü€Êœ¹ö£6óY=À*P‹wuÔãþf×g²à¹Á¹4C‚UÄjˆ«›±,ðù<òþfýBïa­ÝûxÆ’xƲ`aߪïì¨k&¼uä†g—ó·2ÏûY9êš檫¨Ïª¥‡MÀÚ –6ñ|³æC7@¦4ä;â–¼Ný=èN+¼CÖMj5Cƒg3ý”aÇ™mð„ÿs?ËÏrºk¼‡´VϳªO+AôNúß“¼,(ïüìo½}í;ñùy*“éÓ ¹?GÆ;t—÷½V®W'Ò®ììÑFÏ€.ò§€s½qº8fF‡œ’_mbk×VÝ£Ä~R¾DcrnE®ÀUå {Ü€.ýNî°°ðI‹$œ„ì:N5ž©®}# ¶ÙgÇîí  Ïôy<Ãí!;¡Ž<×J"CíÓ¬ØpŒ_.£k&S<+Ìd· Œ:²¼â<”L Á½C ôË‘ìáq²ñÑ [êØçÈSùÁ$ íc÷DßñËgÚXꥢèkS>ø~ž÷"¨×L}æaëç)_4RŸ¬Œ¡ümïì:Ë@g-Ï^g8£ÀÎ(ðo@Så^EÅÒìª*çÏq¸í£ÍDY/ËUNLÜáÐJQ–Q'¦ gùÙëŒ?% ½‚cÔŒpÍh«ÿÚ±œ³fi k$ªli˜Õ084Î!-Élö…Îc˜Þ×ïj¸ùb'”Ö>PaCYÀ CçJœŠšÊs8¶tŽ„R—Ñ:#¼¦Â¸yx(¢FŠ †ú ÿY–S'·ŽÿÞeœUžö„¹{ ç5Ù§¡ý“1 xeéÊõË_û“tõ±§Ã! ~óÆëéü¥«aÈ–pöi¼X‘OhVN“Ód9vv‡ƒõÈL³ÁsÈ€¥Èæˆç°Ì’¹.¾K±¥:È4n ¦ÆoPjôN›õpVmn?Óc”['û(ãöö\'ûŸçÛcå• È}J_βOwWs<:® Γù.OÜ‹rôu¢Àí={óö8™Y”„Æ Ó·ÒÖâ4÷4òœ^Û`Æ–y•^ö¹Z¥Ç× QÇö/ßX™ #¬PïK;=k”÷¬p?Ëz7‰¯>ß@»Yz÷­!ÿ@˜Òß1{Áì±õµ²TZqû€rdÑÁMT+Ø'³á``¢È5¶¢\"Õ}:Â!b¯3ž) av¡FuŽúÔV$¼¯Q*–Ê”¿ÛËJõmÀ{‚›Óò°™ºa¬±o ¥Öîücè"H,Ð+`ÚÈïy 9®ÙÅY½^ÀÁ¬'÷¤}%ÝóÓdMèôÕŽlìc‚4¤í¹§ƒÞ(û*Îþþ¡¡àãð+ìÂý]5R—NL8æCØ Ïܤï»}>B }®ýa1qÌï@%Jų±XWŒW2,,¿ïÕŒ ì§{p) lNK[6d_4ƒcöZ|+lˆ(]GÕƒÜÁ/8v¤ßבsáø#$¤&û:¢ô¦gŸ¤û¾ëk¯C#df>9&8eçjÐWš Úo›Õ‚UÙb‰Uûá-d.µ±ŽÈÒØ‰`Àì½U²0uüÔR¿c‚–?ÔáêšèlÙEîV npßIËÈ%åí–σ¿5ŽuJ˜õaÆ]‚8ö—Ò÷Ø‚/Ãhçޘዼm{C#ÏÅiÁõ¬—‰F¹:‘N£= -©lÆ6d‰ø üÓÀ^YÄÑ\­ÐknÛžn[¬9Î!öæö Ï@F©GÖLÃ7 ý›ÃqžYöîpg%ë¢ZN»C2)–V§Œ1êg,)ÊJ+4,P„aÌÒѱç݇ÎsŸýtû²¯­‹ ¸ÎóÙø;Èb3ˆÌ„Ïõ4ËÛÞï¾tò™•àîµÿª5‰/ü9ÿÃjÓ®rö—•2šZgð›hVX¤"‡ç„h Ä*gÓid”¶+È­@U£ŽBÎÕ*¶M8)»¥G8‘‡ÊUƒa:êçYÎ|ÖÂŒ}÷¶íWì_k‹.ÛŸpÖXÖ,wæV®¨V³ÓS“Pê ¾E€tíeöºvß½©du€eÖÄ~¡V"°¾zÇòþgè>þ«é¿þ;ÿEºúÀ%t‰ôíW¾“ZQ†~þC?ŸÆ®< HAÛdU3Yõ òƒ™Z:SZG¡L&!z‹BT6À»öëDîÕÚ»#˜Ìq :vƒKèA-ôÇ=Ü'ƒsGÑû ~o!O•fé·!ÿáïüidì|èÆ=è_ÃãÁž#ÎÙïœV©§«‚¯Ùçîoýî÷¢Ykî™óði¶w ˜3;±´ž'V†iïì‹€µÅ™àQ{¸*›„3kŸ3(t øöÚ#¥«×sq¤_¿q=½õÊ+iôâί;L##xF Ú7Srÿ ãQÈÊUX#ÿÉêìíq®dÀû‚ó%2x×1g‹`I32À€˜xÒÞÀÛIï+°éÌK—•ÙGfƒmnÍc{ £pg›ûOýñÚµG”`¾Œð’:«U…¬Ø$ðfÕŒ§žzÞÍhý«mPÜ!Ê:_–‘g:àîäÍƃ®†~“ÃŽ8ßs>°]F¦çèTw­ö±/Ø%èŒ(¨L9&y€mŽ:|·¸Øœf'ï…þ¶¸°ˆí`Éê>ïIúÈ/¤÷¾ïý1A×/þÑ8•<Sš»{;}âcOÿÁ'?6…cëÀÙnp…4rÝõ˜1ý$ó˜ºùÎ[€9êmœ©ØJ„[ÊÜ×½=;Ô•–.s6¶ì(£Ÿº§·]Kxn!«<Ó¶ µÜvð+óüä§~=ª†Ý¾u+Íc³°†H¿•Þeò?üÍôý·B6¨o(·µˆ[Õ]% þ}ù Ÿ‹j5ë<×,õÝÆ=9Û ²ÞÁž2t}]ÒrÌ™›½çÕÑÔÏ´# ”U/70ÃÅÿ ˜:ǰÏùÆ><ÄŸì33óé/ÜEuù…qÊžm/S…§€ŒeïZ­£ómæöÍôÔ‡)ýõßú›QmÁóJ~_‡6êÜÊö'Ÿzú'¿û¿¥^øbº„íµ‹!Ðå³Ê1&ö0{Ó Ür–ÌÙLyélÀK»±Æž?Ñ:‡y1 V+ä©ÌÅ™»ƒ~¾¸¸üjÉ÷Þ^2êYWíÊEôp{ìj úžû;C¼²ó€éÿéKþÿúçñI°Ì™±Á 3Í0Þà,Ñ®0`ÍÏM ¿Õ¥‘SagEÛi"à.Í•5r{ЯÛÙ ñõUllai›C·¹‰üW·óù™ÎäÞÁ>B^«×Áöȫ٧ÙvaŽGûð„u¥±h!ç—@¹>iÖÄóä±=ìl‚U¸Œ W¢b (h åÁ{ž]yì¨2rMàU›Ú–u‚lÊ< B—`Nù¶c‡jGèÈs3Ó™lD6{ïM‹Â6”œ?îoÏ Bõ)Ï<÷_”ÌMþVûÒó3ßS*§¯mTÂï H˜£W…û{6Y…Fߊ¥¹š­ê å7îkS§õ\Y¿ûÉ=Í–•ð9zg±vvcOh†Äó ¨#xh•ä*ôÑN>Bþ¹¦H ­zvh»¶Š 5Ocm°™g ¾`åá*ö+•q™ËµfV<£ QÀ>Ô–4«~¿ˆýÏœóÊc[¸™™ÏYÇ÷ TÑ‘7ƒkŽÙKøá”+Ž__] –Å€†Ø‡Œ»Xà̇†Ú}–7w¯KÓl@n»n1 hÁ­ÐFá~Ùù.(캟ÀþnÐÂÑ!}êá9å—À¾WÊ“žúô¬¸U”%ð²÷Í‚:2û]°]ûRúG% æ¶^#=bŸ¹ÞyLð?HŸ<¢m·£-ÃX´AavÖÀ mÆË?çá?å©ë­,+Àëž—÷øyX"ÐJ™ïÜ úSç(pVÊóÚ>32Ô 2n!ÀKùW*Ö£²Ã<•ûÖWÙoøO ÿœØÆk”³¼Yw–…ôàQq;%uÔ ¶[z/®'ž]|FÔƒÍ\Ð0úÁZ)-•].Ó¾ø—ŽˆèaH¹ÆSãÛûh¢oR2s‘rƒ}áøèö˳«´jTê8ViÓi‘K‚§öam Pß²ïf§„óç¥b4ðNÁþ(Å=ÌRCç‹=æ Ý“‚ÎÁï˜ùÞÀ5o¼öJºóök(­f"®ÓÃv“~’ A@Õæ2¿›d[Úgref<ÕÙ¿›k ¡¸»¯5|÷vœs#‘þãØ$78ÚÍ6^[™ÇÙ} C Ã'ú6%¹UJ—è¯ÛÙ7×Ý»õf:zÒ·Cº£RÓüdÔS:6PÚqì‘iz·¾Dä:F g¦­ fƒ˜6qót퉤#Œ¨­í…©×€ JX§\ÖÜì=À7hLè=²]ì[–Ç™z¸8‡SìÙsd¢à·ÝϰÀ3 ø³†jžrs dv÷âbàü]^œ%º~:uS.·È|o¾ùítáê©Æº¼ýÚ7cÍ-¬óËLj!åp€LpîÂdýßÅiò²q°¡'ZÇ/›»Í%]]T, ;XcYgþ àΔšQÅ{8'4Œê=(þ8Äp®i„¡^Ĩ´óÁy ž½99™æ1Êèà PÛ³„Övºƒƒ&Á`¥|0†ÌŽ=Îá3Ël_½pžõ€O1(1±‚¾ÓŒ}•(_3ðÚÈ T¦Oß›à:2(H{±Ÿý@ÖÎ.‘¸8ú*ëd?·w¦9*¬-@{Œš(á oF)- /3?-WØBPCiŸlÖD0û-:&tznRú¿ð±@UŽòwîCû½…‡ƒj™2}ÌÝà—Ìq†¡¿±Ï4d ì³=¶ ‘ÎçͶÌö?NÖ9¢‘,`¤ãB£È×Á¡À˜Ùäa‡¼¯Az€a¦AÛ€Þrƒ–ÀÓ Ü Õ-Ë×–Ts XÝìwu„T›:áz 31ô÷ðL( ì‹ÆcÑ¡!ÀÙ ì ËÒJ{{4:F°fØs½P NÐå;u<4êøÑX楬°d©¯ŽöR¥#2×r8ß[‘= 8l9á÷¢w¦÷ç{‚¯–2Ó©PØô¶Í”<„¡/†9?eŒ:']üt^ÒÍ€uƒxÌ\5hÍ€¢ &G»2¾Ô]¸—ãwÒP Ö²í®£òphx€¤‹s*ëùë¼}ÉÛ‘±ÉwtÐ ÊöªÝøî·6`—ÁÅüK–±J‘­žÑf+ª¿y?·>Kg¡ é`Tþ÷‘™nvÝòâ<ç÷ gáPÌ?ñcøSgú–Ç|ß9ž¾¤³<çXäQéìy´f‰z»Ì.·Dm7kÜE‹A¯²È3=Ê£rf ÉÂ>;ØÃQɵE¾c kòŽ¿£ìý>û“ê3##Ãß ÌBÇÃèg®Žë)gVê½pïÞJâ Ñ>c4®×‰/ nd$ÚUìÉ{_øüçâ{æùç3åþžQfGoQøâÂùóéòW©v@0YâVô‰àÑÂYo 箞¨N`ž×åàÇUDrv¸Ï‹­‚dða”=æ{žsûœM»8_¥±ÀŠelñ¬H¢¾ºlé©gŸJÿó§ÿ0@G€þâ/¥‡y4À{¤ënƒ>Y›»<Kœ©+o²ç,Ym0X¹‰’¶T?Òö €ˆqØj¥•ßÉídïÉ«–æ7€ª°§ÚšQìýtîr½÷ÜcOÆœ¿ôQCÒ©ó¨ûÿ{ÿéß|rr2Î_ºë>÷Ç‘AlÅ˽êÔôsOè–(HИÃ}„%ÌZ4­£™¬YÖȳVpÅ HõX[ý“=­.R¥šÙîÃÇì#‚+98öÜG/ÑrºkKy¦XBþã\¥•Ëô½©xÆcïy2Ý› 0ƒ ãn*èìVž)—B²_ÝÚ#VÔ0;ÓÌMÔ hnV,zû¥Ì8üN‰gÛçÜŠ)L” îøþÁ*`tXÅéoà˜ÁÊBƒó 4BJpŒðxH^٧‚²ÊgÐ;00˜&ïÝI+¦ŒéÑŸùÕRú¢Ò…Y™]Ïö%{Ù±Ë/Y„ͨŒ À¾¾< -|rÚ{ïô¶5†Ù¹Ê\u#õùŠú›üÄdë1•­ ÞV'<6+\0I ø‚5äÞ‘¶¶_0{NþPÎ|íË_MUt»yþö5:6ò™Çt¬‚½~ß± J÷õ† RŽ·Pe‚BnFsx¡‘€?cu÷˜Á)»›é*˜×EÛ.ùËÊhê³ À¾ðÜŠ 0èe»ð‰ùÒ:;;³ÀDØ÷.H±¯ý˜µ¯îå§QZ⨸ŸÍÎW—m" ÆýÛןUÛ1SÓ)íçåýÝGfØûjƒçr9‚s-ww›Ðù»8 F/\B>ΧñwnÄfu…Óu3U¦­`kìÛbEr³½SЕ9Á?ÚÞ[Øk…Ò­‰Þ!á¤?ª©xFùòÄs~/×Ú—?ÔAkÈj3<€íáï"x¾A… ¤¿AŽÊû.ªtAÀÚð¢gÇ«ÊcÏ+}lLP'UûÄ—2¯ÐÐA4ž#Ê"÷\¦«!› —ü•Íæ}6CVØNÅ »È|t¾æy®-às(‹dLxb ’•Ø ºnÁ«‘õ[5¨Š¿ `Ûçz6ËSÍPiÅ3Ï+’x6iƒÙjMžSwkÖƒ}Ð|l33námxZ_Žë­¾YF× “½¾¯ÜÄßqžð¬òRz»æ¡ÛG ršq{?×$@Q|¹\VM}%“MØ€ÐVyz0÷3(iy!óÝêF ÏPþ›½ïßê?‚³Ú*~¿ÿÐk•R˜»Á ‰1q?öë°ÌÅb+g¬L' 9½°L°,~Ͼà} L¶âtÓTW_"ÙÂuPÿ.#ãlí±€î3LоA.¨›9iåzNiÿ*CÕ#}¶ëö$ﹿä;}búŸÝãÎÙ³-ø:XCùm7‘éèO`”Š(˃öÒÛ…pÌÚq™ÙÇš…þÌØñ q}Õ¯”#Ê{U•=Žßê1v~·Bž<)Àïs¤YòžÒB]MlŽL~i¯o¢f’ìvüyØž[ø‹"´B÷g}³öÎcG¿']RO#Àw‘ ~÷©sò9¿«¿È–ÑïÅç~œ³'y_:HûlM¿+k<œž=)ýT›åG´þkŪé`iç3ìW׎‚¸=ßAׄþîeõJÍô†³'ÕÜ7®oÐ>j£µˆ¶‘¾+ï)ªœÇT3  š½Î(pF3 œQàŒn ¨Li|LŒß‡ŽQÍ*-:âå3ŸÃr„s´g/àÌ/À_f¾6ä0šìݦÓy—¬ªãýõÔ¢AÑÓ†!*p±Fÿlîkçšîp(…]ÃR骄‘—õ}% *× ÀWyÇJԳˡ-Èí^JÔ šµ0º:ê{Û/a@Âá¨Ó@쀎€‡‚%, ¥ÍçÂÉÒ5v‡‚†ŒŸYÝ@ÃSc}¨¯#ŒûÔ•åG Œ7 ÐF KïX>ЊñØXÁ¹ƒÀYAWµªMìÒrà(ö, 9/O”æ§QDÃå0¼¶4@5`uˆèÌÐ [ £_Là$+/Ž\` –(;"ÏA›¼ce/é4Ùшe,f_Ø› 9ŒÂÌhÍ ·÷×yâþÓ(àÐp3»Ìòs:…¢ßcÕ˜07;Z‡€À×1×™mæœI rEcOg¦{/Œe>7@ ÂðžšM“­'€­Y¶Þaê¹ ¡2æ}ºÏ^k!@Äu9:Dð¿J­/U°uÆXN[ÇK¾ROåzoÇ‘Y Ébì´P(ëÜ”ÒÈ”Íåé˜ þ”4?=óU;dTcÑ |È~48“µÈQúÌYzNG{Èx¸¡@Ö†x²£@9o{¨»´fÅkhëÜÞçAlšŠN$è·Lv‰>8Á I³Ëj”¶µ|óíñ{éÊCGoq.ûœÈu¾cô¸à¬-Z;¡»f)œÐÛ–ç¡¶ºh¢/] ÇXDÞ;à{4I‡†:Ûq07L|xWšªÜï¥m/VeÀ2m)…Ï•Xç*Õ,i· ÿ˜Å×ÜÆ\ÛVØ©S%Ãr¦:î’íx¸h¬jP^AKÂ긳º„åCÍFÔɬãùž.Ñ^G‹V (10E¹¯sÐ³È fçè•nßN¦:éÏye&òÏndžöw5«m…rÜÒLÈŒHK˜»nV1±|áøk_N»£WS3çÇ*ó(.Q~y`zŽ1UÒÍwï¤ÉÛo§Ë燑Ÿuh‡Ã˜=¶º8•&îMѯþaC¨ì¾ ð>ÏyJð>Ž¡iž†9à3à˜ŸA6¾÷¬s¿¯Ì‚䆨 Mì3J$¶gÁw4˜Ác¦ëi)ù*Xöñ¹ÛÑÚÎÞÆY‰Ìt|§´Œsœ{K/egÈÆ`–g朆Ð Z2k,¨ßd `-¶€ŽÌ&²õ•ò‡–Qæ,3£X~»Ïu^=nO˰zß}³:ç=oÔiÔÍ”î=¨fêxö½á‹W" È}ÀQ|* i%†î«“MPØ,T÷Gå¹urzO[<„“‘ï¹å2%X¹·ã\¥ÊB+{RÀq  eG –k¯Xuˆc•!}òÎAéb¶jgg™ciüÝRèè·‡¬ëÀ4ài2iÄ| '=´Ö)àcÉü2ç½r\PØì{³Î|†@­aZþ|hh„íWŒŒ6þÖoÿv:áRJcç/Athž§Cø›_ÿjzëÍ7SÿÀP¬•ößßýÿ.2‘íñ~ƒÏF»ÔS b¬SÍh>õ`ªXQG0|=u€ûGiò.Õ~xæÐ0é€‡ÒÆ=y—Š /~åOÐ7r´A– Èóž™ÏÙf‹s"œø€¹¶&R×·Žôv8ƒÜå2YEE Ò½eU“vZ7ÌL¢S°—ÙskÒ>vÍ?ð3?›žzÿ³±·^øìgh«tÝ ‚s×à€Oÿ³šžþÀsÁïV0U½Ï@7 ÊìaÊÎæ3Àd‚<`–@þôwÝf›ªKe*˜°î«2aìöí"@‰U’Ž$âìÈB–¨gõ÷De+,AçD°J_ú›ë?S¸`™µnrï)¤±²I}ÒyúrÏúOÁ½ïøÌŒ,UÆ È ‚ÉêEkØêf–WÊÝÒ P)cNR®mð æYád =­À3AUhÖÑM”§‚îõE÷¢÷~ò}O§ÿæü{ `†q|èc¿öŽ÷à• —. ²üsìaæà>ífïv3nù앹»1'õMƒCkž·8àD©fm)ÏO3}»›‚ò'èÚjVd*¢ûÈ®«Çƒ|#Èc¥³rËØ5ÒQ“ð×¼†ŸhÏ1_ÊLÏ,8à9s ìyNº, ´ß°Ú•{ÖªKžW& iWʯî3Ø)žÏ©Î\ÕÊãH >S–…o š «Ië%ªèðm¹nSÀ#2Á½¿ºÁ÷y o ¾;{Û© j›%ƒ$}®ïK+ÙxéZ:ñÌמå, V#ý±C¶À/îbÛ<}ÊiÿeAï l•Ëßoaû(׎ð™H/y\Òëð~`mÕ]Žv³}j ¬^t;Ûnh—¢0Èhí6¶ú‚tǦƒg]¯9„o´ë­< l2óz…Ï f“q|¦®ã‡Gy.—¥ïû·ë$ ¥ƒ|ê¸=Gý\>2x(Üþ-Ͷ¶v9S¨p‚¼ÎÎ ü?œ¯’×–<Úßí5â‘s®·ë·ÎùÍMâ^ò$Cò‘!û°¾9—2¿ˆcWªa¾•(/ϵ³ëïìÀonëA¥Ï3'¤]é{Ò'çwÙgîý>Ï@†¿Ü›ÂA?÷]ƒ ä9Áw÷r²q‰`¸ `+†ŸÑ'‡½TÒvןõ1FžÎ/žkíK\g ‚ûp« ²Äg…þ.+¨ÿ¨ä¸µ9œ[ƒôâ^ú~ÐÝýì:˜H`¼óRÃQ†¹FÛèxî±c|Mi¸Ã½§Uý¼¯ú"·‹±è#ÛÄZ!@?³DyÿìuF3 œQàŒgøQ)àãÁè¡¿0?C²¯…’<84=˺{pì Q;0È!t„a6𮀠´–‹|Òàñ^g¯3 ü$) Â)ÛY*Mþóo_§Joð—ÿ¾7½MJåR£ œÆ(Z+8Y¢7nf”èðÕ“¿u´âú׉²AZ$3K#j]'Žû“êô¾:ä5ˆTδçÙ~æ}uÔìâhÔèw,Q^g—Ž#(³¬’N³§e g'ÞMe6XD ß‹,¥{·oD¶ŠÙu6§uJê°Êilà¨,‘N5–]²^NìÁÀµ/˜Í÷ŽÓpbØÓº  D–i”;1«½åÙǯ¯¤ó?…EX$¸ 7€){§oИ‘¹µ9 ÈA†Å>Õ^‰ i³ayø.íû[d+ðñÕP~Ãy]uÌkç)ï%¤1šÌt°lvpÀHr3-Q~ rÀ– Ôú2½|ÉÈfUþ-!ær ÞÙO]§½%¦´w˜+´`Öçùe ¾p~ò“Ušíâ„lî´5ÎnJã@¯Fxùf~ž`¸Vgw¸Ï|¢ÑÏb,ÁÐ>€84†žÒO'š½O1¦Œtב·…“)Ÿ€Â5xFgº–º%›1,±²swQnJ^b~ Tüuô,íŒa¡ÃÙÏ2g.sÇk8ÂÚÚqë&×>Ùfà±¼´€óþÀ@-bt7smúün¶ŠKřͼnùKÆlÉp×Òèýe27 ŒÓlôN×ÄDG„öI^`£hZî“©¾md±sêìjNuÊÒº§t,ƒö²¬YPÏ=E7 ¢8&ÀÀ€‹p¼º…ùÄà³U¢wsÈt̳XšN+«”©ƒOÝ :N Jì)®u n3ƒìg®¡r ŒA~‡.:Ìí÷Y!(&OÙj¢%øö>Y …ðn¥¹'mÏNpÆŸ£ÒÙÐL0…ÐfäÌj•"¬“F£ØŽEY‘Ð|Çù›Õ‰‹’²fd{L`¹ÚFø«‘¬}T³Y›Èvnjïc¿À\pG0Ž¡È¨Ö!±_ï @³ŠƒO0ØòŸÍÄ‚lf_ë\6û·ÚéD;Ø¡! [æ{F`ïÃßUAà2e˜‘SM-ìÅ)ÆèJäz€ÆÈ†*ýÂØ×G€ïÛTµðzåçÎê\÷y2¹›É×Þc¯»‡ðŽr×¹‡yo> ²éŠ€” OçÉÀö½Î®¾˜_ž9ðÆ)é"?3®ËøN§¸/é ìKl†·Á2êS‚îEÁŠf«l°¿½VÞÓA­ƒ7@èN}$­à‚`ŽJçC¼£Q‚˜óW>(á<´ÿ­àÒ•‡K3EKoyB]Sb  nCGŸa ¡·¯¿ž¾ò¥Ï3²¹Ÿ~6²VéW~5Ýz÷Ýt—¬â ºf³,Õ¥á±aä’û]=ÌgÖ  1)ÎWÎZŸ!ÏG ›GGèeØ-.ðéš”<;qØVâ³3³„¼#£T]¦£:EÙÑ¿?ãkéé¹Û×?{ ëÆë¯P®}˜ìn2àý;·nòø\ú¿÷OÓßÿ‡ÿ(]eq–ký¢¥Í­Šl"P6/àIy`÷jžuP2Ãvh >†ÏÖ×Þ`Ù³ŒKPÎy;ÏßӡΗB´írjy…2èì‘j ºëåÞr/¹¯¾óÍ—·¤¿|<7= ßï¦ï|ëEöèt\ë{ÝÀYþ§ÿ À4•B¨Ò;0D Yw\oðÀ×¾ô%‚-;ÙÈ/Î’"ç„ÑÁ f¶³:\”­f­¬"g†µçž™ý®Ÿ{Ø «æ„\€Æ±ÖX^_‡µà€÷1kÿÍW_Iã·Þ¥ÅR–¾ßÑÅk¿˜}>qç¼µ®“¡NI~~¿úècixôçxVV@W¾?6äž@Tžó³ƒyZ%ËÌÿy² d;à(ô\€¡¸Îu¾’žÈÇiéYuÜ£#S–çY &v=ß™¹;ÿÞŠ{_÷@òðÃFÀ€÷1PÉ*ã7ßAníR­ë.Á$o„¼ùåÿkÍfî³ì¡›7hùýîÛ5t­··ØˆÛØ]o}{óW0Ú@LËþט¯AÕ®õÁç=Y‚´Ü9áó ÁofCkçl"= ä¹ —¯¥¿ü¥4O›¥i?nË©wØ=26/68þÆ«¯Â_TNïuÿˆxf¬175•fn½ƒÔ€M“UTSÎz“K ¿¹úÑ9NæiÔ°ƒ~nÉý¬F ,êh§Í ´ ›1 ß´r>yþXUÂlÁ<•…?tÔß,ík@õZy‰½4:ŒGþVŽŠXœ£CÃïKõדע‚–{â#¿ø—¢¢ˆ2Ľì?_þŒs™ß=+ °>ŸÐ Nö(ò–½©LuNU您sÃà#Kû÷pö¨³(¯Ú¡ƒ¼äœ`´xŽö”{¡z+S´ÇZè‰,˜o¹ã#Öèégž¹÷ÿüÑg¨èÓOPÒyÆ'géã>?Ñÿd$ŠGZª~ ò=lˆÎ.ý‡R¥¬Ñ*MÒ‡Ï @•GäGýÛê¤T1¼uÊYu²šÁ¼1:ØÃÙÀ ¨Ø„Ý!ÍóTŸó&Ò<ÓE<[X!@;8 ºó;tu¯W1ü\½<­ºZ¨ÖQ&P|a‰ýËuîCƒäTÁ ´óL©RÕIàÍêµ€ó&{ŠcËóDîÂúûz'©¯íjGò™v’Á*ž—®q€˜ŒÕ¿åÛQyv†-Ͱ SÕM´3R› M±oÕ¢¯´zó Þâ½òÅ D3†msqˆÞŸ-+k³ Ï*[¯D°g¬´P6Ç2±Ÿž'ÇìÏcu1e¬z¶KkýE.XÆc…2>yÙû¨ï©« HZeD9ÃXÆÅ|‚¹­ŽÍý¨ß—üî÷Z°%PÌ›u÷÷ØÏüÔ_áþ“NŽ%6ùŽïí ÙÍÜGý̬imsÇi@»vuïù²r…×j£Ÿ§x£_ÁKwç$Gy~Œþ2Ö-²Ä™¸ûßÀ¯Îvúƒ´vÝüÜ@]×±È5!' •ë''z¶{³7p„ƒÐiÕ‡´= ‘¯œCüg5º÷®BKÇàzøOŸ…û‡[ó éHµÖ‚3Jþïd‰.®k\À=Ûüó={3ÇØÃ#ÀÑg+Wö¬àÁ÷ÓÒ\^–'{üäyò•sp8u6ׯ³Ü™¹¶~ ¾íMÜ#îI÷^$ð™1ay‹I”¶9ó¹Vlà…·¨ ߈<ßÃx¢ÚP-ÏD€'÷þ< tZiYþ|¹Äüs`¾2®ÁŠê¡e‚7¤¡ë"ÍõIâwý~¬¨«'»¿%¼>>>"P>4:¡Ṫ®³Ç5&—x½ßÍñÏ÷M@Ò>>Ðaò³×Î(pF3 üèð`Öà𜜜§ª=¤»pÌú™€œ±Úëo|ÇÂ]$ÛD›©~{ü)!”º³×~’Ш÷¥C5€»û<¨2éëôóøƒÿ‡Âûÿ ŒfY a˜£ ª”ÍÍ܆÷éÁˆ3M43ÚQäî+…òzVŠ G‡Y•=ý ìûàéLÓ u(¢ *¥:³ÝK*¶îo*à:ª¼F¥6@=ŒZYÉTú}aœ[~[ðB@S%U h¨ùÎþs©ŸLO#cðW—)½ËxTŽ;ØÇµ¶n²J'Â!¤sÐç…!×L„%’&,·îtx/S"ÝìÌfæÕÑ7ÙêÒ@ƒwuvg`­è¡·2C©ööp8nSÚ\pS›!”Ù0š!ç`ÛÆbó6Ø@Ç—Q«ëDðìÐï”hÖNÊïîïÙß pŽfb©èkDª ›É–ÙUk'»á˜yTùng‡¥àvÈäp ™á#èЂRŽbÍ,e­¡¶¾‰aª1&X+kÔø¯Ñkª ²Â+(Øø€6qØAG_QRŠ5:Ö9wŒØÄ ÑF¯4 QK}ÅX™¿í-0bÎ~oyq™Œû¶T jYeã³@J'ò € ON–¬–w'î¥>Êéu‘Mnße‚A+ô™4ãjgg‘õÄ_À¸2 Çûà(ÚÅÙ¨UÄÑ\c]‹ÌK§Å±ßÙ8ÎQ¾¶«pÒW݈îUÖPÞlÃQÔÁ~À9€fÉãppƒ²Õ«‰+Çao)Ê}ú'ŸT=/ZSÿp-½þí¯Äzk<ÏÏ/‘=G_P€;ÈĺVpÌà,£·›÷vŸØ{Ñ@b é¾ØBÌ7Œ Œó#™aaXûA…Æ6 -@þMûØ™)®!Êþ‘ÉÎÜT]îÆìül5às‹œgq–>Pv­t‘Ö«dðS|ºcÔÁÃÛD.Ñû]`i›žÐÅfÚ4¼A¿ql7‚5è/_¶¯+= ÕêÚë±F„c(â0‰,ècÆ_]ÞÙ;˜úGŒŒ]3ññÜG°É ,¦aVq­Uö7èYÏ[b[àвh-”Ü,Ï™±Eö´ŽyÌ} ¡¯1ÊbóV7èH¹yä…Yef™QSiÕ1C°%É• G8™,±\Æ©ÕÄýsœ«U10~qÌèˆÏá0³OjžÌÔ ü´<¯Ê¥'¬Io ´„.!7)#c\'³Ö Í884ÄÛú×ÒòôÍp6T;È„ 7úÌ»ðDçó¼rÆ=áz¹žÛ€ööÌ®v A¡,]g…Á 'dš îWù‰WkQì Å;26Œtƒ¸WÈ n|‚MÇø6k °¡3Ã=ÙhÆ8†¶}Æ NP~¹÷f©ÊQ†ÿÚè­[¤Ø&à ‚coù¹ÉøN g‘Ï[ h&Ë/×YËöLì)Ý9x 9w–æ§X]œš½C8ü¶Rlú‹¦Ù™)xô ²}«|_ðågEGï¹ÈÌ×Q½0s/γ¼ë¤¬ÑyLεýÍ*äØ\YL[+ó* #s(£|ïíào3å x±?„Ëø2h}ÌyCðk+m- /ß1@Î ªQ°÷²²Ô”E·ì­ °sç ÁS€&2[ù¦cDG¡ò]ùmiDêv…£ ÚzmdÀ¹þþÎÏÓ—´×™%ßúg÷US\#Ÿ™}ªÌBnr€—ÿÆvðóv~—6éamö]öÜûºF<(;×½¿¼e&˜²Ö{:ÛVj4/†ß| Ç/ßá\.ÃÇ:ûì… {ȽìyÝJµ[1Ø?ØÞ‡®¿€¥2TÂÀ |:±7ФO–…Á£®Á÷Ô ÐAxLðÙ&`F™}%èf¨÷Úô²G€)ŒÕ²¼©±×jè{ÈhùÂ3Pž‘F‚r_Ô½Ô ZXkŸïç:Ï”…ÚHdà.÷ï!»Ûgc绞7:/¢ØØì0r­ŽƒØ—ŒoŽ~è×éè3ÌRvM-Á=8DÀËçó¨·eàõéÚÿ4~:GA.³ôïÜz;tm)ü\#.ažè‘œ1}]¡OHc4yÁ¬HõC÷®™Jò öUŸ˜7s7ëkö3|¬U3ëbÏlõÏ ÚÏøƒ? dËö…ÎL´øSÇ÷AŸûÌï“Åù6ú@rÆàúðG£GúÿóŸ¼ÃÞU®+G¶²eÉá!m8ßÝŸö”uoëœf¸ün` ru_f]í‚èäy:i©6Ð7†¯>k]@ž,°Æ]Rf%;Ö€ +Éxvy>”qXÛn̽ekE‚»_~é«Á“f‚Zòú[üý‡€çÏ?õp<}Òž›îÁ9*&©_5 —¨_Xùdmqú,Jp£gKy+{nû ÄO÷±D€Sä:´{Àsº pÞ ˜g²À«ud¥¥Å ºU®mÐ:ç Ÿû¿Ð[Ò…‹—cMgh—!°‡NÛ€~åþèê…†»G»düßN-R]@IDAT¯}ç é`ç=CªgÐ2x™ˆË9¦Ý^bÕ 1#\}Úý$(±Îó-ë^eè·Ü»ò˽)HSôæÜZolU4 ΗpÄC#{›n6ó§÷¤yæ¶½>EE*úÚ3–Kïy>2Ï¿üùO‡ŒB3 —–w¾ùZú¥ÿèo¤ü…(· ­/=ø´E>«ò†ën°P 4´µe¥ 4™ž¤í,^¦º‘Á-­´¹ bPK« À_Ä7#è³N¥1õ°už¿Á ï}æ™dFóüþ?¦ÂÌTðpàõîĨT!hÓÊ=¶8›ìm-m´Q:ÈíÏ­þ`&¼cùìüŸØA– oHcc£À¦³ßRóQ=`·Ñó—ã<Ÿž¼ïüG÷b-¤½úˆzéül L'ƒ(»¥‹úo ½óx]„ït¶µ¥o¾ôµ´‚/R¥Ö« 5»ƒµv޳Ów plŠ ’Iª½úí—ÒãO=“>ö+¿{ØàÖ~LfÉ>Ân°¤¼g¨ü(¯ºF÷ƒà¾Fù y^d¼Ê*eÈf?z½@ŒÀSÙâ>”üײníåOu!c._}$ Ž¥×^ùf€û¿ü‰¿’>ùë¿‘FFÆâûÚ)öèî ׿V7pJ+i¨¼- -üÝ›ìÿ /ƾÏÊ…ã³B97c•;2ïï6(ýt@9_€/Pýdfòkz/æf@³ ‚]®Ç¬¯ö»¥øÝ'ÊAíó}PÖï£ûÕôô³ÏØp+7ΊñA~2éŸÂëûkÇ&2ÏsEÛWÞl$À¾‚®lŸae–á/ãÅX³wß~›`‚U§ dà±zTT¥@æµ±Güž%Â;êV¾Áhc+´7óغÓ®“úŒÏeQƒ·ìežç0Øç™¾ï¹~'0;5½†å§YP.xšÉŽ-¹^~¶r…z•ß³Ú”mårvÇÅ1¥ê~¾ƒÇjÆgeh.4œÒ€5sUùû—¿Õ "Û—ïÊî F²Ë÷µ—œo£‡£ÃHGþi‹K‡ì•ÑS™¡mÉø©n(í ´ÇAkºåÒÚ{jÇò]UIØÓ5Ò—H }•¡EÏJÎcÁ|ƒÆõO(C>¡ÏÆxOiË÷­‘Ñþ…{©KïFîåçúëÏSùw™'?i·Gð0Œ©e {¿o@ˆA>ßóvŸD"˜{‡¿=Sä3Øï+§ ˆÌÎZ}ÄÞâÙ>_~rnÊQõ§ØUÚ±µ3p]Ÿ…k¯/O9íýÕ«x`ð˜|+Ý`:ÎH¤®s‚Îú¸ ó1PÌuÍÚò¸à© ß‹¶а‰Š'ê4Ïcª¹Õhe§‹¦Ddw‘ö Úß®ç/÷¬÷—Ö$Þå?ÚI^b«Âú…•*Œ Ž`\ŒÇÊ$ø^¼{Rþ³Š”¯ü÷@ònìõ‰-ÒG±%ÒqÑý'GXeƒ œoÍœ™ù’:H˜hä Õ.U÷á’øžØFøeã gÿùwš²™{åìuF3 œQàG¥€Š–‡°¥~-㧃Ũm[• Ë3wÈ©¨¤õâäX¥$´tl?êCÏ®;£À‘§†zÖ(|*˜Ù .F”8øüôýÓ³RþVÙRù«ì™}¾47ŽQ=OɾǨ®ÐÍm2ÇzV~NƒHC ƒR%ûBåÙ€ËCšñc¶U<”ëüT©Ìžÿ§Î|îªÒïß~æËqj\9òmÊë”Ô¸Šç°7-#ÖI)D3Å-G•c…=#á°Ÿ#£ÈŒK/mmfåö¤‹ý¹6×(×®šŠÂ©3èˆ DàJl+Ž=³8TÆ#sËžJ–Wñ²hgy=Aè–¾¡pÊš ¢ÑÛÖ3„ÜÀ°F‰µ'í–ãÆi«’* £q’e¾Ævt‘ý˜õе¬²òÆÞYù£"ÏÇ€ç:ìÁeÂØ± £Yå:È¥U[YÍ92â0%öˆJR¨»8õ4ö05Úk8õÖçÚÕHöäžU,Ÿ·­á‹a´}t{ ›¹ìÚfåê5ðí¡J7 ÊÐ×u˜f†®¾_ Él5Käkì0Æ2r¬i  ¾Göxd`±Æ{K€Ò|R¡œ§N3ù|#§á¹ð²,Õ‘ÃÌ[ÃÕ(r&¸À¸Fó2mô|³×ý–Y tð½ó–?ŒlÖf’Ÿ,¿Wb>ÿÞ3‡‘Y¬æ9òD9[Bc̈tùÕŒ_mè¼Óê‰èf |h}Œ³Uc^z›‘9È ö‘Þ¥²uà5䞥·à[Qêl€ÌŒ©~Êì®P¡³ÂveÆn¶¬€‡Î»f²ô¥g…³Hgu‰ª:ät¤h4ñ°2Àù t1 EÃË é2 «‘jOriÀ­Ã± @ )È4°-£¢A…¯:+qT(QX1²ùSGð°ÀµýªÎSBÞ’µÜ‰2a8û-»S>OF‚ r‘5ÕÝa=Øê£‚Á *ý7×qòv¤½ù{±·ÜGÛò°ÙíÍôíîè§,6ŽO{b Ä-M¿ËLc-a²¾›ê]±g7Xèà8s·Ñj½ìÄ]ÃÓ5L™pþö÷fí} àŸ]‹ˆ÷ØG–Íx sçõÚÂ]°œØíåöá¸ngr Rî)^˜µj…²Åý°±ˆSW†@vüà¸J­ÊDV˜¶Ïþ¯õ^Èä¡<+Hˆ¼9!ãÛl°–šÁ8ç– *XòÝ>›Õ¶ø‹l4…òW°C‡Y' Î³c÷|£AÇ©ûÏ~îËsŽd#Áßr;Zr|ïL€v:|ÙWÕ½äËsÃÈ?Ë‘ánÖžrÊêÝ£ìaø•¹9ÿžáË”Â'0€µíÑBæABÍìs\ ì[zôÁà½ÃÒëx„±­§Å™qäg- QŽÝûwö¦ |7™ÙÊá»o¿e_åÓ:™üWßóþ˜t´]¾ŽÐ¯}zŒA7Ö€³èúw¿[í€ç³33iwmðm-ΓÆ-çÊÂ}dý–!(ûƒZð{ñ®êláµ×c ö\?áù%øÞ{"šc_ ø¯!·ªf1V{ãôàX­r cGžô“–üÚêô²ª:%ëå¹m–|îØ’…FV¾ý.+- j4;5ΕÊ$žá>PNB[ÁxÏWÏ|1PåOKܪ'xþY~± °Èv(ë·Þ¾ó}ÝBòw[×`<Ëà ƒ5j=£égÀ‡ç¬U ”ñ¾¤ƒ€ˆaò|+Á€ëË€%ÐYÝÂò¢:ø,Iëø”7‚aÒÜõ Ç$ß÷ÚX+~*{=›–¢ú[¶"¬XW@÷p~§Y\ž¹Ý”Ð6ãÑ{øEâŒqÍ Vñìõ3w›²+®ƒO²s?_äs{ûOGžòÏ×*÷U÷2Û©KÀ÷ 3ã5œ¿ŒÇ±¬±›4 ú©½8ˆ-îÓ¹™i\°£;ÍΓÅJV¨gc—×,·;5y7Æ­^h›Ë,;ççeñ„ ö°àˆkåynPƒ 0³[ðMç÷ÀÀ9*\ 0Þ¬ýÇŸxoЬ >œ’¯}~<À›èÇm@ùÞ`ú­·_Oÿ÷3tã¯åÓÔÝñîZ¡ù&úï2´~çæ;Œ¥Âú¬¥W^þ6eÝ;§ÆxÀ]B½Ï=²É|–—im.¥~hå%剥®û¨€öÀµG .š‹lrmÎU€gƒ†" áê¢4+çÆ5²ê¯=òúwO•-×®\ w½-½>~ûVºøô£è YŸ\å6B1d")KQ½ÅŠ<å8[6:¶šÚØ lÛàs3­×–â|q¿ "«µS¹ {àÖø8¼Fycuè&ÿ*ÿº{6Ùðw:iw;Îô!›õ¥…4Îw-·šolJ›è3V/9&HÏÊL2´ •‚UäN繇ÒI?AtèKtÀ®þ‘ø]#€¨ˆ>Z'K¸ت EJõ*ûÖÐKv”ƒÅZÌÝHõQé!µ;*ŒËñ¹g¤³A4V}ÒN²O»àLÕ°¯l+3qëõÔÒužÀ·Ötð\›`jjŠý„,QEK½·Ôaû{8ݼþ*4^J<ÉyÖ?Ä=‹Ì™‡? «£ ŽŒ]ŒÀ^³ÞkÖÊ2™{·‚×Û:«ôZé`; —ºû†RïàPÜ‚ÿêïMÌAxúîí˜ëÈ¥‡BŽ|ñ³¿O°z ú÷Üôxðû2mIÊØ5¾,Ánµ–‰ñ;=,ş/~ö÷"¸vøÒÃq/1N œ]ÈËæ ´ÚÂÅñô}÷_€ìC¯ægî27*7@ï}*Ó¨7í"7Ð} ¢êW"(™nHT»âø4еy®¡·©7kZULÀÃÀï=ö¼v !WNßaʼ7 °_ ÙŠØM#/¥5+÷|EÛBY ­k %ˆÛÙÓYå"ìxÎju®5*8fXNŽ‘è ¶ÊSúY~*J+üffwœ½Ð%²GÙ@êJîG«]Àráʃ‘en……›oÝ@E±ß< N͵E9og§&Cµì ÌÒôujÛ»×´Í´o®>ühÈPÏ,Ï Ë¯vwq.iÇiÓ*SxL»„ â,[œ €vƒ¶¹cÌlj†÷˜›¼î^²ïzk¬Îd‹ƒí ~ñY5.\¹ç›vÇOóåܲ/cn… Ê0 ÉÖù#å=ÌÀù±É¾ŽYÖö«_ú“83û©*¢cŽ@(ƒØ´ á#˜—óý6Oä“g} ûÕÊqD„žvúž²-Õ¹o–Éžé‚ÊÇÐW@ѵ¬µÀ‡€u­ôT¯ìUÊ=HÔ!!`‹öȾzwê½ÈÁCÊd7¥~‰£Š6©cr¾Ú›µ 7‡NdéëmøÕ^Ð=Ý肬©z­ Ì6(T€ÔsÕ€÷ÌÃ->ôGíƒÔÂw'vú†üæ3µíµÕº•Ѿ¹K@b¶ŸÐ‰ø[>RçtìY!$þBÆVãsÏY&z‹ú’-"Zñ1X #2°‘²!¿¡Þ¤ÌP’Wõ!…6 ¬^áóò‚†|†n‡cÌxŸË³Êü@oQ³|Zâ h <{¿Cª÷ù·YØèÑÂpßEÉ|~º?ôSYÌ{ZA¥>Þ·uš¶h€¸z¸ºž€±à±¼§ç¹ÞÐHPóà‘ÐóÇ3à h5GÞç»¶RqͽÆ1i¿fP‹Á ñ7ïkïkg«Óºn–Ù5¡…*¥ v¥>6Å™hpŽôÊD<¥³ŸÉŸŽÓ^à‡ÌÓí×$Ê’ëd`<Û.¨åÙ(ÿËSVÜÉ‚$œ÷ O:ÿJT ²rˆ¼áøõ¯««x«ý?\ãatÖÒñ:&¶‹Ìõy1 ¾ë÷]yÏ u ŸQÈþûL¾ˆ~÷®52ܳþ€ùæYðmr&K]å­¾;Ïh«H9–TbŸ4S¢³N¥BØOà1~…¨ä'ïð<ß³…ŸÏqŽÚ"úà j÷|pIG÷¢àþJˆ ÍøžÁôíqé?s u䀶³´t7 T‚Ÿ\íTí+ïW5ÈßZlí†uìÜh'ÁÞY[DGCî  GÂkj€ƒüÕ‚¤ó>{ý;NöÒÙëŒg8£ÀL &Y:L<Ž=€,ßgY+V j•$3<”úúÂñÀD×Ðóp×!tjÄüÈ8»ðŒ?& È{ò§J÷éK'/ ïçÍïGD#S¶M>ÖPÔaV(žÃ$[§øi&Ó)Ðí}ÂØà§Ê›}­Ý?5aL±‡NKnj0 z;‚Ph›ç´{ÅÞ0 ¹Æ‚ ì»È"pŸ¡XrûØsîQKÚÝzûÍ(yÛÓÛ³˜ÐM9Ó¡p@ Œ˜=®! PÖÍ5:­Íì3Â×,93õ*8Z¡ÉÁ}+QzYÀÜÁ©×,ÈŒò+hm´Ž•}û¤¶÷ lo‧ï9à`…^ÂË”»ÔñoiKKƒ5–ù>Šq¹™~që™ó®zZ‚ôð ² È¤Ûbì­~‘#…»(ò€%ú7  .-Î@S”gœ7*îVöÔ£ü)“…ê0èPÖÃÃÐÐÔ¨8ÂÐÝð¶T”™³–»,@ËF'€ !žfUÙÛÏ\®[2¸{ŒM0©™ <רŠó­­ÿ|Zž¼È6…7¢Ò«ØÔL©Hj‚Òýž•F)'ãy?J_È-¨D CD+×ãÀKëFʲ¸°i³2ÇT”æ§}ùZÈžÑqéúhÄ%o¶+xŠ?ÿ!ú¶·«†c§ô±¿YV%cƒ>2‡¤ç)™Ž–éÈ#Ð#¦Óª »†òæjÖœ©†!htº€8‹ÊðÂÉ%ÇjZÂDÂ8³t¯`ï¯cð”z/àL}øx¬Ê}£÷䨅‹iýÑH©çî‘ÒÈÕ÷’M»žÆp>äYw+3ðhŒu*<àÀÃÒ Ãæh{It&µ’A:3q+Ý~ã%žÅw¸Vg‚^°&F¤U ¶q¾s}÷àç%à“ë.Ïç1¾šÈ&>áÚÆ¢¥Ç8×dÊi:€Ö ìÖQ¸·Ç·hc`Åö:@šÙ¯_ÍЮR#€k[š(»Ï¾+Âû®a3àn#üi–²ÎÎ úŠkÈši{¸µ n _Å 4˜ï¾À°™Õè|lÈ–ŸÍDl`϶t²ß)[¾KË÷°2Í’î[¼×F¦ò!Æ ´ÓHT¾Ä>`_5 js}€YPÀ}Lçï —xùöÌ(=¤„û@ßà ¶{§oâXÀ`V'h„— -ÌZòaÌq=îšèè·zEþ-RBýš(M2í+nowß3ƒÝ©:n3«wqÒx† ¹Fú‡RžVÀåÁ~ú¿žPÎNÁb¥b÷pÐGb‰zËym¹sEæ›-äÍTtÞÒGÇ¿Ni’9wt–0—8ØØôžõ>:i¯Õ?,ïkok{BF°Ë:x#žãÙ £ÎLâîÑ ÚÈ´ ±gF.WO>û‘pÈøìcè >%p*0;víIÀø iúÎ 2Æ®¤Ë«žæ Û¸ÏòNgœ:²<+t"™å¸‹Ìö¬Ú^ÆùÉ¿"ãZCF”Ь t#Ÿ3y«H²'q´à\+ TRF_LJüfF©:¢Î}„yâFþX¦ÒsS‡t{ïÀý‹¡µë:c3 ÎóWúÉuÒÎ’þ¨»î8lü̵ۅ7*”´¯”QC¾»f:)³~陜öùQΜñïÀ9ÇÖ®þT¦ªˆ`9®JÆV -t¼ÞGàA.ò¹à“c÷uDR¾ào÷/ÏírÐ’£,"×ezp|È\<Ìö›‚×[´+h¥Å‚ûÖ½®,±ÒÛ3îíµ:®ü§lÒѵËt4Åžâ}Á ÷Q@¨ƒà434ª´-ð~f¢ªª7”h! ,a`ïàÒD‰}Î&Ìê n±†Q½&ˆž9¼*œk–£Žà'>/°·”=Î׬ÿ*´Õ‰é³\³;ÜC­ð¼g\‰µñý–63\¡ŸuP=à [{^ß|ë5Ú#€«=qNKS_mP:@­h¡<ò ¤‰¡…ë4aì^gf©ó(Н›ýÉXÕG,{íÞh6kº™ån€—™ñ~ßà¿&œ…ÌóÜ5äkæÄw ôMGÊxÉvªÆrÿÉXާΈbßh{DÛV»KÙµŽ CÔ Ç,3Ý@ @%×Ùe qùÒsÚ` ×Ý^Ôòß* ®À ØÊrÖRYù°e/êþ!2´çá+ªÍý¥TYKiåý¥¥kbÀBwwWšçÚRå êÇÓó?û¡5­°Éš»–o¼úÝ´°¶ÙÁ,lZÝ % <À.§úH)õR@z àÆNåZYõœÛëdá LÛƒ¼ƒ cuY3Á•ÑêV{œ·:>[ê€+è¯-èdeøAzy¯Släü•ð>—ä”óÎÅ=¥Žä9?8<šÞÿÁ§{·n¤›ï\gü¶Ê86ogí,Ô%΋ÒSš@¿¥ù…pb x('up«›¯Ñ€­ðMF?´¶<{€p•:½íwìÛÀÚ¨= <휣¾g›>d‚tˆJü  ýÅ/‘ŸË¡§Ý¹}‹5ò™TÀA4 ÓÜC‡ö<åouÎ}t<Á)ðdÜs?÷ó¡½ôåb”`…&[t8ËÔ;ÆVä”Ã^$Í:û†ØkKè:è)ŒÉª"®ü§~¾Î”=ÝÍOMÞS©Œ .i`ùÑžJ“7)éJÅ)ªQµwD`…|Ô¯¶s ¹2;õwƘ‹dE?—ž|æ¹àuù±)'©"¸ ¦t«oî2f³… dÈ®ÙÊáž4n…Þž‡§:ÆõYŠŒ¨|Uæ(ÛºðU,Ùξ4ó¾‹àžNä¬U´eÌ5E{Æ “¶pî·Ôò< ù±¸´Œ¼£gž²·§`ä꽩»¬«x€ãÓ©O@»•ºz# Áñ ~˜œ`50é“cœþÈÑ}ìu O¶©*3‘ÖÏfX•‚ŒnÀ…€OƒC6Éhì#àâ©gžM/óëé»/¿ˆg_köv[Wêã:ƒl¬ú±‰$XPÐ,ø¨þ5Oîh /ÃçF·nöªmZ Q‡—W;Ñ¿–™ï:[íb\¸ü`€*‹ÜCÛI IÞâGèKÎñðÈŠ\ü̸QÖ»¶Êe«ÛØ/Ùì\ùР-ueÏ8饎ñ* ÿÚú§R 9¤pöÌRÖ(ÆoßL‹‹ 顇‹ó@ûÍ3ÌsKKþœ\ÜM/½øU@íKixì" O9*bã(A iðÜhœ—ÊÚ]Æ»ÝJ¶AB÷0KÔ½à™dEAÏìn+O)£”OÊ?¯ täe£²ø;ßþV´84[ÛóêTßøÉ6?ø$y¼ Ÿ˜ÝãÈx+·ù?«¹…¯€€³Gþ<‰Ö–qÿÆ×¿šÞºNÛ ì’6xÁódvû…téæÜ›Tä¬Î…îUŒàtÏçŽΞ±jŠ•iÔ‘=·,….ïÙeËs½ˆí•ò»éòX?€:öcP[^\ `öÂØ0£Õg²›z»Èô¯§¹eõÀ PtÌÞ×@¦Ýð 5祶…‰fY«›i§(W”–´÷wÇd®taÀ1G³xÕ"ОQ%\!€„ ‚ÿSQ½ ;ßgHÏ‚-ž¿Ï½”GÚéî‘Ðõ<·y–ç;„ñÿüÎíÔáùR¿Ld[;÷¸žb¿œ/ò—ºP”:ç~ò–û,ÎnZÖ¹¦ÒÓóÇ}ðNë0~×·`0”ó·½\–Íìs°9ñéÛŠ`ø¹À¸ J ‘öœ·€o`_è´Œ¡\e>¼éœƒ×•Ýì!³£µI[Vmƒ€2ö`è´ð¤Õqô;”Õmá…æb܇ä{[ÿ€>ºª¼i9/ƒ¤¡̜ߩò·¼ÆYf ©º Á ÊƤmt‚ÏDûF~SÇŒ sèÛŠ>ÒÔLµû}óœføÓ52ñÀ9Le ¹b¯ðY–Ý ¸¿ 5ÒÔetÝzºÌÏgwÔi­C ÷{Èw«™D•L’¸€ŠrÁR•°ŒÜ’ÔS\oi÷á«ÜþÛ{óçȲëÎï%€D&rÁŽ ¨µ÷N6[$EÎHšK´F!—r8ÂŽp„±çïòOᇱ¥ ËvP²<Éî&Ù{uí+v ‘ $>çâU%P¨îªîªî–æ½*™o¹ïÞsÏ]ÎùžEÃqxå&ä%øÖ¹Ö=QDÇ€?å¹*NêŠ4h‘_Õcp)«C×.ùN”ÕäYûŠâ©·†€Ð‚~v¬V¡© ºQp4’pO¦[’0¢«³‡§n‚è:.ÍÃ4#ˆ]ÀG>çžÄ>R™6£#OyF£€*õÖ sYÓµ@~߇N›k¬ð¡ëFp;CÂ}—<À^Ž1U'R † Ê–®kòTDõäÝשÿuk$¯5öµúU p&qj2­‹kÎî -;R|L*GA‚ x& äÊ--³ +æFÅ…ii‘<¥l†Gð(E¹¡M!ßPƒÓxªF6ä¯À¢B›7~'¯çF¹8 |[ÈyúÑûÙèúÏíèÓ…óÅFV‹Ûðâg³†Ð¿·§'Í|” t*ÓÝ„ºÁ–߇Qž)¸¹uü¸AÖrÔ ö.›^ÁÇGø±y¥6ì`UæÇ96†£òå¨|ñºÊ • 1kx;>gNc3¯Àƒw ­Ó(ÌfˆÛxƒp¯ZÁ*àø¼‚r½‰BYKe„C ëÝìXOG7–†Á Á +ó‚ÌÔ")Pà´ÀoJ^d£‹ Áæ¾9—,À·ÀÜþþ­FÌ íæ|ˆzŒOŸÊ®á-9sf!§à¢uö€Þ­‚h w®£@3´yòDØÈN…@"F8ꩬàiÈ÷­6žcOæhpã;†§n…"?ß»YÏ!öÿ(™ #€ÝêÒ}Àak‰Jv½Šgs)‚qÏÖ=”\³„ì[!ÌŸÞ¯zÛô¢zq‘Ù($w+æâd¶rxEšoyÜ @€*²ïÜ@0XúQ¼†P°1@]Ìs¨‡¤‚"„=Úb\FPªõ#é¹'«²¦MÞfó¶·×1|,ãÓðvU”Z z©Dd¡J„O&<ý©÷¶Ê½¹êxs*¸(|Á>ÌÔÝXU# j€3ë†H¥÷€Ìéx„O£·58øã ó <ÀmcLo½üÈ·‹â¸¯ÈÝ·ˆ0ŒÒˆ T›„€Å`cxxZ]Èöá¿@¿Fó€#Õø JxóÒô_“æ*<#'%à¼u.o®Eß ²§)t)ÜsG€à2¬ÎÁóC~H…¶Éqá‘æ"æ Æ×ʱ.ž¼ÙÀ›lw >B·òŒsšZb¦>À~€b®…0Êwß«€ºËÞ!³ì {mï£a=Ãæû(aªz¥sNÅU‰0«ý•}–¿¹Óû)ÊCÌ«(ñ›'àQxQa¼ÈNå!ðh=cG¥˜‚±ÞRÑ6¾§Ã¿Î¥óô¥ëƒíý¢#­p# s©žð>§cB <:JŽõ=´]Ïßáá{¤EÔƒñ#oo£ÌÜ•·œKšÌwÕ±ÍQ€5(–#”`QOòm2§ F¡ ÒhÀ¯¶Êña}Tà’Ͼ€ßFNµìÓÞ‡~õìÜå7‚÷úæÎDHuÃ}w­Œ´°ÕÔÃd ÂÒ:/¨0(aHží:2Nds©ïÓç£xdj„´DÎr׊Úè ¢‘ÌcìÁ\A¤‚7ô‡EÍdCœ„˜ Œ°† †Ý—FMÉioå< ‘h Š2ÊÑà#x‚þµ††‰’À_•ÕŽ=(á1 á,³ÂÚ7yæ ^æCÉ¢K Þýô¼âúitÇú †öŸå:WGßHè#G/oxéF\+cÞå¹|ÙÚÜ„nzwÊ*á\ãr£7€u@¨Ñ±” 8…=+Xm;V¥‡ëo^¥zrËwîEœ‡5œÐÈ̲ôÆõ‹z¸®Ë7ŽÕq@t•=¿Á? à 8ëaå¾C;®£ÈRùêgÛdªÐP…œ^ÙCÐJ–ù\"˜$OÊçzÔ'ŽÅà :´Ù?èMíÀ«Tc>÷(*Å}¤³ßåMûÂö :Y_Ÿqo#]]'œçüÌÿÊõ*”'7*m»ÞÌòìÑVô´•*x-ÇÃë|¡Œª{¯†;;”¾>”/X¿ÂŒõOÐ_ù¥¿|ãüåþLe®àž€÷à—^|Ë`F#ñ| ížêÊ[†1>Oþc×Ié¯qÉ"F|òŽžœ¦J12Ê {D÷Ò¡Ýôf5¶F†ž>møæ;€©ìÝT\Û·ò³ýï¹÷ßûUöù§f¯¼òF6a'¼9üû? OØmÞ+©hð½·³kŸ_ÍVÙ#î/£ˆ¥¿æÏxŸ€Ùu÷ò•WY{ñ†½w;ƪm™„_æ/¾BXíÕðì pœu]P«Æ^õ#¼“mÿü…ËÌmg¢Ncã„hw=g_4Æ>‘©&h1Ë8=wábÐ3”ªÌÁzä;ÊW²‰ó¥!ÀÿäOÿ<{ÿî*Ù«ßÿQöêëo³†|¿þ2Þ5°*/ݾq3[\5-†ûàóŸüÑÿ˜]§nÈ[õ‘I¢®\Ó>b•x¿£¾¶¿×µi2ç*ú'Y5 Øf~Ñó×0óîájõNÍÂÓ·¯ zàcß#Kgç5ùMi‚:;Þþò¿û²ÿåßýÏÙÃÛ7Â8CPIÙèôÙ‹ìç;ð²´ëË>ûô3ä–‘0œ¬£¹sójä"¿ôê÷²×ÞÆÐ‹yïÓ>Æ@TÁ¤&òÚY€@@æ² Œ¦´Æ¾ÙòKL+3j´Œ öXÍ¡® žûŽ8i°égu-FP^¸ôúÙ~øã˜»>#µÂ­›×³‡·®aÀÆÌÜnù§œ:u2êòÙ'"ý2íÅ }>Î ¿ù1ÿògô¡”Êx§ÿòûlæØéÙ3lSáD c®r-égn•–aäI K­ƒåáµïõW;Ùé3' ¹ÉwÏ<ËpdmÖSÙÔ@c/ÓÛ´![„Á+}l¼ ÿ^ýüÓèó´cºç¹X§ºÜËîß¿2¹|aÊA÷jëðô8óƒF?ïüè'ÙÿôoÿmŒS~ó›÷²ì4ÎÐû?¼j]Cädveç0Zç×^ÅÈul^~àöA€hÌç05ó%üêx2ò›¼(Hï:d_ B^¸t…”‡ï¿†£ë›ÿ%íâþ¬ÃFrÏmýMÝ1ȹ*ò…²j€¼Ô_CI ž4œ;…î€ùÏô*þö×Ù žpçþá½iëÙÆ&u€*£â©+ØgÍ2*‚kq€Îî!ô< y˜q ÔÍóþtŒz§@ô åe ÖbOßOžõ3Ù hÏî:x›}612¾W–1^w½d”Ýk€ï˜s,¹­¶O†ƒ€íÒÒÞiÀ66ÙcR÷ä¹2ôHìGóY;Y^B6­qNÇ ë§<'ﱞ1!ÂCõxñRÇŽmu]jSÌ-cnwä8q?¢ªÑs\j€cxÏ °A7Ë\^n1õ•)\·Ò~Fok÷+æõ6™ž×î?ûã†6·.îõB6“ž|6 >;â 2DÀ€½8וWÓXdá¢~Ò,rróÎ2ë¢{3½îuª0J‘‘÷¶·IñBê"=0ÓpÝc­åÙ½ˆ=;ó—ôg(Q/ë@qöFì®gî ­‹†Ù%¼ ÷@b;xK(òTÔÉ:ìñž0¤|„ü¸æYP~Aì‹ñߥn;UÝÝÇî `8áôi£ï´Þ¸˜*ÃÊ—ÙšŠ/û”¯"Cµ0Ÿ{¶àË¢Yh´áš¢<íœeÛì'yÂõßö Àgs¤ ².-öî“ìÇÖ©ãby‚¾”‡Õ5 ƒ¾‡îdŸwª+‹²(4 %¸GY'öcÖ„zîR†üÖñAéÇéÇX–ió>½µKu¹ž¾€î]øÎòµŽvl ©×T¶³=§Ì¤!†²A]PÅÉ.:¬ÚP7öÜ¥0©½yXã)eiëôb_kD  ^eÜRFY½œ²åjȼÏ V5öeŒÓ:w¸Õ`e›9Ãw×è/.q_ÒéY'ÇŸåSUxD`>9”ä‘OÎG|©£Ô8È&?™>§ÓÆÐ'Ž0”PÐÓÔ~KÁ$¾/t¨R ((ð Pðpq»w÷J”›l:êÙÝÛ7BÐѳ\%éïÞ7Ãi” +} 0G!€@ì³Z†-“Gí¹ÈæTN„B‹Œ5µ8 |'(ðeܘƒ*ëäéð Ÿ^x bñ«0 ‚AžÏ…Q7ây¹‚ XI\¯’:ÐlbúÞ|.)DtÐ"qP4tòy­wõÌòœÖ¢II¬@3{êtŒ7=t¦±¨¼sc!„Œ}À½s¯½“-£ Ùd£hˆC½‘^ZxB»1-£,!ŠR·6 Å«õ0ukš·á±¨®çd(QÀ¸ImŒ €Ò6Aß® 0HSÅ£4äS–¾´Ošòž$ìð PCÄšËuõ±i6ïl°·QªpPÙà UÐ¥ÅÎxøÄéPöëÕ¬ðZFÜGI¯·»Þ·Ã÷nøïë!а65I.aø1¼iG o›óoü$›;ÿ&Š=Ò,À NÂzoá5)Mì«ÖâmAÚË0Q¡´½·<»G›ÛØU8ûíÂÏZ½wÊðàÈ ”…'/e«¤oØ\¹M?w³é3‚1h0'µù̇8_Cé»AЇÝÂ8@uá›UÖG½v6—QÂBjÚ°™n¶okhøÕÇç`)=âP"Ðwx¡6q&ŒZxF¨ ¸cģϪx¡öÁ ‚…}🹽ëc³!`sGÇ à_í çé[_æ8ê,ÝÌœ£~XÑÓgž|ÔS*Ÿÿ4Ï<|Öjš|·Œ! }´µ¹ÿà KÿjX¢áŒÇÖ4÷l-Ö‡÷„gJÌIÓ§ç£_TN}ãGÙ:ëû•¨œÓ `oƒ°ïÓ4f.š÷TÔmyá^¬…æq¤¿Ý¥Æ^èzûE:W˜ÿ**X¬¿<¼5Î¥¶Ê#z©û¬Þ{~°Ï‰åPy@#ù[Z¦ƒ³zN9ŸbˆÁ¡LÃOÞºŠ%®å¡º¥§k–‡@¼ô{–Ã稘îö}º$Oõ´¦‡7;5sn Å4õ(Wyj;ü±5£Æ({‰¢Àuy|e±<LÄo#»xôñ@ðÂySÏ ¬\‹sþ°þ1¦ ˆ¼TÁˆ>Hk¼´rÞw̘SgÇŒŠQHt”†x·o|·×TTG}áÉGüG9–ÑÛÞ'hµÝ¦ÍŒIiäÜ5 `´ðð^4Ç}N(±}P¾87TKûçé’<\“²pqp‹´Ç±VÒµEž€Žz<†g(ïµnÈV¨D·i^³n‚4*óTŒ«ØÃ|…w³bÞr¼¨¼û ýömÖCTǯŠqCß½u“p¹뱇[°t/" qó&^¦{¬ÿá©„2ÖÐП|ô!Þƒì0¸°³½¦ÁЛ³Œ¢Ót:òÂٳ粷 Ù>Ct¢dŠ宯†aàݤ‰‡´ºpñ2 Él€ÒîCÝCi¸ÉˆfŒ¥¹G¥å¹‹—²ï½óNö‹øEvõê§Ù[¯¿‘ÍÎâU<øPzœHu£ë-½p™ÿú1^`]UAªÞZs§ÏQºÞb*¸ÉY’SÙÓ=ŠoÁ AIÇ‘F&öw„#†¿äCyA~”³­žÓótûƒ?þ Æ2yÛ)GðAe®eÙ6=—ÀðÃfõ¿Ý @æì¥W “D§ÒTµŠa«rƒžµŽ'6]×Ì‘Obð: k$ MÓ#¨nØZÇÍí¾€Áh¸ÌÁ¾8C(dnëÜú~+»üêÑ>TÞ¹Á¼0‘ý›¿,á‹)ãÒ:? ¼òù‹³7â'˜ÿïÞM Þ&‘§]MódFhVÇ:r¿ f•½¡§][4š’¶Ç¹I€KZJ 0Ç00tñ:«xêi8¬‘„(‚mÿåõ_ÇýWßx+úHÐEp2öLIrüàóÒrÕø¬¡Å ‰Ñ¸õ@0ú:Ž`axïð d_æ‹‘J¶J”%å ÷"‚ÀÓ¹GtxÂRçü=Ÿá]¬?Ãzæò۶ʶ޽Œ[€V½€Kx= Ü]—9²5¬ ç@*< §éƒ5äüV Ãzô©IÖ&Êÿ ‡½ªîûO¦;‘bïÏ:¯ŽÇ½‚ãd {WÊ4À.9§ƒÅ2†%²6`Ðâ†yï< ¢k‘ë*j%ŒPàkÀH Œ4J©,!×€{Ýñ!Åáoç|×ÖD3ÀSÞkßmá/?h$+_ Ô[–`}ì™+ÚzéÓ&£lwShuçǦÆf¶U=Nì[ys@«/1›*Âúî…ó}¨÷²ûÛéš¹‹^IyEÀ•âLÕX_cïõ5ÒhÁcŽ‹AöƒMꡃõ÷œ «K쟥'¤»sf?áÖ¥ …²ö§¼ìÖ͹LæxW· Ìáƒ<‡ÓtÖÌçdÔRÆ|ÖÐ>Šñ—@²?:B8/9cXWÇ…ûgÇ–ÆžW×ã ~s^³_”gº<ïwõŽÎ³Ž7uòŽv#WÞM‰ÔT§ ?(·;WÙ> ‰óÊìÊ,iDÂݦ;ØB±QÀp†XFW4À5#"ºŸW餋}Ì3M"?ViÇÏ1%„7Dò‚/Ý'mô›æÎCyM}Ÿ FšÓhÖ¹ÞqèüÄ>-B^Â8új¤¤‘…}ë5Á÷Õ}Œ¨áwLÿhjÈ|$ñ(K’ày—z›o݈·¯}\èvFq((PP  À³S@¥È"JŽò“ zöÓüaæ÷Ôxmé~€@nc:(oÜU`2÷¹àØ´Þ¼öY„m,³ž=y*„Y…•!,={UŠ; ¢€›;7é/ópCÊÞ*6¦ –XØTº¹[CS$S0N!DñbàÚq¼Q7£‡õvÃè¦ÛPæÀô\Û¾» Oe*Œºi6Ê ”+‚Ò ì~¾N~Ì)¼0 Û«‚^EŒŠl7¯ŽÙPä±ñlà± { qS`ÊÆ“g/g·É­ª'ÅPA…ÐÃH$!¼Z`ÌPuÛ(q†i“¬ò@ £:LMÀ!çÃ!êç¦Ôò–¼Uñ ÐDT ›9{ ! /;ô+x)ï ¬׋P%?Dˆf'Á›!6¾û—*lëäVù×D¶…ò‘9uÄ!ÈûO]|3„…MšµP,„vÔFf&ʆ"á0 ¨J:­y” •»…Á~`-ݦçwÁc%]OLs7õ#ŒÙ–Áêž"' µøº Šõ¡ÌÃz¬à@NùfŬáëUÀ:§Û®„Ó­ì^šxž  *A-nûàë-xjð¼Kh­Á!fw¼_€»£'Ï&%´Øa¾B¹ªp©RÅlC€Ù†Ñ0垦9ðH 'áÉõ@'|ÖvÛT(i„¼Ó#Æõð¯Â\|¤ òT:¯Ðd?yOzflæ\ÜgßÉËz¯+,שÏ2 öeòÞà:Ûi-¸l.ê͇Ÿfer~n`Õ^¦ÎSsSY‹üÜÓ Ù uœ¾Ù"—wkéVvúÕŸF¹\ñêT±Ô˜” Ûxýëq®qJiÏË„a¿{-[¸ýI(ôVñŒYEi>=ÿt¼hÍá7î§ï·ü—‚ž†Ñ¬ áÆaÈGñš¯Áï À9]œcÚ£Ñð¦°ó‡!ªÕLE `(ðT‚v_ýHt–þ(©³WBcÿ¤÷#v lô‡ï±ž œ|<ßÑÌII)Vmº–¡ŠrQz”ÅoCÒWQZÙ>D–Ÿcæ. =«ý`ŽØØÅôÞœ°ƒUyEn§dx @ÿƒºð‡‡{ê'Ò¯Tí~÷9Å»R›{nûzƒþÔL%VOÑ©?—¬c€›£=^=t_ªð£‡l¯ëƒ¯xt‰ÒÍÃùX€ÞõFÅe½ùƒŸÆœî®i£¤Cp~>yæø—ûX?TeQ‘?E˜xçrŸ—×ôxt}¨`ÌÑ?(#øŽ÷VÂ0~ÁaøÄ¹àÕàG*Âb½9¨ï¡¶EMžüåüæ˜Îç€ü™èô*Ù¨[:ò¿ùºÉ2íIÆCûж!@çC|Íâ{þü“u9z&ÆáAz FD§ù*`­›}¢—žÀ^Þ?–—Ƹ^ÑX€×§ë‡Ç‘í·W¥ex›³Î Í:¨szžßq¤qEÙ*»x÷€<O[t¡âEùoî9ÜêœP´±v†z SO|ùá;bÞ`\EŸS¤áÃs€:B‡R”{S°ò6ÑÉ×h€ÀzÍ:cßé妡¢!¡¥MëXð’{i‘¨.¬‚ê¦Eaú Ðr€}²3€ž”u9Œ}ƒú …½ˆ©{ÌQk˜z“*7ͳ}’=Æ—‚fÒÐ6«ï=rZúWðS-B¬¹Ü/(ݸâûÕþp2L¸a€™DÂ(p#žû #%­ò9¼žáU=±&xš=#†´Ó±zýó«Ùëä07’Áéù ´!åa7¿µ{ͤwD¡Ï4ŽŸD @€4çmS´º©D•zå¯û3è*Págiá^`þu•ƒ ëúö;?˜CMZ$À=ùWž1bÔL„2¿ÆœJöä>§º´f§÷´4´~Ölwbl4ǦÙëMDä™?þÓ¿ oN…уm¤ï*ê7Êþãý~Ì9–¿µõvìûÃ#}Yô# aÇ–þÃëã“Ùòϲÿðó¿ p7Fms-˜`ë^cu¹?øÅ}u¢›òŠcoGê çš–kDÈôÔ]¾;Eh{£\~åUÆ“|D_zûœy§Ýgº?pnHGZÃ.¿F!ü—¥¿}4ç9ûÍ1¡øïáUü·¬?u¼ç½î?çUAsyÎ\Õö×ùÓåOÝ:ý¢@#¤¾>ç¢JÅšá¾Á}DŠ´ ò¿ÖJSÁ@IDAT‰qR9ÁC9êÕ×ÞŒ6Xÿœ¿åõ9€j |ܧNÑÿ~¶þÒùâåWèS<–CÊ–3;;—½òÆ÷Љü&ŒAtDPFÑx¸Ä'íôÂÕñÀô‚õòV¤§B–Ræt¯íš®q°ë¢†æˆ(Ñ;úy©¥± ½ù¶Çè—·Þ~'hâÜ+~ÓðøÜéyîIi4Þûõ¯²Ûx¬ÿìÏþœ¶¤÷&[Þ4ÅFýxú•b¾h°–Ÿ$B½JØÜa $0$w¬AŸ™ÙSÑ÷†ý¾þÙGÙ@¬óšr•àRN0µ øhŸ¤m 3˜æ` {Þ0ú0ãÞ¾÷]öãë‚ôw­Œ²•ü%?ÐÁ1Ïýò?ü}¤0‚„Æ„«mS ¼ýƒwðÒ¿Ah÷ÐÝõ—½8e™Áñ,°æ<温A÷‡w—HÏô ©'c¯a§‘U Ž##­hÈ3Øfþt“ÇôºüdoÄ5‡q…à[ŒêømiìÓd^^¤Ë#th„°? ‹Ø~ç=*ë-‰±ÕApš9z]7OLÇ81ÇóÑÓæÎÍóc—²—ˆ¢pûöƒì• ç³aæ¼~uÐÄ4-I…q'™»¸ôLÒÅ5Ù.–>–ÎKÊüò¶ Êi‚Ï-ê)½Z m>k•©ÄÂA£IÖ-úO€Mc-S¢^^€{ïÙNÌg¾w'›"t¶F>‚ØX”Bç ôœäÖ(Ëw ´nV™ÎÊ6»% 3ùÑáH /Ä׸ÉõRpÛpéuŒ1L9¨lD1 Í®ƒîwøcDþfѶ|?“öcêÓ~RñŒ†•0BðD‰±äÞïq¥õVši ã°þó OŒèÈÈœvƒ¾´ÏùVÚ*™Ôk)ÒB™}œýjÿê0 )×¹TƒW:8Ƨ|¹Éø3’â>cÈý¡©]§õÂwßçz¥g} ±¯!…h·]ÊøpZôƒQæÅ71`ÖÖ½k'mÔ fç“ ŒRäs«K+#9IWu.îe¶îˬ©±Ï‚ß66Xï™×ï‚íÄâ((PP  @A‚ÇPÀÅÉ;‚×?ÿ?Ë~ó«¿G ^Dàÿ£°Ê7\õBJlhªûY$õ@œÑêÛpWÿîÝìÞµ‰œKx.Ä&ž‹f,êǼ·8UPàY(ü©@Çf³ïÀ»ïYžûª÷ÄÆ—‡CYBÂuÅ]¼›¼‘‘d±©ÒG+^…C…dÅŸ2ܽ–åF}o”§{®ç÷ù×ûÜøZŠB¥ïKï!"Ê‘ñI6ûI(­ ›zâä)¼9”×¹‰sæÎÖzV:£§7^—º šßPÙœ™¶Må*[õT·õ\vc+XÂV;î5¯ªZâzø]!1þC+ù6Ö5”]nª}·ù-S`& Ú^¦¼W¥¦ÀÛâ̰[cxŸ»Ñm#ü`XÝ$Dz_™Ð¤Ðk¥“Þv–¹xÍotïC¼· ‘‚ŠXæ!ÅSòá­ã³˾_¡D|Ãó7 ·Ê êa½J#€ãíõÅ,@vh À¥r®Œç0á•õ¬CÇÎ*mBè)*SoLåÊež;™Ý¿ù ѓٖòÊ:´F銼ºÇ»õ¸oò³Gû¬B”<$íõN7Üf ØpjP,»{ýC„ð•ìÞcû{sô-(pÆOžAÀ?ðä†ÁöNÒo´) vÍ 9GϾšßú$æì=ÞôŒªµîúƒë(ŒFQ¼;χW)ów]ÐÅü’*™\'ü=Æ…uÏË‚‡Ÿ9|ÿñï;r6/äs¬Ñ©¬ãÚõ8T uO¤¡:Gë_ðèéƒ'¹ï‰ú*ôÑû#Õ#/Û1õìj¡0x‚¶Šê‡ ôÉ÷ü¼Þ=ÃÃ¥µŸöÊ'9xna9 ¬³JqÁ¢Ñ1Ánx¸ :{MÞ³÷õЏð\x?¹Ž=åÈ•ö‰&ò¨Ê>±Ä îoÒ‘d ÁgAöoü ÎûÖŸ9ÏœËð ÓÇ´cÔÛÈA(n©¶ûç vpë›ÂcÖ}Ø `ºŠþíM&öcüÌÌu0äºéDöˆ´Rc«£¬>sr=¤¯J\¯æ|“¨ Gú~ç…­-zà ?¼ß;ã:}¡Q©€Ÿ¹¢'ð´;}šô~ˆ£2¢á™Ï_~Y“\í(¨ïÅÚ©,i* sð~þ¹¹¾¬Ø_±¯1"š€µâUŸÐÞ½=SR¥uÉúFýò:Ú‚5h©G÷ìÉY@ÄS–á‡t—#zžÊ›ð+?¦ê1¬¾áÕ-;Ÿ_{ÿž>3{lËIïGÁ͸s< §«ážUk´a½}Ï8{£?þÓÿœö6ÓÖñcx~y7?bïd}mÛÁIiã!?çíéCqÝl&#¼ ÒD0ôÄ4¤üó½ÖÏr”-"78cË:¹?ñàr€ŽuÀÒŒ`§‘14°î>ëážÇúgÎ]ˆ=ek8,Uó=L ¥W/…Zô‡÷ê£G™Ü”÷‹ïÑSô‡?ùƒìÒ+¯Ñ.ò‹³·´DŽúC«¨×˜tE.¬¡•€2ä$Ÿ=kU@·ìh7k–ý"ýÇö_PÖhxV[çà%î~•¯¨ƒï“·äGÏ÷ámj”ˆƒìÏq½¡¡¯ë,ϸ‡´ýÑ?ûÃH `Ý¤Îæ'×ÃZà sC»ë€À°ä;é‰ð†¼xåÍ8ï»—ñòwßêØ‹aüBׇ/P/Ûi¾ÚDãÔEãëjg¸¿´ï/Ó2癃ð½\÷óÉ+o8ær>Ræ™?‰çf#ì½ýà\;Œ\©±Ás`¾Æ,z|nFëì9Ò& wݺq-{÷ÿû9žØgI‹ðcÎ_ ð^ ßy¤`0¥ Ñà×:¬³—Åà‚¹Wˆ\öƒÛŽ›d0•ÀWûR°Wƒëjž[çåd¿o3ÞÚ¦o`¼aôA†R*†=©¿BdB¹¿_Ï®~}`$=[ì“à½s®î ÷‰(࿉|!­5 Ñ»ÞFÞrŒ9ï "Á?‚3z;ºäÁü°n‚g{€ÖŒc-çÜ·sÐ`Þ}¨ É>¯A“6ý€1f>d#œŒC¿ÙÙÙˆ°¾žúÄ}ó$^Ò§NbL°¼Eøc ¶ió`¬´4J ç(© †G“Îü#ø+ %À[Á'"Ë@YTùrn ÂÁÐI°7E³3|7`cD™Öû×ñv7µ—{vá?)idPÁU=XíSf—ð´6 ˆçc6àûÌ%½ÅwAÚÆO[ïŒssÞt î§Þ‚ÄžË?Sqy/Õ:…±7Þ¬‚Ò–ë| 0.O ˆ»¥f^×Xcƒu º[Èž¾SCÿnâ³£P­ˆZ·©Ç·ã =‡œÕi­g°1ÈwÄÛ½|nHuGŠùª¥yØòס͂À›è º€•FBeÕ3,¶cÆ6úþ˜÷à CÆÏÌnŠý…i”ó©=‘?6àqÖ-xÝÃþt}4\½c[c®Aæ ËÝ¢¦”)¯‡ñm /qe: ç!èüéø(ZZQ+Œ$¸ç€†òCI@CËßÐ`Ôz´U¯s½•ówŒ`¼å|`8x'ÀMh° M©mð /þv<Ë{ aêA˜{¢œ3Gû”ÏS)ÅvCG–äÞD ؾºBVêà B·ÀCÌK+뀹ƒ€íð_8ÑpZº†žˆÏõ2t¢ÓóY7¯I»H}¡Ç?/vØCì¸oÐxÍÞ¦?\W, úîbG_Å^~³œCÇÁwÁø2´5ŠB¬WÜDQÌ©”Ƈ- B‚§ù\&2Ÿë˜QvÏ¥é.à¹ÊF#´”GmÐi wb4û6uÜ‘çuâ Ý<É=–Ï~ºœsN¡E1O¸Þ x?ò¹3çXÔÉ]ybŠÍÈD‚æàsƒ¤ ææÂY.lzoºMMŒ#(ž&ÜÖéì•×_Ï<¶ÅQPàëR@žSi‡;²oðp\ÄÁ{U¢ô^³n ˜¸tdÛ{;ãç±@Ý{A!ËÍ´å)œ©ðàkì>õ>ñ¯ ±¡g´ž¬§Þ!÷¡žpý<3=w>”ÏIA‡ðÅý*hâYÊPiä½ y*s|ÏÊï×FAwj ˜¿*Bͱ¥Õg$R0%Õßð„)*>}Æ ~Ô7ªìü ‚HÁ9y( q(Ý$çtâoˆÔQÁưä†4TôÈ8Â,d½Ž—ЋŻ×1˜ã!®)»„Ó†0RwZÐzLÕÈy¾xïâólÀÛÐa†ùlfþD è1¬’¦†w·âˆJ©~ÖèCÚÚ˜œx]AÐ\Á{Š~M!u CÝ¥ôX­áU= ªP_ê{kQ®ÀG_vº ¨¿Iýg/¼‰â Ö°xÆðÓ$/|kå. @:´±•R†Ë§)Ù‚!B‹ŸÃ/3mÚµÕÅ»ó§ OÛPD5¤UÄIO=¿#tyI š„™e#ûW©ûÉ‹ßG·F= [)oÐ.çî!^(p¯‚¢ŠÇN€ ‰ý`’µ¼L$o Uhœàw+ê?2øëƒKÎçzÛh -ø¸ åƒþ´'zùø§¾ÞYÆfLMGßý<¥ö<+ÏôG¿÷^ó3×<wäsÈÑÛ‹ï)ÓÈ¿òIÐò€Þ9ÙϽ‡´FOÁ­ÏôŒU¦äÌ?yÜkžÃÞÃy%Ë9¶÷zïþjŸŸ>NŽ”™×5NÛÂ{ät~±¥>¥´CmyÊ=qúëµóë´é‰g{yè þOÜsÐÏ7Uâê¥|˵ÞûCÙ†27x‘²¿ŒÇT¢K ×7 >ÜTDæ!÷X(±Õp ‡ë°ûª¨¸2¥žÜ*n¿zÒN«Œlc$9Ñ$º×؃×Kп+ w³mòš·YãZ¤ú0Äp•=‹JýÓóW²ó® ¤q/}{?çd~½{ßüžüotqôE¨7—9a›y×›ß'»xþë.¹^iƒ{žÉ©&¹´/Äe7Ø÷4ØÏY7ó±›Ïy¨q-ŒÛ~ò‡ÿ)a¯IE€Lê:;Žç¨Ÿ{ ë,påû‡{×eë›×-x‰{Nð¼ûæ£íõÙüÞÞrŽ~¶øÍ{}G¼‹ÏÖKƒ¤üHeRÙgÎ_žèË“rz*SOâß?­l­~švQV*5/íqcÏÌik×É»ò±òø‰ÇÏÄ>SZ5KGe`žÆ Z’â5®ÄB›“–Éýûü0ˆÏçwîòýzÛV??6&ê}÷ÁzÀýù‘Ó;­®5éžtÞZ¥#/WùDo•å &â–Ôÿ¶Ý¼­¶Áî{)FjŠ( Žwt>•ï»õž;3Ïä€ÜgG¤Ѷ¼¾9Ïù¬ a~xÝsA9>ç÷{N€÷¢¡ÔÝ×S¯hÝìK1òy¸_{ëðèŸÖ(û€”ŸŒÆeD€ó—‰äE´ˆœw-? 6)Û2D9xo^—ÞúitàùHu‘W::˜èmööÎqýIÙT#ŸHÀj KçRå 'ÓFhøñ÷?ÿ«ÈÛ~bî,i¬Ñ†ù·ýþœ89N÷nßìDÐ6¯ø>rè "©ia$ Ã/X z/ÅIïò×ß|+øO™4<1iCö)t‡Ö?@t®Y?çCs »Çñú`3}yÍ2ËhLm}>F˜l£%kœ%ýG0‡ïÖ™S—Ĩ!/dš®ÁûŒ°°øè÷ˆüµ64~qL ?¬¯ã:˜Ä·Ð߯óïvXÛØÈ£.ÐnçSôDºÙùÌùJž^°öó‰™SÙ{¿þúb(;7DÄŒájv‡TqÒÝ‚î–oTAÕ1Àݦiá¿Skøk/e[¾ÕŒ¶@EÿŠwê «ª)ß6‘¿4pÓgPYŠzn!Ouˆf`9êö$wá—aÞÈõÏ5pr·4X‰ú¾ÑÏm<`ð¥]Côÿõl£ˆµUÐæå¯|…~ Jt„ˆȽÎ׺Wù,зlg¿:¥2 °F¥Èò 73D0œ`ìMbb´‘­#ƒÂÄ1f]Û4¶#Œ÷aDϽ5ú¢uƒšðšàaß/ˆ¯Í‰cÙ8FÄFMn…1ZÂ:4«C|Ö … ÑV¡áÎÏzôk ãšêAcͱ?´›ïuúL½Ê×÷IS·K8½±óFfhl TV)k·ã:œÀb£žhôP…®òe–5à¶Ã~ˆ?|®BW iìï-<óM á¤˜úç×ðʾµîòÔžæÃ»{Þ0âñÇ,ï¯ò_Ïfë\«kˆ´áÜ:÷âLîã\ hk>ìúYc˜g$‚ Œ îC+SŽšÛïFЀ£‚_lÐ û—¾«éc**Ï”è'ó„›j­ otwփϬ“‡ ª¦ùëGwOÉ;Ð f‰6iAC›m 4èÃÈ„Ñk“^ØãXu푞é³íDÿ@]l~ä5÷eŒ#O8·h„sLtÞãºoŸºf2vb>2êûõ€ü ªS­«¡â^mM'|BÝ|dÞ$sv…¹· ·80Q)ëh*œlº:æÝcÎs{:"p¿cR££Ä޾c û‡±ÝE·F!T]+<Ïî÷œõd¶±Â¶›w  [¦ëa—h‘ÒU§…6ˆ>râÔ|Ö¤Sˆh°àa$ Sè ™ÚûœËï(ã¶â׋¦]Ìö¢Ë-Ê+(PP  À·E§&­Ö²‡ïþ¿„|ám6„/­ç=H %­g yÞ6Y ¯¤°­§ \1A¹Š_ÿÄ)aŠÜÄ}Å#YÿT²iwÜ´+-@®€ØÅå@¥§J$-ÝÝOêI® §BK¢Š A9Ø4sS®T9T7fêpŠïU0 ®¡üssŠ=Î+dÅËh—J7ï*AùŽl¢©ÃÑ#Þ/-¸æv<) ­OÚØ÷ÞŸ+A|fˆ0Ï*¯µ,÷Ð»Ü ¸J$]3=Ï«CC`Q ²ü™soijõÔf*£™ËF ë„gr/nŠ: ´ºÑå‹4¡¾¾ËÜÖð-CÚÉÃ9ÍÐij„» æÇ6ý“ÂI!‚¢ˆ—ð ê&¿„²‹"¿) ôŽÉÞww>ïîã®ùl~½·œÞÏO{®÷ž¯ÿ9™¯_NQÂw9Hå|úE¼ôe|èܪ2¬x[=X—]óóó¸²=Ñ b^þf)c{bí?x·kó4FƒÓxxš6G¤¡žõjÕ»[o Vb~ðzZZˆ0䜄ƈ ƒ®¹Óó‘“ù&ÊKAŠóWÞŠ°é]ºž:w‘û§bïçHú2Z~%¨럑F#DùÃwÿ!Â%ߺu¯¸zö¯ÿü/üž\ó^•¨®×§Î^ÄC~ž}ŒûD ø0@à û4çÍí­ µËCÿÛ§9_×Öóh[Ü·æ^ë>ûUãÞwô]Ç•-¿i|®&<T÷Õ=‡m÷# þ÷ž¸áàWο~ý²{ó礹ûŒdˆ!ž¬¹€HÜuÀéŽ4Ï ‚†²Åtï;ýl¿ÓûüPjzQ”“ÿ:îܓמ¬Oï=îs@øpy @È_{”7<Ÿö\ŽŽüÆø˜Þ©ü“.=®ÃáwÄåC÷wýiçŽÊaŽã>öÄ4Ê´mMÆ!ï•)*F‡B®“×û²³§£¿0óöù åæÜ|ôÝG¿ÇýôÓáózDo0£g¶€šïì½ÇÏñÞå!h/ï¤û<ƒœˆlvæü%¿ „®hãŸy tî³ï6¯¬¼£A ·Çü%ÏÔÕÌ;îÜ¥¬©Îé4óÒƒ{· ½Ü "Ón„6ØC#„>æ{*É•qúQ­—?ŽûÞ¾±O{rƒAã“̯ÿÉÏþ,»~õ3"bŒð(gBsÎGêÊŸ˜ÀÃ8–>Ñsшn•¨3"F€îÈç‘€_`ïÁ½;Ðb˜Pî´c<ú ÃÙ©­¢–ßü¯Ç\뙃M)áØ¿Oh}+%¶‘Y=$å Ñ%/cröܹ˜?ÆýNxÊj|¾º†16÷Õqõ‚5l¶Àm•\ÞšÖk¬'q ­VÇ °JïRc.B7Œ"èÿ2’:Ž]i#úA´X¿ûúŒ˜FxH£×I6?÷&ýcJ6. M·³J„·- JÜ¿“éß”TîuÀ¦pîÛñžÔfÀTÞ;ì~EÞ }“€:Þ÷tŠiOÌ1?ÄxdoóãwùÌèF”P'dÔry‰êñ“údƒö…Q m ƒŒ ìO ò˜}óêÅÌÙíû œßËÎ΢o‘†”ošaûu–1%€ùÌ+#D› œ•U9,#Í;ö´óÇý¬<Ïæ¯bäQì7TûÞ>ó/å †³’AóvÐT^³Î9æ÷³óLè{¨ü @òîïà}¾ÄyŽöš_Ü~ ÞeÚVçp÷~Žó|æå´6D¿ROʰWoäüœ¼ý¶!­Ïù£»t1²qÜÔhËV”‹’ózˆwû{üÕ1%êjÜ¢k ½è+Žuè³Ë>4EoÀX`Ðö»¾CœÝýͬL¿ Æw˜;¬eµ=Òl`\ºHÚí\°¿×9À¹ë£ú6DZ‘¬G§½ŠþŽñÄ^R ݹq-[«XÁɦƒÈÉSgƒ¦ÎGæØ—;;ð%u)Ž—L™ª8 ((ðOЬdn*\hÝTݹñYlJÎ_z=6îßáBÈÝ{Ý0<¸w  ¡¸”†ÌC¢pÕ+ý“¢Sј‚ˆMé× †@#ûÃszj‡' cÊ=†KCM)ˆûY/Ç”ž,]Bé0Ä8 “1k]øõhÜ© y4ùœ<¯4òóIi,èǫٔ³+÷y„éüxô¼'b×Îv”w>툰ï6ȃû=ŸÎù5í`à8›l=Þ­K|Ïnºyyj_P"ÕA:T½ó£@ªÕâÔPcÃa²gc¯@CaÔ¿g›ÄO|íuB‰JËh–fuo·ùŒZ†Ê9ëýÏ÷¼¾óÌ=Šì“ âý]”+Ò5”¦üM"Mªy(÷­G~ð^ûÛç´¶ýI‰‰ ~ €‹Ë\g{û«çs*âp¹svz•„áíõ_|áOÞ–Þ*¥û_Ë¿Ñ_ËñG!Î÷ô¶ù‹žû*×äiæÔ¹lxúô§ÿPlŒž˜á^Áª‚ e”û1ŒGX§hzv:øÔ|ªxÏ{žvoÚÚ–ãy:xö)מVæóŸ—vOêi4²_ïoŸ|>•ù?ùÈsŸyÝ,¨·MÇÝ÷ľúÈÛ{æÈ-_ë«åç¼ýè]ÒÔºó#k§óܧ¼Àý~ÏÏêEÍX8JáGe=cízé”±éR‡lÙS§Þ"{߆¡}ÚÊ›?W™õܥט£Èdã”™Œ&"p²"ç”cÔ#1ЈB¡QêôìäÚ©åŒD1wæB€ÍiMã'Á ñPÌWós˜&‘† —_½žæ «éÑÛŽ8í-…§¸ ¨e"_Ù ?,ÇyÔ|ÝΗ†ß×c]Ý—é3zÑ ïš;ݸöYví“c¾Ó›ÞöFß?"4DËó7|ómSðôY\^ƒ¶„­Æ«^Õ àÕ%Z— šF+zòWõ<ˆ ã}t~u<;[ /œ J64~b­bÕ$µ£ô—fz|@êijùÖ‚†¦ÊFF)+õù’¾\ÎÓ8j Ò à†§:ë…iØV—–ãY¤ï§Lã]¶Ê¨™zœ·Eþ8oŸúÙÐ͆ݞš SóŽ·ˆÞ· ¦ç´m p޾²´}xN Õkò•ååF&·¬tȉ¼ú6^僀»þ‚KcEð1¼ª W½Q€9Ó«ðD¹ˆ¨‡Ð ÊÚ¨G´ñJðcÃC᩾JÊ/ \;¤…ô3\x ðÔr Í=DÛŒP#íŒÈm.t$Þðz¶Öi¸©Q„Æ ŽÐb€Påz;ë°zÊêЭE>uHCŽc䯨\[ÇèßzòÞ @õaøaã;uRÊ׫ü`!Ï=€éö%7 ÎV‹úJî7å…ÿôô-õ%¯þ ï6¯¶@´aÓpõ Ã. Ú„‡ÂsÀ¿Ô‘E/dúÅþ–÷t.±ô¨7ßvð7h`Ó"ܽ†ö¹ØëÀ ;÷Xg#ÏÉÛæ¶×p!Œ}ãŸð±ò>åh1È5Çq?mØÏ ÜotF 5(SŠ£ÝŒ1#êiøzxu°²ÿBTŒ1ÔÈ›Q§ÐÏ¥1“ëÄ¢q¾—v„9f c=𭻀;MâsÂ4`ŽhcH ÿá[øËÔÓùÓ¯Ûhd–1oˆ=È;èUÃ0!¢ ñºA,J*€ïèêN‘BD¡[×?#i†4À‰¦FÊŽs˜Ñ"wô-]ÃóÅQP  @A‚ž›n V±*¼@€ü“M +2Cë!ºN¨[½XµœÞ&„VÂæ@rál¾oéá]¬sÍžª'„—ç®Qñ@Aï.ØÞ}­#©ÃÕ„/eŒy¨r™¿G¡\ABÓ¿*(ÝÆ^Ø_=G^vœê¹æùØ×ºÛçˆwpâÐýqåè¯Ãå½ê÷Þ2z?wo~ιÃê©l±}éûãwVB>>å+…œÊÁL-z¥˜ÖØæÓÊd@`ËAÞ‹€‘–ÝU«‚€ÊÖOrËV ñÚÑ6æ@©Â©ÕÊ•“zÓ§g,épŽ–áQÿeøjaÞwˆ6¾?ÞâÏrPˆ¯> ×á'N{íðÏõí=×óÏxsNà7‚½ß é6€fðÀȾ¡:¬k*EPÚàEÎMßÀXSHö\R¦A{xÂ[Ò0T‘‡7Dt¿zÛ7¤¢\CóbC‰k/å'ž³ÇW®øTP  @A‚R ”ËÈ*U :¿&…:ó; àç[¿K4eMgï³ÖK`ZE¢ÊàA”– ²êi®`Œ5Â4­Þ>®zßë¹Ô‡ò{j–Ô/é£Dç‰ÈÜ÷2¼ Ci ¢gÞÊé ¼jË(‰7ÖÌ)LúÊþ@s{ºXG]nÓìZ-æûÁ)Z/ zÖè¸ãÙåe?û/öÎ/{ÿWoÙ‹«çÓêø´ó/îÍ/·¤ïBý«CÞç½l}ô>FarÓžn¬|õRyùÇ]ÏÏ%àCïÁ4Ÿå™üÙ¸·§òÊ5Iõ 5g/©›ÄSéÊwDcüªTOsã`D0:r3g9Ç jÒºg4ô¯ž…>ù+««ô2œ±÷|ÙÜo]BæåÎáÚùsL¢…­¡N™ë¾˜§wâ0a³ -j´ Ýl;Ù9¼îO2Àpôyjë×õËêä½ßÄaØ~ógMŒeë´imu-€V½Ç7¡W™³ë„â?áRvêÔéô"u~žž /æÅÛK }²T ¯fóÒ·Y¤]Pyb”´"€eRÓÙ]õ„è)LA2xŒ „¹&àupĶÿôx7kÒº>D´9ø@=£e[†Þº®Éûà•›#–r—à,Ϻ.n¤ ÜŒïsçM€ÏÂ{“¾Y¨0Ö¨}Ye6V%û-ò¡Ëžûh–ÿm)=Ö7ëçwG=ì­p„Nç>C[Ó,B±“ÿ™¶X7AÒ*ù±7©“Þû^7çyÈóÔc¸Fz<ÖÍ@uc={cCÝp Æë¹“ûšõÆ–¶‘C%íï /g@H^XN.xîóþ}òXë‰Ûá½»;€÷ò:sŠ`·cÀ½„ù뛄ä׫¼ @ †ùá×ZRž¶ª;X¯ÓÏæÉ¶Þ˜\â/ýz(Bõ“[ºB…­Ã&†z£[ÁVø]ÐÔs]ÎE{ÛCßcò` Ð8Àñ¡AÉö6ÞÔ”Ø!‚äÃuAæEH`|Tïñ<5Ñ4ºÐp@à :ZÂ_|Àã y'/ü8våÓà÷[vrô‚MIºA ¼OšxÙ¨wÛt¨§©+úØ ÐÆèÁõÅ^tl¥²õ<ŒÆhÀ¹¹ÁäžÒÔvF˜Ý†nz…÷;Èà‰4&óý·ãWšJ+ù‹ºBOGZŠŠh¿§ÈÛ[¨X?®Æ›§¾²Ý›u@>·æOßá b¨§:ç 2œ#4”u=˜w.Ñø! èÿM *ªDQÏM«áþ´]4>·0B‘F ïÇ{Òj#M‹£ @A‚ <+\û¸×Ð)n. )¶J˜­»·®±ùÙF |‹Õƒ»7Byã†Àõ¡ºô.¨°i2ä­ž¹RëY_]ÜWP  {?„7Äni=bCŸz©Xa°r|Õq–žNeÿŽtí›øÝûþÞÏù»;—_ƒ@O||¬¬–éƧ•Ï<.*=³`,N„àhϤ×>ñ@”è=è3ùûBa|PH~îàëüy’žýÙ§ûd•Ÿrã :ÝÓ?/¨Ä/,&ï—¼ôµRî=ÑžzäôÍû+ÿž7#)Þ|Bæ'ãëñ„}Ì‹O}eq¡ @A‚žƒ*.#ì9ϨDÍ=Y8|ü\üÅ«·ªÖ›Ê- JQ× •Õæ…Pò\#åvkùì! @’͆0&ØÀKrÀ:ÂÒ¢,ÔC];”©7¬¹ûŒžð;„çO ¶ÛÛx»3Uw¶ðð¦Ÿ5°ü45 ¡Q ôã5:Î÷鑾I*´!r„[ï5öÞ?8€±åÐVçƒ @ιÍËÐÎa³CÝì³ m”?lSúÚ?¿ž·n>o(ú]¢”ui´cÑTƒð§´5{Úøls„\×€«Û[»Ùú&F†<Ãò¼ãÕ ¿LNq¡þº¸ŽÑböºó¤ïuHà­Ä¨§/»<Û>ȯ[¹Îò׈è04ý¡s# ð¾ŽO¼:0ŸÀ<Ÿ(kèèԧцm@b ¢ÏéwÛïâÕa R-øbýÖ@8]$£hË”GŠÍÑΞao ]¥ßåÓ ìÓ, ä7 8x8æ2Áz½ã} ÚDîzÆ3'bþ¶ß€ÔW‚§¬;u ŠAsÇÏïp»·X§ŸJ\Û£ß;çÎMû»„RgÜYžõÛÖèe›>¦Ÿ©4tŸÏ{ÊoãÅ.ˆn}ÓÄsŸzÖˆºÐ]†—©_k}ƒöÌ´Q#“æ)j××h×~6}êLHK£.<¸[x ðeñ§ @A‚ž‡¬[n Ê,&†úѶ½¹ž­,/àÁ0ßÝdMŸ8a"?›°5Bí³šWÈ¿§µ—‹¨“â((PPàù(Ä÷gæ"àÙûäN&§žãùiuøy‹z–2ŽëÃgy®§ªÅǯI¼¾*ÝŸþÜ“<ñ5«Z<^P  @A‚ÏAççQ%¢ HŽ›Ÿ£Œïô­Iûh¿¡ÒTïLZ™­ ì¢#ôñÆú*^=æ¬g7>ÿ¥à|(UU`ž¿üj,ø"m‚>ÐíE‹¼§2v'»úÙgÙ/~ùËììüy¼þF³ KÙßþüïÈ¡Ëwòíæ`Ù‹|QVA‚_Oßã~µòŽ>åÖ;ÛäŸcΈ/‡ü£õ9úüÑò_æwÁϲÀ™i'ü.p›ý‚«y»Âøý Ò¹üñ2ëøUË:\•ÈÆ‰LRî7ï¶!¯«Dä €êÌÙsÙ<óöÕÏ>¨ÜuæÜ¥ËÁÛcMºyë^6{òd¬Áuk-¼e¹o(¼õ%RJT°”-,.aÄE¨oÀjA»6^îæ¯6”·( æa¨ ñ&% d?áª7Ä §Ý3ò6 {„?‡y\÷ñºääGÖ=di%ÂÒOöeôv‡ ¼®Þ·ö_x wñˆè£:€YÊÌïXpÎgû{ àTp0RP–`£à¹õ7L¸½¿#CsèÓ0à/Žðˆ….zëþ^žÑpAW€_c7éa>t×e`@ž!ÍõÞÌô}F?ØÁݜ΂¸-0[*Uèi%ûœïØÕÛÜ`ÈüÓ¡™÷M¢>Ì~Ó”ðDîPòYÐѽD—÷ª^žlö òµ»ÓFèEoîú*†öam²åj±¾ºœÕwÈÃNÎûÈåN‰y¹Ã%@lÚ­@~hŸ]m †({ºØæ!<ë¬Ã×lú7 ‹ÓŽC‘ ÞO´;XÏèÐ9/m‡t¥¸44DŠÐ3˜œ›ÊNŸ›‹È¼2T¸,ú×¶î2žÛ€º-C‰S#K éêb°y¢../[pêCê,3éI½¶AŠRhÖg8ü.s,G”~hÞ'?û9p¸QƒÉ­>7‰AJ7ûôê+–%(-?€MEùÆòŠ¡öŸu– OnøÀÐï%¸.}Œº¸Mú12ˆú[GÊÓè@^ÁÌ" #œ›Œ €±iÚ R¾^äqèEÏX’Ǥ9c®½Ïh›}k¸ýo”ŽhÇQ#4°YÒÚLk¯â((PP  @A‚ ((PP  À‹¢€?¨å^TqßérT>†—ö t»ˆô5wúrÑ¿ÓÆ&gPfÖøYùK¥ªÑá9…ôEA}”̆½xùrvåµ×ÂcJ` #{õõWÂ1^ôû‹ò xL¡¼ïÊŒø´zÄTU~Ú©=_|õq›_Ƨx7󚩜ÐIçÌÜJÊ'ŽGòG^kÑ·Yé#õ9îkð0;w&;¡Ó2ú»¿ù«ÿ6V³sç/®z#¢‡²]½úI6>1ú¾wïÞt$/úx5[$ùý‡ËÙ©AŒ42LuAnTAn=³ï÷ã#xö@šVD€º†‡ð>Àߦ´í„/­,’´á)žÅ\ß÷û1ºF=Ç m.@/èe>e½ç W­ÃOW¯h½ÀñSº@EÀcë­íV´!êÀÖæ}á-ã7?}gƒPè›xÑŠ-—Yσ}·Q_ôÔöئþx‰Lòyz ÐúùœåïéHÄEzÃ|wÏw×Bs¨GëÝ@üN+¼ëµ!Ëå» pœÊQ'ÀfÚ*XÙÆ3ws£aÄ£®Ð¶Â­SS¤¨‘—Ør Ùè©kuAQè « í6yºw+^Œ7·÷†ØBD^ó‰Qu¹;”¥÷³FxæŸÆˆZF¿Y8n˜“ÝôF Ï7åzÚ_ JQŸAÊÔ{œŽvv¶à¹]òÝ“âÆ¾µë^£.æ1ïà_Ç#y{ý!Àþ~6YÇsŸn©Á»¾mc-ÛXÆHè›ÌïðžÐò”ï†'¡ß.?/&ZûUà Akpa€c¯Y /q5|0ï61ŽÐXž££_~woÆ/îø€±c¾{=å}‡žà5iËßh× ¢û¿F(aÚ=´‡HEQ¢ uïÒ*H!ß]ÇðrÓå)œ½!ê5Xž‡?,kËt?°FÈz=Ö¾aÿ6xo#Ÿ×ø¡AJ‹~æ‡ÖÛÔ£^äCg˜ÞBššp£àyÚw–h´ÑŒ¦ ïhhÓOõÝ-RF`¤À‹Kk<ÂËwH…ÁxÝ!¢…cVƒ˪V Ô˜o&²ÑqR±§oÒVRÈò¼"O 1:ÁÇP6{æ\62>‘-Ü¿Ë^¸ŒAÅùãn4ÝFs”ûƃž-ÞYN—_}£Ðƒ+‹_ ((ð|p¡e¡siom¬x^êÄ{`"6·]B´{qGåÕ*i­½*lª\ÜW—cA« 4YˆÙ̺«,Ž‚ ((PP  @A‚ ¼ |à¹LRu¾€ ¿ "‰ –ÕÍ; @€’rlÜЩ þN„üµ­L†¸ôɵñ׆¼h‘ ϲUŸ?—½ýöÛÙ¿ÿëOhÖõìâüÙl~~>ä*: yð1@QLA§PàE識楟vNÊË—þÂc^ ¨§Wµ^žmÒ6[ïè̇ˀ!ÖV×Èw®7fFØã©©Àì›ÙÐt#{C¨Ï¯~–ݸö9ú¾eæðìÓßË.Îý0»}ÿa4yн_i Oe@J½oÛèë¬K‚‘{ä#ްÏЬM.âu@ßò Ŗפà.®lpy‰éöK{xú³o B òyÏÏ€}ƒxá`MÓ/xfsŸ^½¬FÔÏpôÝl VØhš‚¯YR7AéH‹ð¿zMf×(ž ÄÕ›[º ˜ûÚgìÕm¶;yÓnÚ®7¿oÖ|0€^rLø |»>v¡‡aã` 6®÷9ëg´w Ê÷¼šötR®ø¹´”nÓž”÷}‡g7Ó«@ @0hOÆêý ê`¹Ñë[xè \Ûçëk¤Ú¤N¾S0S ‹øz£;¼sÉ[o(z=»-r—¿¶±ÝÑYŠ¼çœ¬  Sžu×hF‹]C;ó;C‡1re7ØØÿÒbKzµ[ð\3©w¡xUÛñöI ½2cJVý³a¾Õ'ëÙo¿oÚnqNð:öôµ-æT¯E@ž Ç;ôÖs{€y ƒiá˜$ÇOpçÈ ôÛ0Gq”Jäž$z*<ÒWßË–ZY‹6Ô0,€ï³¡½lq>¥NXoÐ/†s^þ¤5-—Ç# 3€/}Bßß<æ yniµ x¹L„è-P,ˆíüÿ$6åÒ-._#ÛãøŒ{øâØÂÎ!@ø-ÀuC›'6˜pÆ(Dž)—‰¨`ÿùZ®u7ãü†îd·¹¯LjV .­[™ztöÉçNùúÅ8¦üJe?«b9TŰ¢0ùâ ÆéaЫ8Íé-®¡Œck>›œ¦|<ûSùøm=ÆàCÝöiè`´#?ĸ¢Þzk@#¨Ò70N(w cÉ26ZÏNLŒ›8æƒ0Pà£NYY62LŠ" yôL×ht˜†¡7¯¹é-xGžãÇž FX$ï¹Æ÷ïÞÎÎÒ/g/\*ô Rñ« @A‚ž‹.KZÞ¶ªÃBi^ôŸ:wéѦÉp*{l")¹9<a|ÖVWX*ÙâƒÛÙÊÒR6'„ iq((PP  @A‚ ((PPà»NPÒ¢ðná]·‰"vrj‚*¿xÿ]ÚÊü j¨ïiÖAÜ‹„LÆsØ(#Ûz}ã)ZÁ{{oíõš5WvÐUÀËüÑNü@{=hõjÝßÈ6–J±¾úlß0Özg¯Æ¾Îys%›ÿÜéz±Kûð¬Æ+$8À°]¸foWMS‰Z Âù×Ñ hØé”£\v—N€VˆaHpšˆÖ“wó¬žÞm¼‡‡ ;®oÙ¶²Æ të=]ÀÛœ5÷zUxÞây½\òMs)ø-dY<—f]ìð#_ëáÛˆÉ×7/xézmæÔÖpÃ>1 öÞÞ&÷sÎç©“ †Ô¦ª¬é®ëÔE€ÕÚì ÌO_'D¼)ºð£€{ò”—'Rtƒˆ°àXpîêÃû|)è«AÂ@?t¤l?›s\^¤âÍœ‡¾ût†!ûWo܈1'øÜn/GýËð7AÖÀ ½B´Ž…‡b¾ŒwîèÃOc÷ôã}ÏÉ“¿ÊøÖ{_à[Ã;Ø\ñíØ35 ƒ 3ÚðäÊÚ&tX¦>¨;%éÝ›Ÿƒ²l“9 ÞŽ1Ù¤.Ž1÷Ón,>¼Ç|@ªƪÎ~KÂçY½9Q– ¿wõÓ '³Jq((PP  @A礫¦V’òÍT‡ÝÂBeh-µ&±N]m­S ³Â›YsŸ¯`…*XŠõÛŸÓfÕ%Øí¢‹£ @A‚ ((PP  @A‚ßM ò 2¨ÄÔ»éåË0*2QÝ!o=Ë‘<¹QnRÑ&7ÖOe2ºÏ8òëÏRÖ×½•ì£w.¢<ýèƒß¡œV9OhT±É;©¢×0­ÖñåÓòë¶èßó‚JÊÚ…‡ÿ?¾¾{Ñ5~¶äE¿õÅ•§ñO1™}ð»ß¸Õâ }Óüí: ´jçHO¸õ;BäÐrñKPL0ê»~Ø®]ÀÌ{äls®6· çÄÄTvóæìÿù?ÿ:[^¸‹Þoжe< ¤óPÖ­zðp)#ÂÉÄÄ$yƒo²ög rQoV Ð*{p˜¼Ï”ó›V ¡O4ÄòöæZ¶º«7/žßè šAÉhÞ!ö¶^ɬU@LCW r«[ÔÓØz›ÿº i?¸vz °NpZ0·C™‚È]® Ù¹< *‚ÄG¸kÁÙ:e7Ç Ç?oŽ÷~Á_žÀÜÔsœ2‡]®—·^ņºÖ©È0í«QºÅó9½û"ꦯ·`p Ûr÷q×ø¢ë`òЀl݃eö6Î4(]Âä < þ ¨K ç\E}] S]‘¡ Æz«÷v‰¼Ý„%¯ÐOuhd½ô\÷a#…æü©!ˆ!æã¼»ŸÜÒ®ÕïK )Ѝ9¨w1|Ø=¥mwé/Àièl ¥}Öã8þ DïRoÃF,êÝ`»oÄôÈ-¢›g\V~Ø¥ÎFvذÞÝÈç\‰º÷a¨Ðíê‰,° ­ü=v˜ ín¿¤zÐÓÒOàúht#O¢a¬Ñ Ïz ·C½õ˜†;¸ ½ÓGDKØÇð‚ºHƒMÀ|®d£õ„÷SF#‹‘ü¸±•mß~—~h7\ÿf›èÐÉgí¨¢ÞÏnç°¡©õo2­¯-F¸u£9ß3_pgÜëÎHCùLðYþ [ˆ(`¼Í÷àyh¨A‚cUà^¶ÐÁBjðª}‘3¸g{»¾§Eäw¼’^Z ®;.œk©j¤ûé0Nõb×èÈFØa5= Þd^ºžm¬.’V¢Jô‹xÖ¶îtײ;·oÒnæá ø¬/Œz ]ÿÛ_ÿCáNßGA‚ <'\ØÌ»lšÜŒªôˆ˜ÅÅœ.nL6ÍÃ⺶ºÄ¾›‚á\:äLqÁl°°åŠ’oZàyÎæ·((PP  @A‚ ((PP ( ¦Uðw%=.6/ñP1-0ð|G‚®õ>9Ký½}ÓJt¼›()oܸŠÉ‹—.g#ä½]Y\ijq @5oq¼, bxDHÞ—õ’¢Ü‚ßÒì»Xµž€¶˜×0âýßü Èðâ!uìÁ…¼K[ëílrfç½±½û[> „%XˆŽïôì\äN¸ú˜ðì‹ ðð4lùN6=s@|ïϵìõs--âqÚÈZkeëÌõ³'O‡Wgwg+€L*ÐÖ¬Íz™ÒmêLo l R™§yƒí{Š i”†>QGn Ãs€nÐT€LÜXp¯XiT½ ß¼Ùê8ÝÀãV€ÍYÏ¥E`{«ÛNÞÏ”¯a(#fY€€Á €Ô@Õ.€\îñmmX W«õÈ+­î(k—¬¨ñD‰2[è7;D£iÄz§×°Þò}z9j`èíÑÑF6B~gÃQ{Îðç:"©;5Txª¼X\vÁì`â;÷"N7^Ë€¬­^«cˆ`¾ôŒ iN¼Œ ^¡~æg—zvžðêЫ‹Gn…º5ËÜGô¦ÜóÑ&JÉ[‹û|Ö~T´NùzúÀrOpÃ|ëE^Á;–ÿ&#A}nŒ¨uŒ á½M]<ú‚Køqê‰q…¸¨±4.k¸ðmhþÝ}Œ@}÷hPЗçÛÐÝò+zXÓwzÅ[†kÝ6†Æ~FGáć1Þ¤îÞ#ý /^#L½Àð>‘x¶á û~ïrX/kTõ¼¦øW©jZ Q¯}@÷mè­W}½†AC¿s}ÞO”„ 2Ïêy.»95ð‘@HîeOgé)C`øÛhC øÖ@"€{ú^Þ-Ú¨W½áñ;%mÃÊ‹cbT‚~ ¼¦Æ:òˆ°Ì(×wS=òwè7(4@±.zå[7ß¹Õ‚ŽÜgÄ„è xYÈÈuÏýÒg ý~D{à㤅´÷9 )¬#ú®` b4B®fµ½ªßè i¬ÃUì1W½Áˆ»|^[!¼ü:Þß”×À ‡.£®””¯CÐŒ¼Oº_RÇAǺ2¿xÏü1XÑ[Ÿv3§˜jã£ÞÏÖ–àö ¨—é£MÓp@OÃÏkØóðÞíl|2…vïR?½éÿªDó…Þ¨ðIEND®B`‚colmap-4.2.0/doc/images/incremental-sfm.webp000066400000000000000000001112201524536416500207550ustar00rootroot00000000000000RIFFˆ’WEBPVP8 |’ðº*Úë>=ŠD"!¡Y_( ı·zMÓ·Ó™aû;(»ßM®\p7úï‘í·—ýGúïðž•<ãߟÆ>íëeýnÒ~þÏ•ÇDÿãÿ1ù1ó‡ý§í—ºí_êÿñû„yþççÇûqî§û¿ý¯Q_ÔÍ~Öû¬—ÿçþWþOÂÿé¿æ¿hÿç|ƒÿDþÿÿ‡Ú[ý·ýŸr_îãÿîþý"þËÿƒóÿæ+ÿ'îŸþÿ“ÿî¿õ¿w¿üüŽ~ßüÿ§îÿçÚÓøþî·~šÿKüeó;ú'õï¿®ÿÙ?Þú»ø·Í¿aþíû7ý¿ÿ'úï‰Oð<;zŸôäÿ2½Éþ?öí¿Ù¿ÅœþÓûgñçøî²ÿâaý¯ø¥üÿøÿÛ?런ÿ`¿ÿ&þáý«ö{ûWío׿ÊÿºüŠñ‹ÑÿÖÿ¾üØøõ—çäÿ¼©ÿ‹ýã÷gÚ/ùËïì_ý¾TüÇûÏúΟïŸ`?ÈÿŠþåþCþ/ø_ÿßó~Ûÿ5þ·Ç+ïßëÿãøþqý›ýÏøöŸûÏÿÿûRþSþoøóÿø¿Ìÿÿÿñ£ó_ñ_õ¿ÏÿªÿáþwÿÿàGòÏê¿î?¼šÿÉþgÿÿÿ/¼?ÿ^çÿq?ø{ þÅÿô>Ñϧ‰vëÀuWYA[µ}¸Rd&ºL„×Išé2Vìa ´”’x+Ìÿç’—ì²Rý–J_²ÉKöY)~Ë%/Ùd¥û,„ +˜Ã{”8¬u6@:öˆÙp–Õ†#ÒPx®®eý17Xþ‰YÚƒ­L™ò¥gÛo™4ÉmÎÑr1§z«zF ¿¢®äMMï/Ùþûýÿ¥ÃgVÜJ²¿‡¢Ñ"ÄÆ…(ªRÓdô«Ö|ÿK›‡¤ÀŒ¤ºŽb¨Ü¦ß-½"·ô¨vmôo»ô¼¤GU¢s)?\ݤ9†u?´m ®k…g­xm¶Ûm¡ˆ‡@+°>얰ʰf}Í‘³^󑶱ˈfç2®¼8º?º†0lËtËÂG€‡YD’` GG¿ÈÖ¢Ó‘·ÿ¯ƒBé\¸¡ð·Ø!œµAÅ.íR28RÞÕY:ý…P#„qkNVXŒ!†a„øÔ)‹¤áBùË•¢ah²4j;i ç³µ¬ºÑÆ\t´»pÿ—ÙÊ!Á-¹œç$M28ùr²C)KPsmÐ G@­xTeãT“3ûúL&ø€¹‘Wu±t‹Ãz£a¬˜C«}©o?ìøŠIÎôÇzñÍ;õìDO‹ þ=H[-ÿ &­ÂBßI¤Õ.ZXöòO;2V2†”'ú•Ë?\­oE™â¤;¸€îÏB5]¹Üòm~o8ßnƒ- ê;Ü‘ôjc¿–XãÐð-»zn íÏ«.á8œŽß¡ÄˆßpÊ,JΑµ9Ä0à 0Âý!—ítœÍ{RàC.=¸Ñ‚k|HƒøTS%ø»Ç #OsÊ)7}öÞ[¶³û_…+DY*?ï53—*¥²s€]ÑÁgpX½ì²‹CïþÞ¢§*‰rGœvä8†Ü„gù·WO†}™‘±¼L÷ß-䀧÷(°¶åþÀØóÈ-ÝG»5çãåLí¨ßÆØ<|°Œä o˜Ñvø²ªÛmÐÉ“&I[m¶Ûl¬VÛm¶Á(Î3p¾Ú_zTZg»z™Û?µo ÚD§Â<‚äŸJǃòÚQ©Ø{±²Ñ]í“_$²Ýsüyì]–Ë1™;’À¯nóûÚ†½Sº¸|©ÆŽ«:©IA:äášÝ"ªX®‡¹#~Ùû¼w­ÛÈi<\’ƒËZÿ"«-©ÆL¡Ë ÓE³%”ªø:™Únÿ,‡9ÊFÅ5”ůàèG`qï¦G Lp|-ÈµÙ»à¬ø,Ì—Üðj¦¶%SRãªNÀ—›jØÍ7ñ+æƒäp?ÿÿÿüùÒ—žO4ÃÒN}Œé'}°‡qïŽSú±—ŸëÔ,+…y²-þ >œ×]uÖÐÛÄÎaÕ5óÿ¡Vf¼õ JŠýG(þ¥©ÔL‹ÁØ&§„f'܉h™5Ü»÷iOãšB•ÌÑò¹š>W3Gí52` Ónÿé,šMí×¼gÇ©Ì?L¥ü›ý_´E¦›=D(Ú|•ŠG§tfÛûÆq+øo˜Euï³xO‹v>¹àYcòÔš°†a„(Îr£4Òà 0à 'ƪ¡P€\…JvЄ›× kžüñžíß4NmXñø¿ß*´ÈƒÞabz\{tz‰~½Æßv…&Bk¤ÈMt™SÔ{§ÓXÎFš§“®“!5Òd&ºL°4¸°ç Ïà2MÉ®øFø’?'`Áó$c:ØqÖt¸ÊªDGkoYîmHW ä¦!+—¨žàŽ×EåŸ¹Ž Æž·yoÚjyg}-楓s ĽÏ,j¿ßs:‡³D$nà¿:VTGTØOt˜‹÷6nÀ›Ëz¹z5/~”µƒrEl°Âÿ£Ì´–x–â­4GÈ4·&Ex®ºçéPþŸ×9ùõ‡a‰#ÏËÌhz96Á3äL–ÛXÚ¨/ÅÞ•IÄþïã힎ܲ¸ä(?+È»¶í´.ÔÑýnð¿¯Õ°:í]6§°Qò5âª_ƒ~|éBäÎ=+ÃCP=!ó†¹B6QøR'E´*É;å=xVÕ/8嵩RÒIé&!5]¤n=H\x|7®tqØÏ‡æ»›‚ùö§‚Å܈ßÈÅá8Ì_Ð;pú$lÕ3ð]s¹O+‘Àч¡#–lR¼ôˇhúú¸Ù’ÉnW|iÙì œÒjPb‡Öº)ÿx%JFv«`brfã»!¡†"Y¼ï;YÎÿe•H´ÀrŠŸ¸Ò‡»ú“ZΘþi5›Ü$0Áæ]TÃ*MÓ9‹ôÈff´öZW)©f\ÈË?±Š;\Qã'æ J`3‰íg4Jв©°|¥IW© _Só7º#qÅ|ä1A/½»+]ñÞñ‰Qý¾2CÐüˆ3_ÕZŸ¬­ÁˆÐ[ ¹®­²h•HQ“˜½]µŽÚc[úoºYÎŒY±>ò|†0Ä׫3|·ý°Tb8‡Ì­D O»)Ýh×?ó•ÛäÂ[[—äÀ·Äû¶fð¸ºhÞI?ØÑ ;ŠlëßDEêüýM.ãŒjŸ–¬­‡¥Ûñt}\^-B'es¾ñ8Sô <5ÜÊ’Ûe¡·k¸]GiúÏmÓZñª¶¾iÒ¬)ãæ& S7põMmv@ó°t$ŠÈP+¶'‘6õß(¯×v‰Kuã’ñôऱaœ?-®°-b¥U-)ÚÝZÀnºSÓ†ŽÕräç/]‰àD¥ÿl]˜S¾‚ظӹ@6|ªš…Õñ¾YjÏë ¿*Ä(îz‡{ž“ §9Ë7â.œìGŠfç{»ªËÀ6PEpÿÀW.šåfIÉt–A<ìé} å…’&jŸx£!€â›£GÞPîš½7x:6Õ%¿¯˜ djr ¡aÿ5g\ù! #€…îx¶Ô½JŒ€¨NHãÿ”%þóy¾ÐäÂÊ›{žtr5âÝáŒ\Ýñ_šîe0`s_~ö5ÕĵJÜj5÷>Uuƒ´Ô™/ÓZFn“ž ¡"·Ö½†Ó¨aÄd…0¿ƒc€I «ÓëÐ7)eéíÑ¢P`ª‡_¢=ªÉ£ÊÉ oèmMñ®¬ G‡»§ NÐÌdÙû€,õâügþÌ”¿e’Gì²RüIäíÅ•îO« ²ÊÔ»ïþw~®â.]1”V*Wzk™ˆ1žîÞ~d‘ˬ”¿dÈzëNæÞݶ×4WÞŒú [¹C«ÑYÐkéqõÝ'¸˜\ÐídÁ¢¢lHË÷Ótñà¬4×ó©5©îÅç]Àüë¸wó®ànW¢”[=×½{šwáæü#|ß„o›ðó~¾oÂ7ÍøFù¿ÑêD Sµ†5åìvy ß v€øe›ÊæþüT_Ý}‡#£‹0m=yø|Ŷ4ºEàôE41À |Ó]<çß|N¤aÂý¾>œ Š6Èq¬RœjÆÆË'_V“óÎ/«ÏϘ;²W=;[–Y-¼ pÅ3’x ^uœtr1È­×ËYÇ÷@#WÕ!|þ N²~mħڎvI%)m©“׬çóа°SG–Æ} ð8p5zY“j§Ó]RŠPTÇlPý0Ù¾V&÷a@ØI› è])!4b…Eü-Â<#^èUò1È­×ËYÇ÷@#ŠÝ|µœtr1È­×ËYÇ÷@—Ú† ÷ï¬û¯šô@ž†guª,˜š¤ÒÓ¦y×aܬ~°¶D98Žö<‘¼ÁÛB ×ÛˆM¹×¨ WZéx7Zéx7Zéx7Zéx7Zé€LûmâM9''²dF™1çT¥>Z[=Ë, T{^†:ÁåGnñÊ¿Í'rVÅ-t9Lb…ù[°¿*Œ²yx«Ç;4Â6Yè$âî³i«’ëÏ€|£rî“/êñ|c/ d™yD}•ó%3¥ÓU’ž¢YH҄ǧ|•¾ï}õIéö!¥Z?Dí`h˜R-ªÞAŽ&k0ºQ,{YS›ñLFÃõ‡!/\Í€¥7Zm0ìIƒ¤, mˆÓ·ïð^¸„E}ƒ•vÃ…\%ž1»2ßÒu[ÄSßz1°‹E9ðQ]ú†RÎ;g,w[¾¡¨z^i ®šF¾ê@¦ Ç9À§@Qµ»S˜&¼ƒ™ôÞÝ*Å!ê;–ú<‹Õôà€§»h#CF4G*)ŠºÏ´YÑR!Þ#ªr°Úž`§;hh¦rvB„EJ’JŽ™âä_MÚg™‹°ÿºÓ«n‡´@•œ…P Œù}uàT\œúç€"Æ®¸v¿ù…ã ³6‡àÚ;—뢨ዋ„qî“¨Š¦ ‹Á‹„ äís»»H4'>è?ªÔ5ÁNkífUªÖfj"§üöR3ֿ̨ÌVà ó hõ¥¶¤2"ºú‰‹*KVÛ]GœC ¿ÓrênkÚ²<¾’%Ñ™Ô4ÎA΄}Œ„ v¡DÅ#‰ÜÆÛÿ;ºû’߸–Òý˜_~f@&Cþㆫœç|;Ó%»‰Ød]Â8³:: CrWîÍKMï§5 DüF‡ØÍºÙÒäH%ì²°Ógö5Iôo àx‡ï*Õê’Óã© ÓjÌ%Z0Ü——˧%6KCsYûZt{_?µ‹¯ÚŒÇ?¹¸sA~½Oh0M–¨·Pq¼™ó¡í­IBC6–¨T4¾à4á.Çæã’ÿ¿IbKßš’õQ¾þÊŸ_õë*W|zG±„LÖ4ÒF´Å3ZÂãeÔmgAžŽÚOº OSU‚Ÿ’£—yßÌ „pmM™P=Õ<2øáL3÷ýó”SJ©” jÂÈSEñ9 `×ïßR“ _0 e„Õ©j ?lX’[]Óñ s{Ú¾ä¿ó:kÌŒûÒúËTaGÜå£ÝïýÊ&(§Hݵà}NA*Fòt•M*Æé°,s-™¸#NÂg:‡ z ÆìBÑ{¯ˆªQE-vœ´t[a²³™€9C)ÕÚä³Ð½T<ú0£ã1ýË.ûH,†´å+¤ˆ5v$J²lã¿CÝË}|ü¡Š8zSmWÌrõWö«#*þøÅÏhÉ=¯RŸèÔÛ襛ˆÀ|ïl¬°äÝÝ’ð@¹^û?Èw¤×W‚òk7æL–ü æ}ZÈ0)ÆÊ=ËÜ0ŽRÀºàƒ Ì´úÕe«Ø-nˆm¥n¼ÌqïÆ"l"ÈmfÒô~™‘ûޯɣ­ã¼¬¾!@ˆ>õ«9­Rœ{`’Ùo4—-âl­‚wI9³sq?èКâ–3^ë$û¡ƒiÍ=JDß‹_-„»çäowõ›‰¦„ïaЭrŽ!FTs9Ì™‡"zºØ»gÞ§A,*%Ðÿ “‹k9nlx 1ƒÛÏþ³d>åãdíWíØò÷sþ1„µD3Ìg®ŠÇÁXºª‹®ú#9K5”SÆKºhÆ7fötã]Ü:þ$‹n”f›ŸhN£¿ÛÝedõý@yG(Cžrì÷ظÆ=Jµî–<÷m!ƒ±ß}Ïo?ГIóÆ/-vÌ»”Š<‚åTJ@Zœ¡ ÖÑ*‰å¢Ccµ±ÓiÞù3æÞ‡Ó”8â Ó6-)j³±|ßJ¥<Ý¥ŠÂÉ[Måûï,1ÑúÔ×ÏÇ}oHŠ%Å›©úBY›®ce­œ„VýÄ(¾×*ú8gÛ?Ñ”Ð'}L¤=[a ÎÜó£ÎIÿ*.­ÔÑ /1f!¥·óÇñXë‡0<é->$ ùXx¥R„äãÖ8ÂæË]• •“—±¬%W8šü‰.Ò*ìEÆ6b NC[wÿÊ/™Át¤ÑØ×'~¡º<ÏŽ ÿRR+£O{n ¶3,-S*¨¡ÿ\›i¦½ @>ªþµfÉÐgó—˜zñ&Mx ’@wHH¢(U–•àÿîµKÍò?²¾WˆÂ¬ª壿¶Ád€½ÿ™Ì<¡pu¿…EX&2±XõPUóI6Ê6›79¿»bëÛ¼fÒqm>ÓJ±˜ž†Ža¸çÌ"ïûZ†#m6·ìH3*RkŸ äÉ/!<6ÔUàEþ$Z1¸WܤÈ-TmœQú !w‹Lº¬0ÅëÖå‚?~aâÜ›-éOæ*ŠoJ’«°Jó>e¿íö&ù¯Sd(éÛu‘‘½™ÂɶÖQ]·¤V¿“+»ºþˆ`Ú5)ð81vS‰.HÜ-‹Íó5Ë.À²sìpv@€šD©MœˆZdŸÁ÷Â7£™­Žç4—µ_x—®-˜{ ÜIB‰—’X{|'Ýx„f„íáúà÷UÜèBzî}ûI'áiYâr´)×:óA¶‹CÐ*_û¡Â ~)fíðâpù‡lÃ5»þ Ú…õBK?^V ˆó?²|¼9ðP$¡?+tÜ:móôõoÄ VŲ›Æ’ XFÖtîœZJBÄÃÂuŒû±‹àv8©Ú„:³ £(ª†Q[aOÅ¿ +Èãž2·ÒGNÂÍ‘ëŽiÖ`éa_Ùw.2ÛG~4lèÉçòŒ…âÈ#L‰û•·‹VJ¦»om|‹ôC"¬ƒl*Œ¦tIlN8,WX Ãÿ˜³Èh=‚D©Ò^ñ¡j{v`lLS`½ÚOÿë7y§œÕfß!7ªä M²ˆÄÖÙÌÇåÐëd$ýX‘j³mÁ8,±³ÑðM®Vð8’/¥?'“Ô½p¦°qÄÙBÉÄIÙv¿ˆ î#6¬EªÍ¿ÈÅ a£sá½¶Ë04¨±c»¤PÅÍâUeTòcô/“¥ È–!ùqî^«Rù0¹ Hp‚éad5õ^ì+J>ÍfwÛ¥8!ÃbíbÆuîoп—…áï§–3?)6•nÄ™®­Æ`ÂK¦a禱ظ^¨|/ŽÜïzÏUáFŸX2az5Ù4þwÏÿ5ÈéùYë!XÙ³í¥Ù]æO =ž_kãœçêÈÀVd •C@·³«à×(H«šªÑl˜ì¦ŒT=mÃZÛˆîÒ«ÜfúO]а=d+}!ÿ èf©Ó¢k8 ®Š‰=úÚº½„ÄHÉ-»ô¢‚Н>ü.ÑÀà×yÒõé>iRÉ´+äóTkk‰‰²—+p„³‡ ½cWoñ %W >e¥×å9Øt?Ü€+¬¨Ù°JÖØëÒ«N²¢ïl1Ûöƒ—ý•@ÆZM9‡Ì 7äÐrx4ä}Q-üÿ‹Q<ÝîFmë±$ç–×<Ò6ðEÎ/cÈväq}U*Öö–5|Tþ(™ÊãÍzâßgÖI‡`ÄÚ¤ÛMs‚yõìûh´lGb9DïfÔ# ÷žë=ÅY%¼·IÛ ½y7€ˆò²k½@¸üàws_¡ÌÐ8ãû¹Hºü<¡ô;°Ú¯™‹@á©àÁš¸ïI×áÞÙUãÈêZ2i½)uÊ÷Å嬴3“R¬‘%u£±èl·ªi£%ÎD¨o²¸¶Þ”gçÐs=çôu 5ëƒME=|Q9 'ÞNüC–sK4cPvw2zÛ˜Z/QÙC85ÇZ’œ%»óµ³«°øRK,JØÇc¯¶5¤R³}¶ör•Þ¥(âýÂ`D•q-Ln Šxí,QC{‹¸gÃç‚Fõ2‰ŠjŒYö1£îÛ÷ãÇ®bކ;sÆþ7W{¦j†þ׃è6T¡‹ÐäaØ!½ö£  öÖUã.šHDz·v £rÊÃÕâ4B¤…oë’-f…góæÑT€Ørô? ì`cÏóĵ6Ùö0E{˜ ̘®“æò¥·þÅÜÝ.2K.³ÝÓ.˜Û•$ý âêQ˜‘cÙ¼²3Qé.0žÃßž`Û6Dª7ÿ¹šÝúy –3GòªvïÆ{ý(ºðÆÃ@ ßÕòM„:¸Ø’«!Û»Ú}B‚ÛçP4ëIÉsÊ£¯>ŠñQ™UHÝQ/FŸ¢¾;Ï›¡Æ ÒÇæÍñÕ'½¹Øž~6úRfZÇ„¥IóÙ±lEæn&öœárˆ'M¯´Õ=å|­åÁ*(¡¼ Tc‹Œç0!™Ìds{ O½¼¶^¢°³¯K¤ˆ·éSMÁË,„rË¥sëñ¾!à€³§ä]kîð¹àXŠm¸‚jÉ7¢g®pÃ$÷òõçåvêã÷³^m›=MŠÁMOií ³xA2.¾{ƈw0›¢+•£Ž+:éz¤ÔöÞBOž; r´þ gŸZi¥ç>^ªöZäÌ!R#Þì퀟§@T¥Çã]ÁLH*ï¶®@ÛöÁ×ɽ6ÿ§u+/¹fñ;Ø6@7‡û¶5[Yî2×x]È lJ 1ÙvíMæ°×DH"ÖžqSGꈳÃÇÚËGü8Šyt]ðKÉE›"žÇjTü?hÄ+weFÕŽ-é[Ú:jó„¶_”ùã—ÓžHÝ¢6Û«_hnMÓÀ>y{·®ó u”D‘p~ ̦ä'tÃW¢q<ˆa¿„}ÿlž¸ç.Fê†ã'%[>sPÏ2Uo¯uõ¿2Á¶FÍÚpjÔ~ñ[[âS5ã—WÒu–`á´®}çË¡´~A8W˜ýA™àÙÁ¹„aÑ™WnT¹TVø0¾µ*BR ô”¬íðÇ {wà jÎ&Œ‡‰£p²;õUZ!PÕGO׺Nʱ溜8C fùp‹—õ‚¼("îK¤à\*SHü¯Ê’X¡›^b Ž³*´n÷Ò Ô^¡-PøÙ‘#XÌw 4¼›hµ³—ˆhp›r¦½g`@¯³,o'Q(öápÑŒ`ŽûÆPÀã¿…+«æ š^ðÊö´5Hƒ¡=UÜÇ`›„L-¤ É[!Á|„;¾Õ%„I·÷«ÒIbv^ÿŠ/‡Â7žMó€¢¥e‚ŸÕìŠ~ž š~½®‘Hí¦®‰€_VqðèœÅv“…l·fe·Bc— ‚‹ã lÏâ9ìàÁq)7ÍÆ`°&òŽ,,žy”s “Usqä°Œû“¬ýšCXËú‚,>¨e‹âØÊ¢—Ȼʸ>ÓÿÞùr;Áø¥VŽ|´ø5H«^«ÄÆÄaÇÄío?šQ¹ôWC½§ŸV¤rìax¥N+ƒªŸ.uª*‘§9üÙ)V¸Uý]AgÖCKr@Š*iÑ>äbĘ¥òÉÖò}GT]Ùbm'{dvÓnñˆ³ôºÉ\õœàÞ€+Ccè›ûÞÒ±{Jb ¸¸àFxB·9Ÿˆ-86d‹Œ‹Ìq$ß6Ñ%øê) ”diwÎD2ÙíI_{›ˆ?ç·½mFïúô”vì™cJ.ðâìjæ T…ã"¿C’Uê+¡nz“50 .TË2ÝZ:pQ¢Aˆ>›ÏŸ1¬Í\×× <ü¿KËÁ ¼ŸœhdÔGwzÄ'ã¬f~öñïwx(Ì?ùgH‚DËBä³Sø…+”ÜÊŽ®2öÀ~@L+ <òÁã9UßDåf ª — iUŠ)Ÿh|ççÓ¢mŸÚôJ¸çÑ^öïâ1s_—œ‰R¦ÝÙËþ›ÛBï¾¼©_ßF°yË£˜æÃ0W6òe…Jjù)pû1»FYmpÃsŠ??];g>÷ý.zi ÞýáQÉØ!g}UÄIXFt3ùL1ÀU€v3°yʉn4žÜ¯õÞ9…6]ö †8,Õ¾µè÷+‚ôÚ¡ic€ÞÉeo¥Æt“Ÿ40DI\à`Œ¼5m™Þò¾³íN,t´Ó^‡{·!ªŸIõ?èÏð?°NÙ1&Õ›íO 7 ¹/ªÝ›ZÉ ?©;$s_(ÄWËTŸáÞòbh4T]~C7'›—5í×áÆqéõÊ/èg0ÒlÌ´ç¢à³D›Ìœ-ê} +Ö«ZϲmÅF©ÚõÀR&éö;ÝÕÍ2ÔNÚ®“KÊàñu«³ÿ-ž`nÛ“ö…N›úyÞWÚôÊ^“âàµSîLv 2YøØIWôÁ.Œ¼Ûµ˜Ø«¡Yh¶æ*ù»î4¼¼Åkç4d›ÊÏ¿qè&áR…!»P÷†þë™gB#Âé?€‹‰*[Äõëî–;«Ð8´*EX`°ÊU™8}’p%Ÿw,ÌÅËPÅE˜ ûÂ>>ÉTx«tN0 ˆÐç_›ÐJ}j}|£ÃlàX¿\%:ÕËqô?Ú~\¸„ƒÂ6ER¥$ I´”ªâúår0ëejW vÁ4/³<^»N—‹=êW˜ôðxlÍ—+q! ‹áêVo˜À‘+”âÃ>í§ZZTCÊøôiïp½Y߉cHžškRN­ØÓØa3¥Šh”w`•¨Â:˜¢ÈiÛ¼g÷œúŠ2­ër¥é_¸÷°o°ì8£`ã›; 4ÿš´'wƒBO,f864· @ƒfså¸(po¢õqý ‰NlL½Õ*Ñiƒ  DIXüƒeC¾µ;ø_Ã8f—flzA)ßZTùñ³bESŸd7S\º:YŠ×†¤GerËSM/ùÈ%DñŒãD¾xö >.-™¨¯2÷qÏÑw®‡[´ãh?kD¿mð´’‰wë@Wm'É{z˜b‘R,}Ú >”ñŽñ" @`v‰"b»w¬òÅùEë3¥êÁýl1þ.iw_Ãf¤Ü:#=!S§žSÉëm:# ¼×ø4EƒÚ¹®vym›‹1*Ô¬ô»‹³!¤ñ;P)ëû¬¤$B ã`Þ’ Â¯³Rq§ƒó3úÓ÷ÀÞ`¶þ.iwY»ÑãNŒ‰‡š3óÚ/ôµvE dèYŠX“íÁãFÏþJ ¹›|ŸlåDêd\…ª7 2[áwƒ_2¨~7†È¼ô•v M¥C&µ‡1òÉL\1„i]ßß gC"ÇT‡À«ã娔-GRT¹l·¨ÉO¬&|ôfüü|_Ž‚Œu\'®„Ù]~TÞdK´¶“eÍiº@Jæ%Ÿ0ÖóŸ{ÍAJ 2[þµ;šûâÙ‚Ú\ÜøfË%'Í;iÓÆ=øã+)4`öfä¸EëU(lþ¢ñ“E/üžØu¿˜'(îx„VêÈY®s’2Ív9Ùàü§€œËög­j³ ¸<¹=OS©Á4“2f>Zhn%€%ÿ_£Ê™ð@â½›hÇ\9Àêu<Úé.§TUE} å/îŒUOŠwUÉ…Êž0L󯔚0ཚ÷¸ËÂÜ…´Ù½¨¸,WD½IxQÅ%b‘ƒhŽoÖVò2fX[¸¶… ¡˜jÍDÕoô¦ŒšØïÈ¢¶n>dõX®¦±Y«DXCé€fYÏoq?섪Œ±ö=þQæÊZöÀM³Sœy¦“n|~@Ð(Ø—²”ºn‡4G8%S%~@´ÔeÔë…Á`ÅaEÿæ³ÒŠÎÄ¥­ŽÞþ#‚¢³#UYÅ3["žk¨-ûìòe'µýޏBêúߎj+9ü±Ž-\šŒ¢BÀÏÒ¡ ^ Û¶´Üe•¥X<K¤R«$´R%tºà6*†w#92®w9zwè Þo04ä&ªêÑ õ‚ËÍÎô—²ßûÔoFw±<"{ðl®$QŠLw;}¹ÀyÝrSÅ–w$€uîQ…õ&®«Mñ€r3þVl`L”°Ù¾V•*PДÒ-öÛÿHnáHºc¸ß¾O´y±Aüs…ûò‰¬£G{ÐXn<¦Õ ÉŠy%Ìï>º+¡´)ïùz:¹­7eiUë1T*h÷@S=]ú‘ÿ ¨0U3kæ4_¬ù±%AìkÕíc ŽF.#ç»aæA0Mú:ŽP6ò%ÌRF•G×=\”{°•ÈíØðN˜¾ùèxÛí‹F–µ1fÊÛê7|0ÊrmuWÊAÉ|ÊXõ"@,|e¦8©¾dÅ–(å q=õ­p9oÓ®W EÓ ïx؃fwO¦L±.â3ƒáµ…æDc"çG)¯õá˜ä-ôW¯O#±’ZÎp“‚‹uZoß¹¡]C"'z“õºÆl *Rî!e c‹û*1”¨}7lž‰î^<º$ÿË º†I•¡Šî†>’˱x–vv¼¼óSŠIõ­ ÷Û½‡Ž*Nh1›EhÊÆ›â£Ëòu<{ÔÆÜÚC)e•…a/_FH¡#ë’Ò8u#µÚ‚mÏÆ‹[_ºš SˆTy½HáØßJ5ÖÑ:>IŽ-©Ô1 ï~'æÕLbfR.u ]ýXœÿïékó~65 æ:¡7ËÄ 9Œs8Ä‘Hµ˜œƒ+h“ËŸ…޷߬Aô *gä\Í[ H6H00»SR÷3¶C͵^mMºÜÿå@ïÎ¥/WPÅSÞïº{ùÙ½Ko~±F/áçÅGQ40Nt·º£±`\ùîõÑaÐëÿk˜¯w_ç&ˆ  ½ËÀª¬tpi·ºÎ¼Pi€²#¾ÙÛ ?î«Õc1ü~Ì*ûœjÓ/3Ó¡‚¾;qƃg+»zй¹zºéœ È&f·FT¡!Úw'ï;òŽßÓ`ìF!¡âÒ*X¿àaG …·â2ГŽèAxªÍ·cg]’™Î9¤WMÃ'ð»üe0cª3éòG;KÛYÊ¡{¯‹ÿ¯ÉøÌ«…tðIF´:SŽvMÅ2µ¾åò{&,ÆãäºÚ¦ ùºEŠâ•Ë¥—(aZß7ö°•»~@Œß¿Óƒ â¹Vð:M$²Þ´anIáj}ªºÛe›Z)°Òì¹LìüxSÕœÞÚÄê³ÒUdÜ3n˜A2½€x„^îË>H§iAÜ‹>ç! R¨pØY¾*¯È$1ùº¦BÑä‰Îà‘u¶ý<Ûö’:67ñ+Jö| Ft±uzÈ[íËÈ+"¥ðŠNÁG÷½òV]øÞI ÍSŠ‚| ³u‘=%¯÷΢î þæQÏÚü8œR ÙµÑmÞnÌSÅDG$RxLiN ÝCLèZBM³ìjÒóSÑ:kÛñ0(œSº¤Dœ/éƒJj6ŽØe¡]jKð¯Ë¿î?ñA›æËÌB_àJž×íÔqUKZ6ñkc_=L[Cä&Øq qø&æ³.`.u ‰ž?Ó@ä‘£ˆgè#™í…9ë&Ö—›OèJ*: ö7ù8a2 ñ*_±ö¬¯gàä¢ì:Ä$”y)ÔÆsy#’³Ã*:Ñ“xì™´Þ,]ʾþO ìâåÿ÷¥ÂçZd÷Yµ½´ÊkP¿‘œó]F]×bÔyïL½åf8h‚;XmöúŒ" ËõE‡jò´ïIÞ_-˜Hš}VJÏT `ó´¿®ª­†#* Nì÷ƒW4Jypf¸àDå6.R`ι*Rd;†wÆ\¿€c¬zâãƒÚëyQL“HaªYó$³×N˲µÝº‘êÍ$ÿaqžI€ú2_\“²‹Äóæbã.õ¶^Ÿ©›aÓŠZ\_ÖC7O覱ÁÅîã…¨²¨þÞe-Œ%B #¼Äƒ¾T ’|‡@ßfKgî/Ù¹¸…op²ÏäÜhŒ M™.A¾^öö()\NF)ï¢o¨>ê-ªRë0ðJÎd üå›@ÍÜ]‡X2çŸ7Ÿ´ÿ…‚Š„,àˆ#ásèðþAIÆUD-é”CþbŠ^Ú)ÁÝf´žÄÏøÚ6{_æÒÊÖ©q†^¦ÍÞ äÌÜàðÞ›]õ@#ÛÍ+mî×ÃeQ”•ŒëÍ“Éàx†t?‡v…¸\a씄¼ £¡þ!³ÓÚ명½I»v»‡ùà¾3§}ôµ½3—Jç¤äh2‡¼QÔºd.{ÂÛàÙ))s`» Ç ÷÷ܸ0¹XààÆuæó…¿.¡ž6ž†³Iƒ Ý·iͤ÷ÂB;çG±É­.f¾ÓªQ-îÀÝÓn'Ÿ¿„.=¢@ûFpÆVÈ„{&"È– ¯;wèè«D”e(œXŸ¡·—Òx¬X:‰T§.fý–I®"Î,,F;(‹L²Än5Ì×êí&<­´h•¿&¥R*LR1w묔ýaJ·N›<¡SDBZ‹mêÓoccrΧÛn/Þ¬úf mbÎ;äÄE°‘)¥’‚ <Ý ÙÝ Àc£ýX7³«€Æý¡ßbÒþ(Ñê-ëdÁ"Y1ò¾ßÔø¦›Lô(ŸN˜ÔçTä=©»Qì¨Øn´<:¨Or4És‚®Øæ&=1Bƒg987kr禾[v™“Úà‰Ýi3ÉÖ¦÷½âi:1€eÜ^1X! ¶6#Ò´<Ï C>Ê(jo»üµãüáÅ3+RšH $§° ³Ü©w–È™0Uú%ý¬c¹û9¾Åé‹_]BDPÔ ùâK>ïP³W°dƒ¡Ö~[3E&ßœ†%IÁnåz™Æ?ÃÙ¹ÉâQ¾úÑÓÍN×A2=výÁödòöá;àãðÞÏŽ¸¼7µéy'þÈ”ÿS†K¬`µ=÷ ®Ìov“ð"ÊÀ;nޏ®'DÞ¨ú’~ÉÜ$º-œ_+g^Z£¯ÿ†Sž<äø£ãöÜÔÏw6Ö D^ÂôÖUs¡!í¦ÉŠöˆT4+«)/cßL_â#Á~%4 Ó ÇzŸðµºáÐ(—½;(E\Õsþ_M7s¢LAYxä;ËZR~þP}¹X[++¼Ävh=‡Œ'F°8'ŒD¹Ì?©üÿè¯5§zhÇ£c¸á^¦R#_ÔÝ-Žcy8k™äUgé¦âŠS˜R…)Ñ{IÁA&ð„-ìôûˆ¿²ðÍ«]ÜПìAª6[:y€¯’í*%¬‹õ?\RàÏUÂx&KxÒÆ½m”½ßvÁtVá y‰æßxJåÒk8¶Èt¨^E4øM~úž²Qš=%ÁŽªQG@V+ÏØg™´aý¤hd'Y“ÅËf€×7 ²hg¯«+œM-bX–SôBršêÞë¦úZø•e¼š·cò¼S ü#ò åxÆÒ¾X2$Úœ,T© µ«âŽ3bD:0;­{„fp×h9s<à3SAï Ôµ„ߟÉEšH@iä€ ŽtÈ‹g,¹÷-õz¸d.Iïã XàvýFGû-Éô‚kSù“b‚騬T‘Vó}™£Ëšö¸gŽAt´ï¥rþÆ~NS7ÿ4õ§û…\í:Î95'¤ŠÊED|ºvu¦ÞY:9+Ѝò!ß4´¹ØþwÆÚÓJQ“JÆ@Wu)íL®oÀݘÞüò~˜™%_Hâ‹r!C6ÿSîÈ‹ضÑWò¨ÞÜS&%ý ïjNÁZ1(ÙJÄ“˜ß$‡Çï3]³Â\½Ê¤U=–uæ|þ×Ú‰I(êg¥KÝÀwíô'"æZQ€µµÓ­$Ôž¹%@u™Ÿ.ÖÞr:*o*-nlP©àóÙ8­+ B;$þžlé? »`b6ôÈ)È?;RL^XaOiL¦€Á1_*á«jWÉþQp¤ØšÛKý[žßjp¹ßÿ’ÍIÓ µá»sƒ?; m{N!Ýwiì ÐÆT[9ôT-ÑÉXÕ{º#ëlCž3sªMj£ò¾t3©Ì:æ‰Ux/{(ÜzD>S‰¿oˆyïnF« RÄ´´ƒñÚ?/´2RÊ´y/;9[Ó¶2à­Çˆ0=†ÙðR‰„ÁÉ.¶o‚(9³ ‹>Àµ À,Ô_©•u»7Âן1©ÁŠ+¸9¦_N£´ÜåÇ$Î1®Y—=S´1S¢Ì[!ÔRWeÃû!*ã5 I¸£~‡ mStþݵ7û÷˜”Èå+Mi}mÔ¤”7y–•a±£Ž}†£ƒˆgÃaµÁ(ÜFteJpl{€k …NÞqõê­›7%ÉH±0ñ;~—\à5.§lZ–Ñÿ {KÝ]E ©®¢Ìö¬Š’Vñ' ÷çÆ®Ò–æfÙ©§»I¸|IÁÝ ‚ÇdÑSsƒžï+1ëDyèïÜz7>¹H+Pt€±ayÖ³ÿ˜ ,@%&7¤"Á#Ê ´Œ“ËfaCÕ‡è–kîŸT4ý #'ìæ/<¦ñ“È2‰X¨ Αޓh?‹=œTÏZýv¼vÔŒ Ê®T{èP’n»RÏ£?B‹ Û1®écÎÿ[-ÎwåT2mõqÌõå(Ú&Õ´Ç,¦™¡Ýÿ.tTö‘D~!CÇ}N¯^ZÈœ…Y_§h¿œÚeV`M½dCú“ý|VVŠÛ#LAìæ¤ª¿´ÙR¹»ÀðÙ umrî©Õʼn1@ÿj«le¿éY&ddÛJÛ§´4†OU`fqíÃ]Ì&ó!*DƒU'càI_/,gŠ8ìjŠÕ?‘mkßZ×%Î^å³Ý{Œ'¼>…Ûš„4EÑv€æ9^Öie·kéˆ]¤nqŽÉÞædr݀ιqÛJ’ó²ë¥••Ó\÷$p¬K¨Vìð^7yÞpû$­cŠúÏ".iŽô)\ “A(tJöøßÁù|–”„+ˆé ¿[ùy‘¨¨¹kNÔÜùG“!ò‚/EžÛ;£ì-1léìþ,|·AÂx“ë@÷r Ç–$ò~±C[°Ý‹M¡†!ª% plu, …R ¿1ƒ¯¸˜0•*µâ<Šò”iòrÙvmÑbÎ-9lô=»ÚxñDGõRFÇîmóJô¹$Xy,J3|ÂL¬dÅÐ  jehAµ:ç,…þâN{ýzÚ¶1Žˆ¿N'¬jªœ <úñK ‰ùÆfèɶà©Ù={Óâ§´uŶub¹¿ç_(&êã¡Ú´L¸auƒ¡@\záöä4qg2ðq*6 ,ã§;¦¼0†Ä}«ÿçíô:l @HÇ›ãê´h'Z‚,þO"Þ:³Q¼Ãª¥)¶°Tü-L/ðzõU×ßL'Ï¥*êOW*ñ§zÆ$)÷Üfàl›ŠkžæJ°è+×îºÙønš¹«öÿû*¯±ã=eÅ'$°·‘nZùùçIbÕhbµÛÄžäuÿ†ûGz†nÌ€²f×Ê ôNF×»‹Dh]Ö³·L ›Gï°ì]Zý cÒŠ¦YÓoØã@(ÀØŠ©=j3*•\9Ùºï,"ƒ9¨vp]y ÒdóºPâ]ò‚¹7r‘™"Ò]Ê>¥á¿q9|Àoòìžd%ÏeÂ=5á=·ˆ¦òš”?ª¸ÄÕ¬8¦I¤K§Ü§“°bCúuÚëïÛðˆ²?&ÜÍ5ü¥¦ì¼d‚Ž—XÕ¥®Û¼¥œ´´\µK‡½ptsl¿×‡Kð:ëµ»ñ¢ày‘PâC©×Çø lõ„0óí¬:–Lµ˜X‘ZL›ß\#…·„k¾þXcýÇÊE(w(žðw1ghÇ:\ØÐ×0•CO\Ñ7?Z\ *J¨ð17¢ ¥&>Œå ãͯYW Ù&iÓ¼É ø›Vc^êP0oŽù”&ŒI Ó¾Õ§—Hhd€èŒR™õšÂOËñ³2çÊ„`Ü…~Ò£¡åˆýsÜšÖòç‹ÿ¸§$÷µ-vêîòz 3ÀÄyÐ\çÔ? DC§ذmç`ÎlXÊš/¼—~†:Äw#µ&°v \Xþ<äÇÖk܉‘·ÈKR׬f(Ø>õjKõfÛ ¯§("ß9÷©Ãó(Já~NŠ—NŒPòÏnòRo›_ëD\¹DEWå¿„ DuÞ¯ƒ¶hG‘Êq­ÅBÂÆŸ?Fz`µo¯¤8±J™Â, JÎsp¤lvæËeªãü`60žSŽŠ%}u.µ¯øê$oþl¿p8¥³Bˆû|hЬôßÈp ‡àÛ‘F›pÞT'Ú½ÎÍ¢J?„ DvÕEuÖp÷.»á‰EGKÛš hß´·­‚Dy~gË>$wŸs,ãñÍ:u ·é‰„Ž?Q†EœÛÆó¯åõðafËÚÆ=e¸^@Hß¡A˜s‘ÈŽ{0óÞÕÙuê1 D(*EÆÓ[5%tTÒ¾ †™Ø dÂLÁ&vM³9BÌe°ÙB!f¯S·wì‹/ë~¤&ð9T¿œ”«Ù„ì¾Q~-%|hÒ ÐÙn+â¹–¸*ôU9)Ê H­6 (ǬzÑß¾´5™J‡oÑE¢ýÔ"”}ÄÏ·ç“*Š…}Þ"¸',OµÁÜ—Ø‘E{ÔÿVªŸ¦Ðw&þ?R»6XZð «—DÕàº"'“ ã÷›ªvºäÌ ¿0z›QgÔRñª²±~~_ä²ÈÑ‚'û«M3+X!í3JÕW Zö¼5‰kDí¸¨¼V6ln|KKªv¬K.%dÅà$*êu-ú4þrô°ÁçÃóÒªÍDúIˆQL-vžÎZ—@~ÛØòûA*bɃ÷ÂZñ-p‰?²X ?ä9{hÛ„é4MÞõ&z$z0¸)x…Òvu$4Ó±aiÊ-…òôƒi÷ÇÌ-ÓW2u‡±õ]´2aÓé kØ]ÿžôAÙÔv–‡OÇ´\KtBjXèÔV¤ ¦w…¾çàåu–ç=‡0=C§™.yu !“¨ÛÈ­µMX}§¤ˆ—Û!7cñàwb9 ¼)†™•r êVа¨¼‰] ÆÁ…¤7rn m8*ÅüëOEÍ>xË F Ð A=@Ò%ÌŠ#ç=;3óKÙS Ú-3[™ÜñbxxÕ»Õ$0§áE¤ ²—?ëù÷!9 ájmY‹ ¸€–TeÔ] Å•d„H¶À‹ôf`ô†S8¢AjvçŸwAge¿ÈZpI3^|=ZøÄUÎAË—b§†‘€ùÃG–ZÃi/²òWÊÆþRe¨1ýÈô@»ɱX0D¤P-‘Äñ»0õlÀfë°.²óò1_}ðZᥠ£DuE^ù/Ÿw@+êÂkkëˆÒhg•е±øÑ]‡B‘šupHi2åJ,‡à=/$ê’áÀêˆ]ÄA ·Æ”ïéð1ge¿ÈOôh®hƒ©j7†”h&0f±\ã‚›™` Y”n¤éØMS»ß$^_Ï„UÞ ZÍâmLl!'ðgdæº|¶ÀŒ6n»ë/?#5÷ÒÐmN··!vV4“žDWÛ+ö±~g-Ô‹QQ&úöʎ潈¨ Ù"™@¹Z1ëøYí3hÆj.‘‰¬ÀEçÄ],3„6z§Ú´õ[…É8Sv>„ÚF.5æŸÒæp;# ÏbMnáÝ™ªN¥\ l“åב·?´þ »É¾Ì€Jå®–=RÃþSë°v„Ô3æ¸'bèº; Œ[mú ÒéHgÀ&“¯0ñï*>*þhïw±çãˆyÈ)#¬=Z‚å3xhŠÁ͸ö—èrÔ›Z›Ž1 SXÓ€v7Ò’-ÅZD¥~FaŠ9¯Žž;1ç)›Ÿ÷¾$ÎsÖÎ͵Fa [þWœm‘Öæ¿‚½¹!“Êáˆ7œSgˆvªë/Aâ\輘€ÖÏ•_tΫE:9=/³ÎÌÕÌm€Ó¼v¼«Pj’kûöTó‰©ôµé@Š;©Ç£OÃ},Œ—Hb¯cAm‰¿ç;»£QõÀA ôJ”¸ò‡tÎÈTdùFEå—tjaµÊ ’~ÒmcMܰ xQp29 VÜgÚ¬Ï[ß­.xBìÎõïÁñUü’OOã=“+ç+HÜß*q›FɌܚ„[ûĆU n±ÿ{ç}ßšì^cÑ’„™uàbSHV¦ }šóŠuØúézE2e§¹®!^ Ín4UÜ·ëì&ÏÅføá5¹[Î’©þ«/TÇÙ_ÏgSl¿k×üA/¤Þwmßÿ²9:LøŽaUôçÃ# @.T†Lj³!EæÐ>…ÃÃ䦌,<¥ Ú'kGˆx‹Œ0—‘Ì7µm¹ÿ. ñþtYŠ!A(ïGÖ‘Ì ³Á3ò|OгðKæ’ý|"/ç"1ICÔZ'$pç…9óeL›õ ÌšÖKRÌDSupXy¹…~~åzÈŸ z—ºÕKýŽ^§OOl⌯¨>À¯ÄTaýÙ‘ÌpETW"Íøœê_k¼/Gòª£èÈë­4ðÿRL²œüßb6ð„4Áv~§%ˆåç¿™–8f¨ç©ëõZI §Dæ%Ì*θÒsyJÊ5}gocì‰^SçÈ ÊÄ ¡ OÝHqÞÅXœÐNàź1¥çÓØï胚e‚b"Ó™¨¶‹ÜR¬XE\LJd'jõ›8>añÅL–@Žáz¦±r#Ø­» /žðØ-§¿?³Àzìöú D×øhëv@XùfðçsF(ž‘¦¾.íÌ# Þjªƒ£»ÊNñÙx ,íµ·ïèwþhlÐ@\(^NBÚñ·¯hânœL,f.´õ=°` ýÌäêÑ=~4·Ûî =“š¨˜fv3ÖŽ 1[Æ @½PÑûIÊae|Iè´ ‹ÊÚËAÔ3kErZ«]9ƒªpн&ýÕjqžšS T_vò;Ò@àXÍÓ½ås±`üЇ!¿^Rd…ÒKŽT jS{;cúáNÆ”Q´j÷/ßo•§Q|²·êuµA&ØW0˜¡°c@ê]ä¾=Õý^ùlTÀ’wt=ÐÓ““e™¯)ÅïK"ÊONqaÖ…Õ€Q Éh…h¾®üÝYrÊ•´åÆ8ÙŠWŒïeï˯~޲tŒ!j=°s$ŸRpý¼‚Á’å Ú;¹.ü(qŸ\̵ˆ²{ ,Ü"‹=Èzé: Ï,y¶Nb$N>òW®=ýREK©ý´_ÊÕ{гôK”6‚Á;6'œ˜¦Võ²ƒ\‹ÍmËW!•…—2zÆõÇ¿ô1äT©²Ì}Ƶ›>QnD¸@ÑË)~`«¤•âø®Æ]ØÎ(¡\ºô½‡‹‚íîyîHÍŽR 5Û^â+|}&E¹8+¯^tÎ ;l7‰1¨å¹8çÀEÔĨì°.а${ä" ïcXÏŽ1ŠÒË'\‰ÚÜžXÕ5][Y(…ÃôˆÚèGM °+Le$q¬ ‚MŠwð²Pخ쳚9Й Ißö9dÍu™X>±fð!Ò{!Ÿ÷®Õ”ujOµxÒÑ»…ôjžH_ô$ìtf{Ç&ð·˜=šÁe™ö])T-áÛ¶ÁøxŽ'G¥M«ÐP. òÎ1¿ ÷ œñ êCÙ&Õø‹¿;[› MÉÅ•ä)WiÏdœ ¦k–Ô‡…æTŒºüŠStÉ"~ô¦VÕ|L>,áäqz Z,1kge~(``5åy3^·MŒäÐýâ.ØjcËUgHŒ{x!ŽMXFö‡<¥_´ë^á8|¶ý!Wóš²k¡µ5›ëãCþyiuýd'Ç="z+ox¢*® w¤®JýûÓ…Ë^Rˆq«p盡†%šn„[Ð ó(»g‡Ð‡ÊˆrÎkÝ`,¯ˆïËÿV)> ‰…IëüÞ±³n%¤Hì«Z·Ù Dÿ-I«²ÚD·¬'Ìe þ±b\ór¼Ÿ{$”È8F`s¡×ñ„§†’›2SˆÑE²š=où°ênËåàïfq©{¾÷Éoÿ‡Š‹Åí•W<Ò‰ÔØ%éY,a­‘Í!xõzÊÑ(ì>Ç”m@Ñ_ÖÂ-tÀµ_Ý®J‚Ø Céí2Û”Ÿ‚Æô`[1(*s³‚ÜíÅD0^Æ&ÈúCfÀëÀŒz]E“EÉä¯é‡$ê8ÈÌ1=làÎÆ•ä_ƒ=VM(Ft.·|Ùîˆ3>jÀ€gN³.u?|^­:1ªÆUìÎfC¤w›å1)õÞ{ìz/á‚*Çu½[¶7!`éÿMt²´˾# §áV4t†÷3>ÑåÛöc–Šrg¼!К$‹0YÕ’Êü£5ž™øø’ALH“’ Ì\ŠÚYäñÓÈ;Ü/Ù.ô;uϱ¼ù:@ç‘pÁŸT?ô(‹÷Ë¢”›†ñ^WäC„Vîi¡8rþ‘™N­Ìkt´¯¶Á¿bç™õT™WbFiÞâ€?Â|/•WEÁž}Ͳ‹Vi^=ï¢Ø~¶ ¿›ïÇ62L£,æ&îzˆËë/êêZá¥ôªRe®•b¥5¶ÔO2Ìru•mÞZ{ ·ãJ_Êï}L`§ +?ˆy(UȘ Z ÌÖÀå”Ó˜ Ÿ ŽUòû_znÚÒ_M™$Âúù¦=)° ê¯aæÆÐíl¢Ö}$KeS$vVs[Ô~†á”¤„Þ‚ê‰Õš l¥›nàT«¾Ð€Ü"±?÷ùïô†k ?Uÿ: WW±J.>]6Øwû¥,ï ¤õ›Ü"§¾ Ç"¯Œ‡-ሑÐj €–Vô$Â#ærwžÕ)ŒDRˆ.VªöjÉMjM>aŠNÀ%áç!}Iªmb «J^BjÎÎE¬t*Э+[Èì\¯»o…̘¬Aô8–‚qt»mWÁÐ0hÌÈãjŸ[Æ(M4Ò(=45ôÈ‹à÷~`Î÷P_É8Ò¸-·p§u5¹Á«Mãòuû^š È âôãÆ„zÐp%טráÔ¦éa+D?š¿V’øn"[x×é 5ä£rU7R|=ƺ¹ßߘ’´˜ðitˆ†ÊNsóÁ½ö Ó'œ¼uàç7ø„?#*“ø=S¥÷wuF›Úîòl êaÇX™ýI!Šï¢ZÉŒŠXÞÀf²£úçdzrh ðÄí^jJy@ K“ÑI^Äóì¥T„Ϭ’_RDßGL¥Èa‚v]AB7oK^Ý]FÑ$,?ÓÙ¸´]`ÙŸÄ2›¸ÄÓ‚›$á_lDù×”ÿ]®E<ìwT.ÆÐPŋޥ1)g‡p>Ón:a9€ü7ÂU¥IåçÎ$Dk`¯#cª¤Ç#È ÏðªX³ø&Æh7ÁÍY"µÏÔw~“oªÕñÂ+Àµµ÷&=KoöÜ]ŒöxQ«òS#9`üÇ,¥Ø¯µmž‡¦y¯")9†$ßk>rèårG ½òÓš`Î#v_…’†"ò~Q=ͳFމIÞ¬~¢9Ä<íØ°7ÝTù„(&±/²Nè ìƒ]4!ášæà“Q$dºÖým2q'¯vG%óÇuI¾Ø4ˆó¯)þ ¹‚a«‚’nˆ<'ãï-½(²¾vÒ¯ÈR9`Aï¸÷ §ÿ†·_K&Ç:RÚÜ–¹h<œápBsøÝý“ngi¬ÞpGtÓÇ`óVHˆ ‘%–Ç™˜A}£R²ê`–¾€ô:¿Ä9ó5°–Ψ9îûP71ÏÆÒª„(A¾Jß§¿ ¤½€ øàˆ“Œá­‹¯-sð1Å/[ä L±[|œD¢µóºR?Î4q<1Üw3Ü9”LUŒóÑ%îÉOÈ_jÈéÝ”˜vWÍÊ䨫ôgT Nœð],bÔ4ˆ dy™œµJ¥ŒÅ¢þW4”|´–Ø,ƒÜÑ:¸ðŸÛ*ѤPlà–þÙÖ2ô T͵oà È>®d@òÙê_G{Ú‚QB_Ï6 ˜À¡™õ4–y#`W/ó#Ÿ½Ë(AšQ^Æõˆa‹Âs„„W“:Ò¾ÓtÙ¢´îmX×37ãÞ뻚S£y¬©Ü#hŒõÛë}>”ºicúB B¯ºíîeI˜Š©&Н Dæ¸÷arv”/î¶|À0‚OnM‘I+<Â2[©¿AÈ@‘Œ€˜£šµø­=zÛñ@Öi GN.a^ŒV²ˆVAŽÓ®«µëM Ö°.2û‡8³¦»—VŽ%ªM븋àðžŠ&yO;ÊEgÄüÑ]a…‘ÅÎŒ°ÔQeósÃîd–çˆÈ}º"¸…‹Ô¸IV:Ð,b°PŹ2NºFhü£LŒÛ(mòè¤ûOi©I*+Tf<spˆÀ’+@ óŠŠý²¶‰Èjrÿ –·)DC¦”å«tãbÑÿÏ Q– 5qðiÏM¯SÚÎG²½y,¡/fÿm9Ä»mM6ךÌ"y“ÛƒS9eRè謘El:³Gcg®#ÜágÙU0ІT’Ï!0ù·AE"}ÏÈbÄÀ8¿€!DÑ{Äã´%¤Q$í¼,ý¶–64ó‹Ôè=ÐLîwe$Â÷TˆTdÂo÷¹W[?\gVuàlÛÅëh3<¢X–îE6JsÝÐn×<¹Úù²¶é™˜¨ÍNrˆœ¿}Õ¡Ù>{ïÓv:nð‘͘–ÌxU±iü:Zx8 l ò‚×ð[ºï  pQ¼ÌäòdB ¨â“k˜®4ߎ0›wZÉéE¾ÆB=Ó­Ž‹W®š‡®H5̹žÁ3#ëašzSJ8 ecÛJƒ¹ÃÃg‘¦ØïDUˆuª!fú,ż&¦õ˜E½©|¾ ùJ¾_÷fýäÑ 8)çÐ"¥ÐÖKaúí¶K’25’¾Z^’VvNð½b±ÍìæuKê‚Mî•#ô1ÚY‹;šÿìhÑ#«P]½(dŠ ²&:»0cï“G[Ùu£el¹ áe¸aãF¶#ˆ¸¾Ý‚½‡KYÂkÚlõå?èÄ@ÛI!qõt£Ê¼© .¤ÖŸû£ˆH¸OK\íáÔ/#gC± Ó¼kZù¿ûõ6ë’tt×ÿÈ«|Ä…ŽYæýý{:¸GÂFZyçž’lÖ¥{{ˆi]ë1«$ÜspÈ>‡S‰±ãoŸU°ÖúL``àÔoA|S`…ñ}­}È+ð‹¿Eó­aÀÆÈüŠt|[g‰¯ ²0²s4%–}lCÃÐ]l^òfÐe÷Ýé5I· ›´©¬|¡§‹X«Š‘›2%:‰Evdµ?Ðè@—Wf˜’‚{wcéž'P}5ÝB 4ÿŽC›ZO²äK ÊíàBÜÕ.WnÌm8ßÖDx ÍFÕ6+Û]¬l‰ƒê¼ê–N9}MŸŠ7¢^­9+l}a –Æêãݯ}“¬ç»\‰IºÄ§äú‰Ü ™€u”xD§Ù;š‡>¯ý6‡Pš…9üCõì{¢µt¼˜Éh–ï’YD[€2ÁÐS<Ò°üì‹ÊsV:«GïÖwPQîÃŒ1ÿúq‰ÓnÂ/ý[÷š¬]]«e1ë*>/§(NB—eY§cKQ£Îñ©>ÌO¤ f¶ßü¦@ƒ==}ˆ”þ&]P$ŠØI4ˆçê¨u¶Å2P¾g€ú»Âÿƒ6€sàÃNª×uVÊûf5ª"l„ŸFþɘÌ"u'³¤ÝˆôǼ¤3· eßö¨úéúÝŠsúlQ|eOé—^p‡xOÅ]˜‹8êñv]ò„¥îm™I?lî9 ¶– ºqÍCEŽX¶þ¬w¼—á4|º`I‚ÛÛj~ ^Gì–~Í^N!ÒÆÂ ŒÆ¯6|…ßóÓAS± ÉC楎°uhÿ„A]½M+žPõà<˜ú{Cl̉ÜA¶ãgÂøEŠ;Œ³ê‹ó‚F6ŸÆ P›­úÑrÙBkù[º1GÙ9$¦Hs÷L⎯XÞ뾟}{h/ha%}]pè’Ûb‹›ÖNšèh…Òç©ãbéÄT!‹ºµÇ¿˜*r…zq¡±³…Ù‰½&aC=‚0wæCyOú†ÝÐ0åeXÓR{Ÿ°W€•÷´vª"=NŦZ;âvctZ,A9Ú O^ßý f¯ùOŸ¼È©µ§HÔk¶·_ï·lñKzG·&3㺚–Ò8†)IL^²ã«Œ®]ÝŠr  „wÖ¿Û)Bã˜¦í­Ši5‘z$ã´±/Fs7ܦ¦,åxdy¾_÷\ÇS¼AÅåòw@5\¡Ê>™ñ™ôÞÀ¶ó”&l÷ãæ»+Þ—©ðm¬ æˆ/v¡£¨Eö¯Û‡Dü´ñ• ”ɤ‹¦äXü Úåî†Qˆnímçý¨ä% ˜óñ멳dW÷ŠâEè<Ô 5­ëó·f왇gJß @܈MVÝ9ËÝŒØQ:ÍÚÑ&íÞÔB©jУ—`™Ìa™-î±¹#Bgõ%ô:dÀ·hˆm¦. n(0›Ùz>£ Ýoø–µSÞï¢RS²ä†æ¢£ùyœlÿñ¡þg™©}–²·Í€6æê\—Aøé†@‘j'δ‹]Ô[µZ”èô€Ä㸻IíàíŽâ3W ±)Aâ NÔkû]Iä =Ò9íC|KîÓ¯?°Ñ°ÊA\:– ›R§"Di <ÿâ>ŠIÌÌ\LûcA¥UVI?ôœ]ún®¦KÍÆí\_4Àƒ›ž‚ÅÄ$­<ãI¹f2O†Š|t5ù½7TŽ— 7‘>þâ‰ØÃä*n²qr{yv¹îЃ¥«v1~Þ¸,~­0œóÛ­ôs3V‰³8–½Y³Æêv»LpUoúÝþÓ +<áüß&¡ßlõä¼=gBšL²ÿUçÀ˜ïd""ëK¦‘(ö+&ê*ó…7ÜsÒmŒäIVÍë¹4ñÔ]]«[BêŽo ¡ïbÕqcº×”Õÿ:Þ³:´‹ýûXe½õâô^8”(oPÌû…S¢ýyjY|K8# ºU^²n§)J~þl €¼.?Ë;bŒ?:ÏN¤Éqß2• Ý_az“°‚÷©îK»mìŠCÝc£¨›qí)b .wŽ ÈèÀTggFɇÚi¥ÑÞtÅ“Ü1.üa%þ3œ¬àÃ˃1 åxû~ÔQV„÷NÕckj+ÓrªTnÂG¦t‰Wr )¬ñ¬$|OÇòɨÍtC»¾£¹0vÂiºE³p'Ž žÉífXúÊHy†ZÝX÷ôTÊP„Bû.kqU³RLoíúÙ±ÎWš¼óÕyƒ v³ scÿRÇmÖˆ#za>¼)4õiÇ7„NPç—Q¤Å`Æ{õö‚Ö?³Q¿Z0? í-P‰DƒIùÛÿ­1Âó¡9›º Ä)25–.»aú@3Ò†ñ~ ´l‹Dúò:æ„Ø%‘§§ñ_þ|o'nç÷¶÷n³a²’Μ*/|zsàòÅJ™vîJª¼…:EBŠŽÈˆ†FbŠŽQÑ"s%„¡á´”\ßW›èÔtÎ_ÆDÓn§Ö„uÖ_ÂÇ×|ìe÷hôhT¸›ÈŠ÷RVG«T%¸ÝXð܃º½³ƒ¥ò Ç€ŠÒ‡YcÚ«)¸æ¨û6ùiv;,œ„Á Õ™Dù*í~sPe†CYÐÙMS¼‰å¯h-Ò5RâÄ>šNïkZ±lPHµ¶dj¸A@èÑ9BgÄ;†×¿]Î3 ÿA²ò·sí¬ógýZ쓌þñ+¹ô¡º#ö*;Œ‰ 8Èõ˜GÈ{e×W’[Úô‹ ²kÜ1Ðooœ`VÃèj)Î6Íá[+mN ®mc¢œwMS†‘ð£ý4â0|Pƒ_ó2ýâBYiŸìÇëõ”ˆ¿~ñ*aF< 9™Yh»WxH×–hÆþ ç¯*hKÌ¿,)ŒŠ°±»fcæšñá“–eºÂkÂéaª·ªHƒ[Zݳmó76@(qÚ~žP\_´›Zï%žmx@ê‘ÜÁ¦ý»ÁÚ[ÙÍëjøç.…+0ôµææ+í~é?=¹wëY‡`uô/o ?ÓN#ÁÅ5«E½Õ[+6ÏcVœ£'2Ò'?Ù×ê;MJøDišÛ­T‹ñNæê22`ÉB (qcµ Ãú¼ôW8ÆCyÄ´­Ãä½çÌŽØ­IF_12Éé  \_ŠÀï+ÄBâñæjœã?Å„áEÀ^¬c€ù*Ñ„â.J½’ó-íLÏã‚¡Ñ­j•çZ‹Æ \â£õÜj2{Lº×/BøØ|äÿÉ>.¯åáð2äô!_N=i ·°?Sšàw-•½i‘¹„ÕÅIx™@†º#v¶$½¡Îb_cÍcªª†æŠf¸°’V·SÚ72S¤1רÐê\û$|œÿ°ð"b}dÛëÑåN;-¶JÎÇ)7Ûìh ¥LÂ;=Ãož(ëR¡|•q¢?“TƒŸ¤®1aÕíùHKiÔ7+^!0ݹŒòê&ªÝp‰vWOVºEËšå]*…f!y©k ]â]bƒ#æw]^ÿjÀöB¾¬!Xyvó`,½™ÿÎð0Å!s®—¤Ì38¹º/NÉ)'2 ηcºîјDj° ?ÆØû6mfmùã3Ùœù71ÙØoç¢D|¢GÂ2ÁHo²ùàÊŒNñå ÈþÕ¼}êXóºòC Å•‡ð£ ‘ ÑÜ£øXàZ;)"‰T#Ÿ=À+TµaÑO½'qOͪmß•‚Š!)6dl8cZókº—PîLÀ-þp >E"01 çé2cé}„˜æ$øxä=axð‹—èšÁ®í¾)}×Õß@ÚÖ°pŒµy÷[ Fùá‘aËß5žA ¬þ—:ÛÞmë >ó<Ýd'¸ g¬¿sU£ÿpõ§qO‹4]k>ëùÁôãÞ÷7ž$€¦š×95˜ ‰)7ãià7R‘± ©Hla ªÀ!¡–Ë<ë^/`ÁW²ÏÀÃO ¾ªì…³©’²é[Öÿ)A4) yMØGPÀRpfÙ#uÕ®¦Ž:E;cæ.š¿)ÝE ²”{裞ÆÁŠn–½àÕW—ÐUòå&Ô?.ÀúäLÅÀÅ…u˜iòåa²c¾/ÈFÍÁW ‰?ëa[‘GP÷AØUÈ`~7Ý'–.#iœWªÕ·Å‡^úË·EÇ9t¶DÄEñþ41&Äèòùmðo­Þ4Ï‚É?œ‹fó¯SÇ ­È †dYá£ýëD[¶ ºÝŽÉЍŠ[‹U‹N)«ÊÄ%¡;ÏÙ,éÖ{6”g¬ÉèŒïÓ-4ÚµêüVrQc8¤²–Ê‹+‡…ÍÉ€d…>27ͤ¶Q¼O;Ô.Àº°©¦´¼írä?Y2Ÿù u^ÆY'ǵÝŒG ʬ]ê'fÚX-íS­oê+ÀëÈ©PVp_·KÕƒy² ¼$ိ0Ý`HG–¸”Ê·Ù©•c±[Uàþ Y¼ÈújÈU­äAþ‰B³ÅMÈâ° —¦”xJúñ§íý¼TÉ„¡Úæ2[bÆÔÝ^ãg ܉¼‚ °[1¼¬ñÙ9vjmVRz´ƒçñ–4f øw”E„ŸœÔ) Az¦ø]ï Æ§ªª+lŠ·wœh܉.ùV%aŸ³éóv ³F” 3™‹ÅŽù®XÈiáìùPlk l½±ð*Á¬òn)—F¥ùU°”>쇈(£2xŠ@ž 3“²a0NîÉ£RØä(•?êÍÖ®•ZÅXYÝÕ1XL¶>¬eÈr^[Ÿ¿ºÝÕôås$Q*yˆT ȳí::xÇP!_3¿ÜäÈÂÕOkóÍ@³Ê´–¢øR9¶Û5udÈNÚ"_"ó›Mû$v‘ȦÖÔkÖû–h~hýõ4bWÖ³ÙDlÉâZXKZGCi¹®¯Beêd$¬ýT„Õrû¯aÎLIhÑQ8'ÊjÌýXüŸö µú +ô¨éœ”‚i‡$©W2ï݃x¡öbýEEå‹ëÍõYérTå/<3`g¼¬N+ƒQ²¨æìÏEïã}ÁZ{®vŠhqŽ;\ZOi]„cJ¥GqÂùÁðHFGñé 5ç¤bî Ná’ºƒhØÔfÿ±”¬žq± öž hÓ3 ý";˜b @`VË Ë…ÚUªyÍ1HtQOЃ?óçi]Ùñ3ÇûÓã*@Œ{’¯‰aŒÛ~¨•[#w^öƒMÍ¡diqN¤MgÒ$⨧MÚÛ…Ö°çˆf†Þ>6‚7oÚt^7©PUgÎìB5…Ø`Ì7V¢åÔû§ÐÎ,&¥Ñ¸¯šm,ùL*÷ÉC\J ²¶Ï s’2†+±D¢c)B˜P¥Ñ¹ÂžWvËM¥ýC˜¹Knôi–ê§ôÒgîÒÖ“±°§H((¾lþcPžnðþÙçc»á ›‹¤–‘tà ëtâ9©Øõ8Ìu`ü¯¸èñ˜ò¢‰š»—\¡¦¹ðšÞ…]‘C ×ÇÇ÷tN‡  b”AS2Z’ÚØ”ºIQ#»§ì°3)3æ Ë´Š³.0÷ƒÞ¡š*á•gÞ£!åÓ &£|¦B•¾Oï\Ý:«}ªê›Ã7Fʆoýâ@`çàZÄÕu) ~Wn./-Š+–˜ÆÎ8Q2–ÿûeßT?_8-‹%0vÒ]œ2cŒÆ`*5 óò• È›Ôo°?æ:XÒIÒ^9r{8þtyψòúd‡jñÆr¤‰Úà˜¡2Rê¸Fzïëû€ë)2HŽœSd9…î &T¯²€ù”n@€òmG,Ò4ÒFá#Ø !Dy"ÿÕ5/óq@ï‘"¿’ÐËOåÒ Gâ˜üê‘ÆInOÜ¥Fp)‘èê|r]|èêX !—\mŸ.¶öË"Xد MVTX3¹Qd¯`$Jà3Úµ’½€‘/ú^xªPÄßfQÚ.°}™èÞç7°/.óÆ)M ùÿŒO})w/|çÕÀùdïØ1Éã¹_Am¤Óªô‘VLðô„µ˜³Ïò³>¦E¡ÝéÍöÿ„LZMŒ×& s@7·m’÷¶"¯ú“ámMÆNv·Š ú@-is¨¨66ª![©¥n–ær(åîEÊfaמeØRW¯M¡Q@Ôa?'ˆ€‰±Ø¹Ø'|°ë!§]Íþìli‰WÊ6€Ý$-»+[lÜ6ž Ž J±:>ÂçÜt[>ïaûx1è—²{»_-%5 ‚qÄ+ÃÞYßö ÆOØšÔ¹©.a ÓFÓ“÷oÃP¤ž¯S»~·³”§a @Ãá@+%˜Tí1І-wáEÎ÷2ÖÀ‚…d‰p•EÙ%Œ0† ÀØ9Ç££*HŠ£CcÇ`‡4uûZH,ß½kŠ5y—Mz^ÕGÂÏLE²^ùgÌeá`ðùÎTŸRl—qSŽbQ ¹ ;ö›¬¶‹BœÙ‘7 ÑWÚô‚ê¥lê„ Àmó.]Á/Ñ4È.ߺ-üUy|/U@º°í#C™jdr£½NS»­eùU¿Ç&-Ü}}™äðM*í 5Dûg\Æ\žf¬›J0Q×:S6ÄÙhDkçêÊû'f¼„Þÿy¨¡ƒN##ü¢Séã…މŸgôˆZ å@™æ".s&N®ÞýŽQ‹góWÍ}âï9håh¡mE'J 9Ÿ¬¯íø‡·œàæ­g®68îÂþîB KrÇQª.Óõ4í &àV[p¡ú8E/r…¼c ŸŠœ‚ÿÈY<ã…Ý)]»E4Ð:wÀ'º\¶ç?¥ÀS|  Z{ó3ó9ŒZ ?SìRP—NsMä“‹Sã<†ìf†³ a 0Ç=ø‰ÎDŽWÊü»¦ÇU)íÈ©Po ï\‰Á·ÆZßí¨j·Âf6:㥠£ó?˜`˜§Ìu˜5*Ú¶€$+}‚ãÇJö(ZºàÊ­ µ"q`œg|h„T"ó5*¼ñ¥Þ'6ùK§r„$¦ŒµJbÁcO$º’Φ#òÁS÷ë¸ït{;qÿñ*NÔ±GËëAÅûØDÚf›£$(†Žº¬JßÍYÊØš¬Bµå“ª@Ñ¿q\ºG.´”‡gã>W°M@² |`ìýDôÁå¿OLR Íû0oskZÿTÙ/ŽH+Å7ëi|·þ=Úi Wª ª^²,g,Ñmr+^"ÒwÓ9?ž2?÷ @ 3Ô§?¿ø‘Qâ}=³\@MUÆ!Á1Uå©ÛÈçž´Ü.QÆ1^hì_`dIõäV·‰U¬ÿMVVV5TËݶq<âV×Ó ¼¡Ò..ÖS²u¶¨,ÿ©¼K§¤¨ã×2€¶’(åTõ&CøsÊ& t\Úϱú'€ã&@Åy™*Êã×Aæí”AÝöÖë ô=»˜§æêXTK4âºõ°V[5’õÑ¢ŒÍócn¬7A¶ÅY¸Ób q7VJ89ÜʲûgA¹Ðézï¼BÜ£dþXWmˆ~œô3´Ñm—ÃÝ3hšÿw˜ŠŠ6A2FGϘZlß#m÷9 eœ×È+€oåçy)É·»½¸EFEÇ1iËwÍŒq¹B’£‚y”%wg}½«b vo|*xu¼qåÊ ‡6ÍP«'ljDl (í…ß•J¾£rf]ÕWs_½u‰bàØó²9m(2ªõ8‡KMÌà c:öžO°Ž s¦á¹¦Ðƒat˜2Ö¸RãÜj ;Ò+b0b1(.Gü[ž#'¤bTáZI:À¬“ {ór;(Vµ+ŠqÜ‹uí˜]et†‹£Å¦U’¦;)ŇR~Q¯'`›`+®üB"ý†(A(VŸ‚]šA–!v/¿—êÚÒ4—T¾N}üeq/5-Ù.®²0µÇ|˜cH^Méæ…¼YÓºI¼r~¾+]hðé5viÒHV:G‘1ˆ„æ£8@hå¥oã­Ñ"Xúzñ±ïúÜÿÉ}*Ùf ãBœGk$ËS‡‚œØ—Ÿ«|ªê¿!‚N Ëü®%ìãÅ0R‰¥ÅÎ?¦Úµârbíiñ: f®]س„äGiAÙfé–~ý \¡;)˜#:ÊôÕQ»ã'7F9qU’º¶Vw"/®²¸ýÁ:yî±—]¹Øôó“x1ƒ]Í„ô¥­TÖUú.i²ï÷fú¬vNþÆÈuÐ\^\”pæ<ï9L¥"ùÛ‚‹ð™_°»£¤¢§Ÿ71Iºw ä0wÁÄT;ø(-ã½ Q7Ò(Ôéˆ>ZÊ ÛÉeÆý²¹¿ªÂ:¾Õg«Ãm´mñY?±ö³Þ&ì6SNf"æ>†–([?Â0ÀÂ^é2ìkÖ¤å#¢kžÑ„ðÝëNª~Þ~ŒkœYÉèÄù` OùoÖžÛáû‰»ÜPÊjÕ@…«ý1!'/é÷ !o ŽeDíéÕŒÛV4Ýønú:.W&ฌ*Kß_¾¤q…ùz–'‘,( ÷Y5Ïë´{¦†íë‡nçÁ—ûýg¡•ŽmK`Ãï†WŸÅ€l¦ðêT#¢b¸lôQuócÔO`Oáv,~ŸÙhü~Mµ¹cÁŒæTUIÅ–¹#+Ú‰ÂÃÊzêªÏcñçîaÆè¸y˜³¿Uñ0ÛPÄ™ î@ÉiÆÛú9¾Ãè+’« h€©"—U6‚5߈öÚøÆ”@4Ž‘hÈž. Ï·ÕHŸuâFqˆ{_ÁÒ<“Ò§jxLÐ'YÉ¢w@Ù2± •]um ¢aª®çyÒ}X§|Ô|‚Ÿè+ÊL[Ô‡&°Ù†%ø\¸“k#õå¾¼³7-Œå±=øÚ0khoy™²(í~;Jõöܵ+ĉ%¾Ã’¼ã{‘‡2uî|ƒ8z¾,$4›(±¯øÍ œ, ÂO›ÔÍÒnäLÿ*~‹Õ~¢ÓÓL®Ó’‘æç؃Q PVMÿüìn®ãÉ\A5ÓgtK«æŠ»!7˜ŽšŽæ—YuøúÈÖåÅ&ÌIgŸ:µ8Dâ"Ÿ‡3KdùÇ"N­î—¤G13 ×|·÷%޽1ðjZå4ÂDºÕyaÒ%­”³M:µÎˆlú»£BVZ²1Çòµ¡|À±1úëÎ÷Á¬vB¬9̶J•2³öd¤Hx£ íÀÁàÂ%žo+ô•¨:? Jò&™Û fÈêT7ü2¤ÞG\¤¤Ù.D¼« RfLn@æÇ"9Êk‰„ûóÄÊe3sQ›³¼a+çg©xŠ\S‘äRuªßÀG̨¾–½Š_¡ôRúúº¸#\¾É¾³í/q ¾­6ü-`e%=Ü^š'5ÒöA·{ÖzrŒèÉ«]uK%¿óî"!§käûÛØ8–J¿R‘prÚ*wÿ æW)®TÙ@ÂòU‡ ,€FqYY0|ξ›„‚óèŠ/ÿá%›7b72YšXåP‰bÄ‘NŽaH°k(O”ëw1¯xÁh–‹Lóû7¤sæ²Fl%NNl2‹ÔÉ!‚, N}kI«–ÝjLæI¢‚EÔråƒh Ÿwäw²=„ϼ|x3×øk)y{iù—íŽëEµeÒyÿvDˆÙKµÝ¨Ÿ¯ÿt{ˆÞ–Û„©Ü5,M†Xô1‚ý/`wnõVffDÚ~ïXÝQ=ì_]ÀŠæ‰”™. ð%¡¡Ø‘‰,»eA@Ë/FÅ¿iÊ¡GòÙ³+ÂK3z¸Xõ7vĦr'õû"BèÃõà»ãÍ”Oå%rÑzýÅBÔ_í”ôéFL® }£‚-Bür£­ÀçÂ|¹É’@š}0À%ä.ªÞ1÷?Ƶ Ö”Ê8)ÁƒÝñ\èªSQoa&‰w5Þh1ÉoÖ8šhkûLJu OÍ€<é!âÿ‡|,Ò÷¶’ÒEèÍV¥[¤ÁÝ»ßå“ûÄF¤ˆe[§"fœ‘˜Bʤy(æäm4÷ 'nT„žD%ia¯– >÷e.ñç#•’MSW*„@‹’¡·¬:œMç ñ4õ`Ÿ‰ýS iL%Îò9ź©ÑýEQŒ¤¸u»Ÿå4Ó±/öü!…ÑYPÛ‘-ÓEGŽX×6vù×Ùn=E‹;IÿþŸWw,NG™ÜÍÙM@•q_k.)³ Õií·ÝÑGÖ6á‘ûY”iŽ)&(<£–@ƒwéô’M¼Ÿ“Øu÷NÑ>œ%ŠUY|‚r“£?Ç…Êpî騎f¡åJ äŸôí ùWu(Py¿Ÿw#a‰§®á"ÓØv.ÙD!Csi,¬FiÌü¯çtåµéåà2™Bƒ~ ƒ1MøÔ~MŽŠsÇJLQ‡ðx-¦$Y°ŸÕ$Ä ïU44XQr¨Õãá©§4e¡M6¨UÅ‚%…²jɬƒÈï:J£’nÂÁ^¡ý¥ É’·NFü]˜®A0-ž’ ÀçÑ`CCJŽû •Qƒ>ðSó²RbBè«?éî#{ôIås º{–ï—µ™D†VY?Ëz(¼/bð;˜Ê©¨u• ð“ ÷µ'H¿#’˜yìíF×íIFÇ€€‹+ƪ°¼“­%+W+=GÆH Õá÷†µsÝ ¼ÌûÙp6{¼8›ôw@Sß•©—ëÛ”°ëKÑ 9”•@ûB FæTÁûXõó‰¢(iÐP•éNC{,Á¶#ûÖã p*ìºîÿÛŸw‚ÎÜ´ö°˜¯kp³²Zèëú$r< ±Üsƒæ!ûm¯rnj°qšLM››‘K ÑVê£s:_嵜üTÃÖ[LÿÑ­]ÉxÒ=Ç“HŽ.õ\—AhÂc`RèýþÓl7å8š#ÿ3î¬ÃFoØ=š rR.ÉqŸ0FKŒïR†øàDÞßà[KëöÎâúbÇáK õ4‚æ\>„‚ª¸^Ì€ rMØeôe lI³ ÆnÆo@’ð¿Ä 1)1*C|R ‰„ŸÆŠœý¨fî3ûéh~'óRL"jwÁ¼>¬Ö`ç¶IP¿=NÖÊÇ« –ÿûi%é=o¯ö˜ä3ˆXÔŸ0[“š12µb L<:’õêHîÕ€Ÿ:â.óÖz( ŽÊãÝÁx²8>“[tºdå/ô(G¥²ƒ!¬©9^ÈI1ƒYBNχ“2*z]ÌÞŸ­ºïT‰?å:=hA#þØâ>ž|wÂzF…æu2 ‘LÌ€*‹Ö•á?Ò¡^ÅïaÖ×ìu¢‹:žD ­Ÿ-:1Ž–hjU<â=¡uß7À<õž'BŸ°žŒ¤U{VÇ;ÝQS)‰ÎùÙÌih±×ðR°ã\ܲï{«TÌÁ[ªª:Czþn4ѪŽCq˜,~:^ûp¦÷>v;&x‹¼eR `œ‹ª9þQ œcH]£âï4¸­ÎAu\QÁÆÃ.ÖêÝ$õ­õŒ£ÜâÖŸ‘-6jâÞ¿“Ž®“VdJ"Ön…ÒFo—|îFcF)­•Ø­¡Î=£½Ï¨EŠ>™ìÞlÐà ù;ÓèµÖTâ„¡O+𗆋Æå4aqK¹ö n½îAìÕf!Ei.ÀVõeë)3ÕèÅ*.îÒ†R³”Ù2Lá•?ûwÛ!п“¡8ç¤íiî}Îà>P·Œœ·ºK(8Á¿ÅŒm°ó7šÝ’soå>º‚óY‹úÙeÀïûcGÓCÙë«¡>v“àý–†¹¶9²7ŒE¿ÛÜ J ªøÒJfC«fR€/X²è·ø¥±B3Õãö—·l/°}•vÂúBõ£Ì±$ÖùÊCnÓíwª•5gÍàµõ †ƒe¼Ó +æoþ¿”úöXçìo÷ÁïI:Üí‡ïx¿ä­.‚Ïôó‡û^cÉÜ×CW4{Y’‹{í?ôÓc 9ãwÁPŰv¤õgˆÁsð}kY”‘•¼'£àuú3ªšˆˆì‹>þv¿f5TPuõV¤xÌ“æeà¦3Bú»‘%€ uÝwu%À§ÂŸÎhÒ`êQmxkX—½¸a@æ_ãÀx“A@Ž•w<MJÏýkaJƒø=+âœt¼«GpŹì£gäf 6º srnʶüníÚÚÐ{d%Ñ%݆à—?”Š+µjbÙú.0ßYˆwÃÿY¡-ÓI‘rÕDí+L~Õ-’±´I¤ý®P²×e­·dÇš&!s"šP-µW\­ÇÚb7ç¼Næ½jxÖ N4Ç&3×Ùþ_͈ú6NÌž…{ÄV+do²7JºR®=Bô`‹&¶c%ºåÕ+lª¾È´ ‹jìÌ ˆàa…7ÕÕ"säÓÉF“2¥ì¡¢n1ã3ú«CcçŽ=yfIaÂ'U;fŽ©«FÓjo~Ý!BnV°Vù~þT.´g0\-ÉK»tNiŽ¥ huÜ?ì7O!Œ¶.m!·dó:ʼ­Nô Ñ—ja`u/딋 Ç3d‹É•Çlü­Nˬrà¦vÑ£JA/´ÅêV+ü7ÁI§mQ`ä}^qÞyþ¬¹bIü@C)HËɑޖX08 è%ý(VЗÒkHQhÕ»ð @ ·h¨†XÔÞ³ q²Á„Ê^ú€plÞéôUœ^¼(áßÐKþúÚXÀ…øuËÇ+uQý€fŸ_ÚM=˜4 nL§Ôf‘¹1ã7(îÂÈҔΠ`÷>Ïð5%À1@¬SÂý)Ü6¹¯ 6§tÙ4œÖc©¡ØBqø“4ðдÌ‹I¯’*v'¾ýϲ2Þ©T¦äïm×  #­ Ö{ôu²•xÿï”™!9ÇJ*>¨0l¦RÞ§éE“ºŒÿ…‚è`—I×1¡MqÛÒ^œmý3·­xc¥  B ¬™Xït9ß}yˆ2Ùy6SèÝ íÎ@ Õ¬dø²\²÷Yìxw0&I’ É´ètO6×S±q¶†‹ŒgcÓämM¶Íä´ž¶KgÒ˜–諨hòbDÖ—Þ«,òÚKª ï1 ¯"QaDscEÍ$ªÐFd/zíC=Oáô¿ùã/¡²®å2NÒÊUp.7Âï5o|y!ëC'ÀGwÓ.– ”jûAcþ𱇭lóX¶Ôø×áó9ýDž«ôá Å.ð¾]„‰D óË+©ÍÕå|ˆÜÍs95¿+Ûkûá­«´1~8ã±åp¡iÁ• 4<{¾SûK=²6²~H¸vVn)Vk“XE 'ÝZ:ÅÚrT9 ÔàjórÓ­qz¯9åûÔÃVtXAA€~:$±ŸÕY6³yAôëºÎgĺ¹±M\Díÿ žÃ¶KqM{ËäÊ¿¼66¨y'ž‚ÈQlŽ/äà!ÎFŒù#âåöÿŒpÛtúÔd1µ•8Ž•}Íð|A«pD(°©aG|‘¾dãpbùóes{“.ìARÁꀟ®ÖviÃ?çé=ësÏôô‹e„$Ïç„¡îTY$]eŸ¶ä¹ŒÒê€ÆÊÑÕÓ&,½oRQX”æò“íªKØI˜¡MŸe›|{¡{äú+paP7tÇ­¤?Šä¥ˆjÅ0À¬ÜŸæö֤ɷÏv…~¿[œÆ»o˜ ´p¦“?!íÏ¢}Äà¹y„oÜ‹‰ÄYt˜Æýw¾iÇq«³áJ¤0ßIy²>O²ah½zä—Å@yVÔ>@ù¶$Ç<~¼”ÜB[¶]³l‰¡ä“¹`ë.-Œ@dc±—3I¯ñé'ȺØ=ÙãÌ=@#L›™{‡ÓáÀ)Lq~öP¹èOI+ܪåÇb2:XcT“ùíXÈZë‚]š]ø0@î¥1Ë TÏ.³{<°>UaÞ•ûFR„Š«d­Ï9ÒCÝÏš¢V+ôlDñ´“¼èçÜjf ÕŠÔl%B˜R˜ÁÐ%EÆK <#c…·È¦FZZ–¼lL¼yɰ´U™×¯hë@B´¶R‡dѽ§ŒàÚ™SОÁs;}Da–) 0k=6ÍK•Êi¸¶1¾/i3J•·ACâ3בeÒ·¬æÊ¾}ÌèhäqéCÊfØá—…èq"ã:Z•ͦõåö˜»H}ŒÃë3æ¸8dÑ\ùÀ=ÎN¤±\‚[ãÈ ¨Ž^v¸] Þê ‰Êk/íâð¹o7³˜ÓØ£.Í¥©dc^ÚS/KqšŽl¨ê®ê(Œ©Û&¯dkc ·§ë]k'zGòhJ×­À…Ü^>™OÃ7Eà9ˆŽ<1A{‰M]›ÊÇÑ)QÓ)”€¹¥tñâT³ÿRlwÒbpª¸óf³½õ!Iâ ÓHÁÓ‰bŠC•l‹¢”W1j°H!çy¡vfížxoˆ×m•‘ž—Ái¦Âò 8jT懘B¬æ1iúƒÎLE(#còPw€D¸WÈS–zL‡¥‘rWw'm4¸x$Tû-3Љ†³ºˆ‚µ{ cÏÔê-ï9°þù‘Œ2{6…ŸáÑ-CÛU‹’sBá-R\é•%ŽR¢¬ƒT­„9Ù~áËå”TúÇñöå\àݽo«‹æ]”d£¾½ä>IG3„ Gü68ýQ¤÷éÃbâTO]ÒÙùX_ã_9bž– <¿þ+‡ìÖ¿¹v’¯‰üM— étÎSö‹Œ¸ë ’# >œâY`P+¢÷MeÀc™}ÞGkÞGØ?Æ‘­—6sÜúÎQïVôÊá-MçÝíJ§¨ Ïgî 7¢NI>þàËp³¢CÀªªÿ Aƒ1ÅÏ4ÓþÂ4rX€&‰˜Ä>BÂÖ¸ëF­Z5á7ü ‚ôâÔ„Š›ÒÕŽ(‚Gï5ëøuïp›4é5×Ñ{Ùm¯B´Rn5FàŽOIÆç‘æÏXÉìõTt¿E¾%f—Wî°5£m_JÞwár"}Žw)L+%–P¹è­èúµ.ìA–ï \.Óñê>mêƒä³¸hC_I`˜ú"×óL6Í ÌްŸ3¶ô›Î%»Ú•OQ¥t³Å*R.M?3 ’tåñ0 <±3faŽÄÜH¾Í»7@9â¹RZ±«Ó$k¢¨h×M+ëhëV›Íœ»™‰­2 ñ&;–&àá¦öÎ0í¾0Bïÿи¹@PÝBÙÌÕZgväZŽd(*Á:³Áóe½nGÏÀd­JTNŸîà³ú¯¢õÌ«¥TÓµp ã´†49j¡TSBANЉvä2ôàæHÑ¿LS Ób†ÅLvýg¨æ>ÓsÔÖsרÇûè¬Ãgâ€{J_º°© §q}BÅVÆËð™ydá˜[øA©O™þrâÈ´ë›ÍuOcßTV]O‘½5Šçô/@|uóB§mÜu šð1Q–:"=[²²ILäTÇ„ÑmÔø’Õ0Úzg-ß2“ТwÌ.Éô©Ûe¤hB‰Ðäw ³PŒ—ã&~\bW–ü°Ålfç¿°ÀC„d¸ªÌHSyü¢ømÒþ*bûá|È?{\²`wÏÕGJ¶ÊºÛ 5Á^Á#Eª¨+ðá!…š¯Ý*¯ 8Âî…®&¤à>éÌø7✘ÕýZŒ€5ç§-í\, ÒÛÕ§¯¦ÐñaZŠþµÌ­ÃºÏëNæùzƒM´nPf¡ä\R_ûÕ^bÙçôbʰh:i‰ãððMך?cµÀ~+clTèºk›1AŽ £˜Áà&³voQ3©4ó°\§v%J .jëÎü|)°«_fBÀòê25˜ßõMãÙ— üWœg%°Ãe\Ä€•¾ã™‚á<¡3ˆhª!ÿc]`$ࡎà,óˆ¸CÄk½a/6RÔDP ¸2ñ–°¡+g—<ÊBÙ™‡ÿÍhEòôÛè  #Ö!C›¿žk·})½‰ÑËwtKVŽ_ÿØpìÉÏÖ/o-äz»75ÆÎŽší=T¹yÇ ]J—ñj"iU0‚âˆJÉØ‡Š±cünèÕUczzÏhï®<‹“8=¤7ø½Á¾ç ŒU|;6[¯%wq4)Ô©¯è@J€çûØ⯣½¨<;\É Ï(Ÿ:Ì>q!\µ‡Ði!ß÷Pç¡IôçlzkˆèEcRzÝYJSç©S¿F’Ÿ•žåd©àÿØy õðô9rÀFâA+ÈZ HÆt›k_Ý*°‘ÁÃ0õBñ3‚}¯QqT W>wio˜zÑb¤a×T§­$ =f·ëJâtº›ß×S6Å00êC¼Å§neñ&¤ì¡¹5ìÜLt¹„Ó~S+I îZoU>H“’cY¢¶^Wk†ÿ¡g±™ž„]®@U CdpÁZ’>•6m[Ü:^5Ù·7öã“| {éÒÐåóTICÃÚŽ37<a#êŠ Ô—þpgQ|.œ:Ò:Öi칟m0úŒu ½H‹ý¾ˆªÖP¤(BZõ Ý„£Û•Àwö‰m Å¡¹ÕWÉ~œ“Q‹÷±jšç·ƒ¡è;NXrXþKc~_yeR7FËuþû<Ê_ððZÈ-:Ä^×½Õ&àë\î8? Ïö‹%ÛÊ[Öh {ùÏC6žE£Ög¯(DvÎo”âjÆ’«ñT›Ÿ;ƒ¬‚*ŽæyŠ!ïUÚÑ·.)xÿW4Ðø’ÄiêK£¡#•NµHÖ(qaÀ2"ñI77R¯=“ž ùc¸a­Ã¥j.[HAáNóGÙBfeFtbªã8…žÝdzðuÀcÙÆ–zNÇ0ƒÆÌ`~ÁXæðy¿Ë¢&ƒNr"f©‚ï—e“¦høKøkäë'‰œÿÂnVUµénŠÐv(Ÿ‘°;Ûsb¢[ô²`8­µ@¤cåô‘ W#÷X¦ø z@ “»õª¿Áöó—å>%Ó½J'ýZ•žUùÇâö¶¹ÝŸoxþÍeí*À)\®f»¹;W:È÷ü]¦¥¥+ Hî〠à£GÙ_Bí=ÁtËù±‡¦¿lüÒÅd¦êéöÕvgÊ“±Q²½Ê·aÞùÉ"4•–ÈÌzTê<0¸ÀúþhIÄ·6YF͹8·oOZýÀË|þ ôAãÞZaeÌ/6¼€ßzRÚ€Ž–¢ö-Ê׺¸ýr’[: ?MmâßLÀαR'o~¤l›ëÚ˜%ˆ•Í ¼mÛsÛWµó~£#†±ßGOm 5‘\šU}`ì0«Ô< xˆÄ ¬´Èó–Zm2E®4O!÷ŽA\FÓˆpÝ’Ç{¯ÓÐ=–ñ²¿fýÓ=[w6ß0k|— ð^4št+(—”ìðò vÈÇ"·lI<°D³{2¤.Qê¸Òñ‡[šý<¢‰/;Ï‘Š ‰£ä j¹ˆ¼P¼$;œï GQ`ÿ²ILžþCJÙˆ­§œ(Iû„­«ÙõyoSÀ…ùÅNï ›®z®#Âè¡ÕCA'ÿB~hË9’§ÌUÂÏ?®…¸=Ûlþ2Œöb¯`ú¹w”µcolmap-4.2.0/doc/images/sparse.webp000066400000000000000000012043701524536416500172000ustar00rootroot00000000000000RIFFðWEBPVP8 äÔ*Y5>1‡B¢! v檂Y¿ãúgÿ€M3ø?ðÿâ?Ùÿ|ÿÿÿKÑ&;õ7ãÿÁ~Øÿný¶ùwãÞ½üù÷ò¿éÿ¾~Ûý×þßÿ'úŽé½ûý÷ýÏò”~ùžyúïûoïŸè?÷ÿ¦ÿÿÿÿïoû_÷¿ä¿ÊÿàýÿÿÿøgúGø_÷ŸâÿÎÿÖÿSÿÿÿ¯èñßæèÿ¶šÿÇþ;ÿÿÿ¿Ãßìÿëÿ™ýöùIý»ýgüïó¿êÿ÷üþOýþwøoô¿þ¿Õ}D¶ÿËþ‹ý¿ÿÿ“Ÿ×?Ïåÿ!þ³ÿÿÐó_îŸöÿj?þÎú¥ÿ¥ÿëý·À¯øoø?ü¿ØÁÿÿôýü§ÿ/óÿîÿÿÿÝúzÿ‡ÿ»ý‡ûOÿÿó~Éÿªÿ«ÿÛþ¯ýÇÿÿû`¿Î¿ºÿàý¨ÿûÿ+èÿ/ÿÿùÿÀ?íÿÿ™ÿ{å_øûOÿßò¿ö|wøñOè?ÝÿÄÿmþ­ñçá—Kþéþ[ûÿõŸü^²þñ_ϲÿƒþõýcÿú?¨ÿv¿vþåþWÿßý¿üÏúŸôÿ¯ÿú§õ¿ì¿ãÒdýÆû;ùï÷OîßèÿÛÿyýÝöñѹÿÿCþ¿ûî/ØáŸÆ?«fÿþûî7ÅÇŸÿƒþÁþ·þßößÿÿñüÈ1ïëÿß¿¼”ÿ±þÿ÷ýO°/M~CýËûGø/øßÜy~¼Ëûÿöòßñ¿¿ÿÿÑô7ã?Ñ¿Åÿoÿÿ7û·ÿÿý ?Å¿’ÿvþ×þ+þ÷?ÿÿú~rþ©þ£û‡úûÿ¿ÿû½Ã~uýÃý×øòß¶¿`?Åÿ›ÿ˜þáþKÿ'ø¿ÿÿýÿ ÿRÿSý×ü‡þoòÿÿù|ü‹ú÷ûðŸæ?ô“ÿÿÿëô øßó/ò_Û¿ÇÿÞÿÿÿÿŸÜý¯Ïïö¿Vÿ€€ÿ½ù÷þ«ì‹øõøß³?þ×ÿÿÿþŒ¨ Š‚E…vÌ¿¸þZ„Á.Ð"{­·©0/‹'yévSUb mè-»Â9(X²üÛFxYÖ%96d#ß%­ß3溉1v=)•€ÁmÞÕhî‘»ÿ>ãÿHˆ Ê65ôÇŽŸ…:iŠk‰r/ÔÅ,'ÅWEL>ç–IÔÝù²–Ô%îmáHõØOÀöÈ3p¯~Ì|•ƒc¨KnÇÒáÆtìg¦gÓ$^Ê>îdFžßfC”5Qîh‘˃Th®Ù ê°ìà.* Û3P¨ Š‚E…vÌÁ*â ‘a]Í_(®ŸEÜ ‚³~úP™—ñ‘8’èÆxâû¶:÷Íáå/îàhÆ=ÎôŒìë$Qñp¾M"£³ hÌ´~N,ôÒ;ga‘iÞ1IbeyÖ’…H[©£}]i8-¥´Û ù,+µI&í|ÊQ°Á‡çMFƒ8Q=)žm 5d·õ—•{Ðü8%îm™‚(QÈÁÓ\©ýzÕ:×iêöJZ0…Ù{ ‚/³ÌDµãoçhµ@WˆYùÝŸ„°í3ê}?²ü[Tu¹ OaŸ¦ÅäVTlxÍ£r[ÏöÈÂFµ¹Afx$]º:`¾úÚ»éQcb€ 1ÛÞØ+NOêV]‡÷jM½Áªt…±‡ï‡üâlá:Hëƒz[ܯ²5‡AÞ-ŠrÃJRàÆr´2±m˜" l˜‚­ÍðÇÙ½Þ› B!‰†ðÔFÊ踹çFü¥­¯Z’ä™|éâþ­\=$a¨TÁæZGZŸµï+¾¼]÷ÄÚìxB¶ˆ²AsžØ H± ¸¼K£~X%঴YÝ%b³©ûù qA#-¸mÚŠ~ tÐóÆì˜tÈ£ì‰Ïï4¿øá\»_& >:+ÔKްMÍ‹=E¶ÀWÄ•žÕTE-ú¯ã~q~>`ï)¶JêÀ¿yŸY¦—j¶dí9Éã¶uÞwO×ßÀMÔF¥­‘<®Ýk ”¿ö·5_ÊÙs[? ,Öiã–$Îí("¦TE@\SÿuýrzƒÖ¨ }؆դ+¸ô›â%Šû„ð—ÔMÛÌEDEItz¾ ÎYj·{ë‚ÅXÔÔe…v¹ˆÅcjë»°.èd¸óJú¸Ðƒ ôd¥©ï#8%Vÿ– {›f`Š;hùDp$P¹€ N¨©'šjq‰‡>O@¦—þUÕáC ‘M/Ò!º#\*â þ”ûíñ6dˆU¤¯à‹e7˜'<ÐÑ5OÓyå,#Í¡‡ÁÂÑO°1›[ÅJ׿¢,.¨„Sþ“´{:×–“ŒÐ½%¥^¿…:K4¥¡¦ç[ `Žà|±S,+¶f› Ušj2zìÃ.†I*=DÄËÖlš\§}h$hÒ°ËÞWt$-ýŸ€4~b½* ñ<Ø’Ùe!3‰»Ë-BàŽvˆååSkaÞÇÚ5:p¡×¦öüoŸ†ù`—¹¶f 7fІ_k• þ¦Z ¤§£a¸w½®Å¬L—Åö_—Û(ì\‚ „>=È¿Kw¼ºvX8ƒþ?¶iù¿Ø|\ü’_crÆ\äºí«Ñ›²DAÖ«?°zxú@ÅyHQÚ† ê-;DéÙtg¢kÝÍ"n›¬0#øâ0ž¥¬ëOI0×$X©ÞR;9ëj¦ïùhß~ûøÉÙ%E‰ä`®>å`f't}¬Lü:‰²sÇ*‚{™©JÌ¥„ms‚à µö˜ Á€õ?àê]ÿvsƒðS·Öïu|VŠ)øìm« ŒÖE”mškVT@´°â`ŠqPHƒ½ãøU{R%CñÉjK#…3LØi}š.'²›vEUFÌçËò"N…ŠýÕ®ÝÍž^lÝÏd˜Ü¶¤ž‰?cI³ëKJAA£ŸäQ¾j"b}£Ð„ÂJãí³CÞ Ö¿psô¢ñŸˆú ý"¤¬äÜñûâ‰/ðˆjº–^6„>àNtzRú$¥C}ÄÇåãˆg ò}4,Rä° U—Š«"ºbV_/ËA‰O—ÅaÔ CàŠþõK>J,E’59qÞp;fz÷ãã`¼e203t`™o¿¢ZôíòkQKDƒ¢³›!ÄæÄB™|:‰#t£"ä¼ñÿcãÒÍqŽ‘uGÕj( 4Ë-ñÏºÞ k°º¢â\±Y²ž¤æ5®Yñ#b¸‹ˆ?5&00Ú!é QðÒu·hcŠ‚E…t·¤^…°Põk±Šrû“HÒ6¥½Ã‰Sâ;‡¨ˆtt´˜0¢ùèÉ™XÉÅÃLÐÄ•|[n¸mz–Ð(ò†×Ú¸^ˆé¼KíH¡á;#KIÅ×ÃcúÃkEH÷®ÜÞOV¸kbN ¯åYªÍ¨HË÷z‰ƒÜßîŽ0b<ôÅœ%p¢`{ïG-4¤Ñ W¾õ6L_j\Ë.¸5bÌ©0­ÄDDz Ðn«5•Ò`Ü0ÍúаÝßîø½Œ“nö>oø¾e—뀪uÐ\ÕtÂx“)ƒ'Öv½u,)Ž °ê‘rÜE¾æ€²-Á‰ÜSëkº£ ,!‹:Ã^JÏÅŠÑðP~/vŒŒ5^6¦rvÜû ÅŪ?¨&®r¦ó—1ѤÅ+ ÃJYôñÝÊÁGÒÞèX Ý4¬ŒùmP*ÎcKcsW"kjú‚veÆCT¥YkZe§WEÅ­oK»à’D¶¢éL´ëÝMêGþ Ŷ)Ám:½mB ÀÝË BV TûóoFö<ºÏÃ'Iöœž06u¤ñï¡Qa2µ²gUL‹ïÊSleU‹è°ùuÈ8ÍIΓ/Èj~:"î4I-„Šî»>›;á¨ÛTï¦qŒ1§ùRšñ¾ ã³såFN¯ÏTjÖý'ŽÝŽž˜öX4t'ØVÛf~r@3 H+ä߬œ—ÓÏux)W{ÖÝz jŒÕqµ¼¾z8«ÂÕ=~ÀfÅHë{¹ãˆ³¥ÓHù{±/š#)¯Pô1ªù*¨çYuÏNä&…EÈÂÞ ––<»ÞÀ4)Ìá ðž:DõènáéªÕy‹pÖOÚYfÀ÷¨»ÎõéP§&1<›¡`äË" ¨;\ÄûðÔfå|+"W‘˜õˆ÷4òXÕKÿO‡{²ÿWh$Ýžz–ìHÒ•ŸœN©ÁØ^íd]=CÐ÷£g‡¨3E‹ÏŸŽ\$ÚÏÃ?QɧqïL}2vœ¶wر“±Ì1£ÒH©|U%|ìÙaïy îÇ™zí¨yx—äÒ’äüiðØÐ§G,äì6´ ªÊý¹4/&5œ¬5ß8éÓMn ¨Xȱèl~‘:ü¾iš#¶pݬM”a•[Qî /]mõ÷Š|ƒÄ§­ñ_îÓŸ¨‹0G²%bu/”îrÄÕ+ê—½ŠÉ?[cG‡Oô\Rñ´2 r'©! ÿqŒ’ÄûÁ/gŽDgõ–±“yÉõ ³uÊ?ørÂ~uTqt7î0×m½ö2Y(ÿ¥’¥G«b'„L@lÊÖWÜã¨;óÌVU w“ûŸüÐì4 ­=Þ9ò97ï JÄiƒÞ×¶}((¡ìgè` èïß2ºU2Û¥´ØÈN>Óȶ?ê) ª©K‡íRsû]†'ñ]M •™ HŠ*Ê-ûÁÕ/×0 ©FϾš§ºt0©ƒ˜í²L¶”ægZf ଫ] ®Ò—~'åŽ\V¬5=ÿ’âm®´ùãÿï€ÌÁšFã9á?vT¡Bèe µi¨Ù‘…´,è‚;³§â ¡áîxoPy:²R%í‚(4]g´k¡¬¨s’ß ø,±Ç$vÈQ³v‚WЩ´¦}jZýv€™ŽäöH‹(ˆBÛHÝ`}„ÿ$L’ÀO¸*òø;ù3L%¼ ^’ØYeÇCp•oÜ@ð|Rº08ˆp¨/Óìè+ p‹_7’|ìŠwØ€%FÇŒ’Ýô!Bc¿õ¯x9 ¡\´Qš„ÙAÀ¹I6x…ßúÀ׸ð-ټ㗑ɛ‘à;Óú Sö7¡,ʪp“})u^™gj+º‡ÝÌ•2«mNò‘”Úaø™b˜grîÍß²Ó‹±B Ñq ›á’¶1«¯/i¿E<æ3û¼BªEË9”ÙÂÓÆ ýCô7Žä¥ƒ”§Vx7%>O9*z€YW/Ч™Çˆ™±ñÁYܧ›9tÎÅ ¸¼-GÄ °ÌÞ¼Ph:.õ ¹w¯1¯¢N²£2½¡/@:ÎϦ=xÀš‰ØÀ¸–š»DÜDÖʲð.‡†ñr¡ ˆ<¿Ù•{1—•ïõ«Eßr±_¾ðì­Ï ‡Ãú5òQjEæÄ/aWàã]qÃê¨,4[UVxS—ò¯ýMÛ,£:êRÞU21&Ãý ôožþfÝåW¼©´çM:`æ”ûÖy´B™};_îºÜ¶¼¢ñ#(ôµ0˜hr&îyæ)fÏò½u-?û•5nD[wæCííš{Òý^ ‚3ÛÅGC’ƒ•Éé&!ùr:ÅÕX#z¢Î8ŸH¸cPTÁÎ Ùø>„úÏ×¶UsÀ®6 ?„ìî½kE;%&‘YÈŽ»m·. >ôïˆ%P<.gHÀ¥jåDδκ̅ugò}üòx*ý—ÏOÄ€á(ðlÇEA±+ˆW¹cõÜ7/œýDMÃâÈO¼…ùo)} plÙ©dÂjk«iüë¾É4Â88Ñ·›*lÁ“#îƒiÕ8H:_bªÎa8ú˜|€iU*2-áÿ¾ ƒå¿A£€5²Ë¿Y#GF|ܪŸwR•ëìr\©e‰+!†9!^pêÆ6ÊwhmÊ Ë1_OVfqËWc1ö\¡-ÅstQ€Cõn?î6ÕÆ3eþØZ¿ËéJvtR&øþCâžÉÕ4÷Ø CL;©äv†®"¦æ|´ßº¬.rk}ã wên@‚ÿ¹v6ÃðÌ<›¯q9QhÎ^ êpÌǘ?û¦:2p)¥k)A¥„†WSž'pº#{ªõ½Š7*wvc–Ó£ñl\ï}»™÷ð ¯ àŽm ÕQPO!*¤•>×$Fc´¯VðǪô· )Xë•k‚¯Gå½(äâLîá¼ç¯Å—¬K”£k..Ô’åô™4©*¡dõÓþEœ7Š4è©¡¿ù¡ÂæóGq$bw@œ&ˆ‚{l®;©Ñœ›M:®q%m…÷iÙËã:À0°U¢Ó;˜VF8ªX-Ì•™I\èP·™ÚKÒ(~Ue±)j¢~íç}5‹Ù8¼X‚^|z ؼ`â’v_™â =ß°DQPÐ-ˤaö•ÕŒ$^+º§ÌŠ®ÛC”3¢ët˜¢÷ÁÏŒêÛá:ÄÓqèv©&IÈn—`é-õjá#Í_a(o ¢¯4`ÓƒWp&òïB¶ý“bõ$l“9ïpû‰'ž×•’•/.ïŠ'[E`Q»¨¡ñBÍdU—‚‘OIcðC@×Þ‡Zž¿ªß‰)q*ìB}þM‰¨h+-Èvî“£ àš™Qôx¼`wÀ:/㯾Õi¨qg:­P.µ¯ªM÷€ôÄ Ûk5¾qË~™{Þ¡ì,¤ó¾_§¦í)=h3>ã©VK×§Ú6w›6¸pz¤ÚèÚ¿›¼¿Œr¾DÇ£É ¿PÖW­¨à‘ ƒe눔•U7á!•‚:޶o¹ˆ?öS~(7äÄÈ5‚@”f›}HôSŠ(.&S$ØèxŽÆYÌ]‘ÇìKdu n‰(fDîd…ͽX|† Û V«uSã6H=×S]ɤH"p£fè/ÆÐwT¬u%à‡}¤k‘þ[ÚBPâWŸÜ¥ˆ"ßFdƒ3H“K‘ dv?§u¾1{ñ.ÿW MÈåFrb ýãÞjäIJZÆ¥´GõKv®«ÝçWë°z €€“Aô7Ý…<óí¦d ˆkþJ̼×zlX±§Šž)Py ÷-ì¥â>׊È×›¶FžöW€ýâ.b Š­ÄLÝ„‚ÚèûKŸÔÐYÞxg.We B› æGO>ÕV° ×ñ\’BªšÝæÛØáô‡@BÖžRãÊfÙЪÍZ]²‡YŸÓHÉ6øe¤†Î¡.Ô¾LÇCýŒ·ùöË±Ê (ž ßöIã¤ó.+¬{>¼Í®—uôi5WRï)dA(‘–aɉ¤Ñcy¯„%ãJÿî °Mˆó·ÐŒùYÌl[b€Õ…YF›<û˜²vOôªuR0'y:r¨_QÆ[ßêgÔ_|Ò[¸˜3,D•ÉÂB¯rzÛF€& l4j¸ãÖœüÆä–‹€®—i™Oê¤w^”‡DÝ—.–¦Ò:¥;¬>%Øt“ª wbh°b”†[haíW`–6þVû><2.,Äw‡~#Wc'ÀÚ_K€‰ßL÷jàk8MëÕàË?=´RüÿáÓäíÈ2¬þ$N¤ÓÊ¿› x‹FƒÎÎÄ^Þ_THH=_àNËr6€§ËSãe"ú7˜PdáRX•âåê0¤¤šAN(×@žU_$h=6–ŠÖ'ª–Õ1Ò¿-Zk cÏ#èù‰{_ÿîAã-.æT¸ÿ±ÝOspI%ƒÌÍ “c—°­gŒÛÿÇÅÙ5ðrm<ù'®[jw2û¦¬d]O7‘µ%w­¡g8‘¿¡5iÂm¨^Çô@zê^Êîõ¶å©'¥TÛÌ £)½mÕTÕr™2o¦‘aÊD‰Á” ¢kR|8/±°xðËç?VѤš'gh.ʇi¥âaÕ@æó½Ÿ¢ûÉÆÄ~tøgݦsgZËSÓ7¥o¬Uƒµ¸äOZº½+z%Ü(oN¶êR÷ÈK§|¯Í›‘ž¥joô߯ò+ÒÆ·ÛazÒs ¸ͶKj&øÅa°8ºq‚ÂxíÊég¹“—²ÚîôAÛ$ìEÙŠï¹gnp}yÞ¿œ<‰KÞx6²Ž‰Ì ¶<ô2“¤²³mÿî…q/)Hbv<ý ‘±ã³·ÿúH.~l júÊÉ‚9!cx³Ùéö²#)·5*ùuMœ„y0„ÎLO ³*6- ëp{cˆÒjýq¸ ,£â¡’šþé§p*%ÍÈÇ1¦ÞáPÕ`M*ð¨çô®_ngTQâT|ùjbı›™É,K"WØ0½ÌjZf8ëJŒiC¦Ò‚+¨ý"ªCü×µvî՛邪ðyÚ¬kñI¨±.O×Wº¡äM6±{´{>á[ëÙÈW#' ¦=]§t_›Ä(@9’>7¡åï-Ir]ÚšM3qÉ÷‹Ià¸ÓšÌxÔ 1ÕÔÞ¥«ßÍcwïôü7Ð][wïÙîžì%o”Í ùöNóåÃڬɴzôârv”PhþmNñ,{ÕÄlZ”éí?5-îã½P;Él°(CèúDê1ýóÙ¬þàõuÉÑã–XâXª—¬`Â[µ3V ®ùÖ¯u¯<$»vÐ ÌCÔ¨„®LkTÙ(Xár¨€ú×WöŒVÀEG-Xï l‘•¡Ü”üGwÿØ·œ8Ê!ñá9ÏšÇéRJm;3ˆdñ·¸§Ôí¾uñg^T!öº„Àsœv×»n/Îtï£hΧàúïþä}qÊ{¤±A`žŽïëDÿ^‚*ö‡Ô'ÃO‘~lW6^j˜sàʸCÖWÙÆ6›ß÷Db°ŠVU LKÑ;·Yà{yßùinâ)µ C ðôw£#R‡F~hý{7o?9éŸ) ï1÷¸WJã\b œU•+ñ]izc «)s' &{·£Éq[kð~ŽP„áöI 0Žãr±œå/q/n2þÎ_} {î!Õ›ä ˜ÄO³{vÙhX3BvÜ%£,Êd‡h¾ÓX~Ùq}W„á°Êi¥þBZH­ãF̉þòÎ’dY.ÐJ™½vsž‡nØyb£ûïèÜÑÎ%þa¬zšaypP ·A ÙvK1J8í¹|Ò4ù‚6wrº)µwÒݵügrUGoV¤açAI³Wú2ƒ;êe%’Šã(™WA'ÜIù4€eŸˆþY"– cßU^ðÛ½˜¨OßP ÃPǬª{|£¡Í}žËØãOJú_ñzÅ#Ì›!åUéêÑ‹ÏÌ—ÞiÞ#*d·„z&‚•ÐlþWuÑÅþv0¼¿ô\â…l¢ Ø-@—fêVd?îͳô¨Ñ}âõˆl\!mçÎ4«a0€Zt(ƒ]üf‘>Â|¼xÖŠL…âÌ·uàd m ò4è)Ð62|e‡ñg'fˆaè®Ù˜"=jTHAC Ìöå-¨ð°›’ïÛ-cЧ±qKÜ'©>2 /RðÀ\ +ý@X©‹FfS¦|«?XyP) ´’âF^/¬æ´…‘äzZ‚0ßs»ïžãg䬦ßÖÙ`¹fô·`ŒÓ¦Mp°Ícõ£ª=vXzUø´³™¶ÁRYMõáTg…É Ø§g4!Îåþ¼$yTavM†‹ .Ǧ:BÂó6xQOTççäÌüsÅ2ë‡jkcÓUQ19ßÜòý ÅCq˜%­ÐÛKül´¥ÕÑâÛ³6L6ÐNuအ¦ÙáqƒÔÇÕ_I âkB‹´¸ŽfÄIòÔd8ÞÖ7)þ~ø¼¥^¦¯ò©d±Êˆ½ƒ™Í#bD‘™‡EúrÓßZµÝÅ\y"j®;Ø©îôÃüžn ìy.Nx3vzVòâ8€sV@¬~8ZaÚÐa0ân“™8ªë¨ýx#9¿éˆÈŸ[Ó×Géœ\¨ÒW>Žy hÀHyþbäŽ §òF! «‚лUtS KoëÌ•<³€õNð"Ýý;ŽEæGrY06kWp»(@X©¨ú>›òõçD AƒÜ8o¸¾òiªWuÉ“O"¸ú2HYöFÑl~>†fEã½5·»ª\iÈøbÒ1køB1Ó’Ï®0ÆãM$Ÿ1`!MmB9p/FLÑ`d$G‚üÂ=8¢ œG0í+ìF4Çi¦-ö%p4hcš£AË%”@1b/dΘãÌR®[rO$ûsFÅ-v (Y—€‰TûÀ™+Ë©§I"™R„¬¹å‡e–k¹Én؇Gô}!èø:š»‹ÇÁ_Ó*ε8ÆX¦'Š»¨-çžÙ£çÃG¢]-³æ6Ïéâ/ÁÊTð©_ õ’¶‰P ÐòÔ³¡‡-éþâ±³U\ @ŸÎOkþ5ûV±Í!²¸!,'T‡`´³é"*€G ²"þÃùb¦>IfÉ«‘-sûס-G¯To:OCNóG D¶ Íó$¨úÜA(çÔ|l-WÄ›i®xã’}mpAµK´ Ô”/ÐR>q‰§±Z³ºàߊ㠭\Ô(ß Ii­pÍCø…ÂÆ™^G]Rø}Øè2Õ¹þ&O‡†¬V8?ÊE«Ê Ú9UcæÞˆ—ôsRÌEO®ûô›PÌÀný·rÙ !Ðâá6ëÐÙŸÿ&¾‚ˆ//€ÚÝ¿®AwSµòê½Ä½Ø5” ¡ýC%ëãëq×p¡‚*DClŽàJ(ýT‘«Ó a×öäÑÚ¬líÉ,6ã…ðÚ‘çyÀ¨øÒ)+ƒ†ŒçÙÅLjFª˜/X1ÛáŸÁÀd>‰Û8\£ÁMb4V ¯B®‰¶¶;WXÍ/›`ì61‚jY‚k-áOl U>2ël¼áïÎ?*hiüÆÄÛ{NsJaÝ¿¿ZAâȱŠK«!3ÆÓ€‹TüÝ{v$†”†çè`4.¶Âdòz¡Û hîM¹9ÍR)¯í°ëqoŒ«r;Œ|–°`ÑmzÏŽ79(ž,j´¥ìOùÜyÅ¿%GÚ:’'Ó”Ú:‡tûdåžC&lˆ¾VÞ¸§œg͹éÍ©—¦ÆÏs¬I˜ ±á¡ ÈøZÍ‘$MWò»óÊâ¦3RJ•ŽŒ#S;lt>èyX¤äƒœmÜÈ s€Ê%üv5‡.¬ÃÑ Š¨„ðJSuO÷ðóΖ¦ª;NãmLºÓz!³«#ù€Õaì!M‡Hôá_kêϤád»Ånœé=/‰&1Å?f;v: øüˆ8˜4ûbŸP¸O­â!ä„MŸöœø®î0­­†åtc•3¯1ÿZlÀiY¿7ù^õ#C(y”V•É«®gYÿ?âw¾ŒÀN>‚÷éêªý3ÕN#Ù®·ƒ¯[?Ð?]šqžÐu–Öð„œÜvÁÍLà”ÈtÞÉ‘M4»c„XܹÕ}Ƕ^dQ{åZ¸SÚ‡B§}³ýk`á_óßZ±˜vg›Uç¦GÇQ¦pü8µ•4ͯ-ž×]M_KšîÉÜ™µ‹÷î¤#{?m‘Þ=|þH“óíLIÒb->jùcã{ØcÄ|Á‚®èÓ‹CÅŠOµßˆ”Yñ­m½&0Œû…¦¶ÆÀcJ£i¬ ¿lÞòxºÎqßk. (wÔ¾f-ñŠG­- «Õî»x?4Qÿ-hÙTŸàŸ¼ ’¡µZ )zlOúÀŸ&³)¼[Ø)Ö`Ù1TÅɲ@kգϙH LáúÍç°•9“q;É㫤ÇKsû˜Óh8 7Ê#_€_ † Ï"Ó»œ7j½î¨¿†ß&@¾Æh(ðïžv:êí}½X8Â@E°Q±‚3±î#ÕÒÃ6O#³t´XV÷­ªšg–½U]Kê¸ï<·Ñ ‹ ©©“Cznüz±*&‘]‰xýòð£á89Ù¿ÚúGœŒõ ˆ$ 6 „j>¡J銂D:$°00ë4½žþ c–Ûr³ù$øîGŒ)“pjb ¿êò6.ѾöK|- b¼0¹ â›ö”“9_­x•R²(l=ÍâP䆼·¢¢ Fý›K?¡ÒÇqÆIðÍÔT®Ãz—Á€^Îgc¤Vâ½%wl· vŸKœ¤¨•¬7]×pgéÏïÂ¥&ìîO¡Pj¬T¡'J•Œ E l¯¦¯TÙÓúÑPì VlØçs^G1 •³¶ãLâàD—ܸ4Ʋè¨Ê‡À ±Š#”1¡ø¹’15€a±.†s—§'Ç4"2ð¸þ¨Ç~õÃn´>&ìEd3¢Á;í£WéVž6 ¹õè‚Ãà쉴™¿q ¹Y¨M<<8Hx{Œÿbî¹µ"B¬ÉeÄ”ŸFaû·°¬o;zäDþ.¯eU6'=¹#‘gë ‹ë¼ w.0ØG—R#À¨k‚¢ҿœyœe´}¶Á–PP©ˆúÁ+;"/¯Ÿ$âÝÖzç\Ï1YÑcHÐ W™†9Àø˜¬µhÿèìDet.d÷pòë•Þ5¦°°HùÔg¨ŸGKKâŽ"¥"‰™§‡1;U,Äð˜üw–R´µlú¯n½¦\Ø8ky³‡ÇNsüØP bæóµ Ôg95 V«$Å YýL]‘T™6-ô¯.  ¤ gEÖ^;ª›‰\‰8 8X[5Ÿ˜N‡"ŽÞÄ—x¤Sö[QÛãÖc€Vu tÖϸ¬îã˜Í]0Œ.'ïõSŽ„F†ÛÜZv`N>([K‘E0|óìJ—;Ú‰'v*“ Bd³üä"ЖŽovkláù/‡°Q9÷há!,_héœN…¿=KöCûíèÍØ}ÛyšãpLƒV-‚¾†º\›Ä:7Çæ°ÖIdékçyÕÖžeŒ3¥k•ØòüRµã‹q¿h¡#9ÚáâX Ä“¤éWиÀÉ’ŒŽ.sY0¬7¿ø…·¾i+Øñp.Hòa.½ïMUÑ2îzí\ÂYš°~A›ÀD_ì€õ[à#ßH×Vçn‚Ì ³rÄÑZÊ3l|S¡}µ%(¤÷"øEÿRÈßÕzÞu •x»Fìwkm*,GÑ»ó¬Î®Š@OÌׄ‡¯¸£ïŒÚ„“W°ŽD¤b¹‹%ðÕCª57f4d¢Í 7ƒL ØÇò÷›­ÅsÌlòLuý0ï~¼gÆ|ùH"0ëÙ&J–+é²6Ÿ™œâ“–D´Ò 8:æXBÞ"4¬&¸ï&AËÏ÷;>Jýç§²o9X{ýCsáè¤iOåW§PަP?z*FcðX#)¯Š)CvúÆdÖ¨ |FC “€¸â«…WL‹&{lÌÀ.*£Ósšj¯øeDWh ôn•&§Úhk³»p[ž¹ª`À|Rü¯çR.HX¿:¢UHÂÆ(Šb°—–’—äÌ‹ÕÐouù?‰ÇùÇÞù0o€ÿ2¶'ûMíªd‘9‘Ek#ù &÷U¶ ’Þ\Û…ÒØ \:#m]k&Ò½UZuGÀNB+i RÁ´î¶If°:Ñë$É"OeÃ?!\#~îÙ#1¿3óð³ÿà9g6oÐØö†‰+òQk„{Ðý——Î;ËârPEÂÞû7c#,…¢ÑÏnlXœþ[Æ]‹ôõVæ}_2ÅÎìùÏvj| u”:!ØEZéë’k+»ïÙ¶G­ \cïàà‚ú>Záv/¤í=¨ }=OÛ€lµ…k'àŒ¤ŠWN)®¤µIšqìâY°\2™"†ÓŠÙØÜÈ+˜0z™eY!®Ñ÷Õk2ªR쬪r‚æ¡üsmÚ4ý·@ Š‚E…v¥¨¾à58ò „Øi;`tóм°®Ù˜"…@\Ro”¡Gö±`æ¿Æ_5j›AgŠOv(1\ê3p ÍJbÎå¥rov1OŸ«8w`ÿ©ßÂfÞv…À’ï¬ —Ö#ÒgÝ*Ò±ÿu#̼h[“6 Kˆ/cH#»„¡Œ¦<”[hx´ÐsÆ©™$$³x2Û^ä7ÊgÄ¡YWœª56­3Ñp¦:Æo³'EA‡HÖ•ÝG`ÖïYЇS7åÁÀÕBë÷×T?‚{µ&¾w}á`¯z©¢­¸¡ë³‘ÐRIsð:~÷C˜Öš™NHè"Ùä…ûËLº}Ý•cå²­‘¯µŸÓ”•aJ ›ùŒ.TùÓ–CË]­™–Ô„70[ù¶ÃÛÌ®¾®´äЏ¹ÊE ·`—AˆfÓÁGÊÜ'GëU[°UŠ¥3IÍìc}8#~Z<Þ4nmª¯?ï§3EYá(5ÿ•[ifñLB .* Û3G£°-Ý?1î]×Zyœ»âÆ€]]R$XWlÁSqLú÷%ÓÀ¡×ÜÇõ¬(ñX;~=Ók'ê’Å+œë{ø¯‡\ ]ZS…ƒ ¯FçhÛq+² +t”Á%4DbB‡¨æ4 TvB&fnn}sƒ:¹÷<¸6¿;Ltøƒ„j‚Ò¬ÚRÕCU热Ƃ7·4)ÊíÇlj0_D£jC®ÁÌ8f'6§ë‡(×=yCj ‡Cý{ï>ºñj OGߺÚX_˜Õ"*.%%­TukSè=¨Ö¹Õ²Ì¢zX_€#“¡c:陾×Üâ¶;È"àÔñC®Hw"q&Þ§¯ûù1ÐL ²¤Ä°q9}÷¶ ¼jkØÑ‘ØXkty®;/EìR â®\èyPH†'`}…¡œU̧Âýê›Ï}ú}JA²—±e¯ˆläÃúÛ œù±$¿5TtOÀ©£SýM"9ê98eº‘j€¸¨$XWlÌnÜØŠ`@OL*X1ú" .* U´Áu Q´Øëé`7lì\ž‹¿Ü­†³l¹=mýÃ^g  !¾ÂÝc„½ÊAÞ!QÝ[S ;»Ì6JEyU08 €1c]PN™/düpY8pد{Û»vO_†eÕ‘G̃?p³Z›v߉½bLdïhµ µõ;¿FŸŠ^òd½C“Ÿ{v¨”ê^¨{t¾Wݬ–ÚNØYe°¨äéôŽúò®T<BÑÊÓ&pƒæ SAxrh-_<¨_J«Ñ–O(‘¥¿0é¼ÇEQ×]†¶ÝZ^›aú›»>¹FœŽ†N8êóÔ•M ¸ÕfJƒèZ¦büõfl¡ÊU¡v´C.á(År’yúûf§x±Õ Tu¢+y\ãR$y*Ÿcÿ¾ˆæ%ت:o³ÃÑ¿,:f #ßel„C^ru€yÏzö4JHm‘$ @’P¼î”T,+¶f¡Púz$XWkdpÍÍÌ+Ö1„€‘Õ´ÿÙ¸QvE@¸Ûðéß­Lš€RÏêÎo‡¼Byqãz³äÜàj„´0þW‰„fÛPiìN¤ïëeX‚E…vÁgcõ«Ã^ÉK÷æÓÞ—{êS¹û8)JuC¶¯WûÌB .* Û3¦¥Ê˜œÅ@i`j»ñ™Œ-2m TüýJ³X¼v=—«<©íôaùdaû•$ {8¾ýÁ6ðÑÓëa3âÂ'ƒt2ðøßÁ쨙Péoxè;E¹»[̦è%V™¬+Ζ—ÉŠ›¤›k-9O$í——<*BhŒOBX”Iâ¸Þì"šî¹ŠKïÊkëRÆi;·VN™8ÍXùlÔñ+˜Ýb0»…Šâ0cñ 8ú›á*MÁXêGk9Œ«¥7Jà0¯bš¿þ}ÇLë2ùJåAÿ\=˜ˆï² uad|~©›‰Ï·X‰$Kj»wm·-ÀšFŒ­žíÔ™A‰’ÀÝ#)¸Ôÿÿ„ë_+È”Ï_‰Û3”înk X¢ÍloJâØÚ·r=¦p*)'ÑßZ8[ô€hÀÍÙ‘,+¶f¡PþS¢ .(ÙœÜHù]Qe L¯GôÞû–íÍûƒ¦‚U¶¾v·ý}þ~±c©¶!ZSúvßø®õ™¡ gæÛý¥€VÈJfÚfvTË6ßüKðð2XÔŽ6G«ùn·ÞGÔDi¹ Ï>†Éò_Qš‘\|¢ðL‹D]˜>ݬ;vªþsÕÙÅKFnëWqÛP}Z^a¦•ê•À§ ·é>­“3ÈÚ×+>øÚ&?kFâŠXÌ Òá!m™S§”DÞMàmÆnã#}“**þ~Lî;ý Õ†»ÃàÖReekF<Ú‹"»ólÃãYóû×h¢‡¯®½ ÂyÁ¢–€ˆqÚîžTÔ±Jc¾—5šòzÏÉÎb×ZšàˆË#Ô¦5ŽÛ3P¨ Š‚E…vÌÁ)÷ÿ5–w}y |+YÎ+ü÷²»su¡ m`÷a™zù¼¦\˜ƒ1¼¨–ÂïûéÑ%È?sáÉÒfDUL9á Ñ,•fËÇæªúÄH±âãbBïígMjÄÑQ褈!4H‰DkaÜ‹Ëa¸ƒÊèhžŽ „ç²|Ïjí»>¶K‡ÖÆ:Ý`Áeœ8²•¤éè',í¼L²q»÷ M¸µä®ÌÞ®Ú‡;7í2+¥P˜â4Ÿ5Ì5 q¸‚ò*â ‘_ü ˆ"ÿ㑚c;HXù´ØƒzÍ:mèîç=e7 Áͽ˜iEƲ» £ôž¥ñ †z(ú×ïo¦FVˆœÅN‡¹©Kÿ‰_é7.p„æ9Ã/slÌB .* ;9vÚÙ˜"…@O´þQÒRí0>Ô÷©kÎ.oºhOn2ø¤N˜òáЋNñІô§O&iÔWòÅg'Ì\Ù®Ùp“ˆ§]Ø ¦—?¾@µÐR,+¶f§|ò]ìb9†÷â €sêa½hT!BÚ۫ͳý%…vÌÁ*â ‘a]³"‚ZÕ¸ð¯2ð’ü0yEPv1EA"´þÿÏVœ]¹MêCò>æØŸúú¦¸ƒàß~¹”ªf䙆 Ѐ /IínþÁq|py–ᚌ.6Y '/qûÙ@Ryèþq Èu‹ÑÆG@m7½°6›ü—´0[ðÖ­ÂçòŠa[§@@˜KŠ}‘zºY÷ŸB[ˆSª¦ ¶R\š(uQÇ…Ø+-Dkeˆ·+X»µOÜXÌ^‚ªðP:‰¢¾n2™ŠRŠŽ,Æ»NÚÑ`ÅÍz˜£=®V9ÿ씕”¨0»À8´Þý”•x{‹+2>àA}ýuAÖeI€ÕV d) 6|^ŒáâàÏû?Ɔ<ü#=ñlO”IvW™¥?6Oì\DaZ;ñü™¨³ ñosm®‘äöDv޹9ͯ05…% *|~WP¥€yŒNei1`WŒåxïêQ J]¼(¬/E®”Gð§bQòÚû·›$M^xo÷+‡ŸãtAÕ4†cx²²Ù^/æif€ü‰Pömòà©EŒ?”°Èž3ÖpÏ®I”§‰ÀÖ÷±àÐÔbY¼LÏ÷—¯Ý,Á°• 0žò¿L0–’èÖioŸš tq®4³^DUÏËS®ø»Îâ’ÔeÈŰ¾7°’ó LOk¨O´#ÇdZ¦w®e󺥗 w©fégú%2¿©þõzÿä´ˆµ¾ÄÄ `ŸRaþï¿Û+!˜ëؼÁë5‡ö¯F²ü¼tò0Âmh-…_à`÷8Ö¡¤iÂT–Q, %úŒsšêbçpƒÍoOÛ®Åü‚Þ#ÉÌó÷ËÉ ‰î—‚ep±ArçÌøR7ÑÆ¼4 5­–ýÀ+Ù‰Ê}TùÖ'.@ëô9‡…°;ßœPJÓÝ–oç§ -Î-›Zâó nºÿ¯Õñ,¿…¤ÚÙòù!ö³\ÿ§ÚΘ; ^h)O°ì¿µÆ™…`YÐ ª € CAÉr”Xúæº#wò¢XMÇ{pM9{å‹ g¼Æéj*ì¤ &HK¤¶Ï™¼fÛÖ7µÖló ( £– íqM ʉº –™f•eLW' ^ ŠÓ<3ñsJÓ{²&™Î1Qؤv‰$º¶ê9PµK ŠË&Sñ‚–Ù·:x4ƒ»‘Óøkp÷o"* p]=²ßÃI›páõk¦MÙD{ÂVóUóBCa‰qPýùâÅÈ÷’äO0K¨ÌÉLéWì¼}:îG†߸Ї¸Ö¥3àÑOÔÿ+e0e¯*ãY#ˆ¶+¸šc;=³Xj‚x¯©ãµ’\¸ù(B+Ù••©¡h(SpUì1d8îF£ùSºà˜dr6•p5'E¡Ê[øìÔwýؘԞ]ÃL7nÊišJþëBo¢WÉH‹é¨O\ÃEdáE ·¥pný)ùDQÁ³Vݱ\vÃA1ò²ñ –ÕÀYdE†oÂ0Mg;þqø@°‰÷íÝ0yãö‚»ì£#2ay#–ýJš'ãjEæ"@cÝKAëPTòÂ탌uþ)sgµ9_u}®wë‰ùqð±µbzìëï—•2!R)I$\Z'5Â"нÐÉ$&K‡#©Óã–e;X+F»Ù¤ôd@¥¤?Fqðý}éÊ¥“ð\rÈŒhs´™÷{Íöx1­Œ²´@% ÔôŸîm{~9È„}ˆQ W=Þ¢ðb˜?/ž”ÓÚwŒ ¼*w _eMt9öýâOíí¦È[drœW0Ž.EüÄäå-È%²ˆªÍf†ÒésDFЪ”—PÕÞÅéoZU@qyÞG Õ£­ƒG¿œeµËz¸k£5/êíÚ$)âzÑHÞ Ï‡Ԕă“ÅÊ1.p<³£°T§ôsFÔÎèlRš!NHÒÚÕV’ó,!Èwñ· oV*•ŸÃ‘qŠo…ÕË ”Á ¸0{ xё˳-å\\ª‰ÁÏ—ÿ¹]bGõç°'ÿÒJÛÚÂp2ØTŒ¡ ~ÕŒý~õ­ûBàŽ¸Ü1Éý-F¨ïAÕna–^¸ƒ»y”Iê&æ/ êoع’üz•t8öøºÃΑ-€Ë³ÿ&Ö^ý7«=¢#eóµÌœâÙ×íJ®FŒu;ã¶«3žýŸ¦–«Ÿ tœFèµ SÂØý*—Ý>Îh— ᦠY† bg'®0îŸ{EGOÊ]U³âç•×Õé앲l¤™xq®M{1òçNH/6¨f2ʃ9ÓZkº³Á¸[´nÌé›±ó¨¦[gÿk .ç×’;殉O&™ÍóÚ`1!æËËš±T&Q‰ò/e³AoÜ*ïÍ¥tÀÍmðw$ÌbÎbáJ—SÔxÊ’NS„6#G„8›)Ènv²Œš±kËÑÂVú 7éÈðU½Ð¶!Fë„Añ~Üùâ ã ïbÇz-ò<'Ç‹ôß0ø¸S{ŒXú)EÈ@ù®@;ÿT:T@V¢Ûå 2„¬ãœ3òkRžöЂ³>ŽT`â‘|è†Ù‰¹é¸›ˆ2ÞáPõô14g˜º%ºƒ(‡s„Ô™~Óo€íò¦£+ž Ù.»¬Ä™«v³½aø"üüöëRÔšÑá¸\ø[€²ÎÌÒž¹Ë÷nGeQÝebï+#Ì_Ý¡àÍŸóV¼(“EmxïùÒqìÑL¸pÃ:S8Ÿ´ÅR—EĬyBEDáÓðâUVÀOæÐÀôzÒà‘!©ÝÞå¼A‡F¬æHéôàzÙœímyԆıežÆFû³ ¹©üå#›b3 ê€ø °ÆŸ^¦£(Sïføó ›g£Të‰~B$èVžö™Nø=Ýɱecs“ÝꂼÁ&Ág»±Àd`·䚾whhd[2VK¿gM2§„‹ËÏ”ýä‰}bóÄæk!™¡Š VdHVÓ/d&Ä¡Õ_5C uÀ¥û°7Ýp8ÈIrÃYEê·“À@öGѬ DŽv"a}}æî$šq9úàÑ(…úÉÔ)[£¥Íœ/£Ö õJÎD—¹ÎìÕ<ßc~šЪ Ó¡]ÃÿˆáÃ$Ê’YTEó´ Cz«·Uo«®<‡h¨àn•€ïa˜?š×GÐ,äHä tEæÈ™Yœ¼–'·¦±Ö˵qï4ênq§2YÔ`/pOZ½­¶n‘D8®ØoÕþo°uE%¿‰tÍ =~‹’ÞÜ›_¸RIó‚œ7¤ëwõ©1䯱Á¨¸V†Ê/¹ »Û.¢²Ç[Ì w*ûNq† èôœ˜ºÂR|À s*¯±–¨V@éc’>®É ü»/;àNÎïGÔ/Šgm´é3´[Ê `PÙpVsIÀdR#M¤J] y7Ób‹—ƒm"_zµ>ñ^Ìc¿¾»a¶¼”¦5!ìê‹~ÄSÜ6ì¤zbÕ·P* >ÿXò‰XCu8Aífø9-Q°eR¨ÎFÅM,'‹”y˜m·ŠjÝœ€7V§=º×7^«úñ_—#ì÷¡±8Dû¢ßPu²ý‡}bn,Q¡&^AÔ5ÕòyW3üê}ð¾ðo@7ù˜I-Ü‚’e?äµ}é'J«)`Œ…cÕ­'ÌÝ x *Kw;…êâúÿ Égcí«mP«Úsq°.À 2a­QlFHV/ó¼­Õ†óãƬXK«]k?#þ²'j_2]•`ZrK¿¤úŸ¬¶…5[‰…Ä*Y’2ÚåàÀì£ÛоBÄf²îãV´Æ¡¿žB±»¦È„üdîBvÞøùÅùG§ÑŒ×Ÿ7Ÿ:*w͵ͅ¾¢JPˆ®ImmISTÊ:ÖVK{‰W.g…àwÏÖ¦?:–-µºon‚VHÈèý¡pxõ óa-ïƒT ' §fE}ÁZ‚Êpe¨sý“£FŸãs-"J…ÿ7ï¾”Œ£µìi Á•z¡Sr€¹ïDÄx¦³7Lîβ„Ú²ùN@FÒïptbMnþ»T>cö™ê°ß%¤žŒØÔŽc—¤~*½>‘ô0$ÑVQ àØ½Šœ—JÑßx)…o#f5+ÔÝÂêŽëîé³ÔE9²§éegŒ©ëüXQ‚ìP|ô‡0ý{»Eáã9u¾ˆ{gå Df"ù]-8¡‘Záé;°#ßʦ XѰác’3\SÜÌóäš3«?þ©Z% :¶’»ÈnõÛlAžøwÄIìL8^աܧ’Š r f}HùžrüM‚=‚HÊM#Ö(\^yi:E:kk4ÿa6áZD‡O€R¸=¾~d‹pF û¹ìËñ£vd×DGÒˆ"]„ÜO8<ûí$†ý“K…6‰£Žw/‡tæ½y> —÷â°n-ü~E”ê·a]ñÑ3†Å¤5ßg—qérdè<%po0²Žë­Q¥Â½±ƒwå9@öy‰ ÎᵑanÃ%]U#|Wœô•†Wî ÿŒÑWC}AÕ`gÔ¯˜hžÌcX_÷z›BE±Vkt“! ­4>¿ w¯‰D²yÑË Ÿ3–åM×áòëîK]HÙ»* ƒÚšÁðaž Oz¢sñYn’|õ³0¼{>F¡®Keýl&%òAÞ¦Ñ@ò$XP²ÌD\o6„0Ær‚~+kM¿V·m"¡"Êù˜e Ì.1_(Øuó±¬Ï‘Ñb©FyꑜWÂÙÊ1;70îÌ+°2(cp07[9ÕÆA­u”xAPgÃYmxþÂýÕÄ#;-WØÑÓ¥}DÞ““3¤§ü‡ù°†H}~¬:8ߺEþKÊ:ÐŽY¯÷*H·KÛ÷ŸC¡ó.о3þ~3ÿ©h6„<­%Ÿ4âÓ3.A#äLkF¾Å™8‘º¥é Gž{}éÒ‰4î<ÓcúïÝ¿uŠ’¼­¼\›¯)c öUdu…¤4ê“—lºI•YY(ÓçG5ö¹Ã™-ÚkœŠíuáº1Zü þÒlZæZïI”—«Ö;áéƒÌ~Ô4Td ¨hµeâÁ'V¶ÁÇ^ 1RÜ€”"çÕ,h†èãŒÚ¬Ü æ!JäLç© .©w0Ê‘ô~fQ£9®ÂTöë2c×gôÛæQŸôÎùmâŒJ–n0¤ƒÞ4|1ùcyiJî=,.êš&úw¬ÓëPÖ> ä>Á™*ê]dúÖ¯ ø§ÂÃ2×ÊÓ¸ŒIÊ×™ÔLí?o(`x†óSªu‚_{*9|›7 "½§åiÑvÆÅ…¥%^ÄOa'ÌäRÁà™Šm™>Œ'Àl¤¸@€òê PCØ‘/º°,¸>PN l']ŒÀ-J¸H !D(Þ/¶•ä •ß0Cüzòx?WŽGæ™DVÐ8ïÉCë|³EæM§…mÜGh³#¾ÌŸ΀*›B1oM‰œ5V%I kŸÏ“7õ!Qêµù-ëìÕåfÊ.3?·ióôã×6Ë÷ $f|S‰üe÷+¹ ] 餅¡!‹êü9öÛø—lÙ»V·\o3Y›œþ&lصo‚Þ¢aÆàïcô:Ùˆ°”Ä$Í—à–ãàG¬­,]Kïw‰ÁjB¥¹ÉDñØ¥Ôøˆ’Ü àUü÷Zô½îï°O ‹Ñi¤?z⠗Ȥ 2»‘ó0©lK5ñç 1‹7~·8¾&·¬o¡¿(’èUœŽ“¥½¡Þ²‘Üê.C»Š C´*±C<^ë‰þŸJLz2+¨<ß6ñQÛQ_2á%¢QàÌÏRöù å†nʸ¨ÏFºk¯}Kw›©xœyìÔnBš?¹Bz‘¨‹CøÛIοWÛ,ú‘¨Ž!zK<(™äowQ v-D×@Ä´³ʨªê}4p‘tÕ¼ôÎÞïûšm¥ª=hÛBùC /åìúé —ú÷R­AJ|\R Eêfû¶Òé˜ÀÉG6Û²æê‡‚³e©ÓIš61ä0‘DͪÀ]×€,¢øK ÙÂŽîrg׸FY½pç™Ò¬ ùºîB•ýØÆäüæüò¹²…Œ¯`\0ÀSº2GÎ[5X„q 3L$Ž÷ÖÈ23·æ«Òǽ¸øø‡m\©zk½ é²@Gf©°—5Ê©82Çì> ø àøRÜ© œ ]%$ 5Œ‘ÑàiGyELc¯ÉB™<¢øþžÚQ+FiRhøª$“8ˆÀ;û/Á+ßcõÒÕÙf+BÏ|‘Æ«¿Ãm¸^•!ŒLz '²Âp™2ÍJýß!ì‚NóéôÛ9ñó2@ѳyÁÑË_hбR¬Õ[…ɮʅà”tP”JGBkã·“²ÓÏ5$ çz$Þ“"xhQÈ‹°@|4ä‰ fýtF˜9™ï·Õº~N÷DëhOætó»[»\ª?»q5ƨçþ6]-¢ß‘Ú÷Á zj¼J®¨S§‘§0è#^¨9âhyr~„íÔž·•Q®ÙÝ9éS]ºÃmÖ,npg®jmO¢P8±¶°Pï‹ÿø4¸'Á%d `€Ë{?FÊ:¾ƒ¬¢¿1ЫÊg|jdîH;)+Gô›á¯Še­Aÿ1öü_ªœO$1ÖÖÁì]÷ýéò±ø}¯ {`W’t‚í¸ÇòŸ ™7ÿßâ <¦fÃm%æ¯R—¢Cþ,½=!ü!SÄù+K£”? TzÌWØd¯ßù·(;šõuAvHâ ‹ùýíìþ2“, H§ìõ;°#Î(Z×ùàéQ0P¸u;2&$f UTRºš«Ýfs~Ån¹Ìq›ÃÙ¿=sÏ1Æô6Š¤È´}Cjæ­Êemˆ8 ¡—aH\LpÕ2ºZÅ`ë5 'õsâÜ#õ·Xõ87«–·h£$7qªËRBÕa<9sŒ1à6éqéÙ`ï@䀟РicyþºƒÚþ6Wg`é>—.y˜ˆ(,frc8-›È¸‡)Íæï¹?ÓÝ„Å5"û+!"ßãB‘›Åh÷Þ Ù±þŸ¼·â2`"ä4¶æ›m“»sø¦ ×> VyrsœøÞ2±aO¿aÁ*‚ºë~þ6|¦ÊìoQRr»:Êcð¿Å=Àa­Œ¹®W¬ûiÒ÷÷C°µM™»fô†f¥Õ|k*¢qª+¦ <ƒ.R¿æìÌop¤&† 2]HYÚq ÙžÆ×úkÖ0fLkºÎ”rä‰oÖë1SõÄ?¶wœäŒwXóyBã˜gÄKá¹@…=CV%Ef](ðCuÉùÈe.¹zP Ñ;FÔ»Åõ«{'FØ%=ÝfmØáïËÑÔ$ ª5}Y̬pÜ,žÉMÿž2ø±ZÝÄžgGUQÉÞ*¨’¸{ŒŽŽQszë|#í©Ú`è’î‰ä$²mOªŠ¥-´¬|œÂéÃ:‘à›à0‰qŽ€ÓÜh+/39^qHç˜Ùuαµ2ü«Ž=¦×™Ž ðeP3"w”¡I)ä²¾ê5ÀSCJgIÆûQ¬ÚŽ!f­xïGi«jèùhw¸'£äK§ó wƒ¥ÚCìÄPJT.$A À†w!%}Çÿ‹ÂQþV®B‚Ay¼DÕ*`âo圣 C³“u²‰® °wØv„Q ?ŒÖ)K0¶ä—ú.N—‡„˜nv˜Á¸Õ¯ ôÎÓ­fÆSØA()Þ=A´¸!+ö=°ªû*&–È]-ÀºDs@Ä%{úe×#å{L[äîx‡QÛZ-‚©ùï‡Ç>Ñ3lbki(Tp¹A·ßü¾åãÔÜĽ¢0™_1I¡¼Uøé&Ó£0ö×0á $çî2'¥g½À Q9¦Ê)vHžø<ä)£ëiŽxÒ¦ ¼nk^ö§4’ÝXµÿ}¸m-ž]Ŷ$oÜÆÕÄ¡${s’„<=­Ï¼¹øcòíJß%³D‰¤HÔùfÏO÷I´51œ‰Õ.ö\–Þ¡ùƒÃV° õÅ©*ÐZŠj_ºb ±wcê(<ìL`À˜¹°`¢7VûõžŽ+4vG²E®@§æ´Ú+ǨbŠàô0QÝåÖ±/‡Ÿ³ O,«ñC€O3ž€š·ÅŠýÔÚÛ,@!¶¦˜a@DµÉù€#|e ÙÃø&ϳ¾ðÉ»J»å>ü‘õXÍÈ;e]—/?ûÏŽö‘pľ4ùQÍ}ef@ˆ“·¿`ÿΤì§JYdÎXð$½I©ùb^W\`ã2*‘"Χ!¶Ó&p&ÈÚ+¨ÊV¿ ^iZ$±Äí¥y–¡†À¼fÌÄF‹õölª€˜^mÐÀÛäa"uÈ^¶Ég³“œÐ³¥Å<¸Þl$ÕËÇâE½ƒS×·­‹±‡Hã—íaß§ˆ·rÝë^C9lújZñQ—òp´n2Qh"|öXª€—ãÉ@ÑŠ„ˆn€¤` 3^0‰Ñ¬“SæïêBµ‰To1Û6ßÉå"Í:’É# ¦X/¥@ »ÿˆ`ˆz²ËuQÊo €6¤¿]JR¾þ6|ëž\¯þÉ?`æ‡Á–€}ãûƒ©ªAÁ­øé‘³öy#5©xOÖŽA>ržáš|Pzno¡q›P´zšÌwg/%N´Vbˆÿ"ÝSq7a˜¡Š á>̶WƒõVÊ-‘¸ðNŽ‘tl›§ùù§Æq<ýì)¢à)ùÒlµüɤ ü{XÏÄÀ»·m\“Ò4˜ËÒn5{ÿ ü,ÿˆºÍf™0ï±u/®›¾TþeP–S…rᕊ‰²«h­ÚºÈ<Áª¥Ç}DÙ¿·í&G€ ¯‘ .Ú„yjxñìyF)|ÔðÒŽ§`Cųñ²vOàú‹‡wÔèÊ=€µ´ëºZ#gZ ç9æ«ë‹rŽö+ çò ±Æ €:hjÊᔄ"ÐiuÙ¹’u"K„Æö|+páZwغ|}7P"wA Ðo €*äúL_æ:YK¢ë‡¨T"RŽ/?»¤Ï›a¼ÇÚÆÝ^wA²fÁ’Ç…Jžª€ \I—jK­Œ$¼NnÄó¢LU±.Y ^Sh æl}è<—¸6ŠU-78J“ü@˜Ù,% )C“›Hª<;†Ÿdÿ¿»"V{Ÿ4ÏGd=©_ÈÔ[‘¬?Ý­n4©GCêwã+m’ÇÉH¨òN{0Ròñؼ±:÷*Àq«O'N«· ÿùw“éùhP]W앾W ã¦ö9¹ˆ!ÿK$T‰kø9yΈö¶™þú„£è‡Ò³s|xl«½oÌ™·Ü4÷ÇÿÔa8Ó¸S0A†ŠB­¨™{r‚5‹ðxžÜ~¢Ý»bÛ=YBjÌõAÓêÛ{Ë$Œ°eâà6 ƒ~jËo¦õÍÞ$· íR„¹f'Ÿ3¹}ÛŒŽ!o:¾B„pµžÛŸÏ6È@™§ž(þMûåeìZµQ¶“) yM?ñÂaTÚ¦›ÅgÁ—GÎèw¨ó—ÐhPêPzEõ¬!ÔÚÀQ°}»mMÝE'­Í—RNÈõ«ÿ¦{J<ñ[Ã&‘d•%a|[8Œ7®-`žaoþ}ßÜÁýÐrï}Ѥ¬¶ØQv?Tž¼DšHw¡{´ªµÿnìf^rz3\^¡ÃÎ uP ]«8##y…<ùħ,À ŠÓ;šŸ÷’¬ø†OzÙn¦±\ö't¯ºëØQŸ4ïIqQà ùÄ¿ íׄ°Þ™úZΗӷa­«ú&9”«òŸ=ÌémT,-ÇCÐâħúæÆ–ÔíÈýŸîLÓˆubb ŒêI—‹O&ë ø1¼øÇƒ#P55F8‘_ö;þdRT3éÕ)·lÙCWpÚ;e“ì“¡ä—Ndéøp‡Òî5í¸#—…1è†ù°0KÜ“j&f=QƒÔ¦BÞcYW€+[ ™t¢0ƦXÝÆOÖŸToW#̆õ(€DŒÿóE¤K¿“_IÄa£QD/Sº[Ü©!½„…é]õåÑVƒŸö{‚¯mëa‘ßl}ÌÜî@|i1¼š÷µÂíWvüðvÂ-äÝ©È_¸ÁÄn‡'9{Ÿå­_ÀJ£6ºLöµp¨ ÑÇ‹@Ð ¬±ZÏfå‘ÓVŠÄír²fó¶žÐUPÊäVŸ%Æl¯L¨‘:[é©™DnøŒ‡âÝC¾+U­XŒf3™÷©M ­hªw3›'?\Äç·t{íý €ˆŒÁ?q_*ƒ)ù؆F.ŸW‚š%$ÛÞæžT]þš‹_ä®P?Ç™€ êéá,¡û1—êYÐG^ØÞ¬/²áZ ‘.š]ø!XPL6 4)üBT¯9?7ì'‹ûóùÁìm‡ÀV\¼Uè8ÄÕléUŽnX—±À{·úyÑ\e»³!šú8›cG ³D’òÂe¿G ?±i[“‚€/·â•à½kÅ/ñ.DCä“Ñdè-ªÃÈPt‰uxLŸ4 Îá$¡dæ]½áÌømâMð$_`íÎO’¯è Ûçî¶PÑŒ»í BȬólðzøÁ´–-ƸI³„ÍŠë¢@t£¡ƒ› ¹A‡c<ÍsÈÑŒX×puZ›²/b¦[-‚›:DLÙKʶ‰}¾ˆ 4à¹GE5¨õ)>$ø¼‡²‚Ÿ¼”XÜTÁæZ‡ åXá»ð4§£m'Ê”KX†;*‹e«ÿcŒ@àÒ„O¼»Ø]T {-1òa¯/*Œ]C}½· …ä7UWM‡ûºì}Gù b0JàkðñE` oÕ¦1 K"è¶Lyà ŠÃ_¿öº‹ùwk§%Êq€g/¯©Ù©÷1)AQ÷°SÖåB/±@È[9gºê_•Œ›àâ'³& ÑÞàö¿”VÔ_Íq"l­žlÈU IWÍòøM•ÛM½0>>j—m­½›z)G®Œ¥6 öã„“€Lf›®|—¶º¨ú¦b‚¦äÉÉÙoðèø3/vÂx0í Aw Õ ]9ôÒ«Sn»x £ÎÔ#Õ'ƒ†¦ÆÄ§¬ZEãŒ!…™/Qq»…"¥ùÁ%{õ¥LÆ—h~µ°^(ÅÑZ8M¹¾I0ƒ ^›n3 îâ¾Þ–x4Hb˜Lð‡éÂ`šºMuë€÷Ê£j.²Ú‡ˆ ¶Õ¿ãˆcu{ÞÈXÈñY²ÐØþvÿ¹•ÇÁ…•ÒÎ~X¹ÿWƒY%ãèSà5@P&àr~ uK_FaËpA×7RO…G£Î3‹[˜pcmâÀ¨aΆ“@¶»›2ï)%€È+óa„ƒæÒƒ“mHÊ;}PG,Exڜdz6H5M‰¢èâ9ÖBãŒÎך_N‹JG@4È1âþd c³zéà¨L23h“Žlô®ªÍ5%! N~AˆXÛ¯iOoƒ3»—ç0ƒê<)_Óð¼ÿI²¸®|w^¬o'fi2j|a“HÀ%—’£†uÁ¢×éoúßíÿ¹£Ä† štúÕ7õ 7Gö2M|î5á¬gÖç!†µ«ÅêGw$¡²ÜÁdŽÝä‚èáÆ à__jõLþ¼G Ækßt ò»¢[“VQŒLÀ#Å â$JOù½õ‚¹H_„i&F;v£h?/J„éÓßOé‘<Ï`Û>ËT°ßâ 5­±}í¼“ß‹$3sé.®òîž[§v”¼r¬w¨Ÿ ÆN|Z£ýÀüðñf?”Ð^†ŒËnÑô.Qe ßþW‰”–ZŸá2P6l3©.ªR_3ì³ö„üÐ9@šï²^`Hó=Ñ×—îR‰Hã= @ÂYuHö^yI§áw™\ΑSãl¸Àp;û-Ô þΊ¥ +P‹Ä}ÊsZY4‡s”."þjIðX[—à»*‚x¬…-‰a¤)@No´©ƒ&¼ÅŽÊ#ñé*1åVçø¨ÿÈxt+»vŒh[°… ’-üÀ¦ü³_À0¹a„Fæ DÞâN«™SGs~c¿34£›²È£ßp :É«Óe»)•€@­³±e攚E~™/´å—™‡âi•×î¦HŠñå¸Á­uÆs îÁ4Fsz«ã¬TSÉi€DQ¯¿IÙ“¸ÙÆÍS<É;jªV.IF¦T!’X1îŸÊ5è¢`XE†„‘“ϲˆ™½6ju-ô²Ðº _æ7üÕÅ]öýPMhàý£Ê4­ºH¦+!‹Æ*Ý]Ðét„Â)í\P¡—ðŸxÓ¥ó\(ïø›`´y”$BÞ˜®öÙ _ù¡t?–YFŽbKe¸óQr¢.8±2¨ÃÀ¥?pÂÌ÷%}ÀxäMÖ`§²BüÖ9œû-X{[E_Ф(G´ ù]ÇÐ’ú¯KÅQ®»Ü*¸XD};åKë›[´» 5͹t-ÙÜÅÈS’(åsAAÜ„ß}Ñü1;tÑóÅõBPå,Aeá󍿙‹$É;,²Ðõ `b É)ä<²NÕvev‰vû!–`™5úiööOµoõÅâ¥Pvš³‚äØ)»²·ìòdê´ Œ>%~Ö›¦³àb˜N7UFV“á'©˜à sÜ¢£`™ÛáÙt¢WËjþg‘ý%Ö?4¡Ç"@6@͆¥}bðLµñ >¤ï"ûÒ†;Ønz$¥¨ !)J˜þ¸ãí ^át|â5J ›»RGÓ´Ž#„dUóæ~Hˆ9\”®= ÇWøçôEú¯¨ŒPÑÑ`ΑÙïù»°ô'òÒ•'/7ˆgJS–£˜ ð>+;HÏ€wTKó‹>%ÞŠ€÷IY¾žp4J ÖÎ9]H(.!€Ò^_69­¯Ëå!¤%Pæ³l0%X”Ë>Ûaê3yBÔ`ólôø¯çQ}ŠnEÇ>o l®e´±UKÿÉ I1ßÀt7¶ l¦S×2xÜM%ìë0/÷² ¶Šô)ø¢ã줯" ቊÐ0ë^Â×¾‡éê[ôa¯nƒÚG‡¥Ù®Täñ¸Ø„E¥‘š d-?_Âã—c\+îЕ˜Še·I>íª‚Œ`žPLž^#Œ²¶`};¥Àw( j50¹Ã¶´åÙmcG^¸Ú€ä ÈÞÓ @¶²,Û9Éoîéã§ \”¤lzÌÄr%³±!£QO¼ÝY ’ÁöóþäØ Ûã7×V¹ØÍ,ß/ìú ‘Ï@¤•/ ³MÚ(‰rãÍwü= °¸Ô‡I½Áí„I~’ÒÈê ¾>ôBDò‚&¬Ð.au ¶¥å"…¶˜¼l¹Iz—ø(ŸNU£ãc%$ÂîŒV«ŽiÙl ®+$`¦ÂªØ÷–øE}Gê"Pë­ aׇ@Ê-«üôÉ9ÉN…²ÇÊkª¸£ñ†¶¿¦±Ÿ^@å«Iöتxçqa¤!¥6ãÛ¿<µÝ)-Ç8ê)mL5úÊav?:ÁÙcá¿.ÿŸ2ÖI”Í͹z¾<&˜9”ÿ‰Ÿ,e7&tÜhÜÒãû„óÔ’´·t{YG+É™Úì³z ¢SWŸTaæ-ùØ$M2PygÁC¨ÞßÞX5|¨Ð Ââ!S ðš®×Fª§Æ«A¸ÖØ.iâ³.,Ú$ßë2Þ\‘ø îû§©×eÍ´—X¢€¢±64JŸ#Ö9JLØ"Qg_áUç}ÌÇß üU)…?d7½ýà?g¯—¥”òrBɽC§®¡lÉŒ :ðA#†ËzRØÅ\•ÇÞ²Ž}ÝÙ>ÂÒ¹&ï-±ºØº¯àÏ“•$§ªP ¢mŒ‚zð؇ÛxܺæE8®/¼„Um“wÇÍâÀèHÓž‚Sö£ £í)Ÿà£œGÀZþWI¨7x‰Rá4ÖSÌLg9䪎  ´uŸ‚QŠè©º8«›Ø0aƒ²úéë\ñùiyaežÙ–në.ËòAKªHÍÓ5 ÓÎXr¯U '÷Àûºœ¶ÿ °Ö>û$µ½0Å“™q^s`´ úr<®Ÿ¢~’¦ÚóiøÛO?÷™Ó)]Úy21§¤(ˆÚ7ÈB%RÝòL=d²fïD_È; oª¸­vÀ*¾ßÌñµdQ°O´íÇÙñ—¤Glˆ±JüŒL+!$¸`¾ìÂc@Fx#uôÖƒj²*V´i¾]içÛ´ƒÓþCžÇúT˜îÒe¬¹ê sóӥȼ,(ÈÂÆÔë}¯j Ô»ú†Œx^ò—BŸQ¬úõäÙvŸ÷´©Æoæõ1ƒÉ.sÉyÝ›QǦó®vB7.+þÜœ§L<ë•߇—RÖ :^©­Æ†$Ã!éKôÞÃy_~”»òS,¸Œ„€,'¬QÍ4xC#ä¡SïÄ=¾9,¢™+ ´ó‰Û˜€åå¬UüFªò@æÌéáè•¢t2\’…ü ¯xÛ%*£ô/Û"kÄ@«Æ *“Æ@íþó·ƒý¤‚%rqGT ê~Þj_9N'M£õCêß²j=•2Ð¼Ý ²M—ùS‚ij%DË¿ƒ·_Ž;7;#A[Ñ:Ý©ð˜.fºG³1¢åêâu¸”\wÛeF¾»¹GE~mùÁ óÂe>“lÿ°-ž“q@ÃÝJÚ“ô|ò2×ê>Ê¥;†Œü)×Ì‹nB±ªþ²Íÿ·'X+¡8{Äí›±Á‰ ³TÌ&Yî§Y)A œE8®Dí©óŒm‹z0÷Ñ-šÌ¾}Ð`¢Ïw" Sød‰åéÈÌÃq¯ª=¢)C¾éäº[Dî䙯…è™+°“N½h 1üØØxÎ^çUX5Y¥o­b ”*ŸB°ääiTú¶`•È“¿Á¹_Š,°6yÄ9¡èÓ’í‡ý…`nYF™%b°‹»¼vR»I…-Ø%šéµºF5¤ì´*¤ß|<õÃ0~Ä;K³Np‚3aLh†%ºFÄ„hy3r ® g¶sk߃ÈÛ)䀤w#@¾¡w³{´6¶ÎNƒ§ÜùÊ—ày2L…àœ@ªÑÜHÔè^1L¡éþ”‚ªÓ Rîy÷² CáªËT,=Y²WèìNx½.Ñr›Tlƒ\FE’¤Œ¾H¦õѾžCSÒL&• ïCá—“;ýW‡› º7QpÕ~Â;ÿ-ýAæÇwkUŒ¼LÕ?J‡æÙŽ:ús.a‰±LLÛO©Û$~Ð~6¼Y†=#ÂÇ(ÈÖ†ãß»€;=dñ»çcš[Ç aa'!–ZgPcw‰L!_]x<±aJ"y¡c ¤‡ÂÅq9«G¡$ï5ì×û z­ë&žÛ³ Ÿ÷K± 5>åÿ–ù]õÍæ±fÀÿêÀ hƒ¢zpXßÜÞÓ¢DZû?±ñ ì®\ †%(cz’¡EœïÊU (kS›‡ûuiŒöÂð`hÑîâ Œ]b#ÓÔ‰)  ˜$ሠO†ðQD‡Ò8gî÷ÄFöt1®M\à ҭ®wD˜ü.¼øri©“Ã?b…äʶù‰18ËPüèBZ~£ts×£E'èõ.É7&*}Õâ ¥-kÚ6|"œŽ¥¤qb ÕÐi¨“ó6è·v¹;ý}¼¶gwV°Å86ÍWœRM?*·"ÔvÂÈLò–àÓg\‹¿Ÿ3žix#ËX'c¸“aqp#™Â>j¡É‚­ÌÛ]ˆ‹¬òÿ{øEDf´àHˆ{ =âµT­Àï`0›oZ-·¨È€¦wìÇ3$hí)¨Þ»ŸŸtøž3tR<¤Ur÷ ¨ ÇNbLÿ9‡.tH",kdÃêÛ¡vmv Ïmç¦Ö‚,ßXƒTã)V™õ²á>ˆÄî.¬G±‘ãUÒüó7kºšCƒ‹LWkÕ9æ\w, (WEîõ¢°åSáÛ_ >pHÚ4sÁì‘/ÅžRs÷QèÞ¼î÷ŸŒ×#úNo<©RåmöߎCáoDp€FnÿŠ ôíéöRýÅÚÝ|šå«‘£•ï\Ÿß›Dûy•©ì;e°`¤@‡Ê ë?ø1¾¹êÉk¸¶{nÁC!ÏKË‚BüTEn."aL|¸}XmV£_ŸxÞy®OöLqJ=f‚ˆRÁÕŸš‹±¼ è”es(•;`O‡±ÁhÚl"Rû˜O!Ù߼}£á!tœŠx ¶ÑßgØ%V96ãE€ä„,²& ¢ã3÷ôêH^ÐŒÿ·?îºa¸‡ðî4³WÓ”Ž5+ØÈS oj~ËT%f¨ZÈ1·£dš^Öõo6¬Y¥&sÿoÎIé( ߢ'ñɱù³—}Ȥ~$ƒ9ÿm¶ÈGzâèNªí|Å ·Ã(xp“+| 8[­å“ “µç;Ð6¿á·™p5½I"óãù+Iã me­w;m¦“!ü’§W1Ê9n=KÑ’C,ãhúz óÜüOß$¿ô(&ç†ÐbÚ ûí–w£»bÿû¾å$ž9éšcú@8̉ àÖ\«¦13zöíqaA‰…ýÀ„/meÙ6›„+_–Ú>f^ €ÅÙ.£öØL¾ ¿>¦¶9è†?J ¤™ãÊÈßëW|%Ú¸³øú’8êªRû$GeåP@«øÇÖùñ p±»+FÓ Ûàʆ-¯Ì_¶•E]È~¤:.l§zàÆxT’†­Äqê’ËxìtÁe£ŽÁPŸNMánŠBÐ&«I™ýR{ƒAïAƒŽâ&f‡Ö×Çe pÖ–èxoDD¨d†–¹ÏTýíkà7,²ÔFÚ¢'â«ÿ;W ¶f]Í»qöíH ›Åé`†Ýi¤ÓC9ºcÈÖw­ÏWS)Àùï8/61øÁ:Ço\uih`/ïVâáôenÍæ¬ô6Ö)—vŸ¿ýŸ(ÿÕ¦Øi ÎU¥‘åj?$§ñ´ÜÍ%8ÚÊ+”}“.zà“ÑdHÂŽ]ßñ©ûùÁ7šÙ †Øþó˜x€k†u.~@|nëŒNrÀ?ßb€sHiï‡#›G™õ „®"Ùde²ÓÙtò†:bWòè1ávÀw(Ï¿ÑcÕŸÃØíó-GIÑz:E¼än¾TñhB"Òc ½á+? `ÀF™"˜“.E¾¡v‡)c¹ä-Œp’Êp¶YÄ)Oâ¡òƒ2ÚÞÔäæc“ú}K•@ÐÜÙùôG1c‰˜0Ùã¯ÂýàGƒÀãDg)¤jå­ÂÐø–K· ø=“\ç€dÈf·KíPõB.ÊD¬Ûî²ÍØþÿ·ÿRZ.²ñÍï|òû-®3Yø3®jr¥RŒ|Kþtë¥àS=Bü†Zøõ`@ˆŽT·ÀØ ä"Šh³ïûÚ‰r$_@¬‚«H¯Ä£—§`a¥”¡k`õm¢zù™ MrîNHEGèŸ^ŠL”YÇ:Ùg“ÿNJÕ@iw» ˆ}/‚m>²ê<ïZ·ÔÃ1[Z=xÄÓÈhªF®‹Ã¨N¥´=F¤ú}=‡u}ÎHݳ¨Uäi™ýslJõí%s÷GMoabˆ"„þÀ %tDÕ5G7Cø gG©XÙ¨¦ï?X}aªêtT‚ü!ELe¨Z™ë$f2ÆrÝg@E È@ìI%2 rk,¬²¤r´Ó¨>j9´%ôשxEÃUûïùŠêüf²ûÕST:É» F5O¾ º¦,,nŠ"©{8bGà˜3(ŸF˜£î:Á‹TNB [Y\XßYè´‘P$© 5ϯ3'Av˜iù|JB~ö1áj›;oÀã/¯“ý…;æw$)K¡¸døÁÓ`ÓÌD§x±r¸Ëd+ä_ޱæóðê< ?‹viÝ. äe|$K‡ìåÖÏ5ÿ`´‰±0Å5À~Þ‘kªÑ€¨éj„€h5 ^RzPÿ,_òï-„ª+bošç¬~Êc¥§ƒú\&é’5fï@»£0Ô=³ë?KðwÃuDdsxiŠ/Ì}AÞ=Ýe(fcÑ´+ 3V86€@PzÐo#jêÌkj´Ôäኪš#ÜÛ! bˆ,–qÚ û`¿S­RWçnè"¶´¡¿U+ ­rÜpV3{'&ëðͳ<¿f]ÈûÑ è¾âhî>ÿ‡øÖîÂÊ®ìÁNgÆX jÐ÷GF¯¤Ñb똸*¯PrÓ\‡úÙw/eÙãŽIâU+cEâã)- ÍÔˆ0€*ô/ffÒ¨eºzû}0µõ²¶e`Ñt” $¤\àvâ.-Àò…Ð…Ü!“’w(¢Ô 'å\‘æÀŸÇow¥$ÂüYóUl8 ,c£@ ¤ø tœ~§b0LÍSÍù²ƒ'¢gÃDŒ^¦²‹m©MÌ!è¶©a‚W°‡¯na%Ç¿ª*ƒ´`R€ç¨æwr[\ÛS$³@ÓPðKu»/ò»é—cìñFQÊJx¬-ôüoü‚ÌðØt3yÒÒQ™p¹hæáåfâLºgé,V¾FEºï$³Q6^ßùMꞀ•ÍÙCAEÇÃ6éúÌ~ÁNçGÐÚ`‡u«¯‹ú b³÷CÛ*íà˜‚^qX 5X¼ §a1ßt­{qV’§1ŒsLdsN¹¼lЗ:30kÜunË®Ï触=lµêÅD²ó‚jÍõ"¸ÿ,^&Ì’gö]«ÿ«u{"u#Ö_çÅü`¬ÿ…:¸GkŒßgùâŠH1¼«¬Ü{ˆ19ùä„À¶¿ËuŠz?¸OàÛ‡”?âAñhŽ pÍC£w‘Fw “–YÐ×^©+ÆA{}>åë–T6«Cäi™F"¼ØšÆšî\äEø lѹÐ% ¼>u¼0â_·(øÿmê’9‹¿q0WJìëå {~Ô׃• ï‡AÎíáoLàeÝø‹ØÊ5 ¢¦M#—¢ßÂA+;Hò3AÿTA› ŸB•Ü,|Û¸Å犖dŒ¶¹ G ØÒ¨Ç•Š NXG<ÃY2•Zȱ>šDkÈ9H¨³ÏðŠ´(Eö‡}PÿÊk<†*¨ C+â©p¹=‡]–²ß’Q­š¹§ízíOv>°·~œ”]î+UÌõB–ýk—ûøÌ:È(+ì[öÓBÞŠ÷i–ÿÃMr®€F¦›ˆ Œ(‘&!òÜbÜš¬î't^3kŒÂÊ'Óúk±kÞƒ'=öîy €3*|Ônúùì ‹+‡€IbM,¤üCà‡)A} »O€Éé<«4‘´>*J—*#åˆxÐ…ç<0ß_/]ì·j£æ¦n‡ÂEÈh°I˜ÛïL§Ôêb|BšÄ>úûaÕà„– ʈC­K¥€943VRf§»WQÆûÙD-‘U$^nòãð/¤›[w»XõMZ¿8øŽÕøÎx8^¨ØòQ3®ÒgŽJ•>¤èE…–/mHR ‹P¼ÿôcÆ©IW¾nšýÿæò;PôŽûw“&;c¸}×2>¿7Ð~í¥D$Ÿ¥€;¶Y䉔ª„3eƒÜ±µB±Šb–à‹÷/Óxn~(¥„`áDˆ¬¯¸»°f¢LÍyÞµUþxÉ¡„gøòéË I×+USù_Èíû“€áý.:‘\•cƶCdýÃ~XT¡÷¦Êñu•A¯½^8njä(œ2$ç˳Wó1¾’ '{’œŸZdGoÄA¬l~,xfåHoz×HmúdN(ûkŸm?M«4WÅÌÑã Âl7ßsß×!ƒ!éu“½Ði£¹ƒ&…¦J#ƒ·}ðÆNœfx$Ù-èt­‰ T&±Í¹cªå¥ÈÍméoÆ ôüAÕù´ûÈ%ýÓÕgýOñf•ª`¶1s×]­¾Ø–HÔ–Á·¦Ó|ó/#½.K¥nÕ² nÿ   ”/@ˆ—9áRç96î.WO"`ïßýmd(º„0.BݞλjG¦ëíäkW•62äm#ª#$°ÛJe^ ,ØU™¦ì`õk­7šÞš¸áê¸ óƒ©­eSš+ÏHùÁi5Š æ÷²ïvvV€4ee:Ù#æt’4 seëŸOÕ«î?jó]S wF˜Ò±øzî+ɱÈ| Wß°5Ñ"2áÚ Œ/†Ë%Û°n@ö e|™¡®,{Ú´öTíª–C!ŽUÿ[_”/5_/yy¸fÏ“€ëœ5o­¢p‹ÒzŠ‹PÝ/ØÜûÊ+h²Lk ‹Í1¼àÎŒ˜¡rá€Ç£2Vx/2+'³iXÈ ý†¶\%Q£÷¸¨5ü{宀Á¿è$N'+t½­"Ÿ¥R¦u05$>‰wj?´f3Òª®~)r ny³íbF“  OÅÉ w#Oð.9æÿNd{¼mwŸ°³Ì1“q¶:‚S¾idªã}ƒÙ,z|ƒ¸¥÷ÂÛú½ÍïáxhÔ)ŸýüÖšz»'ºŽî<ò+Cv7"Ï{DkvlAGÓÍ0b;$FWë²×:wáæDvËý¿‰¯›éù#á¬'ôز¶Ò -¿‡ö=ºÀ4õ߉_°%ÌQ9»¶Ç økŸ"*\Ö‘¿T"WòÀ†,RîOæô–Ê.m8W>=Éj‡,Fq(ëf9QÏ®[¤0f1¢×“<öÉwZoè'Ør–1Xb›Q’ŽˆÈY¥I4Vó3C|N/É»;‰ ʘë·{€46º…øßô €Š&RL·_Ãñº˜IP {.Þå:ƒÂæÐ?"Â"_ÊÁjÀÈ'z ðÇþ]ä?ÚãÆàÁòJJÐÁöM™V —™å ø-³#Æs°C7ŸŽìr}5^ãäö©p#,ÒƒâÁbù[kƒ ÷*£qh¤nWJO\ª½0ømÐïáJ¥x%®DEö#Ç?\,vn*5 ÷`À¦Ÿu—žÕ8·Â<3v¢À–˜€a«u£ SœqÍŸëÔ'ßeDÆJ”b˜JãÄáæûùÿþÌÑ— Ú’‚®‘«%´Aþq_Á¹*r*RŸr°!@|áD -%6HpQÇDõ8ݬä¥ ~Mý#«hÅ(ÚÞ¬éa«ÿYÂeà³>¯OB ²yÚ› /­®C¹;*ðÐW1÷"öDà§Çb~N>hDLËn”R*L;-œ‰ÍXR°í½Æñ*wø›i_Çm‰–þïB«Vâ›Ä!¦Œ=‰é …½wÀå¶n€Ë ~¤„Ëp΃Ԇ@ibFQªSÖÉ=ÑW@÷ÉÉÿ®øùí³†KcÍÁ”]r}ý‹!Læƒ Äòq´•êÅ0sL¨¦ò%ébK?Ð:H0àøî°§ja©åõ÷‘1Xd¦]{\;ÃqÝÜYÐ…Û–½"œ^Bííÿ«Wq*öS½Ö’F#ø£+#‰df7Îç/‰q Oµ–'¾V°k¾¼åFyÏË(_xp41Åз» 9¶Túö‚‘ ÜÀè>#ôS#ˆÄ`¤Y´¡ÏôÛ|š€kg6nˆºÍ¨qÎd±çmPíÐ Ô;«=¤Š£}Øš,4ËsBÊ\ÿ"ÑG.¶H¥ýŽÓbøÈÝ!åÅIÿXŽ äé[QGoR>ÂsMFÚÊjD¸¬[«–~P„¸ßÂ{}¨xð’$5Ÿ$bu€¥š9’ËFygÝÉ¥OQ8Oq~VøÊ”ùJ–ò’lÕ~'Lb¯…qÞ%&Ìf‹çÞr²O÷ä-… Õ7¹óGYŸÄN©êl ª³‘ªæH7š+‚4µêëõS‡œª žŒÚÂ>qãH[ÙoùMdß[uZÌø¢a‡G¦œÙ=í­ k˜Ûä[,º%b·š¦€ü a¬Aîqí DÃlSýèÏå·MжmÿÇR˜èŸxñÕòðÖFa©%Èž¬!ê’áV/Lâ¶œl3"ùt |?HwŸUڶϨ}bilºV Qöä Í9Î^¼-(Ë<ÕÇÄCÌ´V/Ô*§ô„x²XL#0c ­ž’)á­°-‚ËèE¦Ñ_*ýG 2T°‚r”ެÃFj¶žB´ú“åœ`‰™(¿`L³X¥·mfÞžê`Âò`Ý­y‰ü„ÎYìµÅ žhì–W?—¢eòÇåŽÇñ …ˆí¡ðZ­Hæ,³ŸÝ&DB!d/T²âp’¦’œÄ’E™¢ÈJï ’0 Ïh¼&¬»Û (fÜTðGÁáÚ!HŸÓêY¬(…PÅÓRØgrpÂŽ¢Î‡N ð’^|gšdY!ÆÖJÙë•â‘Xñ¾ÎjC¤äí:K<ˆŠCÆFiZ­/ßñçôõÈ0ï9”hÿÖ§PÀ‰è¢^9ªN¿ÈX €ì^ºÂN‘ÊŒ`m«eaVZÅkà4­”Ì$jL?jàÇ«páCæ¾YœGw€µ2]³§uìë8Wâ½ô‰¥¼Wð8Fn‹ÏbüÔcÂI")úC´€Ñ?'ýÉô}8‚¿è̲ŸK´mÅ?,‡•j-Æ'A‡ù=x“Ѓ;…Fx@æÊ39FfJCÝrrûÖ4,%_Š#\­48ÜJë·ioÈ"ÇC°á}LQ3:Ùt"{ÛÁ(Ó± ÒEæºï‚S(ø”òÁ‘Ü$Ù_I˜]©T5g/<Ì3Åð—hFê€çÏMšÒ=ƒÔjK Ûß&Ë]xøk*Mœ¢bÿ¼Ù~VþãxQ”гjÁ“²|Ö=aí‡B•€`Val˜)ÏcËþ³E½d"¡ðZgâ{¸…Ö'ïgçýå’ µ›P*Ü@¹²Eí† ‘ƒæ#å«ÕÝÿ?Y—CŸ~Ìù¿Gú¦Èº€PÀÀû°×Û—‰­Å?38ô‚ÝŸU…ï]\Ï»¨1g±n*½ó¼BOŽx:é’ÈØ­êõOàÈ¡bzFÑÈìí¿2þã!ëq/2ÐüMÔü[rØ‘Û~Ÿs¨¿tì±_'!íneìr Š9ºTã30zÄ5¢Íúè%°Mö"°ë®Z¥ˆcì´F›þ“ñg(ãâø(fùF&ÍmøH©æ<­¶2ˆŽãë©H\Lã-7Ùâ‘ys:âÐ-оÊSµEÓ¾4É(|¦Có d¢¡¥?鼓{Ò²†‡ Š”TMrÌÁ‘Ü4 ]¦æ‹µCÿv:л,ÅÒÞn„\0ƒgÕo«X.XAˆíO¡"«|™qω;¾@¢y/Ÿ`år×o_{³ÿ]pˆ¨Ì˾Üc,Ï`$º×íò·*-°½W¸aS³‹°SãÕ£^ #õ'“¥g9¾ÜÆùŽš4¢’ÜÄ·ÿý³a'_—ÜkW‹á3±G60B*æ/aᄌ­Àc×[ÀcZà<6" 9Tj $"·@ å(/JZá\1`# 7  aºpáL'ôX… 9Ÿ®mgâî‡1ÿÖú§SEÑîD.ÀÙ!$DÏoÌ6.œsïºë<ŒÉg^¹Zú s3ËhÕ´Òd5?ô¢MSÎíȉ¼‚k¦ùçCi\¯ad-º!$F*C+˜ÆÁ7ðFý7ZNæä+Ä:«Jƪj¨ŒÔçúØÒ¶—’¹=câ«MGïIÐì‹­›‚1ð?¡ WOõ›—ð„ðo¶Èνp½NBv¸c©n0Þµ² º¥—’ŽŠ™ð¢&FL5†¿ç¬\¯Ú±ÛðLƒ,»ôÊbQiN'®‘4K6WÀ?6ÿ ë¨*6  i&/}EwÎÊ{´ª¬‹ ˆ =7ö¬<–8Ø9‚Þï[êÄÇ]ªâ¦íxñ“‹çõN,£ãHô"sƯ‚jk…öÔ­PæÒ&äwiMçr»ö°Ÿ¯"/‹‚±Â5:‹]¯iÖÌȤÿÖ†öã_ô/]°á¼-íß`µ’&Èè¨E3*£æŸ•ÑtÈvh_ð¹l“©P¡¦¶ì×mEIÑÃ^ÎÆY€ÀF™Ù0ñø¿Dyfæ·¼:(šD9•Q¨y@½+ìš‘»S!àÛ7+X\¸­Öôš–{Wô—MØÄ¡6ìqø%í‘ͼ8^ºvæÁ\Ê‚ì›À'ƒ°`Wàã­|5d¥„édâÞYöÙÆXÜNõu 1`G&yB“¾u:¡ÍpM ¼Þ4å/¾†üä0ų#Iïó\>ÕqâÑ B³ÅË/MH_8¸¸¦¬>¶2¢›Nç•uÎ’ZA`,n$rF/ꎮBí\ˆÿíaFÄFÜ`/ùÎ%‰–=»›¸ýÏ©&°”,NØÆÉˆ6lë—<–~á”í»ðç—ak cd˜#]àâq%û›\þ$O0LkI_¯„1ÅÂ} šçðÇ›:llÊÖÈ,¿qúÚݵv„M{„ã d®Æ\^÷VÑœ%R¶:™¸{¬ô±Ç9±eRâüJ‘-TúN­tfçß³UÆÿý•ÃÅV ÔBŸ?t9áfÍ Þ)‘úá² ŸïjÚîjÊÄ¡Õ{B:,‚tÐnÿ"x¢=αˆ&\ß×*Tj/Úí¸5uåSŸGz´›9Œ„©ú˜yö’޲ ²Î>è=o÷GÓ·÷Hsnÿ*eíºñ“jÇҘըdä‡|i152¦|×'cnørîvëñå„:éÁR…Ýòè›J÷»öŒ¦^ ƒ¯ÆVÿkлЮOÙ‚Ÿõ’@ñ´%d@"¶É(b±Ìèùš 2P´° ÂVu‰ó°e 1Šz~™bsé2š­äønƒ´–šˆãórØ×G^ªz¨ú&<€Ûb:W :¼ì®(¼•_R7’ò8Ä[{ÄG>qo¸YýiA{›fDïÕ#“ae™ÌqþyB¡lh…@+ÅR ™÷»±v«õgÅãü«¾òþ7?"`_yÒÀfØRf80@û1×uË$]—Ï·Wàâ“yºÃÁÝ6€Žhgt¾§Å(ô{ÐHϺ5Ûp3£xB³d^Zë]ûT/ÀEQu³bT~Q²‹týˆ>Y{ àü|Il`áSLD­zðJöoþ,AD/ R‡¬E bÉ)‰t£–éÉ :I(DV•rjöùe2’8·àg0;I °»v¸ó’ìï"P4oêêgsRä.û‘fër,Ðì(µÖeUé´ w^¬pŸ¿)QÊÌzáàÖ3ˆ —˜´£OnËqroŠ7Ò$WÈâÛÛ›3"lÁšÃp!Ý鮲©ÊËv…À£ce†NUO¾ÁÁ¿üb¼qÁDÀJ”åy¤:a!T gñD?øŠšu©ºqÉŒ‹ ‚ɽZd²€ñ}? ¨òêcŸ?±¸—N"¹tsŠŽ—í6æ  Ò彪-æðª†' K¡nŠC{•Žj .Ü!;wh÷‰îôZ =¦pˆBg¦¾Ë3 ¹8- ̀ĈsãPV³*Èáuh˜§²Hñ™ÖOÐô”7âî›%裼 TbµÜòø¸ ^¨ #3ÊÁػܖþYÜĤeÍ6(qÌz¹]ügyE’™\U·6ÓI©ßè(Ò6”1ï}T<¬!Mj<ÞŒG,y-€ßZè‹Uµ ï‘/[JR85M5—i÷ÄÙÙFµBd âjàü»º^Xz£ñ$,cip× œ€–x6{sÿá›Ù\ÌtýpÇ×¶²ú{ÕÔ¼Z‘ ×gêp|Š1±©ÏF/–À(”ÆÔÙ!Œf &+Åh?EfEôédæ©`#òÃ,t_Î¥ ÃÁh hpÝ´->köˆ¡=gõ“Ðh6c©N›.—Q†qÅAOÎ\±¾‚?ã +\ë¯cˆ›¤I]§DáÎNE¡ÓSáFY5¦Ö,¢°|#ù«NCîF%œ÷‹æ/?œÈ®ïWìfŒ³¸'¢íxí.xBzËqô–;¡ ¶œøÕ—äw‘"æÐ§Q [rd±P„|uð^çvjÚPæt‘íJCï].ñO…öyPœ›üOXV,dÆZƒ4&Ù¹kú£ËˆâZ´U!޽q¢·ù"|q9Cò–s<íâp{ò45k½dz<¯›ªÅñú{§ÐR•OØ,©ŸýßÝI˜>_­#Sm½0]¹·(i©ý¿(–ËÆÍ#%ô#)bô-᜸¹×tÏUBefäf Šs¡‹ËÅ…SÍáƒì•ѳ'!pçCGÊ.Z¸¡è¤²‹ËÀv‚¶}ψ穤}W}–ûŠqoí]Oa‡Â¿|.Î)Îõ¯]Äa2ä­¤¾Lrü}2ñü à "ü)3a:‹„ FLl‘ÂÑïh &BÂcwÚñf‰|…î£2­Âjï³Æò `+“%[fݺò0ú]£ ÍDô4 c€ÁLc-y¯ð\Ǽ*M»gN.ÝI_©±P¦ÎÖíî-8ÿ`ùÔ 2´]…w . êA¿øÐ¤!Æ|±Ûd—[¹ãNt<ô,Õ€ôᑨáç«c+N Zò^;ÌÛ-Ä×ÞëlBíÎjÖ'î¦æ«(tÈ5†HÈG3€µÉŸE"uRö$8jX‰š+EÊN&ÈÔÏ*ŸVؤ£ÛYÖ|g,; 2]6V0TÔ»iÌØÒ1U,¾Xd ‹ ÈS˜Ä‰y…M(»ýÆÍ¡t1=äuQM­‚Ÿ‚5ã]âÄ0ª`ïvn'Ïì.É&ê›à“”.ìx¼îbOqÓâÓFÑXd\™ffK™÷È\d¤R爴a™KõÉ y¡a¯f½sßËä˜Ãyx%¥âJÞÄs²ß:ÀÛ>Òn𥰍~—Q,9TûU_Áƒe¥^å +XâŽø™e´] ©Ú'o0„ n±áûx¤æF¤IGû–ÒÀ÷§YÅ ÚËÜ9 _2”)'sE:‡AÍ{Y».÷y‘%S:„¥RÉϲt®Í"ïðØŸdÓ ¨²x²É ÆŽÿëÎúËäÒXãôWñ ¿· p¦a=©šƒyi:K‹«ï̤sôÚ€Vyô.×'ÄZ¡¤0ïÞ»EzŽžÈ±:[¢’–Á3œŒ¸Ìƒ°Dg1ñ‰F&ifòiádô>­ž›s7_ÈGÚU‚pÛÍ»¶­—s–Î!âúÀÓ_Σ:|Âb¯oþWÔì:àŠRÜX噎4e·# ›­ÀÿIBW‡Þ¯M™û°Î¡>rfp’y•£Ëð¿°¶,}L܃öU[ÚÅeq†õ¢G3i/B±Z›¾¹GÇ ÆÀÚŽòÝSE£[»â¥<]a({-têgä”$ª”+å‡XÓ;Ì \»ÛÍHB9ª«8ÊQ²EˆÅÔsHôÑŸLÍ­Z³S1A‹¹~ðé[fMX«/à FÒûñ…E.céÑîPÏ!íˆób8ôŠeÉ6,½æÀGÖÐDHŒÅY~Ýò|–Ý«ÎÒ2;!:Ïö²bKÃá]Ìu‰µñ:%Ö7t>ðodjþÿf{—DîðA;)· ,Œ¹PëÕ.RêÑ)ÂwŽ®¬¾ywCce³ð#N¶ýô•¼êS`>§½›y1H¥)Ðå‰8½•ࢠó=Y:Îop#ë ÞS¥_}ráIm­ýXÏ”óÇçEgÑ«À§ ÜÁ4UÇoÈ4‰íM´²<ñÝ–qÑÁ…Û8”Ë_ôõ §¯¸ioâ-åÓ˜2Žd†óúq®ÖüÄê]Bj½|ç—]‰4G0~»m£W 9Xë–È ÕÀUŠâçöæX±d¦lOÞj‡%”ÃuÝFA0*Çå©ä\S~]¥Ñ»rê\ÜYû^Âv±œæ¯˜¢ îíÐB~9Öqˆ–õ_꿇⨱…a,-›³…#£t›é,cCHHc?ˆA¦çžEîU´ÆššâüOºVÔMÇEûIš”MγNC g6ê{ÓrɱŸt hß|g7{ÉZwWÏ. Ä劉™B§¹é¬wWˆ¶<Î9HíÓeaÁJʬ¹–:¢G ù¼5ˆý{o9ÂÇúlëØî6ç^ij=­´Æu`] j‚Kv|—äuLÙŠÜÿ?a©õIÈ7Ñ„5üŒªà2¢§ŽDl [=éˆ+9ò·#±(Ëîâôy W3ÕˆEpÝŽŸT {›,¨~ +õð™XgN!ä‰Ç;YmeˆE×_æKú1–ïþÏä Z ×胶ŧ„¹µû(hÌp'‹ÎmÍT⛸ˆ§xfê†ÐÐ%+ñ{äÛ£:ìÇkÛBþ3ˆ—ÄØ ǫ́3ÃEKudî’ä§ó*è ª¢ö?§¼ Å Ù_o?BÆ+óOjÂhôý’e"xÔ‚##»^Á½ÊìOÇWußšÇÎfn"v*gµMXa™ ( MnvÑ=®'ãÆ¯"ºJKBîòOÉ i:ù6© û’E$·®·p”‚n³ÊÉÎ#:x‘pµ·Ê>ë«uÌwÊtÿø†Ð¨ôôÉ|PìʺO_tˆ3eøŒg:JÈJ7ãy”;!è"1™¦*{ÕÅ1ØÉ§9-¨2(F†TOÏñüÂFæŸÁ¨Þì82:çqbí4p¥ 콦Ót…cɧCª4gã@iú'GèYêI<Þ%ワ*–÷^–Ô ïµ¥n¦âÞm AJÈ4Á2y+Ö§™ÙÃÑCȼ±­›ºCÁ1òÁE‹â'c]>7#¾âTC |¸\ߢ•Ê‚~§sÈÓ뽟œ0öˆBa²‚öZà.“35¬îÉ"¼ö0}ÕXPª¼t‡`«Êã™ uÐÔC÷760ä(­:ƒc½O ¨$sÁÖÊŠ%Ýûáˈù²úe³.§¬hWJŒ¦»¦=l¸ÓaAIùÃxzÕ—™vU1–ͺ40Åí‰%î<éÌ fÐ8Ÿ"¸ÍÃݱ"Ófõõq*$š>kÇdÕ•“‹}î”óDô^‘³?þÃíBòž'¼…_4A­͹NöØíЇno%™ðŠÐ2gë¹ ƒ¿1a¢/N°©'7h:"Y²…0¡$oNð‰T: eoYî‰Ö6ä”®UðCÝd^¼¯x ®›çlg¼¯RJ Ö\½€"eÓ¼vê¯íÓFÛ—`ê<ˆÍ …öfÓ×";¦ÀpGç{îŸ#éÛ+?ï*ó`&Ø÷Uu!¹æî²{‰Q?lòGƉ´C÷ãÉ*9>ñÎ|­È=¸îbdòPGÞƒÀ?˜y¼W÷Zói¶ÚÉ“ ¬¢ˆPƒöU/îñP¥A*ËN¬Nýüþk>–ìC¹ßÆCo•ØAZÑÓ7E¾ÃpØV—ÄJ¯©£¨¯œœGЉÁÜgà|Äþ°æ­¬ðô~˜UÔgô`×ÉR-%[eÙpÅ­Dðˆç«•ßÇ¿êA^zGõfÒ ¶ú]¤°Éö¹xxêœ1…ƒÌèµ—ÝWKålE‘ˆÍ¼E û`Ófn(uñ[õ‘jéÊ{mkô#T9É)'›å-¦*HuT6ÚúE„lqÛ¥ñ Q¶È£.Æ ^êL~×¶ªû HØq±Òbí訳‰b.#üœÜ{ ²à¤d¿[Û¬ˆ»–0íá ¿â ¶ðïgÎ)Âcœ^­¬vÎÝ;¯üÔ|zHà²ÅL(ÎèÈëÚ&ÐϳÔ)ÖêV4%%› 9›m´l}U·3°“, T²Å{0VË¥o~šhÊî›gPÌÅ>T,ùÿP@â^µ™`¯‰™‰×ETÕ,›ø(×qQ9Þ‘úôÛe~C2WÎDïÞýuõ!&>âzPD¾²:ÜåÛF¡[ß)؉yf僖È]ïÇm:4)SCR¾ƒ•0žÆ~!r¬y1Ù-û×Öä*pkÏÍú>ôNoD¦¹’%šÕ{¯ßbŽMª¡X//m=YªTFš¦Ä Еû3+™Ÿ´É=jÌ©t.œejßqmSxcC…6KÃgB™ÍBãQg)_’rµE¢ÇÜú9®§0Á/á[ø:¦ØXŸBÛAéÿÃè‰ñ]Ø‹‚ÔM;Ob‘º-QúC‚¡æ@tŸ^ƒUAPíEv:õmˆ©_ò/RßVÿZ8]v‘òZ«t(o[€áð‘_-ð‰× ¬¢7iECâc½S¦13œ<¡›:ŒÇÐÅ7éϹªÒí“ϳÂu\æ©KEØbÏ*Á\%†Žxs4á?x£aìZ oü½dŠ#n½6Ñ‹Øòí †ûl´j<*þ·ºoæ$ùšÎb«fÅø¾ÈEÿ³ïgFm=TáâÑD=ûÐãâÒÐ_NŽ'Z¨„&`¡‚1DyÜ”µ\C²ýd…z”¼ñç@vwð´‚g{J!A­z‹¼Ôí(m.ò‹9?¸(R²Á¥4Raí׿êåsQŒž|9\'’†«ÌhÆ1É¡?ƒˆàíI8*R¨KÂ}âý½D †Ðô>oš« ÚÂÔWØoQù\¿Hcÿ@¤dQ>‡î}€ £Ê£3¡ýÑŒ~³7FMYÙQn–|ǯU »87“IQUšéÚ‰.gk¦°’Z©gGpµYÛgêc êš²¨Wþ‚Wº.mdÅT²ÌtΔ¢î¸o>AÚÅeëŸø!u±ˆ&nüÄÖòèoda"’±2U‹‰_î³ !:áù«Ù¹i’7!ú¾áÅjª&•Ƥª«<¸'¨[obð81£j*€Ø½”¡ŠWÌ w6‰Oøú®M<ÝÇÁ³¸Ö†„¨mØɰï#ÊBÚ·ÙíÙñÿ„ûí«¸"~ØJv­;£`æPšÈèÃ0Ü »|€öøT9ª$žRS@¯8ÙÔ+ü:|Nl&ûKÜž‰ÆPz6À8¡4+£Ëw Æ­Tþ© z¤`ÖÖ`°Æ¤*ãH à‚2r ,iDÈ37£\$È#áq†—`bÙ‘‰±—Lëýú+LŠPË9¢2Ã÷ V˜Lß1QÖð&eib¸-ôþM¦¾ 2äçóæC-Ã}ˆÓ’ ¤Íh¢ 3„ˆ­ÔwýÃe'‹akÖ©T oˆ“O¢'âÜŽá¡Î¦û™éJ–°\¬70nðÀà³O[keÒ +êÕëÞi‘ðUCé“ÀÔ¥d(¨(f‡Â8Æ„µtP}cÊj¸¢FQCß¿ìó<Ç?Çÿ' UÉäM$Ü‹RL¥Ï%BZW_W‘…nýw4('š ÷{£éðÉÑpÔ¨‹ºyüŽlŒŽÐ(Mo1'Z¿Ã$»s~1¿0ìN¯3ufÁfœÙöƒ/è¹4dŸæ0ÅGóä2Dæ&Š0žQÄü°¤À瘮™\LØá!ùwˆ±–j ëH›C`=ìâ' ]C/HÊ`__Ëí8ض“š8"Ë”£U¥ývr»l#¡k÷Z:7Gkå>D¹È‚A¯ñ~¹)N3„‰ÀŸZ·gCÏ€ô4¶‘­=ч°æýp©ÊV#þB+ÃB„À?˜º´ÀcD U2"i5Ñ Ž¦T˜S¢Ñ46fD»•y)ñvÕé5Ï¥‡$Ÿ/U$­è»¬û¸e˜¿lå)'·1¶_”  MØ,@SS{Þœ9ïÙð: U½nyß4¢Ã:Cª `ñ1Ï´ØÉ1 •„`Awç÷'Îl&òZµQ˜PYnÆdÀzaê xÞÖ‘Z—c—¡cĦí2’(g)w7t侜¿®‡KɵCPzóSðÜ®ö0Ú—‡›À€®l“ˆJ—=r8$ãv Å»®tb ó×ú‚/ný(ß:øI¹&ÍÂÓæÊ9$Ø–¶•0{øºŽßƒ;Ý\aAÃåj¾(¶#iÉ"é=4'`xâÙe⮃wÀ?—¾gñ­¶Ð?2‰æÅ”§š(z#nØMeVšåÛf¬Å#3ÝyšN²ŠWnOFAb¹T·ÂZ™/)M2'ô”Ê™˜›ÎŠ–fx‡¹/å‚n#ð$ *FO4)˜ÕiWŒ–éÉÖ{Ÿ\ö*fŠYÎàG _ò­2¸èé¥nk%4f¾ûø?ë;,ÀŽa¢~U>Èè?#œŒÐ&ÿ2äð`/7÷PWÍš8²©P¾YïˆjÌ¿6£Ô«Na9U†Qƒ\¢ŒH_´]€¬é³“dVââ&{²>ª™‚ v€ìM4BÙ£èëѬrŸ»â?‰êán྽5þ6ó´Â(V)áœç’è¼1 ¯ÙPï@Üîè<³Rsà 99ÔUqU´w„W:æu¶Pa¼„ª›ó?Z°ãß,³"w:JœVå ¢™é@láÑŸØU¹Õ(ôKv3rK„)}xçl¼¬Ô9žä©R/A! š~‹’æhs0?SøÑ m+xª&qÚ¹ša:‚ÀÃ+-Ëí¢tªûð¾šzóKÞÍ$§Âj0Óc·V¿tÞW«%½>µã/é{õ¦ŽÞ–\†Á˜9‘[ÒNC*&ã3¦­cü–AéûpßË‹üD2FW˜#d³ÏNÌ¿bþ˜¡ÁäM×ÈÖdp:$Šˆœe†Ýà;jô±‚rZÙ|iNO<—:Õ>Wú"ÄDš­±¿*£Q È“K¿Ý `ü$¼le_)Ë¢£nNÍgì:ë5®X^±“–è3Þ±à^xN d#a´½®ºœjlýÒ.¹ãI™†ž¾ª¸áMÉ(´’uÅ*²n8>Í)÷ÐCyËû¿cȈ‰Üéì:†<ö%ÞW­P/ôîÕ_-¡Xqî¨ÞhŸÑnuïÒ¨ÿÔ5óÉå,Ñ.ð…´¸©TíDÄÓˆ¬Z@°¶Ab*?ýŠAGÿ!%Þ²ùE¢œUçÆ¹²ÛyH08l?ÉlN«íñdÔgÛµ>cîKàZ8euüü-\„µÒ4­ºuÑ…jžKùv5 …³„Esÿ¼Ù¤ÌêÔ9"öƒ”€âÚzÉ#Žå*øqM:Ž}Áˬ6Ü<¶ÌÈÙtÄΚ#ìW:ýóе Ü(¢oyºËï9 ×pDÍÎò•\üZ\͉ùÏc÷ÄÇ+a6ªÌõsªbÝßfq/göá¼Äw×RÞŸ1mO½nÍ©v•›˜l{€qu‹®_É”Áq©Ç¬>s ÍB+ÙºPÎ=ÊRÐ pÁÆšÓx§˜ÕW±LMAߓٙV¿p:(¦¬“;dVª;úŒr!y帄Úw?L“b!kz ölãôdft’„V¶ˆF¸Z4æÂšù\5îÑ•Fñ—?|ýòT¡³ øCZÙ¡!mdØé:‹Š W’}N×eSw0þÙÜÛú€Ÿãª¸9„¦†Áq ýWÒ£Ó!•±Ü˜¹ðû€îâÃhÁ"â÷`Ž»],°À÷Œª"„"”ÀYÅ©–¼XžCd½Ë–bîuR®uT Ý_12 ë`ï`œÄRãN+gÉž-sH’cþ4»;¾©”dŸ¯xl˃cµ'ö(ž¢¸i°yÑ¿]}LÊ+^1MgÑnã¬ñ… g‘DíèzÑ @R6X×EÏB—ÍÀM 'Ió^Øg.¿ÔÒ¸T¥l]mu|޾NÂ[L~>¹Ì »Ï’­¦ÀÄt’ºêšbŽ­Ú¬¦ŠêWÛ¶v"IÁ›sšo{*¸ „5SZ¬+…NF„áí@Q¢à0–ÆPýdQq;Æ ºõ¸j€¢ØqL¹´?œKmd"E³©0ïU;Ùž}½«K¥‹Â6omüç^ÿ†uW&5ç¸Ü‡Ótç&ºZ¦z×WRo­0+Óô"“@ÆäoæáèòK_nñÒOjþë”zÌq[¥Yäl2Ù››Nk„ŽTIé&¾ {d%¢¤õ®¶ä^®Õ䦕Úzž-ígo6y ÄF«Ž¨n ‡¥êRÄ­?hÁƸ¸5•ž>I)g¨O /^\ŸÕàÛWÞÞ@1¦Œ WÉsÎÖah&8ƒIÖ²Aª±IKÕgÜ\½k?;Í0¬ÿ¨ È]˜Æ?„¸Ì!'>«Ÿ=ì§UÄWÚŒAÐfƒ¬PÈ„çWz§)•ôÎZ—WÕEˆKáñHAfîô€Ý¥ùbæ\er;¥VSb=|HÌ'Õ‚ì$ wôÈeö¦˜–F(AóÓØ,L­â g ÷àt™7·*•ç…à¬_±Ì§žÈ:«pdCGÚwÈ… T5[å5˜ÐçæôþU^è&^Œu¯Ž;Ö½Ýâ»D±=A̲Z4†Ïâ£uÀ…wtq6¼›ëÿþèõ3yèqiA=²ˆÕÚ¡„ßÙ\$·¡† 3#(¤-JP-ûÕ~!LÝ—¬9ï¯~ùïØª}.öW [EM>l*§úË'C¨r¾U;j"ca½Óøí4\é®?ßÁÝf:L9ŽäLÇúq ÇNýxëO¦‹“KÞ¤ä²ê_1ow7N$á§Õ»-+!…8Vû<ó½::MXbG{ý¬XÜ)ð,urFX}žoš\†öÿ\*ÈR/ì "‘u´Èö”`¤\…mΗ–E· Wb #oÃ&Ak½PstPÏÙ¹@¾§'M,²/ 2àŒlwŸ÷N‚zOö·ÓF‹Žª™¶E'S3¦E¢¤?ŒQÅ^f´VÈ+u[&m@ûÖf«ïÞ¶S¾Rk¨‰H˜”ýƳT+ñj¿Æ;{Ó^€ÉIKt³_™±zºéÀ«AB† 2C4À`=ÓÀÝ’XÏËÁî&,Ààþ˜˯—޲àåÀ"Öd3ñþ~c  ‡0Þ…@‰—绯Çìü–°‚Ä!œLœÙòNiÀ8 ìî˰8¥é:²iÇæÔî° !öâØnyø³p˜²ðQÅáòDl4úòX¢XXº¤õ –+ýf´Æ8‚ÿ¹¼È&ÙèÕ:HÁS|ec´1ßù•#SªV-‡ÒÑŒK€~Xb>;$“è2*Û"WO1¯Äð´›ê™!Æ?‰÷ÍS(¸¼Â2/‹Ê—ݹ)yXõ<ëÜð‹ÄÛû¾ïƒzæÓŠ~æÀ£±T(‚†VuVl:û}$[UÉgòÃ"QÚ”:¡”GÍcꇕFôs§6eÊc]ró $þ"hnŒÕü<1$ÃlºÊKyŠq$'â=NÎ¥³2!…%c®š·¯ÜÓ9ˆ¶›ý†Ùó½/”7&Ó›M? G$R˜ ãÁ&i÷*:ñœž£·‘ϰ|Ks'ýš´$7ÿ!eËb›mMÏù}ó(BEw@;¨6‡¹L†¸xYJÊZ6Íuò†S ôµµ'1«¥wPÖ‘ñaî¦ky:î¾Bb>vÅÙcJ$²¨¾i³ð‘´%ýöt©Gƒá?òÛQÀ»®O¿' A¹«–IBs:Ì–…Ù—Ó») ³ïõ¹sçä~TáûIˆÌf}^Ì÷«[òà’¼UÇÆtrm|’^@‡ÝSPAøqls÷GyÓa{¿àHNÊï TªcŒÿ–e ¸ÚêýïŸmŸ´àv-'ßkuÕ’v–Z”yq`TÇó]Ô\åj-±Éü7ÎpÒmA5 Å+œZñìf² @›‘þáÛVp³'ËQýÒ"¦ŸÌ ¨d­€´ÜÚ­%ª,Äà¬ÌZ”ªjã:óŠ3a¤ÌÍïÉá—à{ÇXÃë`|Ïñ=lõòsI¨ÿÒ†áÊ¿Uô"ºœ-Q“¦(¥Æëà¤5¸Œ† %©K—úm“Mrk ÀÕß¿;ŸÈš4ÄÌL}ºæ@p£þÑuž®Üñ>«ânŒWµÚô~ÉZ/{ÑG¼Œ'öXà}"¹þ%H°úÿv‘x»•¦Õ_7ß9I/®-`”ñx^Ä&Q7ü]Py @dÄô²o@B“Š¥{I²9m潘ÄÂÞAº‚÷ã]ñoð)Ô:‘óc›ÒÊãmŸa *Þ¬<¼Îß)ûC=vŠAV)¤I`JÐp7Ç« ô£“¼jn;‹ª þd`í8"ŽMýÔw¥½ƒ/òÔ[£¥(Óµ£= ÙO°ÂIAžÆax¾ºuÏ >¸ªqHîy S¼1þ š¢W”§1èÊHÓYµ72-Ê.pã­©ð_ì:ª.5EOm¯ø‡~ATSæ!ëGÞR‡<‹'g;åœz£PžÓm}ãð”Y°ªÀ©üÅs nÇPÒ^ã剙“:û«|9T?ü7¶c¯S‰”Ñ›;/ZËv©0‹°á6ÏL…Ãð®¨) )+Œ$rewäe…EáçTX+S1¬·öG)yDòZê‘çJÝÉ1LP²¬Vpƒ–±üúf ‡@ñ«zÜh‹·(³ð±·ÔgsyÆuI.ù³ŸËq¦V —U‘Nm-›­êvÙÁPßÅyrÊo‘×ÃÏWûž;˜r .Ö´1[+o×>S裖œçx €áðy©AÇٙд±vÂ$¯H¢ø®¯C%k¦·2“‹´(õ}óþ¸”éLÆÉÊÁ‡¬sÃs®9±nLYz‰–D™ÀÀN`ãy®ïHžÌXæÑ¸I×Q¥¿ Î4hðÐ"4Zв Æq/ÀÕÒ¼ÝÀà?Î HmÿZ< ƒœ“Tº_‚_[)SQð¹“*ÃçÕ-Pð}”YA`:ýFÚeGgE–Ø…ýÅUsº‚]U-„6vƒM .$¥^Í£¡¾¶KhŠÈ¾®ó:С|A˜úSTÕ„gKª‡à|ÚwX50~p¦€¨«©Óz›7Ó"Õ$‰cÞ‰&:\««ÌPDÀFs/ÕšŠN³å‚8¥'ˆO\ªfp¤Ü=íLºvl´”³±øÍò|Ñc’®@W!ªÞFo‹kŠ ç(þS±·þ  ºïÿ°”Aæg*?éýçÐáÔE™íÓ` ^5ür%…T¯O i´0P bFv“GÖpÆz!^‘Dzk€Ó$â&µîüFÐŒ™êiJs`‡òî˜ú_Ò’³âûi#Z›§K£ºØOV¹pbzHÑŰݺöJHëµ"± X&HöýMôhQO‹2× “ÏuDÿ„§üø‘ðd3ºexÂx“…›°v$LñUií·3ÅHÀA]E¢ù-:NÍ–íM-3«>$QÐë—G«À@"-Á-Ãïô†¾Üw,Í¡Ù{…ŇûíFÑt„â\ãé« µ¸óçrëæ½'Ú>))˜Ð@òQ¤m(cÞüÛÊ+!YeÞýûiPžH¤+ïN9ÏÁY­ƒÃëftYÒF $´ù&$AÄ®Œ*Ÿ9Z×I¦í(pR%ðcPžù¸ÊI¨7g´Úæ B£  £ ùæh?óVosãê7q›òÒUsQä+ÏJæèÁ(>ýç<#o#xÇËp`Þ¥{„à1ñê¾lÆ„Ð^'›Švþ ¾×’!ayxÊ$Ƈ.°+¡ßÿΚà «÷JúÈü¯\ˆv¡:å©‘®jÆmrŒ¨rsx«öÊ$òPX‹eå_G‘œÃ’Ù…gâ¿cou¨ígØùœ>=¼ ãz6>x_ŽxX=Ží^4äã™mt°]·ØŒPLš€Pì@9Ú,’¯ïø5¸â¹pôSBˆâÂA“ؾÝmFžµ«#ú2¸Ø{°¹÷øéu*8v§ü*rlHó¤ó1ÑÃ:Þ´0ÝiÀ ÛNh^ 6Ú|í\ª“ÑkÜÄëKš•Ò4÷FézaƒR M’/F#oË{‚GgCjñ«ä Ôˆà2ƒªŸ›F¼bâf° ®ŠÏW0¹ jeë¿táÖkÀ§ôàE þ0 bî¨ñˆ È}§O%'BéÑ`ȳ°òÇrõo uÔúFN9Ѧ¢½yÕe2¢–Û¿©F¾H#â»a‰´„™Ý]ŒÅ|Û0gš¥QX+R­F²*ÌMÐ^¨ÆmŸÓ$Gàj3C8éóØØhHÔeÇ·þ1)zõD.0E¿F›g7Òpeªæx`ɭΘ؜ï¬û­<%Êäcyï$½Ú¨‘ ‹ÿÉ0P°2æÈív,j2çfµJ(s¶0Rð§…‹ÙêWœ°”cLÄO yH@ÇnÜwÙºµ¤¯ùd¡*¥ð¢ä„Ë£Þë/¸>áq-98Ã`e†Èÿøï1¶Ö¡Nݯ)óèñ›ðUK5õ–Zzƒ “Ë÷õ‡NL£A±Üˆ3óVªŽÙ–s¨DÚÑEÉ2q# ì“lh @¸UGÌvÌ C›÷òÃvaÅE|+P0PeéÐúx!G∘kG«yTèŠä໲ºð\C¤ ãÓþ6(>¿,ñéMRG°CóF7z"l=€èmUš.zúJÂÄD0ÉÚÛ˜­üÏÌòm>ݶ};kÓ¦9ï}Vðý' ®)ÿI¸ê%ø{×0ÏÄ„9êkàtÛð»ÈË¡R@&m3¼bàM°R¹ÅàjóŒèŸtn ó "š÷ «l Tÿ±$3åJÌsÚ©¡¿ÍüA}¶4¿!O³PÌ:Ð8B ·O:6b]ÇxzK|ŸÇØœ³ˆ' sts ëö27Æê“~•·d3Û‰²ÒI µýžÊ`xàeâyûùSÊûР¼æð5Âï¦d.;‘k»R7Œk>Vì @\âßuÍ2¤ »>^| }¥ž'ôr‰9ÊQ|óá2(“îtö¼«ÔÌ㦠h·ý[lN{þ f—®W‘7.hmù~0ÿ‘¿öHè;,ƒ¾Aüò‘‚ÓC×V׳• ¶¯ÊSåð2· Æ*C¢¶nãÆuÙ/vÄ(÷qÎÞiˆxDô>B‘d;ìÉF:øûÝCÍC¹¬>ˆf+šž(Kòž\f¸®_ÿçÊêt9Lu\nÉF€/"÷úë†PدHÓ½$W‹Û\o0$ÜÀ‡³U¢×%‡E/xæ} y­nf‚òE®uÍú-ršºÃSNŸ B=„´ÍñÓèG¯wáYÆІ¶_7L’€:̺¨¦ýmuzÐÿ¾Ô•·îˆpB)Qß%åizá^;ÞhèÚˆ¾SK™É<ÖÌKŒÔíQ¨¥fêD[² HÞÙdçþÆ~…|÷ykÜl²©ñ E‹ô«¦S^¡ѽ8Þê×Dq£ˆnÑ,B½[Ü|Bq#õç̘Jð³A†ŠÂc'¿ÔA²‰Wö§æ‡uÿõÕÿþj*É 9. B¸^_“O†Rí©'ËûÒ5n >p{I‡:ñèn~š7q@c½S®2ºí Çk-]l¹þûØs ¿{®Ë”te]‘)m‘dFãìQ& ‰iM¥Å½ýêñ€ÄŠ´ ™ ¢ÖDûaè‡.úozï;kN\ákõééÏìŒË÷ç70)‹3ó„ALÕo¤…ÏE(‹L&]DšßPú–ˆÃð^&½.€¢¿j†a•,WÒ!¹;O@•§t·HcêªEñ¿tðšé–eñKŠv«×¦ŽÉ¡ ŸB¢œö•÷æ½™Øß°ÒrkWÚ3 dŸëZs}I_úHR}àw©ºmiò$ ¬®ŸâåÌ¢!mÜúI>ð’ýNùй±~Bø«½­D{>ÝäX(¥|í¥¹Çäw‹ì’©†{¶“Ûÿä2ªvÕТFÛ/„RH¢@¬cÏ–+†Wžµ³Ý$ïLÇ>4ºo”óöGI®íìbz4ç*T…ôJÑ…§gÑK>¸#í‡n7ŵ!¡ØHŽÂ˜0¬¨Ö¨Ì*¾HH½ã£Ð²]~Û4÷|6¯}¡ÍV=Öâ üyÒÊ ÌZca&ì„ÎÐùÆÅ| ,EѲ)‘?ßD¦ÁÄÚy禅îOÄ-ÐUi^|f˜® ©8X°â «ç'ã B5'È£0šäáE·v¼Û» u6ù^ë΋*-wÇîXÐñ{‡skª÷ã.5•¬o”B_¡ð ÈŒérêà3ª  w לü³húyÍ9cp´ùp+KºšyÏ·ã´÷D¿Drhâ+Ô@bW,F§2ºUí@/¤Š#Dë8 poða§h¡tæ©!U"¦ › D¹FͺgÅ@е|ë©–>LF³™Íâ’ái‘“ KŸ~5iOŒáo5O¨%Á¢àOºô ¶0´ðaì‹"X•5Z-œ]»x)4ÛbcÜ~ìž™ÞPË}ÄÿfÎ=ìxÄRž€ 5pŒº~öa‰YÃ`Äïðﯦ’RonM»û)2€;†Ù‡÷lï-ÒíÊ,L9ɸ¦Ñ€_Òb½étÿ¿øD™³òs¢í5^„ÏÉýsžµm5h:ßðœc„š ™`µÿs[Õǃ2œ‰¶³TWúb¦9ûK”˜c#µ ÇF‡ nï} ì–©4D×J\qúõõG¼öîÁÉÒ”$èUJ¸kö·"%] ^qžþªª¨`%É2ð NM£¡…;Jk…¢€WÆÒ ~ : òg_Ï2Ÿ|ùOª'ÄO_L{öâ>ãÒcéÚJqlŠþ®ÃÿíÁwþ „e«Jq'˜ÐÞ ÏåWN £Ö2à=áÀz3 ´bÚÆ4C×(K~þBÖj©[Kv½Ú&¿sÞÉÀÖ ¸=#;´”Æh£î³J[hÆðÕÏoÉ®8vþêJ„kÀg Єo¨Çþ^ÓÏ—FìNºd,VódðoÎKý9âô>®³ç,'GçžQÅ¥,*KwŸúÙ …IŽ7³8fgÆ»øü²¿ç9s¼g$݉næßÄýHž¼ÀzÓJ©Ñ+…™D4$Q~¸ kéû/'z€ Îgxè-–uø}îš¾^{¿ àà’iuÐ|\ÙeG3ÿ:ùƒÇù®ü)¼»/×ÖI¬ßãƒè‰y†Ÿ-i«Öu³RQƒÀ½k¢Ãš´ß¼ ïo'·¿M(ZÕå‡ìŸTî7ëzÆÓ¶6HHf@«ÈˆÞß…øT¯¶^—R¯’öµôjk‡ p¨$/`Òó£ÇÆOÖò|ö¬îµ°´ÃÀÛü™dˆ•¦b.³Ý•¨cêrü "¤»„0'OÛM`Äâɘ*€L%szÖæÔ^·¾&ÕE¢c3“A °œæ¦îaª>Ñeýˆà!Êr·ýv¨ûÚÿ’Lˆ°ˆDf]4”ÌE[ÈbÌÃçg´_A€Ja`Öa–"=/Ë™¨™-ubotŒôÿ«#X$ŒK/³¤ÕÕG¯f - ”v·ÂaMUøo“ÄeJ-Nuy"s¾-ÊIÿ÷µtÈç H$ÝÌ1,ù¥£œȰxšzˆD¾½E+0GQ¾Ê![“ˆ„½[R8™®>„âH@\'"ÛKº´j§‡‘©œ {gâ0t¤“÷€ÊÆ„ÌgBDÃÚ¤,Éœ/WÎ7{—Õöýé5   Út”.ôT…S‚ÄçÅEâTàd£óŒøYž »Ò•+†Èð“û Ò᥿ͳê¶FáÔ݇TÏæÌ9€ÂžQZÉ5LáS›ÙÄòˆÖè®™TÖOfQ,^Ù©íHdõ#bäF/™ºC¨ììÞ§ÏwNÖt³LÍЖŽýbSO$hžqáX~4Ö$Cš¨7´ú™GjäTíæRÕ×¶g‘ ?1à_Kuä¦ß7ê'NY Yn°æI{{s¸úº:§¼Ó¥›Î·Øt+í7ØÆÏCg‚¦‚e¹Á yfÉPÏE€•e¡T,{fe³“2¶TÂÁ@ÛŽxÀ}²¥ãç‡ZoÕ”°ÝÝì "”ÒêŒ2‹Tk¢˜Þf$µ 8nK˜ü±’ƒ‘µqg¸› ¥ÔÈãµ®è.j1䑌0í³¹oIµä‡Å 씌ÝfÞßw/üþæ•F”†J…Ê*'ÒŠ?=ŸDŠÊMïÊ`ËR7Ó?ŒIkçZ=6Êø&K•(ÒWš¦Å]N!^—\!v\ÛXÌ#g çöÄú’)ZDU„æ¾ØúÄ_´F ·F^yáp¶Úx¨fNÆû~,lÊ$$çzÇHh&W:|eg <íóVh`…JØOxÞã­üúQ4@@kЏ¸oßáÐ.ǶÉVÌ‚^B¯9FôîP€Ä ê6&±Ë•}s]T£P†á®_ÀãåŠø¶² –‹ž&7)xÄ`oa>™êÀZ¤w«… m¡êfo÷‡,•hÐwø=EU¡e¶=œ*à,¯Vq8,N¨A¨ßÇF‹gVµóß.•1l½*´U. ÈIÞ¯_¿ˆj^õù)#ÐíÝR!éÆ@op…¨‹Y.ÁåœÂÇ]!¨D[UAœÀˆVÔA«¢¬(ö·îË®(7©ûP®UÒŠ¬á ÃE¾]Ï…âMÜc,ûÊ• 'u´Eë­çQg… 4éÞg L*0p3!hZ%ºë.¶•Äp-zÁoªE*IÆjRR4Í›ÚÞmæRC³LžyÖh$S˜¨pk'wŸNÒ©qÝû/Ï%êÁÑ…Z{$=Ùʾ÷{p¡aA@Œ°Ÿ‰ ÎÉ7PÿxngS-öªV„£nhÝ—ìP¤˜î¦íæe  8Œs>¿«°š-Žv 4Û'_iM2?·–•5k_¯”Oýž¨D )’¿rz~ÜVöÑxêNÈ’õ¿HR/ýµˆ¦§ªc Šþ’B ÒÆmï) BC¡±ŒÅkAØ1qpd™¦ ÿVDCžŒ“ç´ü Qg”eh6ß,*C,­æŠë{å¤,'/,‚ÇÞ 4‚ÉuZù°6 #væßuE^ßÇÚPE Rµòú°Të7˜ —†ÌOTcä* ‚^¾}íï6¼£xL÷[gg‚fq<æÚÓã¯gAò˸}á̱—·Ë¥¿;~Ö¼R{¬üÄ•äH%dÞÙ“G]þ1ÞrEø%ÜOª˜¦*TÄm 0h›A„8¾¶.P 9¨‹ ©¢qwtêõAÎ¥ œwŽcNXtɘ„÷°b‹Éþãܬ·Û¤ âÿÊhp²£©IšôDò&á§bÀ°v$hÑÐAñpqÃH¢ÊÊT9Àõò柕@Ðo=n‹ 0gù¸/e^böÏEI;“W·Jt¯Ef1,;§´Þ&JÉ)<÷™ä¼ë#ûÖ/½Âë'çj²¯¬Ç;œø9B®´>å(‹#içlĞNH R‹BûYò¸*Í«PµŠ|3Å OF.©fó»h2C+¤d­}¤Ÿ¨{ˆêø\ÿ½åoÓ—w,tà½m¤!.%árp‹Ö˜ì Ñ@ÑéÃy‰1µ3bq7´SDÖrH°ÉÒ °¯˜¹~ÖÕ])¬w÷hì­%8l+=•aYìˆÉ·v¬Ò°î¦uû¨a{KÈô^–·1ÃÿÐô™ïšG{srÎJ”Qųfšj»Ï• Rz›å.ä½$_^Fã²ã5 ¥sÄo¼ëIXoŽÄg=壟“†ÝbF¯/s‡ufÖ wRô‰#ûI´â.ªêp>ûáÕ¸K—fˆTÑÊ\Ò4Q@‚‹¬PPýöЪ"7ù±óâŠÀ\—MÓ©0í¶F­-[:…#:Zj–½îíêscr/Š&£}QQyV Ù¤n¢NŒMÒvò¦[àÂ%Õ×K­8ñÏ’Ùð6VŸçIË9˱¬›C ‡¤óJMObœ+ NŸYÉÙ1Ó³t“§ÅyXt(u×·¼NïQ€ ;f¹ö[ì\3Ñhœ¡¼ÁUGsïÁ)‡ëåGÕÐy{~ìÖWæxQs³‡~TK‡º±¡¯ò pbýñn–ñQ÷Z˜(7ewS/ÁXà ø= ćÖ¹»ã†S\Z}Ù_–Ï]û!~ûÅnI‹€Ð†ñ"ò”•Y_Ú¾0Å¡ìa1|œ4WûuNûçœ.²0ýÏ[ÈWàa–ÔÀùû’ ͦÀWžÀ^E9È1N”[S±X15WÄ-Ö‚÷=-)…í¹y2ÛÀU vw{Æ—V|ž1Þ±L=ZÑŽÎep&Š¿"‘) =í­Þì\nGr3kDø±ÊÖ+Èùq² T+ÖäK×èâ„´ý_LbèfYkÁßœì_—€€œ0q ^ûìŠ ëÚnß¹º5ìYÔídÂêyÆ>ɹ1q¬3ú`Ñ(iŸžëýp¨õÖ[øá•ëKËxÁ)ÀdÅ]!¯4áU`t‚h4ìl Z0ƒ´›hÕÑß+1‡Twx§×Ò,Ç×c@€ÈþÀ[0¢FIh× "¯óãÑ÷ˆ«J!®ü@ 7d›ÓîßTݼÊìĸí‡ãÚÏE" M œ6|tQ}fß ÛM.Ó™ö^ÅÏùœ)“I#aÔiF7˜B‡¤\­£a¯¢ºyKÿñ»«,í YÚTÌ-Ò°{ô/ë-óI˜Ù&07ÆÓç}ìá´ÇÿwÄl§H~'^ù+裧:ß«£ë('`LÃr(`!Ø`Æ]‚B‹nM˜{¢ÙD özAï̓4LÒáç'ŽbH[ ¶ÙåóýÛñ‘s§—¼DËÚþ5´»„gVÖ¯’A•zØŠßç›÷9v B7"©}øì¡…€…0[°ëÏÛÓ &?eòD4r"é²09䉶ÉÏêÊ£„­+ÕpÂ’d•Ç¡eþqñIñÞ4ÄÚ²ô/‹×çê-‰[AIP£ª”—À›>á?Y]>9(ù@Ü«@¨tR¡oð;Õ0{å/Ŧú6åyžþͼä¬Ç),À.æñô"úo›¬·=b¾g¬ÃT^s(§ÌÜýåÉûúdÔHïZû Î?aEèWr+²ÜR•,Ë´½õÕÖñ“‰ jÜYA¾Sí€} ”9Š˜H~$HÍ0ê`H¸²ÊæŽÊâ³éÏÑNL É@‘–VË[Yõ:"Ϊ‚MRŽ÷ƒ´uæ(7¢t ¹¦Œ#L;ï#Ö‘D¸K~'Jë‹…*»2 ­Ý@æ %®«—Û‰¹Yǃ­ìì9eCÐEž4ÕðÅh·ýÚÁ§ý0ÝRXQÄgÁêq!×åÊ7ö³G]¢Nv¥ñ!c¸î_AúÏ1v¿¦ÌßÀ Û¸äÞÑÀ8Äý7Ò…Éë’J˜ƒÃ²©¨ªw³ì«Po+íãuÐÉk}ÉgÂÄÆÄÇÀýDðK\ÓFÐrÕ\å9 í‘ nаK[ª™åªÚ©1ˆPT8'T7K2h´,²~îEC”¿åõÔïp­èb¹þ‘r»ª[¯Y©•úº'cj›G¯¨júÉ„ÝOÃSê¤Üú£‹îÝ0kÒáøQéÖ” ,Ä(]h5ä¶¥š2Ö¦±1€g¦ab× ý…93K-C޵˜Þ¡«÷þ¥ËQ¾:z5Ÿþ¢,‰=«Û+-ò´P[}‘sšnÈÓð’ý;ªUãÆÏ!&*ý©“²‘¡vbu~‚WÖ,\„¹@^*~91—Ÿ‡GRÅÛ/|ÏÀð©aÓØ:-KyÇ]æq¶ñøÕ·Ld¼¦,KG(%xŽ(kº#ßÖ±R=4†'wT›æ—X‰½µ$Xg%¸.ÉîyŠš/c舱$´Ûv'‡ºmõ Oì(ÖdfU'Cµ7«kÕMÒÑŒ–ÄXW`8ð|JhC®³ññ½kfÕ9v`q„µ®ßYS­ïH ÍŸçâÊ¡®š/d€ç›ÀÒ†¥ýˆø1ɨ†Ò}íNi0 Ca[¥¼¦ÂóÅææ?ÅP‡‘õÜöç®Ýû¡bB³¿±ˆIwøï7×tes‡×£(.§²rÔ¬ ß($sº‘´ NØ+çüS«à"ë›ý„§’ÂYLe«úÌöß×q9oˆ‚q¼ú Í`*”[š3´r®?0hn8í09mÛébÓü®+-‚œZûð¨a&ÝÂk&pÚ=ŸÚÞÚë…ZuPs2ù—Ê̓60â°ELk1©_ÆmbâÁÊËRØŒò¾á™‹?ïØ¶]d½Ù—ˆqóâBˆM¸Ù§M 1úÝ>Ø;¶oL~¶’z§‰tƒJïÈEñT¸/eX9Íðsi0=SÙšÚ¼•*ã øEJ^šŸº†Ê~®|—ÔæÌ[ãÁt åÅÓ½P&_4·e2»£©PÃíÛòumìïæ5Â¥ø   ×ᜉÊZdêGi5fô»°¯¼` ÔÙ§Ã}Ã×nRJNC-|®aãˆQÍòšË×S[ÑÁònmðƒT†sI¥ Á5N@8*#CçNaV°ûyÝ g’Ä¿Œ®Q¦"åÙ„¥óÒ ªSU‘¾H©0‡NY©Áy¨¼ô-"qϧ øÄoœÓžÅ`¶¤Óå–Œâ±h—Þ“ÙXÑS>|ß½'R,:û±5¬2ƒ~«o¥¢£2§ô!îe=æ¹r¶ÉÜþ9~‘°€¸£aø¾ Í¸©x|Ï ‹E_R²†ÑóòF å3n?Ö. ú“õ\W†k´÷p´ª¯ÀbÂV ×­À¢R&¾ éù®i¦óœn<Æ!&1o)†Ò Vx¡ET9ÄAWË43ŽLnE?õàJ[¹íaXp’põ]g À‹–LÙÈ ÷§¸æÁè*ºB 1óÀæOÏ‘¥°¥Æ=¼×K6kã+2!÷ô<›.ñË!ØŠ»FÊ Ï{ç3„€]K¡¨ðð‡Ò(ʹƒ+o‹,Mµ±2}Y‡ïaøX¬¬&6]Šwþ½;5¬’™mj3 |ÐQqÁ“)†ü\+ñ@=©(r~?gŽ_Ž‚ƒ% ¥[:ß­”dã“Ù ‹¤SShÈíEsÙ×L»3!vk¼€:‚LD#›ä×6ì ö´¸š)ÚÍ¿ÞÒßüÉ`GwÜÃc)4‰e|ñàGÆßc€øðx-Z Ò(FL% \¶—V=ý;¢h®«&ºTBÐ66«îÌ%*…•p‰ ðºFúyIý,\ŒŒ_| I  mª`ƒìJSª“#ßd¤óåmìKd +…S5¸ÿd)Íž“>“fYª*…îMU}>+ ¾¦esÐŒ0žøÕ>3¹¥Å5òL.e¤ÎÜJ0z &ªÈÚRÔ©øÄ€[uÕ %‚±¯¿ÂÃmþpk"@lu_²ßÛ) ¡›Ùd9Å&._Èo«V9ø|rö]4*òσf#¨8hèÇeâlïA–s*‡,áÅh;•a¿®{|3!|ŸŠ3ßÌÁö/¡z…Þñ–M©çÒi'†´ÆÙßåøÍ¥£n+Æé£÷>pŠ[ŒËœ7vu–âMÀD+ø#Ï6¦ø^Ç 6k31`sßtõiAc{@V½íxRzo§ ™ÜÙ±‡)•óZû½ô§þ½Ò¶Zæø-ñ®ßáúo¡Ò4CphÜò3 ¦PòTHuº§r<”·tœÉÅRä ÄÇî8gWÕcøêØ{Lùòއ·%æAb©Ëä‡ÂÈFP[^Ö«œ|â<@ßœ–^Ý=· %äZ{3Z)*Nó(iloáÿê‚’ÃîŒtHIøñ‰>ÝúŸJE6þx~Oo~æ5}Ø<Øëߌ}ún›²ˆ~çä«e›BˆFcK¹»iLSP¤É JÂmsoÖß6î?/ßSÿ~·“ ³“KüÜ ‘ªlÈo-QÓúŠ[;z`~@¾ƒk8ùŸÓAäQ­îöo‰GÈß ñIªÄ,öŸ¶¶c<sºÝÚwÇHe»‘﨨ÕãNQøÅÓ¤ ³ \GÕ$Gs::Z¸hôiÝû‰Œåyºø@;‰UŒ Ëjy# 6ç^ Ì“í¤¡Î½XFâºÁAÇÞ'ªfQ{h¡/ °v«‹õ;Öx¨…—>\¶ëkV(Í&ýGgy%ÛíÌâ¡›ùiàp5Ãä𬴼ÑÍ™&4–LØGÀmå)úø˜˜fÒ° TÌ|«F¹V¢pS–‡í…£°øEç‘où ĪM‹"A*$Ðl~äÍg›Ü%ªù4L{ðiRû«|ä®n&’ÎRߨ¡îõ>Ôo£"Žé˜~½Ý£EŸ¬¸öϲܯcúp^ªIÞxºˆ]ÿ¤!U¬©»‹#j) (#J|Ì!8×þýK;ÙYÍ“7{S]Àöc@õʯMZË9&•i¦ðñN«}2:êBy£wøý$øÜîÖäܨ¹k³}=]´æ3këãž•º¼Ççê¡ÃV®!Ϊ›¶™'™³~ÌA‹+vüÇ¡ òDºHê¡Å‡ær !•øJIíèµ°‰ªº1#«ýwFÕ$õ|º×ÁÏ3økÀ AGÞ˜º4/Œ™üK.ƒ™‡M_%†6¡‚šÊn Úþ‰¬Héû#ßl¹%ô…®J4ƒwð‡ŽD½ìñë,ñ¯¬h‘VEì92ÎÐ}ĸ?î˶¥W©=Œv†áÄÙàÕÁQ¨f•§ÔÂâÏß»â´÷ Ñ =Ž wþaõlSÏ_Ö6ÿÚPy »ÜÒ[,  ÿŽ5Gq€*fÙEVŠÜïŒh~ã±í–Ýrè&{Ucœï'ß.ên(ŽÇ²|·Ïê~x’\ôJ“PëHØû/#“­7{Ñ¿h(ah:}ä¼"uä_ËÏ\]8H@±KšFÖ„C¶#·)ÃêîƒsÀçÅv¨¿-¡—|KÒ8¤ ¹h¹ûL)©ï¯¸J(¡âî&9Í3F¡ùÌýÆÑk«²ÅÂ3åe IeRv{¹RÛTÛñ[6<Ó ÀA$׫Ñ ZO°¼(—œÖOÐjG$Ó\¬ô‘Gú¨![3´ó®ä­1TM,{«GÐÍW–²É5LÚ¸”õN(‚|ÚlyñÇÀÚ ¸¶¤ÖÜ‚£Žð‚#‡V}ÀìͰ“›B#¨òY #µ³yIÅÈØÔ=¢KÍ(%ZHüï¸JŒŒÓ‡„¸2˜×@{hߘ˜°ý–}$ä=–>ùm}›L ;¾ œzÕ$§P“ò¹?Úî ä**”!⢰Ì·ðÏÇl”ΪŽ\n#xEV#ÏNRĪ b/â2,ÀêØó®jʳ3LS§“™`«V#¢±¿.c„} -p:;EUfð[βÇãH°¶ïºjvQb3uìt%9rœ¡9¯iü:… ±ß@k(¾{gÃÄ;F騭³HB1­cá÷ÁXÌwE¦1dÀñ.-X£3ç—”<`2î4à¸cqêÌû>Ë—ÕLkëA1u:2»3R28suô­v¤Õª 5C1·â8œ‰¦-A¶‰}{ê¼d¢{à ¾´JîÆî9þƃÖJ`«êUÞö&1{@NÖ– QGÚ]Ž˜ç@îÀÙÒ*sêﮨl¶Jk£‘ÆDÓ_{s(Uïb".æORŠQùôÑFkÛí-X|;Ëÿc„týþþpÈ’<§È"öšaøå#=yß¶àÔÕΗïÐÁ@‡ój€vrûk5¤ö÷ƒ&/…£Râ9&xP|ö*òbzŒ)‚0 ÈâlŽ" >×òdŠ<îØÕ ¢ãÎBlš!Y⛋¦Cž,G]Ô. i:Ç ›Snwв‰|ƒÉú]P'Ÿ ¬\¹îW’Lµõ4NÁé[ÏDzAFQã(ï)n;×ÙQHùÕ.Ò4‹Ïþãn‹!*7´ËÅZ×ÀŽ—ßT,–Ô3í•Ö&Ïìè€pðë¢ô$eÿ´!|ÈîÂ[8Æ>ll¨ Å¬e…ØS¢/?Ö[åX©ˆƒÚ±› ìGú¥#@÷2ÙƒC5ÒñšüvD»8(ƾT ê´ YÒÎ*9Öu¢wBÄ$(O“Ê«ò$>¼˜cÁ!€QL°âíŒúÊùß&¤Ø@èTð@¼‡¨ÕÂi®6/ä£à ¹wmƘ]:=¤K–ÁÅý®H„|dë ý*¼6[Úp[¹É’©n`G=…5ÞÃ[6´7®ò'¦Ä;ò“ì°•å|ƒ¦ß!Üù“½PHé8?5à8ï~rÑ9® áÓ'êÅ=¶ùÅpˆäjü85Ô ‘ç§¼ *U«ŸA!ÑeNy¯lEÍtÐöúÁÜ=ŒK(¦¡Ô(xë©|ÞX‡‡´óÍ•½#kÙJ²ˆ)lÿ`VÊ´í©Ò"àËGÿ˜›’÷|¢þóFÊ8:Œ…·ç§Ž¢Z˜¯Mñ¡K+¹ìv ô–=±ÿ¡ÙJIƒêœÛsݳœéÒŽ5q–»Š=bú®JÕBŠ]Ô§@h¤ëä>Ä(.¯FÞÄ6G1(‚ÄÐfJë«'UWQ"Fê‡.Qêx͇gÓ@”æœVùž[Þ-B©ü½«œm jÚRÂg£ ü…üu0¨äUµðc{¾Œ[¥Li–‘Ìè¶ÉÒÆÏ†‚ˆo"ÑÑm ÔuJwþêÐ:¦µÌšàÁ›>ðaR¨9ª÷ž½í›ÓNÛázsxxõÊÊv®UŠát“¦ÏJ“I YzV½ÊR§—®ÄÀ;®ózùVgä1M"Ôcfc«©@fJ6B"ff~wË0‹2®*¼¦Ÿp6«¿ZÅ•9™ÑWPóý¶›ý ÅN)b+) ¼ò=Šå&j÷1 ¶…‚ûšHÕ ¡ûüyéBÕmÒò!·¶§òÀ¢OÂhù)X»h¨‰w½p Üñ kŸ†/÷\̓egâ2{$\òýíÊ ¦¶EXd«Ý#“}ÒéMztëxÍp”dìMÂË-« ÚTÔã¤YˆöqÞ=N@åÈ¡þ{?U—“·U¤Ròr.€oyuêËz{ãO6™k%ÃŒ!vx²# R‹Ÿ»2è÷ yX+ЛŽÒ„²‘)!ýÉ%¢pÖ@ZËŠ˜VÂF—&^ReÛˉ†À®îEÈGh¢Ôòä´ Š¨í{Ð~ÀìiÓ6yAíHM³_14ªŠwŠÔ¿(÷âï·¶ûÖ"—qmR‡‘ò8:m¼”Lç>~ý¾ÞhSz›$rÚl0ƒiWµî¯¦Á*Y±ÍüŠ¿êÜè>}äõ>~)U9F¸½Ào,… ¢wÁ£–©@/1Û=õS¯láé­Ç9›WÌU㳉Ԣ¥ýóT0ì‰Fû¡¨ª‡Å>v$Ÿ0}-Ä;„s2VW'y\& åoõΗS.€()•_­4ß=îXH¹‰‡KYGÏ$©r‹E `Y¨1FzKØN@¾;ª–*&öÎŽøGIÏ#êhˆBÉP+Ïëö,aá…åÄgC˜dÚÎ@k«Á©Gýüà ™ +älH¡íIÓ$—³<¶1?ñ‘¢ô>¦_óú.䜜9ä·!þaÔ'4M[RòV•Åþ"ÚÙØ‹‹ñx¥W´RmeÎɵ]wÒé-ر¶M?aŠ™‘8ÀºçŽ"ŸîêBøyßÑA]|¼}¶ÔC‹q²´óÈ¥þÚ蟹3çÍêÏÕùa|fE68ËôJJ„,ãžy\ôLÆq¢`ÅoëWƒü*R·'xX1B¡.âÎoN÷-çÜ7¯¿ª²ç?„ÊéÀe0ùrT²`ªwzš)ˆË»FhMLöqÖÀUñUã΄Lš‹6¤BýZU}<ä(°ÂTUÃÿãëk,U¹äb<+‡ÖÕ «¦NDÛžp›$Ò+ƒÔG¼?Tá]dGE°Âï»,ÅÓ.J .ïÖÎußGÔ.ÝXÌal™#Kÿ }PÛ[5§9øGÙ AšF}eÞ8ëÂe;gä -_´v1«uµJväyesJ@E¬ÛDv|/ē֖JöRîðJ|¥Œ öŽ É•À¶³JÎûpóm™¢ö8þ‰É=™^}“–Õ7ƒô|/4¾)œšbúïjV~ñiÒp —óYW©øšmâá|¸—(õ›s¤ Ë5K~8åµööeu ֕ψA³ío\.gÎ8ÿìZ†7ñT´t»¬â3…ueÛ-eüèn—zcÛ| Ü¦çê“oïLº]¬Rvʽ-Ô oÐqÂ:Í­Þyð©mIÖ¨J/á…."œm¹ŸpdV^È}GöÐ2÷)"3‹ÿÈ1êmS§ì_¡R÷ȵ]6.Š ‘¯µ Ñt©86˜O@ç¢êÝoÛ­{äë9lú_HšsJ©ï#¹¼o/– • àêž×FýtV (—ÇEÀ*‡Öçsa.| ÂZ_™;"Np§qÛ}âk¡‹*„†«zR”L1(Ÿû–öHÅtK­˜« ÑVªØªsqñ"O³Æ•½w£„}¥tÅ«¡~«|¼ž‘ƘŸà»šµ®æSt|{aåϱ%%6L ‹žárÖ'£(Ä^›°Äåƒ/j‰€Iwv‹lx‡EœîÉlO%ÊJFÐ$@%F;±LßåABRDòC&6í !ÓZÐØÝÕØFk.ùLû±ÊXòúF±3Õápë\zBûYx膄™„_bx?ÜBÉ… r¢Ê¢Ö :·j³‰wQ¤D]^/â^@¥+Cªøþë­vù—¥ŸTlŒKI#YÁ%éŒÆPï´vùx²ûã¤Çûß!ÀTŸÉ†Š©ßg’6zz>€AÌTR‡aû¢fYãïĆj¬$ãƒ4piç&‚á\ò2Ðp¿ƒV,y rxZ~+pÍð—üòVì]¶,ÛË>Ì)s¾Z€‚RaZ {n×ÒD‰ÔÙçš°«s$úä.°#˜R- K%Qâ?¼´k·&$Ç_ÈEZR ÈŠ¨wî2L•ˆŒã»YNšËðö^ìO¼ê¸dHßÑ ìR§в—Y‘#^!QìÆövŽ:>#¼¤¯ÂcˆÙá½yX™^z«_ÐTðQây&¿H;©ÊV·£Â7óïkërVæZ<÷ØnVêN À"ÜeZÓ¹û¾Ñíî‹æOÁ·ÙÕÄ›ÈV§Ý‘Ç’8Ê<ý‹Û¥>í¸yêr\0QÔéà]O?ÂÁ†r§ó]“äR•‹³GEÓ|‹Y«µfîòy6Pï&¬*cLØz{Åq»ÿ«fu©»Ô×G•T€Äì‹­gÄu†°%—6z£¤âÔ…zÈYS’2 ¥ y¯÷ºC› Ý]qx¯”…7gh™0O´,¬Bѱæ5ÐüsŒ‹h5_ž&W‡Õßká6ÍBŠö»²5<ÚÕ0Ë)«Ì§˜…Ú$å,ŽÔ¼MjŒ³=A?c ‡ÕáõyIýWg5Ëï‡Õ±2þ—óK(ÿK^uIh¤ì3Ð*uã,Αp‘ÞnµÑ–vµ±@§*"rbôÇé$³Qú4k¡ЙO)—Þ2âÝ/×;©àüâ Þºè>¿åŸ›ù×ÃÆ-tî‘ãݺ1Q®ÁNÎSùX¸NÌÏLãw¥vi‡Ûy‡—“p{Ú¶mîÙ@ð–}@DYíã“þ_eÝO×GÀüÒ9YEVÚ~€ò´îÝ`QÃpi„'Ó¥ÎÝýŠ~½uFUñcÀ¡šOš+|d«.x`„Ž×— 1\”ä‡é§ÊFà~þaÕ†*áž{Ê–a*Ó¿ú»ŽNV ñ»œ­ñ“ñåÁhÿýBšNVU³\`Ö+tøàwt’ø >›83SÔÓ7Ç àtæâý„4ŒVG^ò¡ rmã,¸òÔá°˜ˆ9]ÔTè€JŒí´'¦»É,Gmî­Ûû-Áñ‡ Å?ã?…Ð{Ñ‹À.DùT%f¦ö’~Á–KnA»âjµJ)yà„’Ô1ß/›ò©^ ä§çJ€ /á›aв"e°>ï ¹wÝäÈïŽ–ŠŒC;ùoTBQ˜ñ+2”µhe¦–ÛÓž¨å{IAˆJfžÒ®tÕÊkžÄu¿4A%¢¾s|z\§RßµBH¡åÌ»Aò²ä†Šâ­ðÚS} ûz†Ñ°Œ£@׌ö•ì%Y¡Êá›;ô¶J™[B$’€L éz¹ÓgdÂù‹)÷Cåë)æ‡möCN„9év‚^¦¼ÆåK^‡ø4£°Èʃ…c`'™ÿ•¨Ê…#„/ƒù1‚É|­` Œ)ªÖåp 2bפS{'ÀGÐê &¡Bír'7ë>RÄz–­I¯GIÇLuÜJ©HXÁ¶{DIko]àÒ@kš­ï$1¤}Q ^ÉU±bÄqï”Vä¾­+­¤Ÿª¦•åoqÕmúf‘QÈnäãi­’5Ïw9aàKˆùœzÖ ‰ã_m@—¿Âä n‘ô·ÔòD¾Ñ´¢•1"˜ÿFiÛ¦4å¼»›Ïô¾Äkd[èÓ"qפŒÿ©J—väƒÜŒtú<‚XI [—‘cfÇJ0•úöï"¨57c]U¿æ£ ËE­­óºF G󹵿²G‰U‰¤|ÑñðUÊ¢gB–¼©lÈŠž‚“”Òd¤míl{ƒ­ Žß+t!³Sý¼`@¬¢½qRÖ6@ZÌü(*·r„_|Y³ÅŽ7øf®Ò4 a–ÍǾ‘7MЍW‹„w4¾Äß÷†Ðm´Ÿ*Òš܃ñ.ŲË'H”/pçù³³ã)¨Í+T¹ÙüMk\™˜Qî _­fƧyã¿‚ïýdšØõúÀ8|†í±<(bÍë)•j‰ÜÙÀiƒ1ÌèöÀÔU’½L¸\†•ùý*Jõ›b®@ÊËùD=pPÿ„¶´—©æùúLäFÇgp1ž£_`¹lðM_³üy#Ä”>1¸Õö¬Î¿Îh²‡|½½~àMçD$qÇ^qþ×ïቶÇãX&\µf“”àg?4Ó í ²ºúô¯"VÇÝÀ2a¾—„<¾ÐF‡XÆþ·1*ø6ç’q°¯Ga÷àAý­ë‘YW|#¿Ö×H‹[#:Æ‹‘R|¡®yA Å̽¤CU‘,—À ¸ÕðFì×'äQ@b…f™Z‡i«™U¾ ý%-0ߣœEpb§ 8Ò¥„myº*ôfãý¥Þ”¹XßúTÏ„ò˜”̳ )p&~ÌmøŒ6P—ך‹4º ±¾„ÜSC% ¾¤Þºu–24^+vG$ƒÃ’_ ¤PªG‘63Ìz¸Ò9YÉ‚\ð–¶OÕà܆ë )/ül•Wúˆ8_ÞaJwĪÆòHëù,ý%\’Bª€?‚?Ô,‡ñê„ú"àŽèĸ˜4é` uNË(e'pªë¨S½ÙɧÆ:ÌxÛ•ú{ Vz¬ý¸:@5Œ3Õïš“þ¦[pUrº˜‰ šUæQ+ç,:²¢wEvy‘HrQŸÁ ò‘-›q“‰øÓÇH®¿ÆGÓ”§=©z×í›Â5n/Zra”DP÷›ùP¶õ&PÓf<üàr„”¸Ãe$m…àF–Œ^S.|µðá‚yó¸Y<ýýwW:Õú¹·Ae¢Ì®rä)ÍŸB««^®¼rþ¤ôùçQ‡–PYo‰Ï²D‡³"M©‹ÇBÓum•È-é$}4ˆùrªBÍ‚” ¤ª8c¤¸±åÄÿlþ‹£u lŸ³Ã¯öלf,p;vûK n|°€.*mt5~ÍrŒD;p¯N«Àjžf·Rpñ õ±±q$ÊÅb±ôoz–â &Æh˜l8œöRÞú.Ó¥ü£–´élGG——(‡¸Q `ŽÈEfC.­DëœX)&Í\×ùy)± ¾‘.±fsº“má—Œ`ÛêáfÛgÃÆ’˜2¬Ÿ¾’s„¹ÜÜw)Ìw¾=j‘Í~zê/áTüt÷g…È‘•è7ZzwÐwu,à›ª$,KâQßb؇1ãÁAâAäWz€FX–³$ŠZ·ÞÀ=ݵ߽zÉÄÉ` ¹ˆtÕ¯gø5“i’ñcпl×g®&,»›¦ÜýJ's¾hú ™NÿàÊuëÎ#]$ÛšVØÓŽòñ8dýG綉†Ã¬)ÿÑV}Üdc,í« X00[ÿ½0ËÍ*{L®t,2¢0×Tž÷|.tZÓájæ°–Õ[ä«Sª^c•Z£¯'m–ö$¬‘OñйKi×?Â@*GµNZ|L®e„]~¥¨Ùïá[Ψè"åÂÔ•3Uõý-€«w*ÒÈ󥤙¾—Íkÿ,qöǦáR€î®Î¢§TéЧxT%wŽÒwŒûaÚvÃ×ë{–YÛåáz_ÀÑÇû÷•ÜÂÜ}Vž·,ôs|O•ý圚\ÆóF†þMÆ~úƒÙ½Ñ=*ÓrÀu9U¹a²pfZ÷1´,mBN1›^ú-Ì÷)Oë`-0b3¼!N¼ð- ÙBSûø²t±Ñ0œMB°á¢eKÞý¢ªNì¶Ò!ÐI0Ú¡jÃ¥öaFx§Èf˜2Ïüý…bD‘s(ýWÔ3•îgT½­$ÂxRmwÔÂx®¶ œïKì÷ܮエĦéZ ) ò§û…. ¤6EãéÖ…¯´+À0Ž¡“·&n–©-~+ª$kAåöÚx<”Yzæd ”†žt”f„ÙrÇ·yÌr,·øàË<7ªLÛ‹¤”}‚ÇfÓ?7_°Fÿ`2`ê”äJ•OPA¬6ÍÿUƒ*‡ƒHBØýz6]f„’+„m¹8»Møôäå¯'_ïGƒ5ð X¸ÚU¹§Æt^Þ¬ÄfƒB‹‹|ÆHQu?õ"ﵨgð($ùú8v"Â_5M¥åh D¡ûŒ2_ä»:Gµªe Mwdé´ @XËbD¶ýÁù—u&`ù~´;8ÛÎUG´ckÕqæîÜÀ?=CCd$c#1˜ÈWò›Ë6&àêæ"¨ÝE‘‚|˜N‹ôú\„XJ; CT®ÅW‚u«¡]Н‘»]Å([€.Xw˜f#ZA®Ã äÚßÀA”¹“È_~’d5‘*àÉÇXÒŒ°Jò?ÊÌʆûsâyƒÃbÆÆH¸Ø1©J–³ÈÏn€©']§Ú¼¹Tþî¦I¾»ÈÀ7ÌC³Ó¢x ˆ·úêÞ΋øvZN[#vãsÒ–*I癬1Æ;Œím?&;šÏk͉Õ7‘•„wÝ-3Ý^Þ”HÑ”[Ù'g#y«R»ÜÁŠÎh0¼DΜÆÈm¡Õ_õ¸BƒÜ¼â@³Ûÿ¬í«y¨ùQ€Z`Ó™ùn`,W<ˆ±¬&»Ä"V ÷ï4¦ËVJô± uñ#; DÈŒÜÿó JTK²g åg!FЧYhÆ›æ^ÔÇ?ÿ½áTâö‚·À !T6÷–Ž­Ú­œrB®9ÌaW ˜ëDå¨Ù·²eI%J_·áøaŠ#¹ !šŒñbA5ÑíZSßÔ«pª¡(+wx®¡¸ïmAB†`Ëò¯ ¡aÝ~HR)'³DH뎅6ü|€Gõ.Ø‘Â!±Lï|5<î³1»!¾ãi§ðœ†¡èUOœë‘›ÓöõÓùÍh‚yšÚdW`£z®:DVÀ> ï̶ò}ú>˜/`¨&‰Ãúl[Ù¦X3øc´YŒjZæ ¦eú¥›$GÜ(Ñ8í2M@RÐ67Ümc-¡âqÕõeÎø0[]Q=ç?ÜE?ê‚plˆV½ŽÁŠ£¢×1‚¥i‘ÉØ¹ðÉÉúm=/ü÷9•ýÖ¤àSööëÈD¨¨Ðe©öñ*†ƒ-O¸Néùi €€3b&L5m ¢Üħ­qeçg}A¼òB%ãAŸÙÙ¦‘y™¦b™}B±ÇH†.>1ð³½Yºe7ýÂlýΩ ?ϬùbuICË ÒÍûèøkUeòMïØîö¼ªÒ뎲é¯ðNå{`ƒÎU, g«˜UlM!œÑéMK® @/T‚ú¯$c4ɬ `»ãeòKÝYY?'yöHîûÿ¢ïy‚ùH©™íõ”?•^²Dõ¤<)“‰½Ö—rt;îýeDáßÛÎÞâUU—s î¥â¼&•Kõ8ªV°ç ¹-Œëhœ|Eƒ­mAſچì¤AT± ÷äùÕ¿Á±f²Ò±–éB7äª2’XΦ«F"“ Zc ¨ —ØO*ßèÏ;ˆ Hu÷²]ެ,œ™bÊ ®äf³qfS«Ádˆ¹6›¾‰«çaîü#Ïi·i§ÃxgÔ^~ƒ-@òVÇ'7|V,<Êöûh{éBÜ^hŸЖ&Q @Æ4¨FG.m÷Ѓ»,%PvöØS‡2&·Ic4„n<ÿ!`í¼ûMÕMúóúv\Ø›ߌ‘š/披t¡7»ëOÇ$pMw=tÊŽU—i›]M0´‰:+_`#ß{B<¿‚_‘?yziæsØvÂîÓš,ÿCþ×­y¥bŒ€\Ûðen}ìqA…N€ÐéΧÀLá 'Äœ}Þzc’CåðzèŒôn%Ì(‘wêíQp½kw0t虤;¶ùÕÙ©¬|%ÚŒ9أ티€Ìuu/ Ë™"ØóbTJbUèÁþæÝÌŒN”ã m¾ÞefM¹Ú,0×Ã)œoŒ›ôYŽ™¦Á2¸cÉ{â»äEP–ª"ÁòSÄÝú&&Í'ø‚›}º…lìäJÝkÿ΃ù„ù“à¿6×A‹îÃRàÒ'ªªà|¾eFÕ$sTÎd;ùD‰ù£œ„ ÿh+ªMv7‘]¤wún:°ˆ[¿ðg8àwòKŽsƒ[½þ´fóUÖ •ÐHCÅÌ„BUY‘iß·éô ú9ø‡k –jþ‘`‚„¾NÁ͘þ qRšÜõjI¬7(Æ£ÑöO[nÆ`ƒpõ÷qxÊ€Fë­Eã4 ²}…3ãÒ¼iêˆC?V öfºñÇâ}J¶ŒÏéÂ:l#ã]›Ø…3B>Šñe^ÒUÅ m_Ê63 +G°}r5æ`Ñ›|f¼m;6 “ÃÙÎuq­ý†±¥¶©ñ¸Ès}O¯ÝÈK¿Á3SÛ pÅM’@ä´vŽŠrŠG[a^£v;)æ|êNŠðÉVCÇTéáÐù‚ûøR¡ €)Ò¢û:ƒ Jïjõ<ì“Û5Ž©N¢±ícòÛªé´z­Ým_Öv¢#Ä£íÝ]¥ÜÉÃt2J¦<0“Drx%ðĬƒÄ!¡ý¾–ß¾¼u4CÓ¢:ë*ÂfTèγLÔù@ ­€ >¿T²[vUÝ•z’ƒ"¹ëÂBŒBçÖrˆè‡–‰Ñ÷§n‹†ÿfV|¼ý–õ¾iCëƒ*Ó®ÍÌÝ.¿8)ä,„{¥–¤V¤–ýšÅ`?Ò6\sq¤?j€èINøVñU¹Wß*`–ÊÔ|Ñ1vÐx™ª®œÉI½-È))§– ›w.Q|8†s½$~õﲿžàA#FÞ±­Þþ´P›7é”SZ…_ºÿàFd¯äšÏ»HÞ‹&VqšN%3·²‰/6Äoj1*_wÈV½«;‡^ƒK*‹ kößH«39¦U1µŽ\È¥Šc«N莅”¨¾yܸ4¿ÕrC¯‰-_ BÄø°Vå:Î …$Híð D×Í™+ƒU%9È€HŒð•R»È‡eiÒÆ Ä\zªû~“Ä ä(ÁAMjœ#öT¸A/&†i?÷ Þ_«ŠyÛ…Žï™ê™™è©ÎÞ¥µÝÉ(Xã9©†øä ·ê­Û·–[ŽÝÇod´&ÿ¥ :¯w{Y½S,ôšµÏÌf»ª]Ì\Ú¨š‰_.4cˆVùþxš Ê7!° <Œ¾q”:ÕU7¤%T­·;¨qóiè]†âu0Y É `ì_Ö¾°˜"pÎo†E‘j¥}Uà g`Àø% ·ÚGzÌ0  }ú KÁnæ²8Ô îñìú Vgo¥øžIî¬ÿ¼¥Ýþ4Ñ\E"Bº¡Fȶ(º&Ȭã÷õ¸ë·é…öª6]ùò¶0\Óùƒ@@\2K`ŒJªùdÁååñhõIHh"ÿcWVe¸aå›<ŒóãV‚ÔcŽX|ò¹ÚgÕ|.%‚ŸÜɲøšé؆¹Tl=÷$A BTÔbY®Váê¢"ú]bYÂ|ÿNɗߘR{‹€Ë~¨(ìd«psê„/“¼$¦h¾sþ[½l!Q EN@…•%x«œí3íU!:Ô‘v“ß sˆUû »×bƒ>ßQl3i uç§ýÔ¨Àeî ¬†³‹ Qè€ïÎd}Jc¯î¸N|è7ÝôT×Èʘ6â?ÐyÒC ôј¦fF.=k«Å9[ÀÇq\}Þ±¯Ÿ4>Ìçšùí!”Æfßô`Qn…ÍÄMjN÷>]D/ùs/?Ö?RV¹¢`¹íwÁ¶ÿÚ“GÇÙ`güzª»& ô¦†ˆ&ëÉ^,¯Ød’î-ÙþivµVJ¢(Žòyð]—+ñ%˜³Ü~Ÿ«þøÐqŸƒùhQ`®ÁŠÍ}2û¾Æ µë¦áf‰´¡{E,ͧ2I•þ%‚ð†©êuK;Û;Öáý÷„ééoË¡Í2ç ½‚¶¨dùÀ€œA¡]ƒÌ2ÐÙauݱ«A®i ˜gG²}Ÿ)½÷Ìs_µ>v'FáL줓Êù_ déÆg3ò «•&B¼ùð0rwÎûÇžà*vÈ<ÑXöƒA’£´1{•8Î r50Ú½uâÑúnoå•qj‡NmMVBº3ÒAÏÌ!Èh8ΛÒ@Z´6ßdš„ÙÛ+ª`[S²^¶h?@(„”ÑîãGèÚf0Êþ}üÖÝxöé#ޔ܆NRO—ß3E›^ŒÐv7°£äöŠé n~·V§p»¡¹]né'ÂTé¢'oséÜz‰ˆ¦ zW• aê“wóIô-ÖÞóø)7›oEá[v{Ÿƒ±½4UåÆ×Ã빓‘\Ñ6”1ï1‚¥2Çn ‹‰ó•¿;±ç[Pú•¢®#’„íÞÕEÙŸázi£ˆ¾oš«%‡ k›8³ÐANYéqùµ#a}]úå»'ûxÙËó>ÙO8B2Cwú5¡¶ðz*ÖZX`3 $û}ƒúªTº+Æ6Sú£cT¸P7Uˆj§è*º‰ÁÆB²÷$Ô6êVоm#Jš?Å$OÅUøÝ`yò¢y˵’÷À²H‚?_ƒ1KÜÅ\Ì·ø!¾¨‡¦Ž ÇhÄ-æt.]Ô쿼/^$'þ'æq¹ø¯¶Ó¢x")IP÷¨f=*ÓP Õc»t”N£“ßr;‹ÈQ䘅z(Ç&(ÂñŽžûàmÊyôªíi QÓ0Ñ^ ÔܽlÌ9¦¥3Š7FH$ñmO2dÀÁ>A+C‚Ÿ6[“#æ†\´=ap‰Yh4îÈæBØ«‡?~Ïø?©rO—Ãr}¥!¯Ž°Àh*¦×Ì‹«¤<òRXßU¾Œº ©¹ƒàÁ¬®S(ãoŽ#€žé¤^%ïÑ]w#á&ö%„ .PY,ú åôÛdrúï.ÒrAŸ-Æ7©SÍßÄŒ>&ÚÔǨ”¶Ü¤:Ç‹*U™°ŸØœ¯µé.€Rõì6È#j€ íÞØþ3tè°|'¢Í€ì~ž2ü)ÌÆ¾;SU›Þ­ª •¦¿Ÿ†¯ÑüʤV–æ³]Ah„µÅ5ÊkÂŒñ?9ÓʰýaY‡zÆÍ¹À…h'«mF'à.¥Ö-’ïHxÙ©¿8vš  b”%ïÇ[‹»;tX_´/’§‹œRÒbˆ8o€¶}s=/OŒ&äðfâ”QT³?¥Yú5vßÂ$ƒ§Ö¿<€tì8)\ÿáê?Ñì­Œ·’ôº®¢^_Ø6Ô³Ô(ÐR·~oK«q˜”ò²FlVk긺~/9G_~ü~—ÏßÂècyÂ:`Âã 9]´ZÓû¹ÍÏKøhŽ; ë%vÎVäC! Ìg]t&ªž¾<ï™$‚2ÚX•RÔ¢,ÜQ¬U`ß{/¶gÙÁ]§*1”òòàÆÑ­L¤wž†'×Ö™Ù5[£&ÞÓ¶ò,ÛxÔM˜½¹Y“O&ïe÷(µXNäQ–\7<ã Ñ#aÎò~ø‹§¡NRÎ3™°6Ñ`œIÝu¿ßj1bÈkèÓÛöZYOT”S‰kKÚ¥kn£%:3Pý¼• fô?#NìVr4‚ø®X?Ô>o‘Èþ7˜¬ç:4­¾-ÊQ&ÐyÀŽ’D4TàôíÜ%ÒÀú8tÄa:y¤ÿ&ãƒä0ŠÝðªüピ9ãÚ:îF ²R½ ,BŒ•_UXƒóÿ«Ë?yÇ»ä½1 zºæîq^—rÝÊ•0ŸSaÉ\ê³Ö*¥k‡O:Ä(=$ýä7–šéÍ´æÄígŠ9ѹ­e0¡™ÒRaØ“ÅÓ_1¡Cüõ6€WçN~ÍmŲ1s jÑÚ5ÅðŒšðsõ•¿ÇîõÎ(€&ê=Ïoó’ƒ!ÌÓh[&9´‚马z×V\Mä0BNB9Ûvòćǧð#䔨©¿‰«8(¶[,ÝÞ8zZÿ%`Ë®\>ÏÁ]53ô+Ÿ¹¼+–¹øÎÔä)dÍÝ|ðó‰ JÈí¯ж¾ôàk{Øî³ÕÆ~ ›úÆ^8Gœ&邽ØœÎ:~]‡Áœõ^z?0“‚I~·(r5Ù6ónØ&ºš¡n1œkÖkúñ < «/!(³p­™ ŠÆã .c•ùöðâ]¤Õ3§`÷÷*ÇP÷L&Rh3–/I£ÀiôŸxð®?qÏ5ж—a·ïšZaaq¨Î{6ð x·4>õ¯_4”qÖDbpÌÅÁ$ãÔkN'qN|¤ ˆ“ [h“â 4µµ#Y}ä%ü6ó óriü8Ç IšˆM&€6ã_$û™(—áÖjÑ`+ÃŒu-pÜÒ9éVšø©ÂNI­ óYlB‹œš µ0„k5Í_¤LÜîÿÿ†uhÐ]dHæLÑ `²a/8˜dM*µ½n|Ü‚ýŽDiÜŸz@ü^¦[ÅIÈ/äiIމ¸ýÍÕ'÷ç䪣Ü(xí!6׃õ uâJbšÀÞjŒ’³cr•E:æ€Ùë¹3`UæBÖ7¦z£“Æï:Ô‡•U`¨äo½›î8)>í—¬¼¸ú5«Ò¦êJPÃÎ?ø‘øæÞxk<Ÿë¸»éC¦¡š¬¾‚4ÑV¾I½‹= zÎÒÕåØîX’ùH¡çb`l¤®ØÌ£á,¨Ÿ'ÄÁT:¦öŒøîV= Á‘$òC6àî4 ØØ‚€±Å1[Ó*ÅìRçpËiö‹³¸…kãüøÙPDA”‚ó©|-_t• þúæ-‡ê‹âk%stø*â0³b×Y¸vE™Ð’€:ÍS9ÞÝ®ï•qiü™¤á7¬ê$;2Û#ˆ"Ø™]ÿå6uÖ|Ö!‹Ø‡“òËa"ˆ¬8Vrçžž­Ããt§0fꘌŽæzàozjèÝqŽf@BË 6Xt2­¨á’xà Cå›—Óþú:‚6×AÞ´þ3Œ)Fuk ™§Š·9´ o >¬c8·ÖHø¤ý;ª2å½òsñó‚õ¨*û»Œø¨Uîäúpw^û¯‘jßZ ï–iy¬•ÂpÞ$¬ËeÀŸã#ÿXk-üÛFå•0Ê¥Úåç-fVÚ1Ë_W·¥ž%Wj×M l^;_µëø0Ɔ7£<Æó,ÅÈ-4IÕ,×*HKOÙ”’A›™èŠ'‡B¨é×ö1y$ë+܃[LoWQÄh†ˆ-­)q®ŠŽîFŸ\Móæј˜ë‹vÏá\žM?ØÞ/s3aãr‘çDÎî™U=Õ›‰Áé¿_œ’„Ÿ,þÖÆè<@éw‰zn¡Ð2Úõ|¾PÈc$µ"û1ÁGÜ» y¡ÈOÁŠ×k˜yzÃÁl¹L®Í.‚´H:ô¦#ïvZñ¡Aiƒ!úÐ8³’Ò&óÌÏžR`›m?éËYwÿdç¥G+:Ϋj'Ž·'`µ«c:]îç³jÞvùýÿÍÞ D_ÛÍ.}®j)pâ¨9ý4²Cjöd½2 ¼á±ýiUÆŸ\×JçËV®¡{8sÅpÄï2‚Qr•ÞKÊÊkŵ#3'Ð% L¡ tys²å°s‚š½ãZœ%#ƒ¤”§é«;œNÒþ%;–íq¶ÀnØ É-#„±ï~0xPÂï¨>‹µš•Ã0{âV9ÎöY³ßA°Þ‰:PÛ&é>Û«v¹øË#È·ŽI7R–¹>Ø\`I _QœÄ´RúÜ|/Qo/•eVê fi§zšÝü†…¾ÿ;«n|OÂÇìèš,Â=â^˜´Àà7›ú"’ßÓÛk×ö½jT’?÷Öþw§k¼é˜ã -‹» €Î¯âT€&RWi;åp…™Â„‚Z¨c™Ô„¿º•“ª„ÇÆAu¾ùWe^˜]†ýš0µbÊÅš˜¾ì“ŸÇVHÅ­Nó>æ3̟¬ïìJû¾‡÷ÃXŠ®Öpï¬À+.~¶áÅ:ÿ”1õ’'ð³S¢`š¬Ý§t8ä(à6¢î2X'HÇAH)—ñŒ§éÊ®ò1F÷=gP(­N…mÝŠÜZõ5èÊ—(î4tjµP±æÐ8b¤aãë⡈nY@Ë‹vµ1Æê»"‘6lõ4”³egQß{™68S8Zžq€VÎãú)u•_às ½C¸®ßÅ¥|µ‡äù²ºR¼óÉa"ÿ.Âƾ;} úC>µ¢‘þGPÇBZL¬-KšcAÿº§VÁ¤\£œ}Õ·8­Š÷ηÙXô—ÙáP³¸;0C'ãÉ.IP·Éògº{·h¹OYtò:æm{›ø*Ÿ~ª‡·€þU ]ÉÕ]ukééCY?P‡7Rý?5µ=k+f;R%vþKé0[¬Gp=Šmq|<3Ämëˆl{ù©CÜñœk\bz’ÓŒ±Â] ,`²§áÇG/§Ä¢üœµG1a<Ê´:Z÷q$dGV·ÌÞºÀܦm v3Æéµ0|«¥sÚ™øÜf.û©Áé8§y5¤+ÊlF£î9£É˜·æè Âò¿…Œ-@Ÿ2&WgzíÜ?ºâ~Æüîiƒëô‡·p½PÅÓ^5Rn’Doµcœˆ4žy¢î¥Éµ’ú|~øê‹ÒÜÓ…  xkPÔ öœ”´‰—yѸú2ÈŽ:M®ïºcZsãÚKd¤Ê™pyO‚{{z¿4ˆW0k/Œ\a:UÝêuvr î…Åtõ¼\>2fDc}­†í7xÖ¥ V<œ Wi0‘yÿÇÉ2øOEà"\û½Á|A©‰:ý?´æI3… hIWÀ7åœÞQ[4§ÍL v˜ú[•å£ÖyÑElÊvÝÝëE&æÌ»ï,SÝdl{;£`ÌŽ‰¢âd¢UÜjHX×ׯºâ¹kÛ»ÿb ia|€¨](6Å›ÿÇÆì‡MÒ>mÚÏÓ™® HN1D 61ÌòöQ,R'ãžk7r_¸L£Â¿wgÆ~Öpxìϯ/ØN ‘A˜ÇR˜Â/ `DoVÿk‡ à »”qúmÐ_àpö¦"ŠS 2áHõ]›G?¦|çiÞjÀ»ùHæy¦,e Ò‘æQwn­˜G37æo8Ó«"Ôáw½,ÓCCÌwÊbó”“«ˆžf+öáï×;6Û£‰Àm6o_˜³…pÅ·j^—¸NÇU€ÄÕë7ÆPþe|'ZŽuêe½r€Îì·ŸB­$¡¿P¯cÅzêœGCð Šhœë¢î÷þˆ©¯Ïç´GOº9M2{Sy6¿¼ ^V ¬€c<©†Ú›]­xnyת._ÿu9«–ñ½¨íEî1È!â×—Åꥴ:Ï ÐïïÄ>ÜÌ4ã«£À(•.šÇí[0¹Úƒ%½9ÇOÉá¹bÁ&FB¿ÌʹÄv¦¥Ä€ˆàŸ/P–½7G{9uüýòW¬&(CÔv#­ÿÙF%W{0/¿ÜûxòGØŸÎi=ÃÊ¡„("ZÇ¡Œ3Ý‹.yî o»@/ù"4מ9ÛýÑ#’EʈLE4ä*ÎhÙͦ4ÐlÄJ•v¢K‡Îž¬Ðqgš£•Ê­9C{÷¹ò íuòrW̰•KƬ>Ø»2š”ûþq'ÿÆO‡±SH%I<,=½ÚÇÇ3dw?Át_ÍÇ?ÇíØ9n˜çxªŽ9ôÇc‹ø‰¼ @OžH«÷›œ¤S”ÞrK»_Ou YŠç³¬~Òµia¹I >¸(ÈQÉÖßôoAÕúÜ»"{õ|IzìÅÈϧY@ÂOr[3_vžêO’=Ža¢cqÁƒðп§Lø+Þ<²þÉôëç1cþÖŠY€ìH`|Kxƒàé­ Z×#OK¢ˆiÖÆ—ʤNFéD0þÜ1±^ŠL+ÌËÜèÞç üìËý©ÿm})ð-’Úb ° *Í 7ÞcŒŒÄ¨{ú¹À’~fm”§¯¨Üp÷ã)ÁW¹=+PG Ðˆ9¼6'5îD§Q<ü‘ääøÆìÔÉX$T‹ ôÁ›S:“2@Ë(›Ô.ƒzê»n®Q'%[U Êo‘ÿå.%p‹â5¬)Ä$•â´ ¨À*©qžºØ*ž¶^}&SU¼ÝKÉRô™ØO@™wÛ\ܹ…RR”¦6¤Ç^ÎùÞÏ%£=e8y£/¡CÂ_ÄäÍ—fïˆ=/¢“CúR©ñÉ—M+NJ0(»2$:ƒHˆÁˆŒÕ ¡ÂÐûã¶-7q²Á瀦ƒ3pYF¬©å=`ölpÂ&%T6ùþIÃcqóðÊæ§Å‹”A²óuß53 î‡ç16ŸHFÖæiެê:Úa”A„¥…B*¥7N<ÂýGÿ|áç3µhÄ@¾%¤²(=Pwéíf‹KRÂîü1ñH)Á”ü­Hß_ðJf»#y¹Ì*:_Ðͱ—B‹ôÑ-·õüŸÌHO?–ÜaÜÃzxlÚ¤Ô_fѴؤÄîaçVÄ!76Ös\ÄÖ~°&ïžX[„cJvq^¥RE¢<° â‹Õê'ÄZa£óøÓÁc÷…|)%öZ¥”X`Mä|Ó;›·ò°jÁ[IoåUIý2Æ®/<­DùÚ±´•„Vö!° [€!U¹72¨¸¹ûÃ6Õ?úb”¿…©m5Éô ú«Ö(E¢—a Z¬C-œìiIN€_ÊÅî–T™†A#¶‡‹ì1Øp-&Óü/OÅT>;IãÝP‹³®C©Y³ßyó#൰I²G(ª+øU“ÄRb„¡îGÉÛvógI]…sÖÍ@ i¨yÛxÝõ“‘˜87®tc±Ô"ZP^ÉöÛ~GNÌ`ÍkQ-OB¨f;aös80þAÿ†ÐõÅyH‰ã17>Ó}¥iäŠ~p/åÄlê/ÉŽÖÇÓ™Šâñ¬_µ8Ó‚èÈå\¡»b“d£ô…­åìL!üî3¨„dç%b¿#F Aá+®|¯¸‘|!?Nˆqõ)Ë2%Úk,6=h½Laâ¬Çê­+Çû_|àQóv7Šè@®•-c7>]¤”h[Œb1ß“›Ö?ÅÏdxï­Œ›@QY¬– u—jí “@0bÊŠ:¨+¤ìqÀgß»MÌ…€Gþ‘Îûµo¤:b½Ä-qnt¾y–§ÈÖ]FÈ>ÊÿÐ<'jÖSQÛäDsÊPø|Ѻ».®P›j|“~ £ô?Цm7߸â¡g¥úÁTwEN «Ñ1Ó¶anVv|Ó‰·åzíCËXª!Ò–ÞÊàÿyúˉ\]—‚“ºxcïù†„쬋OÎ9jlF6µØ„#óÍ<ı¶l´1@†6°DN‰²U=Þ¸ý{×èHÐgv }„Xòø^`™•ÄfÌ‹ãœ@ýæLï+qìs¤J«¬JKÉù6³h!¹ Zúµ¸Õ§65×À–P¥F >þÈß²¿” ]ÂßÏãÄ1›©¯ƒ2 Êš±Æ‚¿ÜªŒ]îÑGèÝhƼÂÇð1Þ¾b¢ÝÉÐ4Âù mÚùQjâq2¯Jç{±!Þ~Ú‘FÃ0Û”³€¦*Ý[¯r¢æ÷¯:ë ¿º×@S›ÄßzÐòx’ÜEQH ‡lˆ,fä«î¤6En¹rŒóÁ¡#ŽÎ¦ÿ"rƒýêŽøðÄÏ™.,ˆ¥Ì6HÝrVdžpàhÚÎMN|(Û¿Ž´*áDá±P_°Ày [[ðqØƬ~+ {ãHMڰ߸ïŠ÷µT+ÜÇll›)³(¨”¦åBöç®j^ÀÚ~l惚! ëž¡Šn?jZPê·»ª2 `Sl> ³œ•Ø@ôú?ÑvPÿ(³_©Ã^¥MÜ"²:`”MÎÇêSÝÁ 0êFgÐêùÐÎV™P¾¼Ê4& $žµ%aä­¬åøáÑ¡ó™râ¶œ¶ê¸6.!Nßíû–ò©26¯G–ù~fl„­Ð›säZZO>ìÈçºü„Ã]õH›„üÓ IðÞ®J|WNü¡ãʈң‘bTw /ÿŸÞüÆ$X'Œ?SwªB<’Hb( ÄéÛk†Õ¿‡ž›kr'¿Ÿo‚±g^}7°Çe‡05_¿x”Qðù*΄Et5]Z~ºbñç Ph:Zï+[ݧxަK?оòš #Τá?Ñ÷ÖÙŠÖïã"Ã#TÏL%¤CíA@¯ž±­±äGvd3›c'Ï÷œ,cºþè„aËÞpŒ¦ øË%K0ÏΚZœ§ £­õܧ¦ÉãžÁ…²Èèknç"l6ìy ±Ls0$p á>ò¹÷¼„ÔÌrJD§jZ\Þ=ÖÇCÊzÃËU(|؉–¾)Æ£H¸Þ®·,“FÛ=î9¼^ ‘XÑbý,Í&v‹GŽ‘s©:áà‘Æ)Êú… ½ˆýp‘SÑ„L 0¸dç;Ü"Ši㻉§´äÝ ‹2mùPVHª†&S\%Û5¢ ®oÊE#´&~ö㸤t!)jÙ/‡»#Ó¢I=¶Lh` Ebœëe<²dÇÑlÖé}Ã5´ƒÖ>€ØM’|ïyUšÀg 3G ácà™Éô3ózŠsÐo|åÌ›.-±8|z êZÉÜ n÷ùŒŽFª˜ Bu9Ð6œ‹lãQò°mãûñHZRöê1”Ž¥œØñöSÝ+ Í±ŽØNs½é•þø?E¬Ú³%“ççäëК0v²ì¼Ï¿†¾'àF,ÄYv)a9:îΞy©¥â«w(¢™‰åÀÎðJ‚ ‘vg¼yd4Ÿ]C¢;s3õµ–ò#ª]&°'áÈ„ŠÎÜQªíh‰­¿å¿õÊKKž^uGUˆ«^ÆÑÑ1HhCY _„ÃQ;¼‹õMâöâ°…–ÄeBïU_ÇÂbB)÷”Œ6hn”9î Ís)ö}ç }E8^ݽ”£Ó\4bSÅzw²ï 9ÙJÝæ_ÑÉOªô«§±Ñ£Õbô#>"‡r^µÃîæÃ)9ù­µÿíì ^º!¶«õú1Ï æ¨æFg£€\øKfB#DJFQµà"üLÌoä^w$×úÃÕ@ÛÆ_ô7Ͷ9q9^aßdŒHõ£Îþ=¹€@ðH{ØWZøˆØ‹äXÝãÇÜN›™M—÷#k<ÛÂÄÖUËÖ7äS_e»©B9l­qÕÁ´”вûõtnÁ¼Æ#¡Øâ ·ëˆDÒZ‰m©`mF[†ni6Õ}G;á´…™}-òÉ 8Ù’ÕÉÁwä¿5Þ¬ JuÂ2¨èVžëÕBˆºž~K‰êDÏÅÈ&÷†a—²­þ^@§S‘úÚVLu%›_y¨ã’U4*©uTð¬Ž-Cûɲç÷}1ÔRʨ.8RY–*ì$¸sôÎ߯ óG¿V,qA–Øés‡ÍN?Æm„7ˆ†ômñ$çµ2Ò÷ÿ!÷öʃ£艨FÞ ¸§ VF|è o_Іgò9פ:Áb‰FyÅ,äb BøWוþ|Ò$²ÔõÖlŸ Ù²úžIåW kÝ,v‹Ø–´UÓ–Õ«gi†¡·ð€ncrl¦€ˆuT^ ”Ôþ§´i àaIØoÇðjNfÞñ H9ÿ#%#Èýú¼™ºü[CÚÌ2ûÁ5Al‹ø¦OãX3±(\”¿ ;–rð¦Ø0‚ SY}Ù¥dã, îÎùßµ 2S9 2ÃúTbÃñC¸·BóéOÄž¢&¼=Ä⦸D€ÄŒVËÌ?ršÆ“]ÏÊfW$Ñà4Ô8·Ëà5ØÑýÀÔ³z­á¥¢Ã[¯¦LΗþòŸ@Bó]øN33êܸ sôù¬Æ0EfP×±ˆƒAÝÅ|˜Õü ´}«¬q^Zc“R7Œø¶JÔT¹#þËz4R#?È-Ó©Ù7ò÷Éu„‘êL=ÝT†®00 ‘€8ä±6IÆðȳŒj¦ð\öf¿Ï·5ÝÚ¥H!<·Å€ó´`ìP| ë#¹ÞPùSèßøN¼ÇôB”5‹Ê™Y% ò”@B5Oy!vI0Œ¥ïغaüت_Æñ÷/»üc1M þO>ðîev±XžO¢Y°÷ˆ$IÉΨ0`‡Gøƒ1Ôèúì¨O\Xë\2ÇUo7g°-HJÛ+-Ê‹›ƒ&⨖nÈ‚gYÒ›Å/žàŸpÓf¡^DËB‡j¨xŠ2­ Ñé2/‰«_ Y’å$¹òËBÞÜq¥`Þ‹°oéÐ*ÖGÈÍø*]w&Z³%@“φj^ˆÎCòÇoœÝ¶fܱOdpEÀœ3‚èi¾…Óöõ“6Ó&%O—:VÎ^äqäSbØêœQ¥hÊkNMZZ±Å‹U¿oPS/ÄÝr6ç¿ÀùÎW$éòµ ‘x®rÀ¼Ì8šúz°áÊžÌ:87¼ñ'¶å7 [À¾“ؤÉ1ÿnƒ†þ„C샅+¹°Œ’ˆ4Jß¾,Ub-Ê„N™ð˜þáðO^×7µ“þÓÃLŒ fòZà;[»ÿ ðt:„âÊ¢’d×Ô<Ëq|…ÿCkàÓÔu|‰r¿l†LØL©Â¸íT4mnšâ18wp» Í}àT(q~GIÍH3lu´1ˆ°0:o‚«…úõu.~XÙœ¢HuD"(#ñê()¶4~ž §x$ez(uóæY-ÍПœ¼,â#Lÿ_1‹+c§÷&Ñ¥^:â½äñSêåC¢¼úÊq¦30LPêÌ%½vSá L:ºS©øÔptÜÍ(:²×Ïm%%Ös–M\0Në#ÁOÜ뼉lüXc`rY×^èÍò÷ÆàÈ­Û*:3ªx„ér/%òåÛ’/?:4dZ1èŠ7ÕFêÊ“‚š¦pŠÈsìêó…>…‚@Œ¹u\#VÛŠC´ä÷m \I)Œòĺ-ù]Ñ4èCoÏIwm‹íhj8öenw©½w»1[¸T(Q“¾î)»ñ=Ð…SH¡ø;O¹Œý ZPˆšÇGô ^sëp³©X¨\Àºiãì.^®<Öˆy®¤ÑRzo6×½˜”ï'» lÔÂGV¯(Ïûp¼UIÓÑ-o_ƒù¿•Ülz&1ý1J{î˜n¸EÊ †kþÓF?é÷z™Îþk«øÑ3Ñöö"™Grº‘Æ^4\A¼«ÞËn«ùØòqpé(øî‰JƒÏåàΤ}>®m / oÝ‚o§pÓ¦6Jè±Ëñ^ªÇlg¾ù>ºÜ“t ­–€¦Üuº‘Ìõ=1r{Ç.ÿº >ÞC2F3fÔ{«ÆùÊrk`<"#Ä•^¡à à"b4•¬@Þ4­pu›†€øÄ¦¾UièHùD‘Û‚K qð®5ÅŠ@SžTZT…Ñ]e;|yÁ{ërY—Å—ÄÒ¬d< ÎAiè• 1íE@ixHˆ—…/a@xÔâáö øÅMè/Ą̂±‘ä<ún«¡£ˆˆ;½³2L5r.¾hñýÌK¥Ó<žïš2ƒÆóŒ8HÆòÕη5a¨¡¤œÆ€ËôñeEYaªÊkk/Ts8ýôA–š2ÚTßœ=åÔ}^)Ði`2­•–E°TRÀrK¦Ñ\Ù+k© ñl±;t¢Õ VWQà˜k¹Ï6HAüBÊX`Tl¬êíõÌ¶ê¥ ;1&PŒÏÈIüÂäZ_rá¿m Çr»(¢lT#âlnê>._¼»”Ùî¨ÿJB ­ž%`βàìåM X•ið8öZ}zÞJ"= ÈØÈîo:TÔ°¥S<ç-×Ìe¥§¨Ôº ß81üÄÍeîo!£p¬=¿‘ù¶®Ø"È_ã/ñªw'xAV ôîåBs»†¼|¯ãLk²$›ÅÝÊœ–§‚3÷!O)ò,:â“™²ì6*:döɺ>Øó%Ç @/ô¨À0øLèŠÊ¢bDúôE„7vÉ…U䛤ÞU³–*ia8 ’?U ˆŒÐ°„é›&z£‹ã†·Ãbç¸poˆ!/Ü ¢6~-Ms$Éõð¸å2[õª¥õ x(0V 5]dy««_Ú!ƒµËÎÞ7¶ótßÓK’KQ-.ð± ̸¼Ýš¶à4 1vÚëV«eùPƒNÂàAa¢î`×·ÅUÀ„ it‰sl[á¤èòk[ÌYXCèTë~ˆò_‡>$Î7Í|PfA)·•_SêÄ^/}ßyîégƒKÕk`ùE6Ê/š„*‚Ö°®Ô·ÛàWPT«ëz¤v~‘$ E(SÔ#®Övúúxu±@@–ÿaªÅà ÀÅ•—ñyÏéO挚[l½1üѵ?ƒüDR{g7‘ßRÖÏì}>U|y@zT3eöH<ŠÉxgW2˜z×2¿GGפé¿Ì€è»¢r_¥Ê ?Ò£»jg*¸ð–ÜXí§„»¶]¥¨£ B|B¶—Ågdå~‰D˜Ï i3¼= ÏaÍ|døE…$õ”+¡Xfg¸d‹ŒÞµ û' GÌÂþU0 ÍÒDw(µ{ü_“ZG¹ôñž'»‘ý¥q[A®˜®à|ͨ³§;Wz¾=¸ÒK’$‚CP µçÔ0Æ»BOp×mQªÅj§Adgy‹O #þý>âvM€Öœ–ÇšáÛÁ~kzn”2IÃv‰È÷5¨ÙyƒÂ %¡§qÆ >gUÀ©q29æ†j)dÞ®B¹œØÆ¥”}žã±õê-M)/WËhE}s@²§Bí¢žÒ}È®u§i¬°|»”úo©Œí&ê*Ï+‹ª Ï l\C>Ïniõ’|reðàò#Р̦¿cb¾d¬çƒÁ\ÑNE'0#·ÏyË-n­Ïºw\GPB£ñ©f8Ñ9¬Mo+Wšar½/Š–.ÏëLÝèäš4oÝ©à+FOI“qº.¯¨ãÝòŠLÕ`5HÌ F^^û¼Q¹m²vR6k·€.Ñç£¹`âîªrƒn4“ĉ›åÍŽVyÙù4×È)ܬMQüÚïrƒ)õˆ£†ç©À}% #¹?+m®>PÜPÓT튆Øþ¨}Öÿ´½Øâ_Òû/I1D¨¯œþÑüèØ×БV'ãÙpX –2FE»+ ÒÈàÇ“Ù8°Ý/_„Î5ØRmÃ9âåEª¶–ćb9sуª¿Bõ§1¸J’uJæÓ/@ o’$]屚ºlQ èðBz‚½s§Š“ÅUÖ9¼“­Ct×ôCÀ²ÊáŸä’/0!1yCà¬:ô4év=°ó> Knä~”ãè©‘ç®Õ+„®Tä¾´ìÞ®=[½î¼E|úIˆ@Ÿ7 >C9¹wùÓÉÏz)Sµý²\l–ru¤˜ô ý·mjz)e…s¾ Öƒ¤á ÈCK"•ÐúGtìÄ úÜ.¦wÅómºåŠ»Y^QV,Å«v¯Þ¤.¼±0Æô61,5"]¿«Pè™%&/ö¸Êuú9&`È(2ãÇMÔØ: ZÝ}øòá[µb)k®;«ÍÊE\I¾OeÑ£ÚOÜ%ym[úYÎÜ›¸ƒŒôòßïZÇx)"Ï.¥¶©áóÏŒÃX9¡±r’×Ä1°¼ŽãˆÅCœ"M*å9G‚ë7>k¸>µeìꆈE-d ˆp@ÏÒ¦Ú;uB3Û¾ŠžKÎÌ—} ïDß|U€Ð’y)ÊÎÑê—¸éßZ# BZÛC°=Tó)Yãˆc”Ÿq¶´ ™!¡ßoOð „ÜŽÞˆ1+2 stoˆ>|ÂϵZ ÌÌÛ=þ-9Õ÷ØÚÜXVQÁj(¢äíú»užè}“-ßû˜ ¤ÞгƒT}¥fûæçuû“´›Måoâ x9RÃç•ɰI?p­½è{|‹¸íÈÎÿÏu8ˆ8é„‚tXîYÉ’ÖÅLyð:öVŽÂ¸ð"X*N§y3ªõ†·»:·‘W«±U[ z§YF\EµNymër¥& ‹û¡]o+ïO mpü^3è˜ú; ÍöUô%?̈ŬŽf‘ª#ÊÒI&ï_)G˜°ºs[&÷]g\ôÛÑmìæúöÉŽûÔc±îU›Í‡îZ°B-`¾¶RïGgøV.–åÒ¯ñ¯á§IízŒ`ÄHp<ÃÜ A;è72·*†°& ¿M ^Ê= ʳâ´Q’C‰)ûdú \x–5­\„ž'Ö^aôÌJNd ÕJëÒvWñ5€#„$ ú± 2ê}‡§ÐªZm«'êâÞrl‰>›ñ_wñAMâÔ&W»½àqÊqÌÍ•åŽiµüèt30µ|Ä•ð¾drâ·ŠÞ’t%dÞ§D1H—’äöØwÁ›æSÞ¨L)¥8ùmûå§µ6ãJAwò„áx´n:"0¾’Z±ªŠ~àêPD›ÉµÜDoKl„®+ŽîÕþéHr#Js…ÃÉ+ϯ›vFI# 1±xE%AÂîÄØÚj"²±,#λý+%Õø±L%dݬ÷™…|DØ—«×€±…ˆ¬¼M,r?É+cµ)FýUAû}¨ÿ¾ÞöŸŠŠJäëWRÌ«¨ÙänÔ¥ Ø…¿hAͳ(çPßÈ[ÞÅ)Ô´‚O -I`•%§óäˆo_å )KvžL9¸~3ý뤶áŒ÷úYrlÙݟͶùÁ¥oâmÈùÐëʼMS‚¶dÿb¿ðÔ\ñ{Îd¦”6©Ú‡óÿDVÚ4óÕ‹xßÛV›þH:/Á_øbp“FчÔ8ÀˆãŠüÀÜ^üøbRÖ,È4T,áUå6fD0w2±Øj&Ó•oR›áî¼+™É1bØBÏëD®g&‰–ŒügdU,†edeœ¦“vŠç|QéU´Vs°KßqɊë;}Âk>Z:¹fD@Q1ŒK™’­’²"H$Z4Þ3´7=ׄ Æü[ØÇÌ 9ÁÒ/atL¼uܑՉóö'`¾[¿–ß\<ÇܸDdÉX¶ý¤—šSBþœª˜»"?Y² £õXQÖ*@ãê6Ãêº:xjˆ ÿžÈµ-x%UD7B\SêäF]Ü Çfö²ók vˆ˜"»Ù+”Õ~á^ÚA­d³ë ·ŠPG¸’'hs¤»ã£"ï!+i,|´Z¾&³…_oÄ{®gHBY¸Šñí*Ÿƒíûá§iéEnšrîP[å“uÊê F¥ó‰«I ư1QSú²Ê·²Â2Sm0Ræ”î‹o‚µ;Q矻T8»¢û?A_Ê"½¶Ö×ÅWæ½â¨íƒr#Z*zücgz‘ ™TîXÓ3ÏÙ[0#¾Dértñã)ôhL¹Î×MŽ—ì€ë].Ï Ë @œ k[æ×ÞÊ"BÙ‹ë¹¥K?¨·Ù[>Ðý.áГ呞¥m®m1ý×ß8ˈ¬è*5V›(PãiÁ±]èðÍ,õyÝå<ãUP,>‡—…8<Ê_2<1&¥êL·2™•ÿR… lÀm9/¡økT†T¿{˜KYÀ:8þ‹PF6ÛIÓ·Û`.:KôŸŠ;)¾ˆ©s¸IäNêg'ûFÞ5v·ôÐæó*fkÏÄfbO[‘^…Ïôw2šàƒFµÓÍ_?]—Øó¼t©´ÞÃÞaÜZô÷RÃW\Ñ'kÒ£c-"ú»+i ¯v~Í"” Kõ°Â`åNZØz›lZ*3DÕÉ|EšDÛºÝq¸,ÑDy¤ŠCnr'JMë çLÊ—žÑ%B8ž/ ÆžÊ}D1{ ûòD&ìÀ2Ê¥ýy~àÒʳdfc×ùI}’n¿ŒŸLÎí®Ù1õ¸~«Ã‚á«z¿B'PìʬBµÅ™È¡ÿ9O‚ LÝûæW({þºR‹p'DK¼LÃkLŠ¥È”¹}×ʼ­gób™Ìr;[QPÒpM}'Žšµ–û7!ø3ð¯(,š[g¦»R5åóg˜ íáÈ[×yCГ¸°_úª°Ùc;\´Ì#´(ÿÄðåÜ5@^ÎxCpϬÕ/x5vo<ÍÂ]AèÎ §…8Ø‹º°‹5ÝñÂ#SZºé6µ¶5¿&‡´B?ë¤/àÊ=¯0Z¾p¡\¬„!ŸU`NUc‹íl@è4ã÷ž÷fª‹ódTÊÆ¬iþE„É¥pü¯Èåé—‹D¢°j-½ùrí5“Œ…-T÷¢÷G¿2¹Ì ÙPùëC6‹„|“ êÎÜ1_z.K¦ÿW¨Â»“DK;Œ‡Aó}Â#&(oQÑ×Y&Ö"”µŽ"6Œk!§ ½–ÝþjJ›p<;Rß Ñ >[PŠ<Ù%¬k.kÈ!ïõžoÞµm`ïê{×i?»)ô¥Þ¡éUdÎEql6ªé‚Ö,²ÚÉ yÏÄ— ˜Ô½òW¼e|ʵÎγ…JñÃÞÙ=cÅŸO•Lý ©Ã¬Cñ-pùb@P8öñ–…艹T¬M'Ö¾%S‡œçP•Bý«¸¶©ç¦6WÁs±•_ShÂÄ<-ßJTÈi37%áäk ESEB{l¨§î,ô £âŤéj@ lzªf®Ï &$N¨«Ë&c¿6’ nb".üZE¡“³†®&ùïÙ¤“A01GcG¹q'[¹)Bhñóû6è™±»/ ß'­«„ÁQ¶˜þËÔìÞ¾9#ä½ \‡¾PC*:`gºÚ<'ñZ5úß×>ßm’Í\Œ^ŸAR…#$÷h¶76C2€6¸;i¬~UÀAG.2Ìõvª‰¸ã3µçñ!¬Ê˜¢Â»'¡†TlÑ–Qû…Øèp²â(àßKu«‰´ÀÜïÅ£¹0_vâyQÅTÿ ˆäqÿLÈŽú˜ÿƒWУƒ„Æ]³öB0u–@#‹ÇŠDŒqª[‰{eé1*6]BX´´ˆ¢C#[½`{aîõk«)ìÕ6ÌV]Îæ> íIìZÇ9‰„ë$¬É:4#F¨Ñ=€ ¤â)D0븇Øõô:šdÚùw‹º¿s"¾lF¶$±Là®a\(oЈäæëƒ­¬Â|²CµìÄ5_)Žõ>Z»´ÝAþšžÀT´O ¤ë!ÌûsõsñV0t@!E^þÉþx¾™ ³…bkId!39•2£Ú*/ˆ„›iØm˜ÇK&(²Ð¦’÷"W-tv±ˆgiа‚Æü+lOƒê“×W ÿóçHX_,æ‹d›Á pvöK¶Ú¯H ^ù¨|P€Ïàpø½Å–ü ¼EÛ‰Ù†YY½š´áöjŒÚ×cîÄtÅR"ô¡˜ç‚™œÏW(€ Ô·"J!4Ý|zUCwÓöÑÜ☷ÉJl2÷ž¯êèvÓcFvÏQÞ÷Äh*´ÒÍPšž1ÿ\„©”ëܾaÅ^i k¸9ޱìnšéUOß·ÍV”i²û—Dë2sA;x÷HÓâóÚÎ’U µ[š‹_?Ó÷Ù{³M„”Ç@ÖÖ`Õ§ɜ]q×4Ò´aÒ-æv^'² 9‚ >¢£ÛG¿…ÑWõÉOü›oË#n_uò“%ˆ¯Rg=¿ÚÖ€7ÿ•–¢G«Áê)[©ÔFù¦ƒóQi@ù8¹NkX4ýÀ]Ö~¿éñCÓz› §Æ_›3dz%Tk›èÇ -€j/¡–´Ð0\Î'bŸAžø<©†=V´¿uå¦Ôa窯­z ;çàÞùkykµõ_¶KikãuÏœáeÃf£gÒ?Øgú8Žüé@®…ThXš‹™D­È@gíAN§ƒ„5¨9zäj! q'c@¾¬ ð2? Çi ‡·U¼Ö‡ò?Ùʉ‰Ö³.³ÍÁòcúßîKâKÊ,ëæ7c'³Æ}túB· a8†—jGK½µh˜ô‚He.„#¦D~0z‹ÎòOk€¨oöÀ¿í %èˆIHýÙ"ÜÑfëÄùNu 7àZ½‡_eCµÓœÄMxud×Á9*`£" @€LWPJÈF* ˆº˜%mCmWÅÜ*t›gVèvd‹•¯Ðr¿ûw„Öû#º9´™í$HjÐÈÿ„r4}m?36!QÔ!8˜c#ª–õ}çŬ¥° +8Âß±¢º`<•µxËùci×¾O¿D™SfVäcôº,¢@Q&m-SŠi{V Ëδœïß«pLŠ¿"_ªJÄ/Ú“ÅC½·(æˆn¥àGB–fÿ6WCájá¿}Á]2Åìæú²Ñbs¥Ã¸vÙ}ÖÞÞ a#7@ÏÜ{]ÝŸÊâ i|Ë|nÌè©Áþ¸/9÷#éx°/ĤgÙ] ÏÂaçœ'iä„ œçÃfeûïkwQñËâ¢káVÝ3n.Êûâ(_ödVõe÷D‘ÆÏ2~@F üØÛñÞéÄ;Øò×.dWå2 /ç©õ€ID šÛ7Î…gHÛ¹õŸˆ¦8¥°qËÇÕžÛÑ û5é%} &ºÌ¥>zïyƒ¯–éŒrÆ¥¯ÏhØ“1JB—ýÃÁb¯qs(ÄÞ`L•7žÙ_ãþÜä7 œDj/î]\0ÅL[µvLu,úì&ÐÃ¥¶ÉgR}‚TzP‘ ü=Õ<[ ­Y2+1»{}/þ©`ÆÿŒ÷¿×¨§:“ /Ý,:¾}‡S¶¸Ð´6O¹óg£yi 1s.d¹ßõ-LMÙéãKÞHT ÷ë{.Y¬°¥ñ®m‚)V]üWûO<”&K}ïFh\«,7–êèK–á),`ð£Ó×Þ+—Ïæ»*ÓûÖ,¹¥Ú¿{Ȩ=K}Ü…y¯ÅO:0%ù ò(Þ¨`ão­e à$*ñ#eÝÆ6Möf-²§¾Þ-µ¤µ>žžCÙU¿3oHÄÇÌ~ŒÉ@oè@0Ó\Q†¸¤Ïƒ å82`8ù¿[LüÓ¯wš¿4Ìò¯‡¥J=‡˜-=¦5û£D§Œ˜Í<µ©-y¦€W žj±q´„ØQÓ¿)Æx D\¹¯#B>tòèµÆOw¿¤\Ýo Ítº-°¯öÛ5@«ã‹Ò„N‚`bÖ' àƒk¾{1Îñê½ÿ^ðXFߤ)¹ýA\im+Æ:"~²FrÛ™–° )ÕæÿϺµÚ`%ÍlHBkft™ÉnÏ4×0íNˉœÍ…÷ ,_À8nÒåÐ:Z:Õ¢¸ÕËÓK_ª¥é™¬Ù9´KX¯ÿºB¤âkU:"¾‡$âs)ɨx=Λd¦Kÿ7í¤€8Š@Ø1‡ÿÔ•ÏBøV[ðÐŒˆÑò}[.pŽáMÞpè™ÌûY¸rÏkt|,çýhÂU›£3mGe#%òÁë"?ÍÔpÉv¤sÿõ.Ã=Ö—‡zß;Š ÚlÀ ©¨½Â>ÝÚ0p9¥sßC"ªs¡çéPVu`ÞûM§y÷'ÕoÍýØÞ›ÓáÈûŒ‘SfÒ>Û)D&s}Ǿ5Ĉí:‘ —^œ‹ñ ¤6•ŽPøˆáº‰OüÀߺ”V†nôÛjÃ#”4!œmº0H?Ľ`s9·ð¢œêKÝúu.³ŽçzµÎ⪠„`vúår]-›…œM“±CA0ëðQ@ÃAn”ÀC8dÿ³F3ð^Ÿ«× ¹ÝxÅ Û$G,Õÿ°ãv¤Va4 êÏ8B2øcÚëâ{Ïnìñæý곿ÛD»y ùÂ;ѱC“zÖ®SØ›ŒJ÷0ü-˜†½~¡,¶©TMEá´¨SFá+ –‹/¥û<j5ÿ¸:Wd Ï‚óÉ‚[t›Ãþ’{¾âãæ~Z¼é²!Z¦-bŸz¢¯MØüc‚˜2Ê*‰Â‘é j¾”Ð Å9´6þ”ºÏ!„3²m½—Ä<˜ú˜Zu¯m>Ûp7ûyW,w_˜ÖÑK_uÛÉ3x%U0CëK”1mr£ÃÊê>6a÷–°õ93²”ÒuòÑØ–(ù o6÷ü5Èè ÆU/½±ëEwŠŽé©¾hÅŠç>Ÿÿ‡y zÓ‹'ÛûšСäJ ?Ñ„±²¶ÜSöÿQžÓ¾rí>þ˜ˆa©sÖiRK%˜âÛî€0aÉß‘+ö®N²2 œí%•¨Ëõ¶?Å>¨\u¶>Pjå‡{ùL€u©Vh,ÀZÙrõè}=ŠÌÓ];xžV2ùƒ\R^0çSz‘ 1Æ='‘ŒÏ(ðÀbÖŽg#úT\^ë-÷ÿBiÜS1vl ²¶ï ø%X~‚‹sMGM*ÂÐSI=è¸H ~,£C¹Ix¶xFå[–ÝN½~ø ƒäœT2‰Å7§üd_ó ž™¾‘ÆPÝkŠ/¥°ÈÜSp¦^%,¥aÄö0\Tæš´T–¤ vCÌ­̃Q~9L©Qq<¡Æ†‘+°d1¢5f²ýÖ{räœ`é_+Š|GÝܪX\¬Üï†y,z4r¿ƒ—Hã€Wëˆ×j¯¿ôN²Ú~s¢ÇQ£]‚ÐúK/86²’–åD[Æ7HçVÄiÊq9W¹Û| ¶ÎwÌxüP0ÍØú ÕA*Ê,”y´ëð\Â(P² š-` X­£;.]tGÑ¢"Û2’í ?¢‡Üw2[NŒ|1þF§¶%ë/;úGT¥<1îü/1ØYÝt/Õ²U4âÇg)9wÙ^½½:ìU‹1Ãõ¼¢aF 2þqIë¸8áŠÿúC..Ù¬!Ÿøa–bj–ê¢Ô,T46[±ö …Å')–Òp ²Îòà&ÆÓ;O è}r…¹¹)>ü–Ø¢“¾4ôi¹Éêm1# Ÿ!ŠÈZ©K’œhçñm”‚úâ4ë  sü¾Æœæ”{²¸VwŸï×þFzBWa¡†>KG•y½Æ¼­L7¼¯i,·´@ç°ÙNUD·=7ñ4tÁïFäpÿâõi =Óso®¥¹D 7Øæ<à 96aä’Œ`¾ŒÆäßëEõÖQ#P`ì% Ùûmœ£'N«‚íŠ&VØÔ„Ö LîÚ†ƒEIfÈ6úÊÊŒáßÎb,æ‘–¸ß%Ñ'”ì™SGÓ|„sˉŠ0GÊCi€2”û4™,ð“)JÌ6@‹Ov©Ôvö¥C¤©ôÏ”ƒyĤÞ~³ÆòœòîÏJ‘O€UfÝ\(•/`…æD|‡ÑâiÙ#Wëɱ&ã… ³ÙŒ¦ô $7×½U^>åOnÚ˜elêkpÌã†véÈ\ó©[N¿€|f¢õÖ”©ˆPT‚Ôx²S¡ñG_¤+ 5ÉÏF@K›?ÔþõzFçµy²_hŠæË߼ح“…Æ ›Ÿî¸ú£½fÍ‘©ëÜI){<5+8¤It^¹F ÔO§È•z7Ÿ|~§öf\˜ˆ}ñYo -îº<5”OVðb[ü 0ãMyË»š…Oé¥ð`K;ಖ[!™7]n<©ˆþ’{´ô¡Š)_I…±=£æ¶’, Kr¶C9RÒìæÉT¶ývÞgQoÁkʳ!¥LE¯Š"‰F.!ƒ ÌØÜÒA8‚;&åCäyÀîÜ-Ð ç%–%bï¥&H½ ÷–`])±RLNg™ÐÄ)`ÙJìåÆ`*@LÝ" £—1z2ØN%D9®¼T׊üN-ˆõ³  â7r/™ïçÍþ´$;?ð›>Ý@CIr'XAÄgÛ#G¬ YÊ¢ŠæwG¥EMÕã;‘[?IôÙ_ö¨ F™QÔˆÏójzs³wè"€ÆJ:ÌdO«Ô+8œ(oOIBøëzÜŒ~ ¼lês†U×h 3md޲c0,4Ì͇øMLÝÂ[ü[J/¼|óËGªþu¶;¾tØSK:‡—ÿiÀÅ“oò߇ƒ¸„¢Ö¥ Š{’uÂÆw›`‰,"4û†쥮îZ’iº†÷ ¯;'‘§¤«fT{ —?§ðd+ÊÅx†šÙ8¡b`¢lqP!0:n»{ò‡£1󫉰Û6­« çCåÝ«Â>T%1ª/퉓pÎRTÌY=׋©˜ÒAzf¨àÎÜ%òsRÇ-ëF_.Á‡ÔÚUKê^ç÷Ù`=øa®ìüO¥ šóÞ!rÌp=Àê³o©lóÁ#GdÇbaIà‹j@¼R}uUA ã*_/^_sqêäH<È.eP¦ž‰ŒODZ4ßö´™ ‘¬ Ømåew¶žÉï7„`øº£¾_ëK1°B<§)ü™ YN[!¸ù!*îÌORR$‹Ë æ¨?SLŸªÎò¢KMŸ·êšàø#Έ<1ÚnàItÃ9’ÃÀgz®±QË­œ*C]~Û`x‰&¹¥Ž©êph×v=z–d¤¦Ð3·•ZéKjÅëx³l›%VßW3_~ö*g€¬±ßzQöúYf¢?Õ]z¾sLu>7ËT U &›Œ?Íi즭hÅ>ÃÉeB÷œdœ, ë—Í"2Ù²Ñ35õCñ¥°)J¿a'ÜVo{©^¹£÷ÍŸq™8ŽC¹:«ŒRJƒ¾pj—Äö”ÆF¿”¨$¶nÖiÊÙ»…BɵJ(j“ˆóZjûÍS |†±Ë/γDÅÌÄ*GØôÑã©iùè#ZwUB5³Ê7T[Qq]i´Ûѽ¤N‹Kl±êűMÉ*}8°¿£ ŸeœÖ2n ‘e1ÒÜžÆÛuô«ÖåÝqì,>C&¾Yþ!'O ׉yQ"½ÑQæ22OÜÈ’ö÷3ñö¨…ÈZó—![tÀMp¦´!ÿª5A~Nž³œÞ¬ãZd²Š¹ôqÿ|$Âtμíòi?r»’ÿ«U;aià‹H9ãjäp6^1™p<¬ýá°'ëñ€´Û.?AmåÀמK šÚhš¿ƒ@‹ÙE¼©Öú«ØõsAi €Ïseສ_ö±WV@«€Ž)RëH}:,¼üi—½u+`l)>¨@'9âmÛ:ü!üf]‚&nEN³‰v†tCÓ·Õ7xš”Qí¨"‚“v)hiPÝ!WÙwqTÿ*…¢ç;gy<ÞP<Ø0+.„­¡k²í§KÛÛþã¡kúÉí§âÑî£öJ§#Ò `+ôÕÎlkºåmùB8c/5ô݈Á1Á0*ú6™ÉÓòg¢ 3Ê~³O|©ç9ÈWÉÄTï§B¢vmÎ<•œÜÓšMÀVÈ4=ž˜”''¥©’Ü äXoçï,élWpéÁ+Š(ê®è\bžã¶ZçTÑK¹×M¿AàÚ¸(T=¼à‡uÎ>ä*#T#qµ:|׃±'}Æ«-?Q¦q¹¹n¹Â¦*¼!6.àïf’ø ¼zL"P:gxËÓq1ûƒ«3˜$‰–§.¼rþªßCÑ´^{Ðu FNÎãŠã@3Tzv^PMxð #¿ÆLî3­Ëƒˆ5œ'Ç}ÕwÚ%PÔ–ËEë+`g= r­pÚ‰¤˜7A0ƒv ÆVz²Ÿ^AàeKj‡1.- ÔÐy³ÝœCê%×FÅÂ¥xØD†©ª{OyÀ¦/$’!_Ðåõu5ñó§o¥u ™Wrà 'ùÝ–~q³\Ðk’ªztá x2ahˆ ]7+åÊô ½÷ußÄ‚Drqhá¸,{Ñ;H"¨F¸.Ä6ÙTzH­°Båí±¶ÀöìÍ®~MÚï+p* <„–¶ì-™¼'ÿˆ6Hè5Ö=Œ‘Xw<°Ù.BeÏbeàþLÌ]‘H§ÌotíÙÞ«˜¬pbR‘>º´ƒ“Ÿþ‡Ë ©ò)ð¢çzÈl<“ÏåÏå7gïY¾–¢¹{SR¦ïh\||™0?lÁ7ªGq×öÀÏkPž×°ëÛÌí°¨¨¡Ê~¶wþ,JðpP’Ô_²¿*Љ¥,Os+_Pß Ð÷ëK 8~ ª\Zu•÷Œ!ƒ5Xìea±ó%óݧÞÿú7YFx6ƒp(†ið&ó5 ü/t}#Ø'Arv¥6“±7±N[ÏI»´§ã÷KÎìI5pm{ lAQD ˜ÚN<ªœüÉF¹õͲöâlÕuƒVº‚1OÂ_`ø3Ðt~ ß§,9i ë­ÒÓ/¯WÓò”åÛëÖipMsë‡À«"Ož¬å5Šƒ¥Ô©[èâªÎäxJ˾èCvøhŒ |Û;رgpà(û•è¸è5•×.'Db©ʡþÎ裬š‰S"ÐDÅ y¸Ðå¤ ­a©ÛIÑX—>%’0Þ‹ U\‘[!Üa@·geƒ)ÐÒ mÄÈQ‚âÖþóîâ›ú‘„ëÿ<Khd:Áƒ÷]tþe, šé‹¯¹£ªC¦‹GXó­ë»ÐC¤²ý~p/ /"‘útuHаT^¯ð×mæ>èМ°1.ûwä'`9~”Tûõã6}ÊÊS/¯pǹ³ÙB3²I'prÝ"Wë{FíœÒýzèJˆ›ý¸)F*DôîS«?­š[Î~J‘du KªÌõ*säͽ)Gã3mCPôØöf9K2ÑÞkçºÖŽ\ë¾aŸ³*ø}g¿t‘4o»“dÅŽ¾;à¥i¹ ®ÛË?ÇêýÍOMeÏ4øm ÈOÓñ×Öh?Š,ª/¡·‚åi:ô] G® %·úÉ7J`ð´ u"R% ðG†@ &¤>‚ -§)¢Æ ƒñQ>~D‹³6Œé¾G™&Á½ú R8Ëм¢ûÒKUç'Nô5¼Ÿdõj&ÇL¬Í:ÃÐG5ÑÀbei…˜Àr)4Å(òsÄ‘yl¾ù"„ª¦ÔJi÷BÝç÷«0ÑQH™s»ïM¡lÔŠÿv ù}¹œ'„BJì°åÞd6ÌTTâ©3kG±½þ­ÇÏ˵ýyÑ^ÔZñǰaœK+EGsÆè(ËMuØ¶ßø§™$†r/ÅÅÛ~×Ìcá¸û`å…X3óKçcÃ/ÌþÍ$á…rí©EØ v£Ê_«´6©¾ŸêBKMp¨:>`þÍ4¹ÚCýÞÌö p¬óoeÛ‰{½¿o+ì…Žd‡«÷ë?Æâ¼£ãêÎâŠCCŽÚn¹Iç²;Pç­pî÷9 $7¦ß0åíµ÷£G‡2ùkI*â·C`˜C·"ç ÷ìÇø!bgÚÿÈ éuGü",j"÷˜TÈ ›iI¢wÚM&Ä€2±¨M¨à<ÍêœLd@$t°+÷ ß%& &Ÿ¹#è£üùMT¼èÇZ :A£_gcQ™ýá‚"ÈúO2°ËöVåå¡ÏDåCé‚âY>0êi‚ÓA%5dP‘Q}?Ãü“äIJä–×›(46Þm™R¿3³IïMòÜ|lAÓžtmòîÚ‰˜GñZˆRÄGb ¢Pø±R¶Ð 8Æ>EëåçwU‹‰ÉSÑ|ö[Ñla&ß:”\ÍÅsyð'¿J¬Aq½WyaÑšoP(¹Èz*Ôå d×iÔÑ3AÚbK× Ôõ%6ÉN-µHN\SïQ!k»&ä,Ã,í|¿¯f L ™i²i¬wѼ1*M4wâ]òL¤»»j°óþŽ]ÃS, æÞ䛋éýù(<_Ùág|Éß;­ÔÜ6`¾_ý…ô5xâã™Ç¢³ºˆŒaï³ÆŸà(xb:ö3ÝuÏ¡;‹´ð[TàÁÅVÞÓÇ’®uF¾n£ý,xøY’d}Ë@¡à´T<öê#ShaT´§h¶åØoü›õª‰9¯NîŒb¥¬8!öŽTqi àšùžÇ’(ü`-ƒZ<ͬΩcgŽÊµa@«ËM ö½fïfß8ÓwÇöq=ÚT›Ì‚¤n['ž7‘+Ø3t%Âî-ô›«ñê~0»ÛÕIg÷}åc`ˆ†²¤¼×‹/›TR×(¶qζ@¿à0ÒID$•K²Rß·ÆÓ€¡¤¶Yh\“ýZa4I¬Ðœ}’ÈxLEp´ÿCFm[“SÎcƒh\~'ñtÚé½ÊŠÐtÔðYË,Q}-gÐãKWV\LØÃBø<òǰPõtÿ×[ÖÇÖ*˜ÈÀî¬Ño‡5“½®›³ö>ÁÚˆ½‘šg™¦÷/ð Dî ìjC³]ðF¡×ŽBeίØP¯öeê¹ ÖÒ÷SßâÐйNÈ L¼øS¨ кzÃakg<Ä¿,Þ1®!’®Lfêï~ P£ç½Ö»}˜ *¨Ã¹G†¾.3<;Cpí‚sÎÖ“2«†Ï‚Í<Ÿk“Ú°‚ËŠ‹ë3Ð"[ø öçyZl¸(¬( r'ŸF @×bÎ ™¬=o*ì@ªn$$‚Ôw³V03ÛeRXə޽ûÊI#¿[Ú= YÂFEÆHïÖ¤›tÑ@u€»>J¸i#÷‡pÁoÊ­ˆ š«TìjÐGLôN×~‘bëFÏ\zRO»)]ïi¨ât죔®ññ;b«™N ‡üDÖËü§Ã_Û=²ã¹¥¢JÕëÚ@ o{Ü¡ks@¬}‘í—érÃU ¿ §x•ÿ´e/ƒO8â磡¹^Opä=` 'ÿ‘aíyžÁ5æ¹KóÞVöåsjXö'×mÙ›·` !­>‡ü±«¯º¦=I}:­=üIþ9ÝúqÉÖ@x_Æì}¢Ð­Ÿ\Û~‡(E;ëÜž"Ü„êØ0–žj¢ÕPÍý¥Ûy )9rßî?UçZ® U·hç5*{¥z  Hµä˜Ñ,ÙÇ´­pqfºájÏ )„"×˜Ö ŠÏÂ7 6¸‚ª+¤´œ-Ì«öf}Qêaúú™§·ÅÀvËüÑWižú¦S|ÿ‚®"1àÛªšnü>×O«$Éd{zRy+Iù i“ÐOp›ì¦‰IÌ]¾÷£„: `0}úÁ©tÜqô ? ^ˆ3t¯ZžˆÏ|u`óí’QÖŸv 0Æ£ZÙ0ßHo_²þv‹ÿê,Òó ýÊrù‰Ìx•¡¥‡ÐÑšŒwBÉé)õõF !nJQÎ#Úu»«· ÄË+‡¾J¡ÓxÅÝ7¥/«†/=(6ÙÙ4’5xÍïíU?íktjTl°õÝ=õXÎê^¼ˆ=uºÆ~êØUŽØ‡·Ê‘w)©îÓνºgš1˜ÓôÞÞ6›qâ!®RÀn"JX©ÂËjM­…{ƺV:Ñ¿º º"Ùpó'¹T¤ÊWÙv¯óîÊæ2}Ã.’­Ã}Zëï5yôYŸPW¤èQÛ]‰ûÕ+þ\½êÓ¸•)';t«>}»5ÍE“òE¶xar‰Ã¥ÿ€;àñ¯Á9—×’qê åÞEa6Oà&~}c¿stuIPçùhpŸÚÛâ™+5;› GrB‰ô¹Ïו)Þ¬Lß¾¦âþÅt Ð8Êq½ù™Î”4>‰8ßDÍ# ùkýœ¢]Ψ0ÊYÉVFý.‘Q³…¶¶ÌÆÙ"þ(Ž-ò­M6õfÑ™YóšH–å“XK4¿`ÿr“l~œ¶ðæï ¡¶m¡"7ä'í÷{ü =×ý Þuy6“Òƒæå_$4&?¨a¼w}¦W°© É îïUF«$QÙí«ŠüUSû€–˜‹ÔÅ>É«=ž/Ùºæü‰îQ8VVn `f&ççsu|Øã)u¸Œy· ÇÌ`µAè뤈êTKðnÒ[®kËÀÔƒ.é iБ[|NaÁFªzóbÐÚ_4Œ¿çôíŠêž.ʃ‘)°G8b>-ö“ª¼uíä벆„ydö¾ãÊ•ñÕÛæjìµ$Þß²Q![«—;w~¬ÇrX'i·ëå§ÓŠ|ª\±&lÑ$&Ͷw^-!Z;‘]_“$j¥rL¥JBöSCíÊ‹Üã|ýy¤@ßÿJÌy«H±SD×­R&ÓN9ê›Â¾bbóS©ôuu—ê¤ÐBåTxl²º÷túçè±¹3U¸š©žÅ‡ræÚO+£‰Ñ¬Ž™úÛ!õ—|W€ZBM?1D ¬v§jk=P¼ X6þÞŒÅJA¹W¸Ùéyt"[ QTzì ÷›Ê9ÁUpÑf4‡Â<Ëpé.GÕæ9sGÓÇÝù:ks“tâÙCD'}õkÛC’ÈnïO?11ðÐTeww WÃD6êð‡" ‚R¼=Ä:>u×j˜òð¾&d/žP „ÄÊ5BÎ.Ÿf†€ÅVØîm]ì&}’Ä»>pˆÝvÕ¶ìt¯Âw÷ïÞѵIÊcÕZY¶ïÏßTyâñÛ^¶.Å"D%6Y%ߎP—á¶Tí¼TôgCÄ û Kñ̃G.ÄY}h|ù:­Â0pÄDÿA}<+÷® à"Íÿlíå¼ê"Zby÷|æË‹ØÇw:§ÝDÅöQögÜ„CŸû…‡6:jáÐÀ–_y„ ŽRËb%s|E·j:Õ[‚‡Ï‘!ÒÊ[-n™À´µÎàÒÉ¢Ø[Zá{¥Ww,‡l]D‘eÌ2“¬ÒzÀœôŠu¢QÐÖŽ^ª›xÀ ´¾Õ£´ƒh•ðŒ1wy•_œi¹_-?ì q)~Úà»!1Âì¹ÿ«jÖ¥¾™}¿ údv½¯ÙM|C@‚à¸IoF]BóÏP›Éé¥5$¨ê¼Lë`ÿ‘fD,9ó‰¨@ˆ**'k‚y’Ÿ½köŸ Êx¦uI){NÁϬҮ…ûCw¤q6Bðiâ]e6˜ÿO‚[¯¥í¥ù&ÕFlx7Ä»Ø.¯'R'k"ã÷a2iIø~n2yZìÈsåýMfÃà•e-˜×’MÒ°.Ò’°=õE2j5ºò ßÅJSŽ3¢RÓ z£Uü{EA/1gÿ‚3-²°\Wó®ÓåEA‹r„‡aaa3/û!ö¸xõ‹¤ÛÓ)ô,z ×eGŸF|“Ò Í‘uÆåüì8fÉ>$ƒÿ÷0ñKGX¸r;ýt\}Si¬|´ïTÜXžÓcuJtnÁ¨ï“’åz¹Ò¸2ìRá´¤ÕCß)ãÏ Ýt¼Lv½û×óq "mFaô­õ ×dê1Ý·vîÊ<"öØ*µ¢Æ›¥Ë—Û9¨ ˜0ö䨤]wE†€ÆÍ`ýæJî*p©jN$e ]¡ dΉÔG*ù¸=ûfíN ²jD÷oФIž”Qq©@eÁ—’oþ\áàÆØJ ¼ƒ&ëªõo>Gqg²XäWZÕZ® KÚÅk²„Ü¡‰T±P|ä«6ÖôóY¦È9™B—œ/VåTx/".u¬Š† ¹wS<ƒ²|Ijìüçšp:ø@Õµ2ü¸Ç«ÇR:˜v^!ÜsÝ™®bàxp)êÂÏOK9²­ÐŒß«ØŠ ¿¾Úwÿ¢l`5ÉÕòB•( ™ZtçþD`éqÂï† ï2JãK÷K¤ô (L¹Šì<ïn†<}[¹KÛ­¤›·ÞZâ½ì}2×Ò¾§¢+ŽûHnAêKý!µž;ˆCì—WC7õÇh÷v÷òïø÷CËÖ±ið šÎ4F†µm^½¦}f¸¶B-‹÷¥½=Hÿ½Ór>‰,ÉM„œñ&$½¾R/Mf0¥=·«n7œ8d€A«®!|Zc¯ófô§I­4P»­<Úw°µŽÖí$I!Š'È»¥ˆüï‹ãr=es÷¿hµ0ÿêó£e…–¥Æ•X›ÔT`—·\¾`ƤDÓîWsùÀGŽWŸdC%úºŽŸ\Šë¾¹6üµ±yëóNCÅúKY nn±+ž`fÂÿòè¯ùBOhŒs~ oå>*ß%l˜_F\'Y–r¥îêP\â[h€.ñ‘@Ý»ü¶zཚž¿a“Øð¾H´gñxYÐsx-,Òlƒ1V0&Œà)Ì£ŒgSåÈ}õr Ø‘ûYÝŒ‡ù[P™è’¡—°È’¶wºÂÈg÷õåª ëPgu!ÚÁƒc˜ÃŸ"ÜüǬýûkì Ô—ˆl}d¥»ÐŠ„J¯“,gbËÔ›GØNÞWe™}I/ÞþÑ6bÅwÖ.èÑ‘%.\aë%ûbDú¸PO+ž¤<™®ŒÈ7YÖø-êÖâîèåQ0­B.ÏÖßó©oTà¸ëí¡€tÔ)ÑžYœ%w N/âÖ™¬O Óa¦h¢˜p÷Oxçòö¯ê”í!:¶Sσ1Ë|‚Ö¯š%˜§±M„µFRFÉ¿Ïý˜€çs„à';\5'Òý [£±%ç„ìPÓ¡0é_•xV¦6˜Îª ½áœhû¡þ•¨û…0 ¨þuÝsfÞkæu vÛ’}íø¹ f¸m§7zÆõ¼»ÇzQ‚;d¾Í q %ל‹ýÔÞGRdƒbç+C?™¥\YQn®îŠþµ´%ÎÌ·jŠÒäDÕYš$–UD– Ñ X̦\‚zLšÏk±n€9?Z¥~ú2Öƒ¤kÛ2!$TWYB#ÕP[‘ã7s ü%ªEˆ ï,×J±–[ƒd-µº¬¼ù¹þ6 –Óº;…V ‹ˆ2 ³¨í€ê‚ú´é:Ô¯Æ'ȧ;öq2‡P"54°56Ÿù È—Ê£°M΄0Î{v8ß žÔæë­1èô¼s†œþ(ÊÄľÃ~ÍÔèäö j’hPl§ýd›rÀ&ê–•uâiYÝã4Üœ­vÈHŠ/š³?²€Ývjq$)— b"G;k-ÞUw·ƒ+¬¤ÃQ½­!(±If”g,Ó(Q½Ce_­åÐ.îâ\âøŒÐ5-mcJIMvav£sí*à?A¬qS‡íPÇO^¹eÄçóŽ£ÿ}Lú•½Uíñ’s]ªð¯{òï_ä »Ü®ÖŽT)²òò²äÁQ¬¿KøËþ™·UÝ܈fXçøXó¼L(ˆO8;/Ï·±W !c¨«Ê×[̽÷ÂQßÚaqÚå]1ݼ=™O(}ÊŽ¥ `¬-î–ö͈P¸&… si/×>ÞÜMGæ<ßKkBÍÍŠþÛÝQª Xx)/÷Œû]<ÿHadY'‡ f)Ô*Ú¼ßêH Þ_?Ã"mM…,ŒÈX,>꛿ [žÿš°éi´Ix|mh]ë®ÌcÅë-q!1K‘vK¼;>¼NÉŸªœù¯Á!¤¯0“DP9¬ò……È ÁÔÈ,g+W^4ïW_ _ùÚY£ÿRiSÂIfÒ }ëÌ¿!vNÖÙ䯧Гoôƒ:¶X´(_Fê.è!ÿ}ÅLÛ7¼f0ˆE©áìshel°@ðΉKs¡IÃ!Olµ´ËÐh¦R¶.eÈg+‰F6¤C«Ö×ÒÇ&µDâÝ&³w;zïÅáxW5t†[«G€§¸X™!Õ#áN\‹ÑUj"áÕkø3 ®ÝÜÙ…·ÌË Êu…Ü„ü´‘¦\5DÏ…"¼fAÚ…øóIäxÒ²•zk%îwЛÀ ‰4ÁÓ‚L>™øÀ v¢Séøa\•m'ƒ^ˆo¯»ŒHæJ'%éý¦LR™GöÜÙEyÅ‘úúAؘw-+ˆb}·Ñž¾v“=ü Ž"Ü\¦ÃãĆT“ ¬³þǃþ½úû€6ô`T^2ÐmÂëü¤±‰˜pTlôU´cß½§@àjÂ'S àògZú&ʦT[ÏB”fh£[`éìEC¸¡‹¢zÙ\¼Œ ÃL 6„߉o8Õ¦LŒõ3x²Úä–\•8u)NϘĮ©à4¼ÛÕÃízðÄuŠ™ù<(Á¬a–\z‰ªÐ€åñ~XÕ?l´¾fšŸ$ÈÒReŸÆ¼;â¾Ô÷~dϵ&b±„–XY5­¤=û–Þ°,#ŽA¼Î•¹CÈášZ…yBnóÊéíé‘õçê* Š=ÒÇ$Æþó÷†žD”QòÌO¡ÄžT ¿'‚76äö¤„q>/t'éÞV„'­°¼µ†~;í¯?ÕM˜é¨3¬øîUЀÑÿó=B3sìÑ;þü¢G£Új(3Õ÷rÝÉX¾é3tÔ…·1Hb<Ža]½Ëþ±wBUžÏLä…‚‚Ë”4½Á²Ëd#Á3Õ±D9º:—':õц1Ik©›nÂnéÀÖÇ´Kp²Çr(О©ƒ;Î0.v íP®Êp–òʨ„¹C8Î3h&c¸˜‚R" ·£Ì¥r˜o°8¼<Wñšèÿí¾9Õ³5äåTL,ÝvH13ë6¾° ê2pG¤AògïîDGº´‚¬‡Q“×Ý[¦ì=¼þGŸí’Yñ«V*|8JK’GôÏvm½lË7 ©½¿¤|7a˜Jøq¢e½ÃqNè¼ÁÙ ?è1ííC̲Pp5ºÿôHGTQìRýª—ÅÝõlæõñú|g'y¿¼Z‚"Û¡‚1Bô{·U²Ã¥l ˜J-ñè­Gν8•rû Z`81œ>œa®gš[9i=mýÈ?³æe# Ô IGâ73ó…?ÒǾ­‘´;hùP#Ñv½ s•eîù·~ã#…øŠ×à¥í»bÕŒëÒa‰\qœdl_P (gÐÐxÂf™¹Ï~Lò±Mçw# –um»Økø çÓñ„#0¨HeѤ) Åi?¶+á.rVŸ!—L/ ÁQs`£gÆ`cÀ+f‘/ÂnP}¢¢·J%€ÚX‚ÖhòpÛI´«FÌ4øâÏ[¾úåÏu;SÑ@—™µk„È3—%{5Ò¤#LaàX.µò´Åû]D) Ö}§B„t5r¯´ÂèåNȼ=þ5{Jû\º'Íd`ÎÈÿÌÈr®ù4ÈW<&jWäÒ#ªÆZè4ÊóÏ‹¶; ¡–ú{à…&ú$ D¼(ó¢_Ÿ½™´à©toŽÊèD˜_Pbm®$n¥ p"Ú„z˜»ã£"ï!P8Þ_sŒ°xÌýœÄ¤Üæý¬®…LyÚ§Ûû*»ËmRǨ”›o¸Ÿ éê­t43«ÒÈËqËÖ€&P: jP€6fì{{;Ú%>E˜!O½4íž– ¦Žî"Äè´:ƒ¢ë!’ 7gdŠvªžadÖo@†È d³˜j9ÉBE»àZ˜/)Øœþ[Ámå¿W €=‘s>dZ,—,`dNG¡¸±4HX¡_Ät4 xioÀ¦“Â_V/5þž’Š{¿ èÇév6zr©å½OA 58F½W½²ö0*a.DŒ½2ü§ø¼îú e”÷A2nÆæ¡A´@u)­# ívœùwí)éê@?ÿÛz ÚÓ|ž¥(õ«ã~ŒáÅ$\É\ãôLJp‰àt§Ûèc¹sZ=%ˆPöšOn¾ïÎRÆ`hH<)ÜPí“ábޏ/lØCO^†ì2§Â¤±,g#²ˆäð^Š„«mŽ~Ê’MâÄéÆ9âˆþáQ_¶Ùjòr•¼È½QEˆOÌï2ÇgQ›NG¸Ô z>œÛõ?Èc¼f4!á·¼ÞÜk‡p¥*·ú©f•¦ä™Pºy $¯ý„Åi="7´bÆ7d÷o1cE2Ý/†—Ïøu`ÈZ¡…éFÀ,H&Æô[‡ï!èÿ©ÛǦð´‹-Eaè–‰(¢ªx©˜kñ3ŽïÊaзLᤃJ?i™.|ï’.`|Ó»7Êese† ªs#ãÛ;‚‰Ò*S;÷mñ8þám,§$aqØÉ°‹ÏpT‚= Š#|G–z)eŽ–hÂl9‚ëþ*YØ0k&Ìa‘x`Ṏ€WøPˆn焨 ]åV”_…\¼¢Y²“³M·A‡‹C7Åx‡+M íš5èÕZÁbïÉnkH<òËK¿Ÿ„ 'î,>å9’#·?ùª$¯ùJ§ÓEã…ò«&ó#¼wÄ‚N†K÷YIhÏÖ&JÊðhò§¢âÓ9gÓûѶˆ`…‘[ýérøûšÑ m/Ä:ÛÿUm0¬‹oÁ"ÑýÝïè7íÙË<Ÿ·ØÊË#ˆó#ùùpxjrhË–5@VÊGõ½ž¨e'–Ö‰:_ô6òšduÜg’Î_«jïôÔµŽ´ût3oS*¿ZnäÄŠ¹ëxÁëÄI¤`X”ß“ ˜½gs£Ûí² ¾Ws› ÛN Ôg]ÊmTdÛ˜Û…þHëñ‘¾Sþäû–WŸ±pqA,vœ«Ì¶LÖÝxÙñ'•nc´~ ¨7k 1À‹¨ãýfò~**ÖºBð²îhl9©<,‰ºê dÜÂühÁ”×,ÇtÑceßÏ"Þ¾¢, ¤ÆFIu\J¶ffDòÁ×Ý$žK„©¤ÙVöÚ Wåæ’çˆm Í÷1·%ŸPÀ ½*áóàŸ^Ú¤Ì]ܧþ¼d¨Û_@e‰dp;ŸÐÂO!ðä›÷n\¶†5æ=T¥2iOûÛÍîÍþ–;†ÍBÔI‚45üÈÆÖÁlûÜ†Û Ó‹´Ä+ŠGCÔ±Ó/Zƒ-Fó˜Ì¸Þ±>q×À¿l(RìÙ-¬! ÆâdÒy”Y&ÞþÂI1 .¹Xña÷,7õ¢°òM`…M߃°,Ü®ÿÜý7ÿ(<øa¤Xÿ}¼`[ó8ƒÅÕÊIÉÈþ•ű° é ô½S¢B‚ØúII÷oñÙáþœÑÖS•ÒúðËf óÆ-›^Gº‡Ùe˜À´¿ƒÃëãðüßÇýàÊüéµ!(7Ò—qCNcôyÐ]w¦KHcá´¥EÜKŸ#þ;AÐ{†2§Ä÷ò«Šø~¡µk•œ\þ~zéÀ+oKµÒÓ-±¥¬º2êȇW9mj; ˜\Ó¢Yª–Q ÙZNåì7„–­»©Åñ› ߎB½wgâŒT>ëtÐÊdÓ^½þLo:1Š|c:ðƒjÙ×”9Â2LÕ{c¢Ü•΄à@dúg*v‰ä +r®ñ"²>Ý”¿×¤¶\ôHëEóÂýØçTf ËD.Õšætš[Fy¾?…MGbSWú2œÅ| TóŽm-OÕ)ÞIJm4Ì1ÙŒ ²t¨2¦QFü’O.Ñ·–×oºRÂÏÐ30gséRk?ðÊÏyP÷…£Uj‰†ëÉb-á@)±n4‡nĈLl­¾lb—°³u³4„!h”“£Ÿ&ãÕB]6böc¾3DZc;†+ãû<ÄÔSjŒ¡´å©î®çe/@©˜rOi„Œ·›¬¤s©¡gü˜š8Ôp¤p Áo¼Ø“iHÓ|E²?Mï×ËŽ¦5øC÷ˆ€H€øZÚŽ:Ë͆Ά” æLév wh*œÃ.““ºéÚ|^é–4 “ɤ,0öPá¶ß ¡[´ÖÂ?ÛÞ©gF“1T¾©Ë¤ ~­ÛÓ4dÛôÙ“ZZýJå Ó–u|%öŒ¼t«tpä9ðVp’0sªÛtm¸#VÙ>jáUŸ½Á ŒSa®–QQužï%³ÊvÝA¨íUËS&!7ÀOܬ³g­¿§w„òἆe/#ì¢a¸lP¾íqåÉ ÷tˆzÉ}xÖo›#¾’˜•eðPQ3ä’¿Š¯,Ó1£ –øÃ4:=9;¿—œÀo†,ãTþ)î!‰k•„v-hx²pÈ×Ìh{¡‡&„Û5«u±®d|’ó(˜Æ³kH E£îCØ!“2»‡ö÷Azx‚¿l¦h”î^cÜ¡}zsöæ‹ÃÊÅË-üB&Hhë‚DcùP4[Ÿþ&Z"Foy[‚±T}”N*êxŽÖ³ â_âò¾22 ò³@à ·ôEH¸vCò]`… ’¥@{Ój‘Шß|׿ÈÑ^ôD´zÅx¾Ú‚ÎÞÜõ’TÕÈèÊ„‡e̶Dôð×Îe€O† Ö²à’Øt$’°1à2j1‡Jp-nbÄñÓmÖíEÏVG®Ìgõþ¿ÉNÑÚµ˜—/µøf¶FÖSP€Åïe ê»ÿy¼E÷|ÞññÎo<¤íÉBëÕóRñÍŒ[rÝ›øÊõG µòðûS1]ùÔ˽nËiä$_ µýjÄ{ù··ÉJAxÝj \×!œ™NR]Íëàb@>d¢xü0”q.ô&öm.뽈´`Œ+ÞX%dQÍeÀx½ü•FiçSDˆ®É¥ûà8o™lÜÅ[r¡œ¼15ŸN¼c×!ˆ;ßÞö;RÑ®(Ötî,o[§ît ó— ƒ´ ^Ø[î„¶ÄQ/8¾° ZÕ2âA•‹íƒ* âÚ‹S æÜsÕßU¡¨e¬÷Àa U옶Æ$ïÉ rkàè± šƒÊKŠôú0Ü5äÔ&¡~²¼4±$†zõ3Þ]ý L`©9ª'^ÿüÖác×WžzÍŠ­£*+¦>o"'raøŠ þºôƒ%º;ù¹Å^À‡¢ ¬ÂŒô] N)m,G;k¯«¤YÖ°‚ïQ{<Äö€]NîzdâWÝ'fx¬`5=Ä7Is¬<&W\e¬[@žï~1”JŠáb„ÚÝÒ>7ÝÛÜppe]wàñ¬9ÀÉ*×¥3FyAZùX¦h§ª^‡jÑQ³§4dõÑû¿á’×t÷<U£qtäÀô74†šZº£MXÍò§ñ#Y¢ø5F2ãpZÓ·ÖãôÝJ+9r¼ˆ¦¯Ð†µCs,Aª씊†pÈ4h²!ê úš°^¥G,cÜ€H›Â/sÃ?pÒtó¨®)ûû€°M–ϸh«Dõá¾>ö™ÌVpC ¶ÞHΔ8ï‹÷g+Öž¶1:¼)Ñô*yÿ˜Ò^p_?¸YÑ©ZŸ£aºÌë±™{fHøénÜŠÅ5‘q)سv^ÐÏéj”„U¼,8 Î~^o¹7bêkñÂ$­"xF\eîÕ^Ø}¸Ò³æÍxêÂ=5n ú:<ztú· ã`,çƒ"uߘ `8,óUבZ±Ts½õÉÜó@4<®µyŸ\ý M©…u¦§¸_~œÁ7¯ÌnÔggß•h?HŸúÂß4UéîÈç†Tþ6?rò–Ä’ý:ü”v×Ð`]ÜVãw%A¬f’æôÊ»ÁuΖä*Ô C ¨úï÷|²÷2\ì6°<øg¶ö¸iWáf»­P{לּ÷U¤pp uÙwc/[›''KPWn4Žã™Ñ:(%ÜŽð»ªí, 2wdc—pÈPSw`Ê‹ú«S±®XP{~|¥2EjH*ÙÊ—£…Ðí'‚H[ŠDbŽåíÅÊȼEŸªA¿vh >IípÔÆ_`r±=æ¸X~šôR2ÎÕÄ}o3Œ°åR~p«>wM½°òÀ““ÙN´>•¤&¼lÁÙò¿Mà¾=WYXÅ+¯¬ø×¬ ê#Ÿ(Š£f#ι©éh;Ù}þ&ÄÏRn²Fl¿A·ë0£·Y˜‘Lm'v‹iÎ@ú¾yèqοöb¥®8Ž’*_;¡ Wò`U<þ4BX«„PO XÓ *†vGÿuë8Ð_CSR_tRlöú9'^Öä,¨C!ÌÇ}Ž¡;¾vì.â§rašDÁæ[µs˜ÁÒ8`ä#œÁgNW~ý óÊ_p±lwt¬ó'ã Ü pSû`@Ë­æÌ–^³› ðW¶€TBÞØ,îÖ€mMJ%´±?Œš›Çü‡ùþc»ä¡Ó⢎¨¦Å ‰ÝÊàFÃ~EÏê¦}1u·¢˜#S½‡¨¿MŸ=ãò#LÛŒV‡á¬Â_1¶®û1¢âWs9´ø¹ßM·8â'ü-‡{_½<èD¿‚èÁR:üXo­!ø/æš&´p÷ÍíÎ:!ÞñçˆõC+`’6¥'š¾pïÑ‚C¹ ç }V!9öîè°(Ãàº!tÖlhƒK›œE`ÐÙM6\ Žß°"ÛÁ¿'½À|üW°’ZZ›Ì“‰L<òd”UCò†KT û®C¯§®TŠÀÇxBg›‡ã]D~ ÏlÊý=û‹0HÓ¿ãŸÏ¢±®ÊúȌр9©À\´ä„0Føº†1/ÐsÈIŠltf»èìè^&°’kPqõ–üWÊÜJõŽø£Råïcëb2LÓ™nd̳@ütŸWé‘F¾ðz¦ {–'=ÿ ˜Ñ%mðZ?ýFø;¥¤ÌÞðÑàÆ#|íï6´õ1Òk›Sõb¢Ù?~©<çt[^Æ+†ç! q—^“û1:Ää³xܳ¦m\‡rwÂŒâµÇ¥»ÊŒ¤Û,“?TÔá»ôêH.yÏÇæFâd&6/-–ÇѵÅ4hq?gêÃæÛ¿µm‘NLÒý¬y¶‹øpØnÆSPlº­ñ@„-5ðá+·•šJ÷ºà·9ñ€nÕz“– ¤Ç¥¡>Vø©pð(clR¢³Ü-»§rríÀ4¾s" 9%GóQE‚*ïžð.Áúâ 5ÍnË΋c¸N÷¶zÔ3›#gEpÂ! ^zià¸BÅŽ“z$¹þ²øj] ý[;Ú$ ›ù®Oºãk6œkx)Z û'âöº6¿V´^’=(;'ÌYcÜÛã T‚ë1š½"IrðÍ2M4ùî`ëSé@›è’b°´ŽÕb‰¬×H™GkdŽ9Ham ÌP‰ÀËh¹áùŽ—Œ`ÛÔ‡sn:º³ªœõUÔoO~Ž]äqÊ ¦f e.xÚ(‰GO (^@š·Í4íÍ@xE)Ü4l«*óß4$â«F;ÁLZQ m¦'ÊÝÁ¡W¹´Í…,ŽàÛ€1/'G@Ú§BÚk º _²)XÚg~ï‘Ö­/òâTíëWŠ¢Àèx~¾8ˆLµµ7}t¶ô@%®©ñÇîeÚà·É~™ÐY!í4IºP|ø‰äÔˆu½ÃŠ) "ÌvÈ ‘u¼€Ôóúí⨟;âM—¥¼=‡ë<=¼®¾´ãh@¸zÑ50²½sû0ÝOÈzûão38ë^DSy¦öi‚º0“È`sÓ*´\¥x–ˆ°U¾ü+ ø›Â1M9€˜ü‚ÃXûìÂ\BªC¾[©ÓóáFÒLÞÇ$d‚ƒ“\(½KŸÏÉrÓâes,"쨛`›åN’ø'àöD] .¶ÒeÃ}‰¯®e$vjÄÕUM6§ ÛTËWÛ‹®ñ¶³®Ü-UŠëÜ¿žKã6(–œˆ¤­î¥ó?Íh\N¯ –uªEáX×^}D?·ˆ>Þ\åñ¡nÕ]Wz–kQ¤Ÿ~çS'"âyÐ “Žú=—Ýjaœ§qⱃ"¯X:j1¥Djdðb‘­™‰$ì¤X/ýz)ôZí·(I“Øç#–Ö>¨Ì߇ÜðxƃÜôb?Ò,'^|VT@FÎ53Zho8OT‰eÔCãõà/pM¥ø†g¿¯Z# ÇÂή)W•¼éyºãIá|Îhmjý&Ñ ¶ºµž¾} î£õÒ”ò¡ nï›~ÑÁ¼ÚX‘‘E¤T̺w‘¦€1d7¶J6´4ÿ2éØóÙÿIw‹dR‡”E õð::¾eS ¤×xøÑ[8ì£E?|Ü8„W½@Ì ÙzZÏP¯=]ua§¡ä&uÈ}M­£2×§ž‹Ú}TIޏ¿/s¸¼û‘fÚ²„ö‘Á/-pß–P±®jÚ2na†ÐÙhJe¼…´Gœ%íÕ¾«ºƒS 8Eê`ÝÉ7;0‰Õèz6Ô·ˆÔI©×îï5ôV.r»ÿ«ÀöNh·Ø4ܺãö)*_‚u\½í£šËÝ-QÃd·’§¶O¢U#°§pË–ìêܼ2š\r™38rR†´ƒ0 ù‰à‰7À(÷{?ÔÐ::–:}÷ÜÜ'ÐÉ4å¦y÷a‘³ö$ /.·%ö²‹äý‚L M™×J$.Ã;/þ Öèíš\:)®ïzšx’"…˶ò*5Uß¼þ¢DdMÁ\Lâ!ý«ÏŠtèY»¡ÜAe÷Ì~H¼}´ÛøBHŠ9„UôÏfjì¼%ßõçæB<´™ÚíÛS¬ñ±¦ñ4ZT­›Ç[ƒ©0’I(+DQ®Y<*û†¤¶N9‘&„¨®,ÝÌÛk¯›ŒÄ÷xEå0Jù]šîË_©æPG·ñá…µœqåaM¥3™Æ´µ¿ï¥ÞÄ·&Û1õƒœ ~ì%¢NʯxJ®ûMDœ5rW8²6žC#Yu2ééѹž…©/©âZzlÊÏ5bK'«ŽÔ=¯8f {þ’øµü ªh?þ[Ér3•¤Y·¼\2‡ÞªRÔçi Q¨÷V—÷†ó6„@2' M*7>.“1£Çj­C"H„ÝX;›áÓçßǃ^+Ž>T¥É$kç­çæÿ j/=IÄIŒ¢Ý°{½fâÝYzú1~…Œ¥Äúöqê¨ÈåõŸjWÖ­—üj†å2›AñÓ—uþiGNy衱j²r{¶¦Œb,?]€Ã¼¾{X>ßzuCm 3ª¼¾a6Áä0FCNl”Vn¢½RRnÆ8ß„€»ô”×ío· zU¤’}èo6ÓÖÖžœäË®6SìM!YX؆—Xês 82ã®Ï²¾®?VbµÄÚR PŸjÈëx#DjÄÿœ¸ ƒ H_ÀÔOù™Ò‘ũϜ§a &äÙ'·âÒá+?lÉõ6„í3Ç#N%‹¼ŸfnN *¶#¿–¦~‹s&£Iü×öš¿] ¾áF‹noÒÍóðð?â3´±a«¯zÌ‘¬ôEQ³Šíç–È£KCœ~{×dPr¢ê¾¦\ÔÃìÉKÍõ$…ÙjBz;X*†ðÎÏ1»ž¬è*Zm…"À×FZÒˆµ1©îæµ^—øµâ6l÷Ê¥„ã(<’ü(LÖd¯-…ôþ)™Öû¨xÀ7·fšoå˜îgTB4(Ñ…Šf„,–2Íí¿Ý—m- Ùæè}è¾ÛHì÷<±•‚ù-™ô gñŸ‚·V.mþi¡6¡ øò_®ßAžîGàjàÀ/uÊöjôÀʽT jaúIYaÂm0³ÏÏ—fìÒ² ‚ƒæÍ{·Gv9Á…O˜FÓ¨­Óuk¹©»›®æŸÉE{ )£`bËØèÐ$9ÍRƒ½ŸøÞ¾›Ú4HJ^ÁâÔê7ªL›]YŠ Jn§u¶%×®qµJ!©¾*`!‚ÄÉÊl äVViD$*[‚ó—ëdy“7Ïñ“sÝÒAÔ4’…ã;¶(— )ÓX¾Ö€ï íG1·¸¨D„º´. “l–:•[ת¯;{¨l ¯8ç]d‰¥Ësš†Ì¿=O÷\4GÀô…Ö£¿xÑ!Ð(†.’¿Ø Qn­¾õÃ#ûNçh³MèèŠÞY@†¢Š .ÿü%8U‘õV‡Uq®‘ÚœÑ×aHr³ñ_¼¼òMOoùí“‘„NÂå~jg0ͦ³"ˆ”æ€&G|YX‡ëBÝ6Qç ÛŅèÏé„SêZÄ#SGr^6¾ ÏoëÀÿ<䛂ÕÂEˆ\ov-ÄenµÙÉX›‘¿Ú ”q…U¨ŸB‘Àx«”­¶C€ÑØGØûç+Ò„Ç%ËÙJóAÆÊ2a±„ŸtüŸæð…%žƒ•‹†ã|ľ~×6éÞˆØs òþêIáCùô´&\×”ßJt®xAp×Íf2(5 yÏH+3½Ó‚Õ3iÆ&¿V>—ÕÛu*ÌèõY¯ç£ñ!çö{“û?WÌ)—¯ÞéùøàÖ‚äö47™™"9ª“ «J=ÊŸåõ |$; )ÆrÚSæà}Žúî]ý~¤È™õ¢°«‡°SÏ+T@r Wƒ‡aŸï@"ˆ*‘î¦Rž>Ñj:ꔄ=EnŸi²±¹šhŒ©„FÞZy®[¾€«ÚCX@¿mN¿Oiáhž|imÎQÏsd¼uSv†žY˯+Z`¥LÔõf½KƒŠ”ž5Q&âob*2 ™l7‹]É;$ÿ~%³²¸ªÉ;ãßA†½¹e"νòŽä¸æ==ÿü8m@CCv¶F–§!Ú`¯Ô'I€E›™áNóŸjp’ ¸âÄ¡ÁŠ\L:"zUHFϰ øù¾]×(…Ý\œŽÁ6P¼Õ‘ƒ¤{•þ°"/§•ÁU7“ Õ‡zUÔó¥ÅÌž?hþWhôlÆ›—_2{¿|ÃiðœTÈ¿ÕÅÈ\1ò-týyý2œT>ð:8?î:ézÙÀÕK­ûŸ{ê¯Y&é…tËôÎ1ÑÁ¶\ƒ×…]ozÝ5˜4‚gW¦_fnhO2Åk´ëáFh …Cs¥„«$Ó~³Ýôbg´ ~ ÛþÞ³=È"™º®\¹ðïÓôµ¸(*ý :Ýy;ãJ›d'ÿ—Ø4õ¿Í \@óê‰Éh~æ´Xͣ隌Ÿ;¾âã!~D=¿K|ow=ÿ¡Å`¤Q»é³æh æ EÖŠ•F7c³H¡PÞó‚ï„mHíöÀ›R°ûª }¤°TŸ޳I*êN"\—Ó`)¾ÉÂ6;):EÔPÎrX ?溮›x|@•©^ôLçg¡ M¦¦Hv•ç éÁøwÎDä'§Í™Òþ )özžD¬P™²§¿üra¯X=¦ÑÞ7öà¹îE0Ô²÷zðø‹ÌØÏÚ»‹Upñy S0i¢ëæôßÐĦ9ë¦Aå­qáÕ‹Xf¥žO‘úÇiâ·¢¤5ös*Šo D†ÏêkÄȆçsÆ…·`@m‚̯ãmT“œár¯ã‡Ù‰“†èÈcrãÖFdüKóYÙ2¯L?—j >4}oCtõ¸«ÌnÓŸ=ÓñÌèü%ÔÿS‘H Gºç„í9ÙXøþÏdâ`éèlòÙÔýfqTeYá Ÿ¬žqìõüDÚ]#|x—ïÒ!¼@XhgLP²™¢©ío{'z¸U£ýîiÖrtF[ý¨ ×y? yfîïŒ%í6讓 ˜¢¡“Så|GîÂÈEçRò±Ùµwx )ë}‡×1 °Yþ­].¥É3ÃÐúZïõ; å–‚&ËÄTøãÞ­2*°8Ú‡:Gk?©œXЈ¥ó¼ÁRçü ãè½Á¤þ'ÈÈæ-t<À¨’1‹­ò ”àÌÀÞx$ûJ² €b¿ëØ @€¿`À N˜o?ªvè5¶ÇEµ…: ¹BÊÙ“l¡ýÇ)ýÝØ}šä°&;˜É"“EdË–þ‰ùÌBl8ÀémÝ]¼ï,œ3Túrí#aÑb¬AQõÝdClð)’ë 5«[< l.ˆËñÞ—RÆÃC‹¼8CªÑ›t0Ò"ÿw:ä̪ø»pé}-™˜L–4*Ÿ2&Wg[,pî´¦Œ.×UQüFLâ‹ý98Ýz>kÆŽÚ3T6 /ü…+º²”³¼«‰~ù¾íD¶8†w¨EŽí]•S¨æ`}KÐøÛR½óõBr¼ùÄEàÆç>ö\˜hÝÏÝ7¡›¤¶I¿CÌ_ý²nL¤4¨•¹›|Šë1Ë*@6Ôò~b¥gð…ZÞîQ\™ dº]Z¯¸}̇xÄØºsdyNôþ`g`­À:ûšHÕ ¡´øIdÖ#ÙÒÜ:èQÕ!òÔYDf¬Tý;t­tó,Ï;9ê­-¤é"åô¦¥& T¥~ëĭ,¾”×Í=¸¬Õ£gx1~[ø-yëÎ>Ë ø{ÒåBy UÈY±äæ|`ü,B“ ƹæ Ë õå6M’„é‰î^=^)jp<\Ú®} }%6Jâb´¸–fÍ–0Kpd…Κ¯÷ç—5TJKEfq*Èi̵Š9µKWSQÑ1ºV®9î@º Û'd^‚mš=uÓ `äfG•åè—{º4%"{ÝØäjõFi>ÅØËË®m~û¤IÆõ¬†!3ÃÛÇþ ó!­wËmÛwh»$L ¿OÊt$O=Õ)ð¦"ª€o@êk†yk‰ÑL@•tÚ }¸CkŠâEALV.m^Y3[ Äw¹’§ã½\šiû†ù`Y%Û5‰ò^¸!2I},dÃ$^ÏdüÑ*œfAZ)ïÒv$7_à]µÕ ê:*ª9©÷õ•éÎ<~æþ…;Qº¯R×<&)´õ¦£´Œ0r¼åÌGŒ¤ÃoRËü» £á}K={ô¬“xµËÚ¿²5ܵÄÁ Ñ©?Åú(¶1pm SX¸è¿ÜÌ#ö´žûOak­ú³£2/tGÓx÷ÍFyÝk)¦Ä®KϾ-Óv÷Ìÿ"0nʼ8 ˆ3ƒàØÏ€óÄÈ«OBì!‹“ˆl ÝbçÓaË£”Œd+4â$T­"Ï>ËÚíf¯‰J‹)³áV“)ôè°ñt¥/ÛSŠš¯Ûü™wqýOðJ2Üñ®ÕhçQ3Nõ"ÐgݬÓÌ«¸rŠ‘Ï:š¯ ×ñf[o7„Gp,;)Û"ºÝérþö•j~¿½…¹tj HáѪmvr ËP¶RÛ¢ði7#~ƒ …;·¸‘ošØÔܲîJ÷•1H Œÿ¹E^ ?€ûß~šIš¬ÊÿÁf}“BpQ¿¿¡;ÄÛÊ%”âbæÈr¬ž®§8cŠËþ±"M'X묺¨fSf¤ÒbEÔsˆê%mææÜ÷ó—dš»©à[óЙí&TVå ‹© ¸&X"sSI °z–º‘Ù;ß`UØ;ë£2@g™GÊ@¾ú£´š†Ñ›º ˆ|11@wêuÐf‹À'3wV*ý“éú­„HÒL@“ 0ÈG»Öl†ì"Ém„ï¿}·ý’330© ‹ªßþ§tŒ’Sqgxb)ÏwLôøžÈvW²¡h¾ ιÌmOÅü‘bk§Œ¾8Μ£¹;© ‰=™k9â`Ë`Á÷‹=ãÙšõ÷8Ö’måræLîþœ@{÷ž˜+k²©‘sÆ7Z"ÆýûLKâD â¤j¨s•.,afêêjòÔˆH-±¤©*È7j‡óMî{Ñè¤õ0ÿ™"zêÐ"¥) ¨ì+¸™?’vcäöä¼÷Åû¹ð0k6k´Ã¿ò žÍ•]íPŒ»FMÙmÛ Ò*âŽêêT2 p¸`®Ðì"3[’ñ¶ +wâú1uj”ï@1X–&ªX …hC¥Y7Ý-Øþ~G*lü¶´ý†¢í aN½,@È®õå86f™¦ ¨³1 “E•™¹Œ3ÆbßÐÁ3z©(þíÎFõÜäabÁ–2‚ Õ`ÏÿTö?$¨¢U·Žü¯ÿFÊJ7ùq¨y“µr%#-7ý]NÏÃ)ÿã6# ‘ÉFϻڬºhIU¬/_®!}ÁÜiMêmžÛ‰à àñìÛd«(ZÛ´/ôíZ‡‚åJta9¾¼X’µ"™´2 ;£L¿¦:÷SUXÒˆ‹/˜ÕAÝCÜ'Kç:E¬¥‘ðÀÿû‹'§ 'ñFáI, }“ºHD‘1Úu.¬ó!XíMö¢}ŽŽÁv­Úá[ÕWºÆhO.ÉÇ&§<‰³"É0‚ͪŸž|eÈeö“UäÝ`tY°£Gj¡Åßä´‚ƒp“æ8ÒD×¿ä¸«Ææ)^:¬E›ðÑùˆà:N6.ªRÉâŒuÿ¾×°†bÔΪ\ÛoÉ—ž5³Å”žµ¶ùËgaPUU5ë3s{¬G4ËÇ^– š¼NUi­|´~wH X$Ój<ç×7%Üûñ.vÆìLhÍD63üC‘„cÉ£O‡_’g„Õ¬$Xž‰1Kî+—>çÝ®æ»ÕËm4ÒEÖ¥fRW@M%e€Æ•åy»”ó~¦û½–P—ÒËT[RˆßéÛèD“‚Ì%ww‹4ÌÈ¥Ðû¼ÚÂyˆÀ³ùÒ ÜrT\Ö©€•‡¬BKîr,Þª y}Þ>$]“¤oòÎ`e¾lè×aÑu«ÇXÄ£ȟ¬  >ãÅž79WôÜ•ž=‘ʯ^vœ§"A*! Ùû I6OHfxQ…ž(üY+0ãy˜›PºëÝýûRIDEƒŒ­ÏTz/ôç°”]-Wè?9 ûEZG‰bÌ„Þ)–I¼©y™®°\hR]êQ$@]¨.à‰ILÝaÚ¡ÚùbCO†É8Ñp˜µ+´†õ@—6BÑwÿ|åŸ:û M3âæ«X ½ˆ»ÑËT¥:~ XÆ0UнbŠZßûÚÆÏûyt>ÓÈzsþ…uJ‹ÉÇWîn·~61îÈs²ÛõÑô}ÏT.uäÉLm² à3Ñìçéc4o!âùoë­1!u¼N_²#Lu)ŸâF ¥OBÜ¡å A¥ÓÒ[e“×w z묨^J7ºÙÞÜö­ævHY'ÿa­´:Bsš›ï ^ÇŽ½ø ŸþƒûúΫóè_]ô»{HH¹Às–Ÿ—çÆ :pSþñíÃÙ'‡ÎôÏ·#ù7lY¬}:ˆ· £æ_ïѲÚ:.NnøG÷RïjŠ óà™-©/T÷yÄ$ÊŒº¤*ª¦vJÊ‚i¸?(÷€¯wÃ1ðŸŽ}³å°J†æ <žÞ+œˆèojHLƒÀfÊ W¿ø+lÙ˜„!2AÊ\ânXuƒ+áûÔÉo{‹Yq)atlX8Ç|sn‡¤øW›Çö yL½ÏÎk޼–k gE.™¹]ó»Â·pxW)kÆ †]\¤ô`x˜ttž »ÿŽZª%*§²\åÒd†d纀Ͻ/ƒÕâxõ6ðWX€¼få^àf¹Ž%:o«cây¯‚§Ë mª$÷8—åÚÝï Žm3ï$ŠãÜ_3Ë{ŬkoJ0}§ó¬r§÷qãC:±¾Î©jOX0fÎþ£æêªfXeuo„)(tŠÄ~Ú ABÂË-—fЍ[-U`5ÙÏ~6§ó=«£T?\ïu_ËÁŒ@Ť=¾c¿0¯7¥“ÖO™i5¥.cÝÌ F£ârJFHjuB´L—âÖŸ—ªMïÝ­2龎઀´µhFD朠æÜÕ&ñ¸k<7¿—ŽvŠë<¨4 úó$òµlžÄ”jg €ŠÂ-Ex*楗 M]^åYù–Zô¸ü»ÕÈEøIbÝMLÕIÏ9ì·2x·¿‚t"è^Ò1€%ÍC¬ÆCâË[kj‡4 3ysä Q:Ï´èQ 7“þ¾…–,ˆPøœÓ‹|˜—æ/Ý ÏÇÑLšƒÀAGj÷Kè?WÃUB`»/0ÃÃAFÒyèX‘]¨§²7ÓÙ‰´{v]ð6$ŒÔTý äÀ‡äÁjÔ·Óì DèÐÜ÷Õ½›Aä{sZH´³–‰–ü­6¹ahÛt¦ÌªO&ƒf†¼t©MàKô*?“Z[¹ƒÆÐ ÚÄ׈Io …û\2|^Q]‘XöåZž§oÅG t.=äü&«AÔ³¡Y‹MÜúê6óô/®YJHe·Mq]ÖõZÒ¸em;ܦ¥<º˜¨¡ºb˜W,eüa³G”è'B-§(I§ŽuïØüÚÏ¢Õ"‡N€ÝéöÀs~‘¤‚I,°UŸ…âÎ&ÖfŽ–‡YxÀƃNy¬ó>U¶Cçh$ú‰!ŸÂ3RÕŠOFÃÔè<_«œ« Ü(J±± ÜÖòW››~énÝê#yó0}5oâ"È6;Á¨Vo$N;€Ôn(±ñ åe¤ØŽi“Ã*€wVÀ¢zøO©”ÇsÓÁ“ç4Ž; Ì^Ò¶Œ.í·Ü-¦à<)ðy§h0ÜYzŠq¸f¹Z´€ø´Am½ãrÖÓÌÆÎÙ¾O?.Ú`Õg¤ÙYý:"± ÅòúDjf¥ßMm„íîGdC ZÉAt¯±Ì–£’îŸ.ž¢šñœtÙÆá pðüí çÞc Êï%}Ó°…’¸OpæÍPšR ®çM-™8~#}¬Ò쌢'™ƒ+Ks×Ä$»Ô¯(_Œãáa y*ø·uÁ ØÔ\^;*ÝžX()š@?ESÞúнšÈ£Àø\qs­èZ{zá)Î߉ÒhF-cmÁêÔbSâý q†É1²ÈOgŸT5¢Îƒt—² ´P§›ùù¶pAfØý_åy÷ÍÀñ5êcu‘i¡X ÛRÝNøº›ZBÒ;/ƪ­cýŠ{‚Š–'ÑUš™«wüa~\p92dý··aÇÞvi )?xvÂæ‘m‚!z¯H#=]8 C'úœ ž”†—rh‹ñ -›˜f0QWáyÏŸ³™Q*Y“4¢+G'H“™å2³Z A¯s×£ZLáaLl™Ž»Â˜„¬õÌáS,èZ*<@F´‰])H4zNÙ\bò]—1'¥&²˜Û»PEîà·Ü8—\]c-›rty8¶.®¤Yµa[«‰_¯¶Ÿ™ÐwqŸ©‹JE<0/ü1yG]I ô{*Um¡×QÍÐëƒ* Ä|ÄŸéÊØÕÆ­¨/”Å©M¯µ®i³ŒšoÇÿÀ •02vøÁðZy+Á£e ðˆîd›Ñöí>ป:Åè(Ëd€›ØCæC“9à —B ÷¼»˜.e͔ϳ©¡f{þb+¡„Kð j’¥cÊ=U‡SÇë=,F[æòƒÏ†žºh‡ºî+­Tx—Ì’c´âJ¢tGëOžî̃ˆ³eæ‹{ºZøï³] ê A×,¤çò1jU”žN1㊙äÙǬ ÆAþ6áòYˆât×XÑñš•z«©ð}¥¤æ“XõÖÙÌr'uíûBA_~ï:C¬ÀõÃFR˜Ü»äø&ï ==QAØy‘Uû—4çuYÑ‚}‘g…Ñ'ÚDÄFq齿}e¡ªÛåì>|ý–H»†Vþ˜2ÑÅ¥´åŸ•y@49rs/¢Ù0µècYácGc¹Çhá^º0Æ),tO¯A÷Z a|»½,^CM `¨ÆìÙk:&£Ú ÀËßÅš<Òm„‹²æjL#dÇpmýªµ‚ â4Ÿ÷wrGóéÃíòþýC³Qmˆ\ÐJÖĪXg˜›×/MYóÐ!8›ý9œ[óþöÀqÍrVì›LœCÃ<”£l²L »”ø­YÝc©=yS,‘"¥ýjÿ§‹"ŽVÅuøÜÿg”?W]†-Mv/B•˜#8Ño×A‡âÓ–í¿ÈÞAœé_ C·YÌlbÓÞÄ—š§/æ Ìý‚:ïò­cO‰s ¹ ‰Z¾ ORGe.̸ÙÊtp­ZàŸàN8%BÅÚ`»)V‹Fzüö˰HŽŠ+ŸÔG®³NÜbÆ‚ô'€+4A®¬œ!Hï²ëx4Ñ@‡Lë$°¡*:_×›æ'[Çß}v´p2}¥éˆÃ :Þâ­°+Ã;åµV|Ûýàß½C×6[Eà†s4ÖÎZ¿žÒ¤Ô,&^ƒ18 ulæy²ÐµK½h){šÊ(w¹Ãø»à:{ó˜&eP½vHâKÙÐD u]'‚š‡«m¦Ú*ø‡ßuÒ”_8•âl_;í±ëÚ2y¦²”ñf ¹xÈùUbüëñqšìH]S;˜\½»e¢È‘™ncaH‚H<ÿ@1œM哊E‘ÍöÃHÁß/k+EøÜlXþ.Çm¸é)©òϽ— ˆ°Ÿ¶Þ,tNèq³6âÏ–p)â|àßîÖ]@÷ÌO—ÇG×äó¿Rrz¢xþÁ>¹Ù:šŸ­‚ûûÔ:Uh§¡Ä÷‡Lßü˜~]îQÌL.ÉU>Šm+žF;–¯çïåKT~–¹”_ù—Ðî_F/hz&‰=Ô¢æýÁjIßgº "†q*8ááŽj·›3\Õ6:Sj’üh¶ë¦%à:ò×”lÁ|Êa+Ô£/+%£Œn“uc…­p& ç%Ñx²8a Ë¥º¦úf užðÍsbÙ`eŒEÂOø|×aEm!ˆ‹~ƒ–à×`ˆYò“Vª´Ä¬›Ä&A.vœ»j—‰üá_«ºÅ.Phx1ÐPèV¥ª^sŽJÉ]Ž.?üAø% }öõ´Qœ—áÛ3<À(ÉB2çÍñí<4( ø¼™4¡Ù]ªŸ¢ÎÂ|±2U4»ë«֧Šný!°ÏcqD~ÆW„§[R ó€(¡ŒMmo”½ï=¸¿«–µµ´r‘H»Œã¯¡¬ˆ¦DtøÍk`À|Ãtô4;YêWlŒèU¡Õ_ÞÆá'òæüÆç–ÂÓüœµ‰!G´¦C&@ßPˆ•–„‰ã«mÍ^Û3%—NrKì܇HªÚY ›/?o1OH©è9Õ4àæyÖ V_‚º&»¹5°ÇîE)>x»Ø 'úÑåµÕ´‘])ꋾîÜþÔ‰çÞ7•5I«[W'â:nØë.ÁäÅnèu™Ú¸ Áññ\±ò¬ÿ—«‘§iDÜpèÉ"UÐF;k\­59•<|ôBVJhPKà¹-t›µöÍãc8¨°Utyý·¢¨U¾Q‚ ÒåÐøûŸÐv9!÷™GˆÝlÖý@ôb‘)ÒŽ`EöþÍy_pù í”'C.2 §I NÀ)HÔ$8¨$ªXÒ°v*à--i;¸¡×L'®˜þŒ.êÛ—JX¹k£ Ý€ L*rð’1Šå“:ì]ΰÅ>,q`¸ÛÈ\"‹O>­“"ÇÙzìÐx—è¾€À®ø*1¸:ä'è§y e·ÄÉë~Úö ¬ }x;“T%`ÜÈ}þgê'Œ{w‰Ÿë³“”Q9lÉ«zVæÛ0nq1>1AuÇ›é/£&9HŒûLAEíö»=/?ÎþÂ#D—ŒsѶ‰Lÿ¬p Œå0WÞS ZøiãxxF…E@ô${=&°J¾²†èR€Ï—_»¦õܲ"ËDo>Ÿv³õ%‹®ßªòWêN ÚY )EY(Uç.^§åÉHi]`*ÒÈÕþS¼V÷vñ.¬W˜‰±â7ãú„ƒmŸÕkÚQ /£úñ¹©ó²4"P”ƒ™ :³¨ vŠu\¦;;1k=ï>yí}´VŽH<þ.ñÉŽRõ™Á1ÉRþuà1$oÎöëâ†]þSdÓ>žÊ—ÔÜ3hœœ]nòá¡D{òYx8 (…Yñóæµ Ùß²þáH‰] ÒøURƒœ$Oo–©$F0ßTs:«xDUí· x±¨!=ì?˜6Œf°ŸMe p†¼µ¯Õ©#¿˜%‚pìÏ jRƒ­ß›ZeLJÅïäqh|E›¤˜Ù­ŽÎžÛ]oýG%ÔñÿÞÑÒYáã4ñúa2Îý¯-)¡rªx%Š»WԬˊA†œÆe:0e¾éÚóPÍ™º3ËÑé3sz¹dŒHkŸ=Ðe„ß9|½Â«‘èÊ€‹3_„Ô$U‹½|™ˆ>Q\½Ö=;äk†ÑZ k‰XD!lÙSZ­V¤ûã¾ë´è¼zHŸ_Ï—øQ=ÂÈT”ùÞ‹>óRö7³Âºä…¸ ­pÉ(º©íÇlk;„Y2,“ÐD'©TeºŸ’õHüÊ~¼¶¯e†uËüs°çK¿d–MnÈ_*HRlœ£eº;’í«UeÅoîH/´ï… ߴ£T#ÛP]uÐ)}¶ª-¦ `÷æRAÞß%‡"¥’ùCFBÕÛÄËxw&$ÖO©2FVÌÐ.ÊWÜ…"™CóóY³}M(q7%pÝòúˆTcÆáó‹ÄG6mBSv¼[¾"gÒÇàø¹·ã—bòçë…:h>ÎRD2Z¾OÐlÆTæ- ÎÃBÈ”ÕË5ÐÏT€€¼œ,ƒìÂCX“ÕveÞ«_Z_UÈ ¬/½‹FDs©| BE>ÛlÚH®‰¡…†o¬ºÏtKÐÐÙW=‰Üÿ¬‰†=3αZý¤äŒCdCšú”B×2ÚpyÂX5À …ÈÀ½´l—À| cõÙ~&–Š]WcÙëYø¡Ñ§¢„}_<–d¾Ñþ¥„|ºm¸Ã6fÞÜksG´Dð,mT#¶e3÷mÆèAyHÂGQóÞÞJ$u…rÀ@hÆÈÁœ¿B.·½u^mp_µ‘ƒ².`’#ï~Úòq* g")Ê›üÚdª†0EµkŸ.y—þ`;Éœa›{R»«3W§àíËh\‡P÷ð¬LÇ{l?8xOúyøñÁ¤,.ï ¥ÿ(`Ÿk€!wЇÞ&ÏÍ|©E˜8›¸[* !g¾š©.òoÃ+Ir®0 Ù=‚¥OPü=†N†F€o ÂØ‘Žç_dR¼Â©Æ Ý|Œôe÷`(ý=ÍŽ÷Ãx/³¸×à.l? ÿñW‰UpãÅ ‰¸%n:\ÛyR/|Á׿ú©;jâ¼Y72AoÆ{I¢®¤ÿØÈà4´ïë5×¾JŒ(“ëùÜí3±™­)iÍ«·Ö–ÿó-Ìy̵u®—_½siÍ$r8í+nOWÿ¡üD²·n´Üü¤Xœø2é¹Ü£ÊNÅ%3Í•i’MÕ+3’"É¿Í-ƒ+«Þ²lo&Š-˪ヶ=WSÕ‹RÄZœÓCÀR0£½ê%dQbÙÜã]/µa1Õ_Â5_e €æŒéÎòS4$ÐÙÖÙÕ´m±<쯠|Tè3WR¸†ú2š«é≂´ÅŸͬà;É ÀÄpôüÓ>yÌ9Mj7iB…kÜñâa8ñÏ{ ^ðÁÿ—=#Sßó'Dݤ°÷tZ…ñÖ4ñ ŒË#)mÀ÷{’{qcn$.1 sÃT 2? ³ Mµ—=ð¡ð\I"ø–åV*p6ú›QCúìšfFqhBy»‚8äYP@œÙKå+Ö3NY3Ÿ§\×\Ý^W…žóõ×]$ û}B·^uä¡ $8 ­¡ß¯še†ÚxP•YVã³^2œ8áÔˆDn à¾Ñž†°?ô–ä €å“^U#c1ÿta·[M'B²%|³µbÊ´t6Ê{¾£ çúµ “Þ¾·x;#…ôà?t¶«Lì0Nh˧°ÆA†.n©”èz†~ ±rm•詵x?ßÙÓ­ÀZÜÓüú¢áävy²ÔB‚S‰Óé'i) |¬ŸK—›²"ÌrßòÊñ Š–$‘AÌ „|½»u]ÕÞâßB?™!sBç6µD'PT¶ùF¤öwD6âk}Ž•ù1,2à|A6¿æRm"дiŽŖц!É…›¡£%°ûšj)îÏéŒ`r*s4(ÇÔ|¤?]e5\´v³/Ðz1ñ­¥/¯”B Arm ªÓþ«ÕÙ\¡r?½Åg¢º®¤úïÊP~zVª×8¿88ÓútúI[× †$Åg7õ°gé Å‰ì]‰²Îç^à)Röp ñ‰Ú®ßmXîüÁ pÚÜ/…êöòÙ´ÇH™Ô˃¶ZÂÑì9'“¶1~žÔCUÔØ°ÝÞ¤R äbކηmì„Ú΀¼D2Úx¾œ9ï\ÙL[… º‡¨HñCl@ýMù÷+ª9 º`ù°­£IEÕ‚êí$éÖNÅnhú°å0vævj‡(ð{1ÂgµL4Œö¿š„*†h!sà¯9.•úQ|É—h-]ïñ¹ªùÿ®î#úCšè|{¹DŠXŽñíþ˜S¯To©©VíB‰æ·¬i(FB¢– ðÆrœ•¨7àQܳ q늌„&ûj!ú¶ÉÒ»R¿NºÁÀô%\<¯Qµ#ëÇÂZ»ù®~~ã@ôRÌÔN†ª˜Û¹¿M •͵ò°$³á)¿øÞþP¥õ™}çtÒýS«=‹©l7®yGٺݫGݺ óÙ Áô³š§ùó>õ­Ç¾o¿÷LrYå&5¤³¥3Ó?EO‰Áü»Pj›5 ‘æ|'ú¯SÈy<Àá rGªoâÈÌq—Ô©öÝm`T¦®_`õ2ç”pö¸Þ„N®ËSk¥6^Çç{*–VÓÁFwótÇ"‹ªI¬ËqC¢íÍòžvgXÏäž° z0dLI¡gØû;¤™ü"o&CÔ͘šë Ñfª2n´6¾cIÁT«Êá(%›²DÏ¿lOKÓÈ+&ÈÓ‰®U?©…ì3¨HÕ|r¡(9ø®ÌÑÜ¡° Êç/,ªÔÎï»ÞÝy[g¼šÒ7/gx@ïgQ=ái« „x:óͲ¬eCÜ3‹ŠEjj͚ۛ`ØHẃ¶N“vJÂf m¥7+<ëì¿îÁìÿƒC @mak·¹ÜÚmñPœÜVó³"¦/¢îü´Ôî‹«Ôê¡ \ÜRÔ•·¸h¤.ý&>¡zÏÍÏê[¡ö];–/G˜ülŒgÂ&½ƒfñJ¾úz_ä.E[éÃ…ά Â¦a•îTô²>çÑ1úó¼ xìÕrl‘[²ò¬¿Õ'+Mà*{N¥Ð‘ŸçTU¼Ðqø þ§•Ú.óÖ©šÙ¦j áDÚöبa‚ "×7¬£Ì؇ª%‹oÈ~l¨›d„ç`Ê}Ð “Ó5´‘wt §ºþI1hP5Ïw$9{gí¨Joqâ ˜`ñ¼Ó¸tIòŸ¶ji Á{OëÙLÐö""vçü¡°r@ýöûrõu±²O'm¨#ü÷EàÕ6¬ÞøRæ±CŽWÉëüJÍ­¬¼FS¹ 7+å-âŽÍô!²Œ‚ÇëÖ|ŽGÝPMÛe“WWÜe'kòÕðœVv#Ãb/¢ÅÊ1_NÆ=‰Ÿ˜¸ÚªóÛÃ)påœ6ÝÏ×%™@!)è«ÆóI(WIŠCZ“©N'ë¸kvIó4ó÷—Ó¶ÁÜp’«§úW²SN·ŒÈÍ_½åØEËäß©/ÞU=HLŠ„[P±,€ï°À¡Ç-¦Ws bN•’¬®šûu÷wž5 º[¥ x­É„“ÜGùÍ<ó*×$ÕÈI®SµÉ›§.š~ºÉ&¶8'M<š½ÂÛeX'kÃò­¨Y,q¹ÈR‡uÝÍ8ç æcô»+å ™»­ [xˆ_0S43ìÓvtÆîa¹Œñ#®mIdÅ0VÌöÅamE.V-=‡„§(`‚[_4°ãŠÇÙYBž¡oFBˆÂØÚ§Q¶Ù¢sn–šžëŸŠ—é ¢a«ºKŸÖSÙäéG,…¦6´ÉŸh|?sùö)íq`ß7¸õÞs÷5”kÐUÍ•Z²p4Úz†»0³û"ìÄÜ­»ôðç(¯ï¢\5i`Ÿw›Áü:ë¤r¦Tž¤xY*¬X3Ê¥¿|Ò•,+µ¿é‰ø¢“9õëNX1È€ÈF•¥É·_,ª6@ÊÔ‰†¯ |DiÊÞÒ¥v ¥gš2Ñ7©=«[¨ Ì)UÓyÄ&Zê;àîÏÖr’E°[ô%¢­ŒQPöià»—¿²É¿k‹ºÛ‚κ?¾“Ì«ù~GüLn*p™£!uWðÁ…K$°Üÿä ˆÃI6¢âƒí ën_ÏÿÌG-y0ÊÄ Ù#Çvà0XI9šx—¶ o8©gïÒÂi'6ñ]¯+á™Èãà‚Ò Ë.é$ÔÄÌ͉Èòλý}çŠwzrÄ=;ÌÁÒ¬Ÿzd¾ž€;÷òÙH—HÉ × dˆµ¼¢¾™üuA=Q@ÄL]h=éL”Ã<9úvy…z Jš¡~]RF¨UĨãü‡•]D(nÜÚ¼¹ñ¬Ua½µ™ê€Ti9¿1u¹ÿy9Ü t¸4I Ð,ôÇgÎÚÀž<æ•7Áؓ씜‰寖:Wa(?Ad.âgUü×ÉÚ“‰ßkŸ9¡¼úŸ|ç#µÍØ¢•ÚU×òµ{hSî® ¯Oó“)íLÃ.ùý60qµËÔt{° U•ŠA¡s!ðB–ñÕ"1”Ç_H3eh …8“"úèâäë"+ð•ÚV¾…”ÊPÉ÷ØÆ¥AoØòGÉ©zò'ýž¨Käò6ŠÌÓ³1õ9×ðWš9=¨r_ŸJ)À—LJçh[ûúa¦M'>Üa³"HgZ 4Òj<4ÓVÜ0ÄÈBI ÛCPÍó¢ºä¡ßBÖYƒ$fNÐß }žÃ_JüFWÃý\CcEýW”˜?0×I$“Á1[ŸJÆö•iGú1~Äš£b𓹻13àŠÄÅ“ }]éR[àËÞUÚ1¯}v´0 ¯(-9ò¥·¤HQíû}½_ÆÁuØRÖH“Ô5˜¥Ú¹?XÌ:-k)M÷z¡Ž Ù©™¼<|F  ¦q,çóïZ iÎ.b…:öqåUdÍ<:¿vñtX¼ú˜ëøå)8w/}+6÷Rå³eưÝwt P‚¤&ñ?Aª[ ÑæÛ Ñrs>µÇ¿ìP¦ù'üú–«2 ßÈ+º¼¢6ÙÒÍ᳄%»ÑtH ü‡bãÐ3ØŠ Ú¾^Œ,LLÁo—=abD²K¢ä'ÚsøtT?©$éC‰ËçÞ† iå÷­>`o–qUšÂLÚAèJ}¤~0ÛHÒņTŠÔLÿÎÒ=úñ2z †N BùX!(vSÞáÑÊaÐâè©<©ýcaq‘= J0™4œ´òhÎ(|C;‡f\ @‰²õxàÄŽ3sþ¬šÜà˜ÃWÏS‘͆Mö 1çýôÃj©ý̸[F‚W"%E°PæÖ2ÿƒP4ÐÏóßNýc!j(nÕ<å÷I“·s‡ ¬ ÚÇ5Šb?Ì·G•ÕSwâwõ'ª<¬‹cµ•­Ø@Hdz6ŠN•¢c÷ŒÛ½?Çå%r§Ø÷.µ•ѤÇþV©æ#WúBsÈԽƧà<œ2«XØD#7šÏAñ}Î:JC(›i)UwƒfËâ Žœ»~†N@æø‘‡{çÀ¡»V¨m ö&¤Œ/,wc]©àñSÅ¥x£“ÌÄrѽömáFŒ5ñk‡oyV¸Gal§Y§¼ W+S°×„„¯§â6À¤ ô¹Iг²ƒµj0C×ñ³;o¿vCç±ØYºr‚p) ·1))͵l”³aͺdjš:\ãlô2Ëq˜²Ì¬¨»%£®KmŸÑƒ=Ù$TÝõyà þµ.*¶—0ýOî9¾ÃuÜ\ª=Sø\&fYÊÝ×¶—oO\m¿BጱíŠa(ûgN[Ü&;²DºÓÁ]—C¤Æ`0w"¬ÂÙkÓ‹ß>Ù¯üà¯ôÉÕ04¦Ñ,ði~ßþ¸!ö—¹î¦¤`Ô6m¢KÉ8Q¥¥SâfTJJŸÐÖ›VpÙpªl4ø:lØWŽG^ >Kây¼/úmm"JJzVf O)Mê[÷@y|ï„/ìå °±tS‰¸M®xi·áè7Ìw¦Ðúzt‚­X#ÆM2Rl*eÁìz}èjñëNgÌ áDWÝ@@,[ t¶ÿÂ)|x Pe|Lí¬Í ‘J&–üþ¯“”±–šV¶t¦ø#†Ú£•²ñ²À~ÎdmŸ‹Q `ì©ZY¡"|Ã2ÿäq•}ä>ÞPÿÑèWe«†Æõ¸c'’Ü> ωƒdcÈ26 ¨Oi‚ö°þû·Šéõkaââ­=6ŒKÆžã=ÉÈóÀ5 æ ’3X¤sI{A§Ðø»Y<›®¾ ½ö4Ù Dè~äÿY®„0tR&wá§§Ìвâlæ.`Œªe‚öv›ãFz¤N‹Z·ZRáAÃ%Ñ/5ÈR%kþ&2ã½=66AÕò8”m¶;W3eTf ~3¡^l~s/|Õϧg¿‡pþž¥”WVGë¡wæZ¹&ê¾V3ŠéžšÁ""¦i»ÐB^{‹÷îÁš£NJæ¬ì¼ƒI’?Ù'a€=°Di$®ÃYUGe¿Ì—Än~;(S½Íg$ÒÎKÚ+`*>ÂØKY“€<Å¿]12‚Ã4lô*‰sÓ´-ÔE±ÊR{䦼PЈ–¹;É PyVqS\3ðïâ&~w&åô(õ'€…±cÖRÊN#hDD>ÏËd»g¶Y ð? •SgÒ·Ï´¾iÁyšƒ¤#öÕSø™>%¾z.Ÿµõü~j¦­÷Äg·Pj»×ö:P 6Ùb¼d!49×óàâOæ‡!!ÿh=£B¦ &cZ tÁ%p½Våm¸Zø8 Ü/€çà]ûoÁýËýëÎolùMÜÙXTÕ^c» ˆZ+˜´Ê“·=€Ÿî+ÓYhoÌ®Óݳ/øé/t«´h¦jëbŠPfˆçJ—ez6wÝ•“fTí„,¼r0èöŠê%•3½J36¼J®sÑ;,¾#¦/Ì!ˆ6¤Âi/[tˆYgÔ‰-üXÏj:®>Z›g8ÎÉ4bÐp‡ Ýfé©&™Ì­õý#q]üBùêý7èøºu¶¨€:5Â=,'àši„‰R­‡ì’ƒlh”Ÿ™y~Øxíµ˜r§t­D¸þ4¡Ë˪־ßåúüɪb<™~\ؤßâºI“Û8EbU©¡ô,ýWíϧ£²ï…ô 5ÀnùØæ–ñ‰ v´ãö0DôîÊ_2ÝP¶«Y š¾rEzýBÊjX¦ÁC!L¢Ý á ¡u>¢zâIØ¥Üüé4“^83üwà*ºÎÖL–+xÎy)}óŒ¯ #jÜB±…ŠíðŒpSÐ’Š†ö‘Økr1û%u<´n¬É‘8ÆHoɼß-¿Ks³$ÏçR?‰è¸ËÉq3hêº!vÏ ÀPn_Ò•Hjm'fp˯_Ù02>®ÔÑ„g7ɘ$uX%²[WØøØ8‡I_rdUp‰[Ü4‡%ÑzKÓgºQ³’FK U[ÌïQ2º1,ÓŠ¯éIÊ{¸¤A'p«ÌœÆí¶©‡1òK¢Õ•Ë{—Øn71X<¤ý’`¯æ÷vD»ãYy¢µí_5ùNSt¼vÚ ,cæÐñB;éFe6”²Y…5+Z8/¥À’ô]tP|µÚ/Ï;†áÉÆA¾˜|6¢ƒ|ޝ~¯ØD(Mñžã—ØanIêWF_ºÈ{›­º¦ù nÔ3}?s#ˆµf“)δ¶x5-E&^Ë>¼IJéþüâØ™uS×\öiç['½lMKâ<<üvÝäø½‘ðð|^^Ë€˜¤å[Ø–³/ËÍÂÁ]ò“¶«FÄ©¸/l¦$òǬ(ÓVçC(“Qò}=ε}åÒÔ½í9ªn…¦hGÌ"“vI'‘Ï}Âói¼ƒž0\mo !Þ–Px°ÃÎãpóÀ=UrIã"­/n‹1s6Û ªŽÙŸÁk_fÐ„× é¥dèuOaÚ‹mšîEa2^úâ…¤;…-4 ª¾ïѲÓÚ~y< QÀuÑØVýPŒV%ó>ž¢“LžÖ'¤Šìëjn [ƒ=‚ª@ï×]N¼$Ñ»f6I÷Ó*UË÷<'ü¢W¿Žâ²&åôVrߪþ2$ukñäér—Xa-^àÃgæˆf#sŒÀÎoáÇ´² ©©2ù"Ó'$(³»ñæhR7Ô õ±~j<û(€M‘yþª^WéÈßÙ$z¿–Þè1Š_ ¡V ƒ³¼.×Ô.¶•3æØ–\¢Ì§Ì:Uæ$‘ÌDl-¤=Ùg0þ¶Ö~§Øvæ!€‡_šQ*ææÉH"¡NOÀ¨îLИô«o/þ”y’±IBë‰B+Ííµ¹*ë9nØÇF•‰&䓺£r rQg A„.Þ6"ïÐ=+‘¹ºd]s?ßý~Q$Îy£í½*ÀiKT`šûêoÇ—JˆT<÷!¿²Ï»LznÜÌaÞ³^*çÅo<ôO‚M†.éÖa0, _8Òš,Çgÿ\ # †2vÝ!Fþ˜=¥‹\êüöy6ÚÖ²ÅÖ̉û$Güh jææa<“Ð…ªýSò}¯çÔFDdNñÓÁÜ0Ô‚Öå¾…ºFÎ>dƒ‡àhý⯭h[2™Pj#ʺþÏ÷qw)ÓÓ«ñì\\˜=t”PFF^½iâyÆÒႇ^52„øÉL\o¹“®ßJµ‹†uâ\³9£y™«§]Ä>(¸#{R®œßæ=ÁÀˆœ¿¥Ñ«Âzι8ÂK¼©Ó6åÎabñqÁ½¼†ƒ_cíbêÁZ’5JUÔ‰ðóxV›×—ÛÈDðs:K.—ƒbŸö`5‡Ào²ì㎃äÜ/p°Ã{f Õ«›CìÍ-0¿Y®øX³yFþ}Í-{t!b2È/?èÌš.U•®Ÿúò"°CqtØë!‹¶7ò]ì•Bè½iÓx¿œ3dÛ-à’»bž¶Ui­æš¨…¬UáüK,{â- ‡\»ÜwÈgÚHÙq^}êfúÔH@&Ëå[“·ÓWÝK,¢'ZX3øÝ•߃ȶ—ô¾J Üú¡²tóœ!4`óä”ÍÍhé²·ÃëXB!–„POñ,Šä¯¦X¼Þàjÿͦ.@yj;¢h¦â’) l£´XÌõ³éÓ3„çCq‘³ÁÄٵۦ¡ êª9ÁY{#РÜg<ß{Z¢Á¹LüGNm–"Mô1CµÏ<çö9æÊ/ÀZó‘ ÐZ¨'¬ç¼{…ká µ:Má—ƒaâp G ®Oïý·fÇJSa€_Øäì3éäOt/™{zaÇ×îj!µ³~ë±iñ¬gñÒŽ¿ëÚZñ ›ÛÕÿV~-Z2½“¡T‘§ qTܾ®ZÝé° nóŸw™PD2aíç8´ï‘¼#ƒ%ÄAGÜAm%¸\Fš7"5úØW_¥>ÌýIçh®2”–lû·ÏŒ„d¶JE4þjÚy¾\"ž¡ïô〜êT1±7äØ3yCøéb:blå¶\[‡ž´,Àk±iÂhiRß9Ó=}]1_Ò9ù2.:ÊÌkØ@¢™ñ ×K›z¿~ô^øï\®ꋱHÁG}»ã·oMŽ4ˆõÆæ=K÷•8æ§Éù{|c°@ÆYØ’éØ"J W\§Fv°° 0æ»ìªï"㹔Ď™HxýåÕš‡y&ûk– !FIµ3nŽãñO§sòpCÝ“Y:gb½*'ƽ¸¦¬ún7¦{îRíð#}ÍŒBrQRø]áRŽpïcAjŠB«¤„cÐ!DT"öM¾,6~áå<Ì•õ¥Öð•Ýñ]8nF@—û"p·}‰-{)Çÿ¦˜SÕþõçSÌÒà5ªh b\ÆõµœÙ¬%eÀ«ŸÝÜÈiZÐ ¬­8% t”Šì›dßx+‹‘ n.ÛXÇ.–CákbÆüG2;mV¼ÒÌÎ×üœnJ,h„]ûªùëгn3pKÎUYIVåªÝŒºäO õçÒ2ŒêúÀ˜õËc…s6ÿœüfÑNŒ¶1°XWj…¾b9PP9ñÛMÍ­Œ)‚oa}iWÌVÔ»Ê íð+=¿³ i•ïµ»qž2Qÿ”,»à‚Ä`Ž-\Å÷¹Yq¨qï–&ZJ³Uzï ¼Ù¤Ðó#ÁøäýYµFíÀ3Ud[ŒÊãu ¶I÷ÝÁ ð×¢: 8剸 ýO Î{š¶|ÏÚ\¿ƒ òÕ´2¾¾?ÅCǺíÌ% vQ^¶v¡1¤×™wùR„ü¡—¡2¯à90WÈLúÿ®Å$Ìá®Û”Eîç°¶ˆ¡Š/”íh¦hêh&›íµoGAùz-‚+ÝæŸ¥gµ$ZÚå»C{QâHL)vc!ØÂ©WOrb‘“¬$ÏÙ㓤þÃWW‰ÛÇuSV± ;4:GIQq×¥XçuJ*3Ì]™“˜=.ïá”øbfŃȖ›{[r ááÂ0[g¤Ú¨5K2ÜQYìã°Ú¨5ȃyEÛÅ¿‹ï Ì¯¾$YÊ8ÇcaÖÃ÷Xlø;ýrë/Ìqò>—ñc‡Àð¯ù óµõÅzWË…hyðŽEæ)o yM²’/te#B O$<„¨¯æÈNÆ­aw8:€D(½jÍ'±’7>GßòÂ,ÇÅ ÈHÙ*ƒEÿœµ ?ß* ùÒQ·HHÄC¸½;ÆE’Å Lw¿% Ð9:Ü9ë“‹ƒ¥!wT!ï„éˆåÄÔ5µ¦C4ôʬ4DŸrRe_äVÒœ»KüÏ“%-ª¨gî ꃬËš˜¶g’2ŽØó€ƒ$î}Sâ±õ½™OÒ®yõ6ËoÞ¢âpdéU­` ÎÎA¤÷8DüÐÓÎ?ÛAë\ûlñ¶ÑªIDÞ„ŒŸœ{øˆL2Ï›ùg(³Þªå7UÏZ×c—ýPÿgU/ˆpbÍ#vÍ2 *@þgö‘öìÊÌÿÞ@“•"àvãQOAH^,=o`ŽÑæºé™ãJø¥]Éyí¬þ8 üc‡6€¹ëÙn†±ÇuM÷ë±ÖÆë´ü4ñg]éñ”¡fá  Ëñ!Jú’¿T¯Ìµ»›9à Œkl”±Í?WŠ6*Bcƒ â宇ò S'T³qž÷’ˆÉd?ª×˜¡ÙNòRWu2é›g?°8!Hà©XÂÕ×vnNË?ŒNò»×J†ˆ±tý8AiZAžÕŒÚX’pÔß@odr©ø»õèîK$‹•DóJn84(ß<$âf|¨@!œ@£³lÿF[(;çÑ¥{$m‚×^…î%öxqF’{d?!}lƒÁE¼>2¤ôØtΨÒg+ ì#øàÕ·påðèäÙmfþò~êÑr[` ár‚#+ÅûÿS•¤ù?‡µÍFn®ÙV[ô‹~Ⱥ 2U!wðŸä·8J,”Ÿ‰vÆë9‡ÐaZ`q0ȼ\zÎpö-ÃÕâBæå”^ã†íÉ婎¬»1'ÏV<$°‹æ½U¾uØ[è`BŠŽD»‰ö бL0sÄÀ Á¸‹æ)íßX£ »xiåfSs)8oX®M%m˜îg²}ûÄxêˆÌéAîgoîÙÛE_ïz4Öf[„¯ªÍ@Úš¤ß)„‘CóPø%ºˆáîMÌ·¬l}ñ¼ 5QŸ«îfjíjÇ&bÄMÃÂ:HäÄÜv+…¥ð·ëÀÕBx ÜvÜûmÀ|&´ÎòLûÇÉ”ØÊ vÏj^$þg¡C€à÷ÐÖ굕Ÿ†ŸesçÕ{ý2ÿ—Ô-~mëÖ„Ö&Óл½ãiZ¡Þð‡cFc¡DíÝYÑò>kcèUuù­AS¾Ý¿ [ ‘‡3k—·’jì7â =؆Ae;€‡´:˜*" gl Še‚íC$ŠsÐ]èQ‰¹jT«ÖÙ8àõ×ô•éKïü*åXh-- [×÷z Ÿë®?uRXÀ ÷MZN+'_p‹@õŸ“ËæÔx“Ðd(2s]p[/–`<‡šÜHu*Z¨Ȥ¹¾ož)ÿ-Ük@§š,Zªð§ªzÝeû “–>“67|ÁœiAŒÓ~1)¢ÈR`ïÚÓ£•EGs ³WfšÛ|ƒP?V$º:€„!_½RÃйz¼Çƒ}¯':µo2»B+š6CñŸF}QÂÉÞ ÖÛ ƒiqe>–DH@v î*©ºKÉ.¯ …f˜.ç<Þtp}øM¨CËhX¿;²ºA8@n1pvW[Ií×NÛ ýl–`ªµÌþÕú#H+ HÛÿ8ÞÌ×v¸ñ=·qŒ*ˆO™œWÛnPmôèa³0îž!j½K@ËS1CÝWt—¨ÈëŸ ùWži«OÊ. !ÂÃY|ª²M&è°·+h[cDÂâNììga¼ŠQ†uXîdÇÓ õ“–XŽ9(Û§[³SBi4ÀÎ…NÎjæ­³9dßÙ·Ô#B\ÇŸµjqp}Y«!ø\^ÄŠ4n>´³™|,ÏZGÓú›‹Ï rÃ+¯®š=ÔÙdÏLæ·5͸5Ç·ŸÿåÎï1öî»ì‰Àx.w{lË´|H÷²ò ¯âúkU/©6‹êöR²³¥yXIS2´û5Q|áäãé ^*ïHL<„]Ãî¦Gf12ñDt`õ-¹AsøÚ…Wí\$”LÀ ¢—Ïó"#ÿã"+ Wu¸ñ1=ƒÖÇyÊãµ1Yg18aˆ‘,¤±²ÏéíêÙŨг—¶ƒm‰4n˜wóh­…¥Îÿå.®jƒ½†PWfCû°RÞÃóz¦ýdHÛr‹uJz5}u:ñȤ+m™5kDÜZ} Ø=2vÁŸ¦9^æQÒT‹UϽ}?÷"Ùæq¯ìqÄÙÒ­>·Ííï°`Œò,û†ÚOÙ^}h+öN,‹–ÞÙIiß|€¢FžI5zé2Ú0z<´ö™ Höt› â1•qóÂüNÖ §î7Ë‘ÉÓëû)Œer}ÍÁôÙ6ýÓÆ)x°†–xü¢Ewá€ç_Ýw»Ba¬Í±ŽõMÀ@às¨®[Ƭ÷ç,ÄD6³)¤ç?ŒÖ˨ho÷?SúüVØtVgÜDUÚ (ŠÍ=â*h7Zo[ðQ+ Xâ$Së£ù+±bÏÎÑõ&%.†íXÙT21‹ðYf'ZŸ•Ý{AÃÎxhÈ ˜`ݹ ÃFtIë~×ÚÖ¡,ïédÌx pTl5O62;USØg–¥çcG‹1Kø©kimjŽ`›ðÐh`e®v¤ˆ±¥»%ÿå§dróÅîžj6ë.º€Ò %ž:BšÐq ‰‘ûK ÎIÓãš8§éàrãxŠÒämûƒ={|PãN¥LÅ ¬ã"¬T²5Õ¨ÃzÆ#>:KÌ9ç—'ÓìÙÞF$užh¨ntPV‡ÿ«ºÙáêC>>©Ï : Z 4šÞvƒoŽØi‰Uo\üY03ECÉJ Œ‘7‚-=¹ÒW°Í”uƒËÿ”ÇÖÕ±¾¡´8ÀÚ7ëÞôF˜v–Ù}š²•<3¸S0[¬ùL,Zö|ŸH7-4ó´ Wýù÷\o Ú½P5›Ý`€û/Í:k ­˜ýÍÎ[ùâ¹Z\Ú÷õ§_ `ª#y· €NåÈÞ¸8ÍjŽz?‚ Ý Už^K²ýH²¦vöc^¹¦ÉeC@Hì‚ÎØ¯Ÿ:êt©‡ñoWJH'©‘ô •B©VàôJ87M¶Ö½ÁžÀhz¿>*~3_ÏÙ¨Ö AƒŠ"~! V‚4‘ï;v4Tfð]ßˆÞæ¶ÊºÏ#"t{#‡<©#ŸÚ•ä–RÙA³>DÈyÚÄ©à®ÛkIħZc…6Sè=ô,Ö¹}g7äœO1b»4æI4ž¶ô^-ís%(ÖÈ+¡‚}%ïÙ5þ­\&EÓˆbF7¿:× pS€†òV0ºÜ.Fèµ^í>׆˜:à>ÞÍqv£†íI­:µ'ULª$¨ä²ÉÅÞz ‹NOXÊÒ'%d¶œY¦zÕ ËÊ7ŸÀpÓêš]¸ªš&Y¿«¸ gJJ‘Ãn0VK(ƒÂØßÕ‰õgñÍò±¶Håb>šN T~ƒÁÝ“sÍäEU]Öš0=½#çÇÛvx—K%¥`볓!Lcéè#Þ=sØ×y´Ûº 09ºôÑ×6Ó?”Œê9'Cc@¤®ÑÌ÷«©Ù Ù²òŗñ…T="ÜÍy˜¹²9ÛÝ=k¿2UévjS„÷ ò[…·±?IKßГɼ úâEÔË‘î§<¾ŒvbÚQ€-xÖ?=µ"Wûô•=7¸ÒõÎ%RRÄ´ëÀâÇÖêŽ%cÊÅlÞ•$Ç…žÛ¢uµŸVÁv QˆŽ%0 ;Ír&p0>ïFKÈ®* Ú?ÆpМ¢tbl!9«“í&ð€:g·s Ì jž2·¶´ÕâA³ò¬šáÕà÷°`ñŸõ²©çÇ×Ë™Ò> ahdB“` ˜§šn9ÄüÞ#™ñ0Á‡épµû/f î‘ïzžK¿L‹Á|ª ·0áo§ÆXÑIWp=1»¢V*êâj5óÂØôûßµˆ[zó™-Ý‹`o¿­(²–~n& ùɨ›;tëxŸW‚w›Š=ó¸—iüÞ&‰‰ïÛGöè¶M/ HBñ+18N¿ Fù@lÙìÔ4ÈÜò`Ðì#Ôp€ÿÁ2þg-ULn›sªÇbk;äÛ0©©ż#2Ñ×o%jˆ Ó°øŽ×™‹ ßiRg-…Þî qy‰«Ø¬ð¸÷sß?±GQ‰“&RfèÂ=3­·–—ÞáTêþ=ˆÜèß=±û¿âÏ»£,¥]¸Ç¬Ü—;zÏÝÎÚ|ûº!ŒP¨m¹ØÏÎúÚ÷/ŒP¢¨{1Ü»ÀE¯ÁßŽÞææbWÌ6ÜŠÍ¡»ÝXU§.õ/Ã2WÃû‚´ž V—_õS.«HÛÖáÊn£þ]0(é9uöAäÐ+)€¸¸Ïæ¸DI3¡›£ýu$0~nÏ•­0þ[ñèq9—³‚Kwˈh6“ €| •ºg_Æ~FåÁtÐQÖ¦ÓÝô2fø{0ƒX'W®rßÜÌkêÛ‹Ö2ïe²_cÐv¶#>%1òb>ªÖ‹ðk1¼\r¥!æÜ[Ë9ž<2~ÖÖt³å¾§‡òìðìÌ¿SÓ4©^êÌÊnöσ ¼Ïž¤k FF”˜å$ÞV‰ t[Ué@Ÿ€ç±v¹Ü_ëL¼öi±s$FëøøÍ·ZxåØgÛ›êš(¹s¥òd’ÌI†^“¤*ìƒ×aEäŸùY'$àóS"øßzÊ.ƒŸÎÎ Òê-"-Ë Cvµë÷#fÅÀÒ<ßyêrŒ0dx÷YÛ“œ5!Óm‰|Ë";¨:ÅEî®X MJ¦¸Þ´¡'ad‡/[lØ8ú#KH”åIZ!;™y<|Z¥øR[AãýxN·£¯žøòÝo‚.ÁB‘eâ<¢ûaq_ HÔÙëŠm©,4>ø‹ÓØ[’¡K¬öŽUø¦GФ²þ¾5¯Ë Ù Õä†ðÁÅ ~Ø=¸§t7©gzø1ÅÔÊ¡Ì9“µ† Ë(²öm÷ÐByœ¼­ËúG^>’¶õµ†œšbÞq™Q#¦ðĪø²ò7·)õBçÆ)g{ä  15lï[Š" FÙ^ø`<&;cj¦Õ°÷“’JL+1ÎĹŸƒ¨Bqa#˪:³a-çïÞ"ãVñÎfLà;¯uÔù'ŒCaXæ–ç9ŒRFG‚͆EúGÉ·¯^ㆧü8neÿ$MŒp‹˜0ÛÂ3ÌvïO•ë7΢½¹O”džõÐzdlåÜLßgéÿNºU[þAMl:wû9J‹À91”Õø¯×hÖ¦¤Ÿ¸%טFqPëâáˆþŇ5¬Ä1̓wØ4§úf‡k.ÐQÞÆ{›?žGDº 0Sj‚À‘xbÁ•¼„KÇÿÖ9½ô”N-¼ËÊúxWÔÓþ¿"“~n/I±!”·“UØßŒÌ\4%Ay“k÷mê´º1Ͱ4yŒÃnYo§„ƒbue³3ùù½®QAc(=s]¸]÷ °;¿h=ø˜8xVzý]é|t;¨8GB¢_Dtå$ü¹ôѺŽt@ˆô.Ý Ÿm­ÿ¸Ö MÔ×/á_¡;–;ÇÁê´ØžWe=Øå›š‘ǰÅOX×dQv ' _d‘¢`%Û+ÀvÎKóË(«úU'¿á³žÁ¬g þú0Púxál”’C‹Ûî‹Iô CË¢©go%zÿÇàºÜ\eBáfŠk²%ç”rd…q×_]€@â‚þZMâþâ¼­K® ±€Úõt™)R§kX/{l±sÚÂ-;7”ŒKœÂ÷©ÃѺ Í4Ëå ‘&µê‰³vh-æ÷s´\Bõ Ç'‡ô”iÉ\U´nâ©~5ˆjj—ˆ ÒÉ}a?^ÌèšA1Ì‚Ce/;¨àos"‚o2‚Aé†i©êlD¢b §f\È?âLg¡feL±dïŠmDÕd7DÌjç!Ï›íê@~ áÓ Ÿ6.Ñ>éŽà(KÒT€"¶ SÛa†PèÌ;x`:E+“F¤‘*y">åÅ ¶M¥í†Xp¬¸/£c4F†ò¡ÊPiÿ…è4¹Šþ|K 3Û%¹Óýݾ%(ï5Lޏ£‘ùà—6A/b’|Ô}s9^Ìì@¾Á¤±:lCT¬‰ì!²"YíïÌËbç·_¨‚gém‘!:ª´­Á+¾„¨nó6$ÑLoËwð¥XleL<„«Z‘2< LþXØï+REKe]˜M<Éе.»{ñO˜›ìà #×?D¤‘ƒï‚ßÚ®þê³âòÿë >¶„{óøSš¶@IQ¨·EˆJzfa?Q.EØz„±Ôçà?©è¨6Ƀ#×P¼> ‘’à-_yq…ÝÆ³Ø8˧烄Ÿ©îÛœæ¨X¡Z2q¥m¨è¶^HS¿ÝCf¦µÏàÀ´Š72=õ÷¹’[‹¶ cªÓá>Ko² QY­›Ö8Ò~þ‰;º>jAþîY,ný_ž£hÖF7œG_ðpÁgiiòj½ã~­®–ˆBÎÑ%R—­DºaEåH`êÂk¯CÎñÅHMýrû¹X¤ã;ÝJòAòÏeÈPÄýšÞ‚pê×úŸQr{|‡Jc”XàÊË¡]¹åƒ|è\è7\?‘x'÷SaÌ­{ùßùw×ÑêÏÙy´Ì„—LíÂÆZÍnQuf?-…Žâ‘]mk²ˆÇÙ)Øæ[±ø^<.³ÎA‚ø,—»wެÍI­ÙÞÿ¯'¹q”•Ð@•»kewr8¿Ý€Ý·¥÷.7´ÞŒŒ‚û¿Ü0Ù iï‡bƒ¯â›ßdè“‘ ?‚—¼üÈ•V³É¨xæŽʱøÅ&þ«œO:zHú¥¦Me–Q (nÑ]^å_Q¶/׫ÊaP*÷h¯©~ñ?óp=ñÓ–†Øa“LÞî3îÈà$@~Þ‘kª/ê °SÅ}­\’XåWÝÓòz7ñq•<·Ûˆj¯ña:$3e•¨X ±d–”d1.¿mF{Tƒ ?§/xÇ66³fßÄ+FÚ_·¥QÌzñ£xZ6lÆVië ð`«Â!ó+ŒÔ3WXÏœìSƒ>ù×ãÉ:†s)Ç(éègôü Ã>­âÖ*F·:Üî0› ÄâüÞC^­ ¿s,+¿GP¾ªÕðØyE}=Ä÷1ž£.'ÙuÏoväÆp†Gh_´•Æî6kn¿PøÊÃúvŸ*b‹ik2m …wÝw5ÓÉBP°hñ­Lk}væ·–÷è@ÌfcÃ&‹Õ°u+oÈg)ÑG®Bްb¤2]ö[;‘žàVý¡ªÞ˪@ zi·&DUÄ—µ%“û,YÝÎÒ´&Kƒ “IèÊuADÛËû#oæŽZ³ o{ñµJ6° â §orçeA –KNKÝ÷$ j,’ŒÙPŒl£³ã'¢‡¤b:}Ï»î¤Ü²Ìp€‹ýink(™ñ½ I`M‘¸ZcCOô‡E-¾ÝêÖb?˜,ŠáÓ4]±,íŒå®cŽ,-$’‚€»z7è´â܉¿dš5ã`tð¹pV×_ÊèáO=ãz¯-Þ¹8¦í`Âà®Ê|e×K@ùuFM»²­Ûå×Ö¼j¶³—óOÕ…²z¶kí¶lŠÿ3‹ÚX¯Ì7’0Ög\‘ncËÁ; S  'C°’ÝcƒÖþ©»ç€¹Š}rT¤2²µâ½ŠD™Â<@>Ö5‘½J7Œç”ù4w=ñ“HN& AÌ, úl¤%´ûØXC–]Õû–„7S®5tÜß"pŒçóUj3^»k¿MÏRX ׺á[|©DÉ8-çqãy  øçqñˆ¶nÓ(ÿAá)VÊr­>F†HN!ÂÅeû1mÕ™Ýx©ÿËÏð^a”vIûåËû™3ÆSjû‘{·§Â“2™-§¾< ñ*= U?œHé«x¯!)Di#uÚêÐè‘~uë)ÿ1T¨ æ'jX(¬çÄ6ÄFe6¡ÿ6‘rè ®†u –ŠdK•±oT®à n$óô•„2éö²/.†h ^å=JŸÅ„õý—D8„¹ÍHðˆ|Ú¶Õ5‡®æ»QÀ’3 $lWÜÞµÖQ§«hç"Á©1N=åF#š·5ê4ÇbTjšƒ©ŠzÕÆaôŽMÞ$ŸÀ¤Š…åÜ©øÖX=ê̘‹c+Ÿéãgút„™$Þ¦’7Z].-æ‘Ó)ÛŒù1pû49#¾9lÍúwpÒßú¢*tü†›kÍ;¶”âæùŠÚ1µP@€ºË§[¯íìÚ»¬ƱÞ)läJ+øKu@­)"CpîÇ@ây¶73Ýe æfã5÷µ’Ú'Åñ]êºÜWcEž={ZLû®o(‰÷î¤HÓ«s¿é›6ß[€‹œir½~ë†`‚ëv|MЯöÚD òö}ð´=«±3õ¤þU7†Ù~"c¿2všröFvŸÊu›<n?\êîkëë6Õ¦àçÌ£»‹Oªâܱš¡Îö/zG„‡d0Ý8éücæmx4‡î,êgÈiå`¾¾%ɘ´aáb~AÄ?Ò›õ€oíó«[êõÙ©Ë} 2¹(ØN飅úzÜÔQÌ™À©“¡F²wâ„䪜’x®8<&L:Ͻ;Zž öpVùå០Ž÷íÓÈ&ªÍ 1 Koqq\C[ävª‹¾ö€?/€¢1 2öõU**@ïÄ©%¤ÇØå §Ð·½n½Æ×îHÜþ€žj›‚e› 0!þ¹.Ÿ“G‰Ñ:'b Œâ¬ÝqPRº'«iÔîŽÞZM§ïJ]ì=^ÚÁ>}—ÀªÇ„–SÿA¶ŒõõÉÔŒ¯Np>±‰Š·‚9Ýáþlð§U”ïÉj!,Pý ä½÷™ÝfeÔ…Å…ã7m&Ö¨æ?zK☠R7ïÀ…¹o¹ñ1ÎýR´x_¶t½‰ÄD5mVí”hăO^!+‡‚8õ9Ij¨¾¯–§y•'!SÌìûy µgi1€jiÞý0Áùøº˜Wf!÷ÞŠI]CÊóÿbP£ð u3^:I@\ûÜ éó ñÿ_™¬OÍÝ]$‚"B~'@>óES‰Vo;!ÿäN1le[Ÿò‡Q†>v«O=ˆtìè‚rÔåÔ0›¾Ì©¯íØfW“«Ýh¬sHø3ñë@9ÁÅ8Õ,ÿlQ@rf±Ü×îl|l…|¡­ÎxFp*ý¿¼BßÚ³3-´95ªÕ£•ßëÙĦ×Ñ_~½þ­=¿@ÀfìÔÑÙÊß{¸åh ž¨(á~aû«>údµb€/ȳ³ºoOöÿ|ßY:x²¤xdÜÅ‘c—$WxÕ÷:9pŒ’¿MŠUëNÄ^ ‡‚¼ù‡—ʰh¼›CÕÐÑ´vîgÓX\”¶SËõr5Ì’Ÿ´¶éÛÐ_팆¯šŽÇg‰xãh ´ ƒ@±™m°¬iðÊ÷Íá)ËgdŒ"Èž Tˆ3é² ^Ôˆµï]¿Y±Œ£“†ábÝŽŽú¢ »dô•k1®!ÜÞo<‰ÄSÁgÎdÊR„¡gꋵXÿ ^ß<µå9Nù¡ˆN’} ïáS‡{É»Çù—†1 ²¯zì™Í?‡–ÝxD,{XåyTð,oJ´`2 Ïrª¿sÉZ­üâ&¢L‘'ÚÔv¸QœÐV=¤ç^['=Bá=?ؼ€þ Ç¦ ­NÁL/—ìÑû&þD~W4´Nm•2œÑ´%ž ­™œ`ºyW½ˆ×7š]¶Ê'ÕpƒeÒ#w³z…)éœ_e^u"­sùIÊt‰Qi„*ПØF_sÑlÒ-l!÷5ç:ÑZn²A>»k2¬)F‡JÊL5¯xz(tŸ”h££ñܧ¼®'^6kv}äŠ4šúoˆUT  Jª$A¤,˜GÙz„FjB‹_ÿܾê&£1 Al7ªÍȧ…-ÓL •¨L5yÙã€æZº÷½¯ްaú ü,M¶Hòzb`­­Nä¸b¥œ¨ÐwÄ¢Œ0ˆ”*‡ê'÷Lì’EtIÃ<ô±¨Pɸ‰‰gÏö¦æ}—1‘VéfÖªH¥åûVübÔÏR‹“Æ×˜*%­ån9öLžVh}ùJ€ºgV¡/Ú/àE´ˆ w>ôm)b=gpL ³#xÀ =§oÐ_¦Ì|î·/²3y7øÀ ÷%Bf°¢IÖ]Žžê‹°Ud2ÙÚC½Þ“um Pk_Ԅ¬ÄÎGÑã„ð”Àp0‡Ä­ÈÊÑAœ#õ™sý|ýIk?ÃFëà†RnÃ'F3× Ú™ —T%¡_Û¡¨„o˜À!º™ò$7c\ù(Шâ_!ËËy¸5(8Þx6ÊØÅÆÉe†œ¥^“ÝÛßuòÐ9Ý¢°"ü‹ wä4¶"3˜ØiOäÿÇÖœoWúÕ‘;üšrVôÅ$@Y‹[*ž!“ú=g ¶÷(IŒ®—n8Í·çøb“¸‘ƒ„Ùƒ§‰KGÏg¿Ú‰wyFÕvNø¤=˜ã,}¾0sl©BsXÐi||1êÈDzœhê}ß=³^t¿<Î\ñznHÊöÞs3Þ#°7xåɫƘ·«1QDŽÍC[†gúÖ­ñª¯!œÊštàíâåù1ÌZ¾D}œ]»<ÉÊ-ôî NòƒŸR9SÐ>•ƒŸÀÌß¿@Ö~?öÞ¢×ãtýi\zغØ»= ¶Ë |ÓÃ\«‹±.ÎWÕ¦N ë ­hDÚÖ~„Ò`”]•]îÑHg)N›gIÜ…ðµÐ“)ˆà%7DÊP}â/-3O ”ä9úU\®FUÔ­Š»âA!¶{oÙ Ásá!Måô„5bœýXàaÀhzÁ¿¸LÜÁCœAxšß¬úÝ¿³Éœ³¢wÀ8ÑAÇHK_ñÿdÆ–,pdh <ÇŽÕFtÚå²ïÏÈÜJ®ÿ›ˆ`›E)‡0Íë©éX õ]þ£Q,dy%eœ8-]Ö'šʾ^°õ¼þU¹æ—ª‰T¤ (°ZX¢R0ó¹Æ'1x½Û¼}"8ÊÔê¨l¾W FKÓ£˜ÇS£~ÎÜxÕRØRÆC} ßFÑâJÎIž/ª•^Í"©F låËe©›x¤hñ¸1˜²¶§‰·ëÜkX¶KÎCw`|#±Ñ a Ž9ùŒH?æ9þ¤o­ÊŽŽ¯_}—[e  B:¦¼ï,Xd‡à$ªCì´uŽ••f–³ã+T+°2…”[g™”P£äýómê ½M¯ ¶`fX ³!Q<–ÿ-òÊØÔ³ÂZ±'÷g”ýfù~W”ó6àÌ•\ŸÍý·1³Š?»ÿ·ˆ!çÀ¾¼4q;/U6PÇ-ijsÁ¤E´ `«Cï”±HPÇ?ja“”hðnÁí×RÀê÷ uÁggôÝõÝ ,<çÁ( •\¹*O X1øvÐÙÈ7›sB_ .i†éÿgú9˜-ÝҞР3r¿¥ 6z*¡»PPAvåßa¼d®v% åC»¨§èÀíärP«³ÆuT‡]•Í2ɨ[ã^G¸ëŽ#%V|{¸x7üäþtÿÐûgaS£”†8­¶šO6H#æ1êBc^²¿õ¼­Ød{¶Ò¾ùE;0ðÿIû˜ÕEY, ÷g „M¢¹È¶ŒXñÞ.’/öÄÀz\5ÄÏ\€RÊ5̶¯]ø»ÍÝÖ ¨:¦Œª=5Û-ŸPª–”Í,qaÙ‹#‹gˆ;,]sð‚FlÕ´zKòUqɉ=›HÞ@Š¿Ž¹IVÖŸ=W˜‰»Wx.&‹^¬¨ñ¼Cú&rEƒ³K‘îÈe04ÑN^cã`×Za&Øý÷ͦÀWŸË”ÇZTð_›Gq*¥7`Øem‚”{zÙ_¡a(u:ÚÝ®j³t`HVCl°„Ý1s âÈ­¨öˆ#3Éà/{L±b§‘úê:)Déq»òzTAìÁ“ðŠ|¯8ç›@Ê VpyåZ©x¢V|D Óþ KŠ•¶(4Ä ’FÖ('¤HB$L‘ë²ÊõÙ`mNÁ¸çÐúÁP~V}¢ µoUÇëý¤"ãîQ¨:,–èùÿ •[“²Šdš¦¯ß¢nii3+bG¯ƒ,²Âb [Ú`~NV½…ÁœýÐó¿g‚nm<$¹©›¸„4%YÏߥFTî{žÏø.à;2ÿGM§åTO:’ÎÛ9É=)âXë t=H"T£Íîü%¹{w¶w&nQo´M£c¸-jǵVïdtSo…½>Ýǵ³ü µýGàråÂû!ž È•ðÞœÇy7ËK½,mBœ+——LŒAG“It–¬1=ËDœþâ³)ü²L=syÒ±s3…„Ë%nË#³,åP$ºÆÖf¾÷¨I6 I8:àÆãP/øín‹Zbþé3“Çh‚Éö¥÷ðo|6u–ñv×ëU´¢ÿž?xà<å„TD²˜äûÒÀÂf‡ˆ8æDó\lÙõMíÍ|Q¯_ÝY„ŠÈB{H~ ”n3ce¢ æ%X—½ ŽMßìGv@­7ô] žztÿö€¶VÔ`bßf }‘¢1• Òa+,Eíû¶3f›’þ¦È¼ ŠU‰‡%–¸6¢OX8ÄlXŠ=UR½‘+»NYó ~¥O@±ÉM3ß$‰~û‚/3cÉU±îƒŒQñ,-b…ñ‰BÚV£J‰\E–éôÚÉæË ËAر@?%ë±§À$r'ÈYX+¬™_ñ­±ŒÐÍÑ¢z¼%Ѥ)²ÓT„¿üz–„7hÎJSÀŒ¼!ø†‚r»kÍÉý½/(ܺ`IP6bõþ z>·)Œk©ê ¶aÉG$ØgÀŸ)ÛDñ#÷vxrø£^ï¡â@quv¬ˆÏ°à½¯Îñg”eh3û 0ûÂVÔv±¹®é%ô±­fNˤj~‰5uPX `¯Q¯yxli‘û0qÐ=µ©k$*ý¢ÑÖ«Ø,Xh“wñ6CÐ… âªÚ@«ëÛÿ \ǃñ]ÚðÏü¯w÷=­ç…¡ämô~Û”z›¶«kZ˜5bTuÇX"¥#±™àf6K‹ UÁñA öd»ËWë/ÅÉ%µu4ÌñFµóí˜ý@¸0Ü‚²H9„Žë¯G¥»‡8¸ˆßo¨Ð=OBeNÖ°ØsÖ°#2ú5ç’¯=ñ“ö…gÒ›z8>uõPµ˜q qôé1Þ÷K½,ÀS®–”¸n œAš9Y•|òqÙÁò:‹÷Üñ®œ¨õ‘äyß1lCn ÿÃyeÒ嬽ö$¨ó9̼l¡èi ©mvq2>íè³ÏJ(ÎXH”…«TŠÎp™V´³ŒJa§ÜTÃÂJ'è!mÒ»ÜXŒ”°ËÄã=7!Óã¬Ô{¥éú"¡™óîY]ÓÊß?··›Ü ßFR´?8’û·OœŽóѬ5Ã*ÃÄÆ…&R,¢Ú‘ÑGt¢ãbÀó!Ý\…Ùd"úAž:ó%I7F8lJ_êŒFªRà}ƒÖ§¾{ƃTÛ— @¡Ïåd“œ§¤Äe']¥ÑÀئ™j)µzNõ©`|?KúŠE¸{gÓ€§:19ðoBŒɸÕSù88ÎOñëFÚ\™§LR"xwHwkqú.9¼ð þ÷~ 9xY‡†Žö.ºE&J|NY‡à5¥…Ôÿ“Æ?—­»€MÐ1Š©Ra† ³ÂnåìxKCÄ-:7tãz]/™ {ûÊHö¨†Šõdõ> wÝAuÝÖ ¸À.'w‡’ ‘ºZ‚G™ŒŸG4ª )ĉP¶ø6"•.¼Sõ¥ýÁ°–"æÂåDÄÄ _º¬L©ã´Ö”’! d°ŸK`¶UÞáö7:—ve.Žâ¶:ô{Ô…Ä'öVÝ À—¹ÀR?"z–—)—IÄ¿ÿS4]‹Ù'1ÖZTáæµ')yªÄ»ä\`çe$ZÌz9†Ç¹—X&¬åŒQ³]aÊdË,ÚˈѵGÑ~%œ÷û«×A÷[)µ#Š ŽpXB'y\ÿ‡ù´/±/¨?¿{h>È®¤ârÂò\*u¡Áîäu­ð„K¼ñ)zí¡ÔZK›šQ«àNx+ 1_ DKÚMÅ+â4Ç9qãþQ°˜¸BÀø'ñºÞîr«ïx€­‹ FeŒ„ÔÑ$ê@i¬ŠVΛûª\g‡¾g¤ ÇPåù zO„ÿYÒHzó—ö¸¿¥ ã·Ð ¾0w¨¥ü¯S¡>nºîJ°8á:ºÇ† qyÑfUŸ½^·-yY"ó žqO1<#WÊÐ,6£ÅBÅê‚'¹ÝÚ¶™ª·ª: ¦¬>ˆïÆøžct­FYxÎijȪß+æMÆÄšUh’ƒX—Ä<ê u¹í´èEiòé.{Ï.ã̱הԅíÖm¾ Ìé+` R!ò$‹Ä„2['Ƶ‰@S¢_ZÊ:ÐD; Tµ‡‹òLAh ½ï'CDx_À¬ÓS–â8ÁKHŸj*ßjŸº-L¯`RüÆë7¸©0Y—jåÙƒÍe4HO–v‘¿’ë9º3܈ˆtxÖ¼Îf]¿ 3’”iwº¢¾2•±ôaË¿ÿø£F|Ù6ŠŒ±7ïKÄà”­7¬°FÑUU2âð·Úö¬lñAV-áHáÉ<áÇüž[†(wP_e‘‹·ég˜ù—)šþ$AØP”´A³L'÷«{´Q_Op5‹9¯q›w*ërLU%Â+)%îðx`ÂÕoENÅÔ4vÙ4*òý‰jLÃwx ÇMš|",¥ç”Nã/’JAÊC¦9öcªúÁÝï5~©ƒ‹”A"„¶2[ÁãEìZðÅ?‹>·L>RT˜5‰é3Æ?°m€°{N¾«%þ̶8œ<«!Îmì‹sâç>ˆÖ¯jÕç¹ôìåW?KÓ®¶SH©Šîˆ Ò'œ‰K_¬ké>RÁ·œ ÓÃr‹ ¡Oa³îŒD!O(‰aª\¶þ-Ré²»¨õÝLÛ—¼?e-SìÝ}îå›[3ÎôP·a׌µj]2êîèþ¥t¯ÒЋôuŒç/ÌY$„JëtŸäâ½–è*X—Bçá;O1¦cP«cñÒƒËW´qC†:Õ¸oî\›Þçiö\(šˆ7Íè%qay×*ÄØ¿¹ìÍ»&kÿ¨ódÍ|9ò‰¬50ù£Aiñ'¯æ§@¢k?}å!Ë#(—^A ‰sxøŠ]%¿¼{eÊ)b~áôfãôõ®ñQ^÷\`KDuÿôsߪ” Pîek¹€{¿º)ßÖb@Z>ì{¹¹ Ù¤Sùòq´#wΩ'³fþÌðéÏOž ü“Jøüc#f!ÛÛ”ç<2Âê *ãñÐH”ŠQÚvغÿ.$IÞš®¬Î£e»£Úë(¹³eâ÷3¢>,Y)°Ùç%æÇ\\°Ú¡¾€¯…¾‚6å6UÅc\ZæGæO®¾JdÅüvÝõ®ÿ‰l™ìÐgl]*ß–:dÕ¹ã(–Ó¢âSÇôŸ´sl¹0wíÍìG×ÒÔùÕ‘oÒh‘]rÝ>ÑúÞi·"°eý/ÊPQÏzõƒ²[-¹Â½€‘?wŽ3¾^¥›mB×ÙJDl!ñ(MïI—uæK0hwBŽI×ÉC' V‰í{µWÖãƠå7aA•CÄ>³h & (+Û}‹èå=TMAËEgNŒê–âhT~lÆîN™¦Jí›Ù’¨á»¡N—ɆÀ¥ x„µ ½ÅÖOLÈÎHPx÷¢4Ý“H¯‰Ô¯Äç®BÍK±i÷.íù•!ë—³ä_W¸òäø{)Ô-‚¥AûV(#6°aeá¸ð<ɽ5LóN¢¡š îS£S&ØÆ³ò gżÔà\T­C—Û©¤¥¯Ðh¹ùÐgÓyJD4ƒ~h@šYƒ·yzD¦`9Ebª§NžBªFHþœ¤Ê2oí¦½Ãž' èõ9w•®zÞ}Á‡™Q.¢Îäå:½ãÕÜ1›$Ib ϳÁ­C”Z|ï»6n5âtÝœ¢F`¯åؘ¼±>1&¶27ɇw?ú…¦ Ì©n³šç2@‚2©JNÌ5Ú ËdÊÈ{‰1Ì´W³Ï#êã”f‡<—UXèXv¤G‘²C²•qm®Wp°S 鬣`oûU8iÜÚ¿†V–â SP>¥,+íµ‘²Äi|w8à~‡µ2Dc|Ïy¥Lf¦Yf®9DöŽÿ,;æÎÚjìÛFhHÓ‚aBÄ…5Ÿ•ëÊPå!Š¿Mª®¨ø·"6VÓß¶N±ÐiSA¸·ÝIr¯¬äSÛ¾³iÀ “´Oú#+ÃÞ'úúw(œÒkÒO Œ«9!ÓD"Ж51h TZ¼ ·½¿ýxP†åãÑ*ÍÆÕV¦m\ ²¢ðZ÷E²Ì¿‰.ä¥<ØÃœ-pnæ¾ù‘-F5œ€9 /®”¤iñ9j.Êïbˆä›'ã}¤Vç-³¿ÍõË]j¹Åð̆p‹‚i>VÓRðžÝS9°V*KŸªxO]rFıº:­ç•5>ýÃç[>#ìßõƒ3»÷A>6~¢SI¬ ˆ~Àx4øÃ­o":¶ Ç΂&¸»›seVïóiô\…!š îð«ôº¦—õ¿st$N÷¿.ZŠ[®,bùœjFX;©ØÕžxÜU¶«ÚðÔÎêë¬tØÈ||ªw1xWÜî ³"™IÜÒ^mß­þn~ÊÌãÇzÛô-ÐXÇ©Ï}15YôËÚ“%#£#az= 6†qwä\`% ÔxI4Â`f=å·&$ÆÄhùMéz[º˜RNm€ÃEMñfy‰ÛVfk4"TµêmÚÛ&ÙrÎ@L*~XT.Ây½(:Þ.¬tñ¸ ý»uæÂQTD£žäRüÝdù%È…eÞp±ë4ÊnTtÖðçÞr˜õk1‰‰ŒÄ¡(öA2R«Æ?_ÉÍŽc‘ülÚeÃU,híÕ¾Ž8÷SC>B X š]w]ϫвaçö5É ª!> Æ&‘7ö"ýí©;Ü4þ¼u·È¢Q˜~ßœ9e_Wã÷©ÕXˆˆõ·ÂÏð(®ˆêî½R€yë©f𺡧÷LÇ:x†‹÷>Ag¦McIg÷p©2ê¹á¢ $óŸÕ1ÓL„Ï=5† J©Î²Tô$e;rq³Œ‹P¦²±<—å}A!¬2a›X›¿6W¢©Ú·ç_1þÍ&­GåêÔ–åaÇgël) r˜xÊõvÈN†¡ ¶ ÀwlÅØçälkp5Ã!uI„Ž¢ÓŸ] MÉ`åû Áè§MÄÆ«l¹í iû|rL‘@^/1wJÇNL˜p2“ÉSÍKê‰OÍ:kT‚„¦$ï›`áµ§ó¤:Bê€Å€ç>iÄ7pÒp§¯…@–áñt[!¦ÂqÚSÔçS"¡üAE°¨-ÈíRæ—BYgi“¹¡\ª_W‰gàœñ;å¥ÇY~nLG1Ù“²Ó¾4™õ¼p`/ËŒ?þeŽÈ‹X\e}ɧÂA{ù퀵y|é"ñ<kNíåŽÝýqº arjÔé{°¯9ƒT(iÕ'ŽÓJ…#R%›%2íÃßé"\(œØ{DX0ãX2ÖÄȤô×üµ;¥ ¢4³¯9L4ÌÌy‡»i“}›#¬¶½d4ái@çtQ/kCvÿ0`z‰¹¬bæEò‰eÉ‘6|š¤ù÷áì 6ù#;í”_ÀtÂ&>ì%F¬üÉ!ñqYÂÂ…Gª6öÍ$ˆóÈŸnôp#Õyw¾)ŒÅL}Ïuº1¢{¨±ãilìÙïº<šÑ6Ò# € Á ½íÊeÁ/|ñØ]TËŽºÜ2øŸ]«Ü·–÷s‚-î„3°®ëÇ7K­ÜÁ/úeJá?*×߈ûŸíƒQk¤þêã$ôÕñ]——Çwÿ8C®±ØF1(åêõ-Mdi*Ác§q"rNƒá~• ”û¢‚P ºž‘r"6£›Ù÷-?RJbAjö ÅŽ1Åý­9+–ú*žZÚ]Åa뀾Uj8 Â5”†–¤rMÕ6¦¶ø•>ªiºׯ‰ÒïeÒï¼³jÒÊkœ¥ŠøÄ® €Ü“nÓo?»Üò``Mò=ÔV ¢œÌC¾»äêÁ Äf[c5$¤yzMaŸV?ß÷‹uó]Fb—ÅúÙIÿCæ¿q=ŽJÔjø‡ow”…UD8±”VKzBy¥fðüþ§…“HªªM3Mp¤HöH>”²#Ó¹::kÏwP¨ZûdT«¥kßèj‡ÖW‡= É;®1pó÷`v„\:þLö',²}GÛšÈçŒïãj~îa(¢]ÂVßPA3ÜͼÅtmk[n adþã9÷6™çd¤Ö~!¶>®ÁZ¾,PëˆY@ƒ^/`µ(ç+ÕLsíC&öM-=bå,²ˆÆ Xû§´ ‡BnË=ª?óϓܒ•ãŒÿ|öW71Te¿ôä€U9Ñyd¸;¬Cœ9-Ô­WÿPª {5)êQµ†+„ÅQâ8AA*\M|æÑÞ•Á-ÐJ>z³/›Õ]‡Žåsèo§+§9¢°SgWùB\¶BÖ0¿øpÓ¦çõëÔÓ ¨ôpYß¾·™ÈXÉ4…ýEö?Ñø±`0n]:*I™Ÿ’Ãu1Jè!=Ö~Œ°i°V?c.]ÞÁÔÈ ³—PÜ™µ—ª9{Ú d]Ïc÷Bç_±ñV`fÏÓrFyX;D‘‡n¿“‚k4¿Á §žµma{ùXú§åÎ`ƒµBÌŒÑêB¶T…t»Õ\ìŽKK– ó&Qh3ôáU£¦/P9e–u¼ÉOOÃïr&‡îˆVðåÜËa$“ð‡eÞ뽪ÜÿÀ-ñÑð•ìrBÖÕ.Fp$ Ãþ½P¡t|÷®æfl£ö˜U´þKD YÖ+„ýS·¶–yÍI[\K-tÚ¹Æò[†µ¦\>j-3FA‹möŒŽ‹øAÇZ°Èá÷vUŸ\JqÂ&Ôàô1J“µ¤Rë“0ËŸF8ZæØExÌ+cFV”Е+_×.ŠgØd»vÖËZb質™†›Eu*møwà)zͼ;³Šõà@¾ ”8þ~ñ äeØv Ó{l«állbl#‰§„Ìàs­Ûa?Ú·Ajû;`F¢Æ!!Æ#}è5EñÃl…Wn2™~˜ýÃ_<0gÅI³íÈ”=âíÊ3 ]ƒuŠÂcDÙ'ŸÂ«J(.Y÷]jÝåM­E­¢:î­1ׂ¸‡Ip—+ ²ÅÊ@% \ÄT~€*pÚ=œ \Á›˜Êc7”²ü97ͯbkÃòboKžáÊõå§—Þo¤âÝ?ž¶Ÿ­4R̘ê'†-lÈ+¼¶éK¬‰[+e Ür†K›öh›À°TÝ øÒ *þ­é‡A<£ôš[-.'§4H>»îà¾Zþ2¯Ò¦Üæ­³rX_¸õÉõµÄÞ¸Ô ¢GzD>ß§vØÿOpmõ¹_§ 'ÞW¢oÚ>ÏÎðnß@ÚF)I-$ì„ ¸ƒu~Ȫ1Õu/¶X\A[NŽþ‡ÂV3p~Óú“ÃnÉ-+SÆ™;ÛS¯hà„×é|Ñã—ÈÔ“ç~µ’;hÓ€[êú »?4r¶ïÎÍ/w²s†©Ð±i×™•G›ÆVæúò[Ê‚ÇÊ’ó/»[W&“§À×=årñž6ay®ùaa“a Äö¸Ú½4i àþ² ¤ Ê«–êË6n»ûS3†Ì•y"½Ì2=\uu¹Û_iœ ‰ÊãõLoä¶Þöø+Š¡béþÓºtIrÿF"7ÉúŸåÒEmØ kë‰=8#‡RÙŒ“5^ð6¬âíJ™}_¸èÈQù›jÒ!?¹!;·uNmKŽÁ­kŸóB…9w]^Ÿv={š±²dA‹ÖôXk$Mà6²¬‹#Ù.¡ŽIˆŠÇ+KD˱¤ƒ† ÀN¨ø tÓy°+?áPÁ(.`ü0PvYyÒÃÊvJ|z(=ý—¡…®](Pí7×Â.ývôü?¦Fh?/w:ÎÀÏk“gåbš~3Àžia씼â|œÃÁ;îÝÞ]ÉÞôÌïÅÎCâLJÌ §Ûld´|æ´ƒìÍ¥ƒzðk™zI*S>¨·¾êÝÓ'H¯²JnÞçö³s2†¥%gqº'­6:$ø$³æ„]`Hó4•¨%h¸7ÅLF.ȶÄÐ쀷‰¾=!zH7Qþ–UÝ •n -$f0º­Ñ¾F! ˆ»Æâ»øú;_Šœ v„ŠdkHàSM^¶1âºúÓ³ò=ÐÄD´GžqÀ¡:㶯¾©N»O é*eâÁõÆ76Ëò ?Ýç\þp„ • ÓéÑŸBBÇÎ+ Ã×¼øŽgi\S3RƒÎíýðy-h‚ TöÙ´RwºÕòFUò5qXDE]½]kK_ÞÓ¹XÚµ¶Ø^®xýÂ_KÕÇ:Îl ¸LâËq÷8ÖòþÔGé’sïr-€BžŒß¡Éñˆæb÷«i g0(uví™0ðÿÓ¤)Ñë³V|Ï™úy éZA‹)nØp.Nóó²W®áJ'BøÒaéýGaå¾{ÐÄ{±?fÞ;œ3ÈÝ'¥sEÚ‹VÑæ\’h‰NNWñ¤¬ UÀW‹ðghJÏàþ„À ®\ŽdN˜¸dÏçñ€Ý^7ql·:é „¶Nyö·ão»“'{Ê Ü)÷„YÆþNñß»@¸Pü¶†| -Ðø…”Cª/½°‰«¯¤×.\px‹&vûb´Aâ> \6²ºÔ6 ŒÞ ÖbRG{TÔ` ç¶ÔR<$ Tä,*`¸´êJ5U]s,±6Kv“¦"Ó`p¢3 $¹|·œí”ÏtÖîÖuŤ¸ Ÿ Q3Ôß‘ÍtѾ"Kˆ ǪI´o+i=ÀK±þnaÒ‘|¸Í»f«Ê-kÐ3¤i]šé°RP·3V„óù“Æ‚÷qXZcJv^XÐ>¹NZWMsZëù‚,'7; ‘ÔN¼Äöõûfiv½ø”îµëˆÁ¢¿L}-Ó§X2`êqM¦·€&ŽÏaÄ…¡qŽÛñÎèƒBoºwÝJˆ‚ª]6 É7BY¶V,`h>†*‰ÏlëÂLëðÆË;Fh–Ÿyÿ=n$^î=çI´S¡a¢óiµN˜=¨ApÃùU›-ÎZ3¤Ï.Š©âHIdÏŸæ‰Z©Çƹé«“U^GKg1›–åÄ÷þ“C9÷@páª%Cž•øNdz†F&•b‚~™SÁvѾøäLF¢ØŽ+ò‰lÖ÷Íž¼_à¾À1ƒizþ¯ËX”[Þ|w\.ë׸T7U“}°üÀ凯ÄGá ÐøÖq_XÜ!ªoÖ‰ª¼ëœ¦û ò_KðÆzâ‹}’ÂÃBm`î€"ßëA¨$  wX_Qt2†´Ywãk²ûpä*Õè7/L´9˜}MYg Žùµ ,e5Aã'´qùz¢Ût5¨˜=‘áœpçu«••ÿìðáæM±Èãi‘puo…·o6iƒXOsò 92m[bFÑeYüËÊzJ°^Ñ5¦½7†r ªD¿WÌŽ@Ìî:T:Æá$ÔP™StVxk±ìL¸®Ó|ËO‘ÆëK.c#æÙô *÷Ú%LXï²ÛöoeÒ÷ÒN¦Íi I\’ȱÜe‚zt[{ŒÍª¥ŒGwlÓmþ?Vñ·Ìƒt5à…ÑgíÈìü#r’Å~9;gñ}£a®NŸëV•?¤hDçi BÐwBuõ>-özJõj|íoJ¯ãÃX"x¿CÍsyð _*©8dë]óD74x?µ•ò˜ÀÂõ>å@ç”?×/%e…/užâÅDI @Vœ¬VY›ª1ÎÎø³öB$ßy Ȉ‹8y‚¸AÒ=æ¶¼@¨YŠŸ¼ÝÙ›!ÑNð± t Ì<àR‹j¤ÂB…öY¥âÙ¯Š7Zæ¸vØ’ ßï_˜³GÞêÌ­²ó™ 0ß<[Hã,9*`Ç÷1ì†<w±²yoú^Kh`‡§oˆ’dhJ2R‘F.ÓM-áüeæfQcö½WE-â©U¶—HA·x8ƒ²š=ÜX™ê}0IºgURüÛÅI´pèqA¬ñbn\ñY£Bb7Vü쵎Q¶"+&H*tÑ…¯±,·Ø5ù¸ex4Àù'eOœŸl¡Ê¹£r.¶æ"5Ü6Û2ÿKÊÝ<ˆ­®°(Ų˜_m&©Œ…®‡’K¯Ç›éUhP»ñú~ñàë8fØúÛ:2ÿ¥ÂºszêËÌÞK¿Ž_ˆwz)Î~‘f0$‘ªxJ]îäÉ~üO¶M§ÀB刞-ɦãS4q—W$ÆÖ…Ò×Ô÷y³%woxe°Å ƒw»tâµù=zkq¾ðÌ8Œî‚>g|^8tª† JÞ¬¸¤¯_lè^¯x5ÉyêÍâxÊ舃ÂG3qû™ÃIð®©xn~µsf½#D¢ºpË E:e ½:T*Ý#ñàÚ³žðŸi5¾B©¡4ô‡®ìO~v '"3^7~€Ò>µ¥ÅÓ ß5dæKÔ¾Ý8 Ùó  [¯õ<`æ¦>¿ìÂDèõ9» hâ°ŒüšÖŽ2ò¦Xˆ8 ºò£×JßøÓƒ^i1c:@è¡ÖR@×¶ X§€Í³–íøá÷g~½. ÄI41áÜ­0­âƒ°DQyï |U+oQ3lAqÙeNâ…Í…•šçÍÿ¥WÅö²gѸhx&Ó-þ¼µ)Ér®+@ ñ’Sãç €zÎ× ÷÷'ðŒ™ “ø õƒ@£¡ˆ ø(y>¨Ä™ýú½cæu4ô”hXŽÃ‹ÝÝÝ+ºÂºðóÃûéúà:3ƒ²-lódÞwÅD*ìÑ2Oé1ùD4íÕb¦£øµíï)f1Ú®€«ÅY8LEqÏà!––j7]V‡"¶žÂSÈkàÀž¦[¾2“‚e6$e [#4þ—.YâSÐ"Ž«M¬S@áÙ·#Ô8†Ù·^€rCÌè5þÔäõÐ’€HsWý‚±¨%f^¸JÏší˜ EÆïh¹ÙÜ[k +òk¦aúç™T.!¦GjŸÚn,È'Ê6(W.‡ñAæx0;ÉÿD4ÝÄ–À-~ÞYø²–tŒ±uGz ¤è¸rJ»ŸõÀZnî„¿Bïi2ù Z@‰–iWô€WÅ.`´µÎ$|›ÜǾœ:÷‘JHÌ–ó?og3Lô§Îpžty°ùZÄ8L[¾'gu g:.›”Èö˜¸ïàÉeE¡‚T‰Úéâ†^Çá`4¡sg>‚ˆ7ª«è ÞÙL¥ôâ r“ÓÒÂ?eòLY”¤guìD¹ EÒjœþÚmx‡å§ÿšûS*Î-K±ÇÏkƒK£C¬=3¹[ ¦@ò ®ñ¥5sòÙC¸£ÈΟ-B&ÏáæÈõB^¤Ú€p£Šš„¿Sµ+fñ¢¯‚*¥£_z6ohºÖTç«ñàÀ‰å@ðd·î<»ÞúûR²¨‚‡Íƒˆ·©[ˆÍzbœ47a=³ª2¶m|€ËÄŒP>¥«…ç¬* ±¨õÔâ¹<<ã…Ÿ€x“ v‡~‡…pÜc¼-7ó^8Ç$´ü`ƒß¤jšâÐ$uÒµÖV:ËîmõR×:ýGªàÈ-™ïü~¯#–N/({R¸j†8H„èÞéörR[>Œç’ŸÁÀ£glK7ŸM4Dþ…M1Ù¿Ñ!VOÉ·Â ¬bp™D^&Nr|±º¨»G“Í—Âow|^ß j9ÀóÄz+®ûtÕÓN-£ª6ýáÄò_;¸~•h<47%PƒG¦ê±ãçÄxZf]ºfßšôÐ"Z>”Æ`ºI#Ž"p¬©•…º“ÀôŸö…D…s4½飫‡} dCQwCQï„Pàœ «"¸&cò ~›}ã‡-£Ch¾Ùp<ùº©ô®ÂŽÜªG@/ÌõÑÛÿLÍUIP"ìÏ"ˆ„¨më%U…Šëiýo^~s”¶å>‰ýWÛKŽÎ¤kÇ,@ÿÆ7ÅsjbÒEà°u—×8ÖBmóæ€n4>jDç=Ã)”*˨õü¿Å«oÈÁÿÖ'ÉñžçL¿ÇžI¹:åò§„àÛ¶øÜh•‚mÙÊu¬;5žè×0¢í…2!æz"F(‹ò•ö\²ï4Tµ¯5uö‹½ÿŸf¼'i ûýÿ=t{“óìëVõªˆ‡èË!G‹!þ Úæ›Ú?ÓP²$—íoÐu1¬á°L¡ê;gˆòd¹eì{¾iž7èÀ›2™Ö$õp°t¾5öÍ4y~ïìa9¬À4UþÞ~öÉ›^c–”*³&i2Ån­w#Õrž~JˆúÁ¼°[C¾<Ä@yÈdÓ‚õá_„am­r ŠWξŠdjY¸»{g¾'¼KN×&³ì57¦•rsª®jùØžIô-ÿta ÃLv92{ÁݨÜ'¸!Ä…Óß*R„ös .‰*¸°»¥Î‘¥wnO×J-öXQ‡¢3Jÿ š˜€Gª°Þ@SïL9 ¿ |Y^H–ZLý%K›‹0ýYLýBì·8 Ë•ºï‘ñÞ©šùKüˆNÕõ1¦CñH(´¾»ðq•#I îHì ·shî*Ä(ËAWgsŽ»iy‚ž1Âa:w£èHyMD|(ÖMë!ÖÆQ|ij‚ÉPÏÓ…þ¯8t—OކºÔe›qÜ«-ÅH-·ƒXG²_8ÖŽbùǃzš—12ÿ,¬>¿\ÛôŒ±™>”ÔЀk„ëï“'°OŠëOj0më1FTB5Tû·ìÁðã¬g™›(w*ÔÁ ?¶?¾>Îç},ý]B˜ Q ëzÀmãC°%¬ …8Ó.&š!ªk,pŽÚH ‚oȨQc&d{­˜ z‚Þm¹N¦;kPRjé–ǃ|¬ûCt2R§Øu·}¥ÏôÖNqÌøôιú¾’>ŸºXj9Ûi>zëeM¶87žþÁñò˜º@/îÖO­‡wKFŽF.aù;D&wÞ½Àm#i&×ø?Kfj´-'¹Ð9€Å>‚ÈÀPRïâùt»Ž¸q8ç =Ÿë.æÛê‘ \/\•Ü7-ÈØÊç׺6PµÓ1~²¬jc¸ñ³ËnÒ½4*8P@4×ý©ØÿýƒöêzŽœécm}†å<Ñ…žbü¿ËªÄ]ƒ/Ž5˜ÀHdC÷š¾ ö¿·~ÝŒ§_³¤Øñ=sí©ŠW®6;s`ÄWç’Þ &H|€çGEì4péù¥K—Ú68»{ä' þlèô±˜ØÝE‡i·^×?4äZ„ KHéW‘D?=AÖ¢Ùç%c¶5,ÓQý³éeÙr¸R¯f.Fliû”œƒßOýé°¼“•¡ã‡îÔèö¸‰T+¸ÈE.(„ú”ÕFcòcyYÂÂØcHùÆ8bý~Wƒ÷+iv"HÔÏ“Ø8úÕ4$ØÈèEÊ»¬äê9§Ð~Õ42Ìý#‘G‰¿¨ÜÕëJŽž”­EÜ &“#3IO%w¿ {éíÁÉ^±ˆ´‹5ö‚ÈÐM5š„§à#¢µw­*½NsA$1U{›_ tìÙ‘Jî¼'K÷ïjñg TóÈc¤ +È×ΩãŽc׸C$V* ¯Þp"t3f&é¦äáÇz´9ž{9>ô<¸¡šæJ”Éù /7¤”&®7-zÈÂw eF‘¥‘h!‘-R·ÛÖL³¾)/Ðb.|;ñ±•ñ÷·”Ósàö'’É[:”xÐ_b—êO² …Î(„±-4ЛÄΧ°ÀÞø>â<$g³ºòäîªoZ›‹…KˆgÍS+}¹ÿAA…|Ј@>¹µ¡d ×ÙEx¬°vÔϬ»ÄL-!œß%2â+Býñ2×gÈGTnl+w\–ì‡tîPgÉÒ¶°ß»Î²4~0û 6à0…fªYJ¬ ;ÈóÛ&ÅÀ+Lä@â&B=_¹£ ÇÑ(¡­Ë"~æ!Åp*×ì@lË“KÚ'ÿOš\óIr擤¢zÜ›„Õë$ÌNüÚc–0­ ÍÌ‚»šåΣLpìõ<­é–Œ<{wfœÆ†AëŒ0s 8—›@0jÈŽw_ëO(ƒað^Ÿü@ßûM)Ž^·Œ{ùâa¥ãש“Ïf€püoѧ!µz(àHšü£eð‘h–•ÂÈz-4®¤ ÏÖX3Vœ MAò¥QÄ5±bÌœ™$ŠÜx¹6si›pÁÉÂ)`‹÷½iÒÑâ¡^C$….âÓ§Ìèø{W¼ÐZ;Õ¹ÜÉðóúÈ3×:+Å]N×Y*Ö}|3¡™0{UI‡Òalõ¿Ý›ò¸â¢Oös"¼ˆÕl ¨½¯wGJ c¨Âgš+:‹ö²:ÉH½Qtµ½¤¦ m€ª5\?öàwýI7œ uqS´’1ÖÍ™'ªÎ§ÑoDf ]_‚â c2w]É¡“Ë*òžãQ¬‰—¼~€Œ–Ú­×Ò1Žmì}.ÁXß_ìD†¡#AueÖ¼X²vb<9yÌÛâ/§ø"'`âP¨X@ò*šÄ%ß-¶ÉѼ™ € øÅiQV¿ÏS;à’l(•±šN¬ö t#@°›ahŽl,ˆ„M¯NL£Ð$—&mÖñŒR‡ÏZiùýFZQ}–cl…µ¥„ødN¹:¿truâИAÃp..?'Õ{©[ð«¢ôÜé©ã$Ùu«‘mš‹Ï·´øƒÞÂÆñt«¦Ùß>¨9m†õ¿g8³¢ªøz‘€­â‡®&ôÆpWGôÒùöl—TˆŒÕáŒ#oðà•pÆìŽùä ô¥ó,ÔjÂbÀœ…û·z<£Ý‰bñèÝV¯xCÇy|/Úàg˜ÜnQÓò‡›ÞzGDî C›KµÂíÓŸiƒø*i?3¹– êsA͘ e"® öiyc8­ð/öq"€ýàw¯S> ˜êUIOˆY;ÿtð Ôró)ñ!B<Êd~U\q(O”§¥…“F`ð›ñó³·* ïÐȤVyO¶à¼7û¥Æ÷^ÕÒeÞÅVãõ2m€½,ÖÜv4—n$êrmXåeÉJí34 ·¤{TÆâÙ^‘†l<…¢t]ï%¯*SOÝyÆä½¬mübñ7 ï-LµIÖ–$²}lùó ÿ^ÛSª¼×:„îs‡›Tgg±=R ›‹qfW´æT“Ñr"šàAsâ£;ve–fñŒ€9û_I8ð?½–]Þ>5!ã<•--&o.¯h~$ÿ" jì4¦:úˆ[p#*·1Œ[n¶p«·¥ªà²bë<-I˜Ë×9ÿãt0’ËXPµä7Õqþ¨Áüôþ‚7äï5À< …Bå\-lysÿ[ó*c÷cÿª}9ä­p©gm3)zU·Þ¡ÉCë зâDÏ9¿…ó^;âá «!eûR£!*´:ÍÌVBø§Ó? mŠlð#MµAR;ßP·ËùzÍŠ' Ê7™žl+QyH ù”–uâñ/+Þ‡%æg²XÚÞþöÜVÔE{hë–aF–ïf7è¨õ-MJëå¨C ÔëðÐöÌjÕ@‹°üÜ7Dùs»RúØpöw²éDäð‹ 6§ŒhYø¦®‹kªªJÄýɰÃŒ®é·Îx"\Â~…éw²NAa=^>‹ˆCu£è'‹è*ÉÀ¶‡Ó³{ kåyˆÉ‡¾#f–)c³Hf£ç^Ô¸¢lŒL¡8ãFSd¯wO´'‹‡Iʹ=ÇŒ‡…¶ÞYöÙÆâô‰=–„¦¹WŽr¯#q^FÊùêq£·"ÀyawLˆQìÜpo 2‰²þjR­Õ ü`u²ÍD&ÊdÝ”r 6¶©ªTà|Ú¯÷æ‰Ø=fg4Œ ØØŽ×Qˆ®ª5ƒRÇGƒì¥ð•~Ð[Èœ¼­òK$gônÔh–ذÙ +ƒ¡‚,î¢XäóîÈÒ*†9¯ ª™c…ú2t +-!©†õè¡ ·ë>WUæ‹ù#»„ ·Ü49=@B€GúÁ¬´sç+¿qøÈ×—Aæ¡‚Cü(oOm¦®¥çö)Žx„¶£u“UWq,lÝ– nCþIÿ+tá_7Y&AZ º<ë(†žý6(§÷žÇË€¬¯€L­8û„¡ŸÂÕ¨§"IVÎÀ´Þ`üD‰yü‘F–+ZQ(³PÉp² (º×¯‰;Ÿ,¡Œ¬qŸ+"ƒ4±ü„D})E8‹·©Šêðl~•?µ?4›úÌt„þíèPûž±v -,ìÏÊ1/¬äóröÚ.¬—è§áaŸïòÍý-Ý}n½Îí † vc jþ{ÖtÊZ¼#:üNd8¶¿Ø5ôžƒ7pPGëá`À‹*#R“"ýꛞÔ5]—+?îp­8.ÇåÍ>$FIo\FÐÞ¾VÀVE«v*xÈû(KAÄÚ³¥™¶3xÄüh]ó¿ uE{-[ ÅøÇVx[‹¼U"ýzà_#$šÔUwΤÑäÑŠ2â"M‘$£xƒµ&&P¯ŒÉ MTçîZl)L|i/¦ð‡¸¸¢$…(Ø]ÇhûÇÉÏ;L ±ÍL0¢È·µÙ`†ŠMB…5®ÚˆÀ•¶NßÀ¬ô¹œdK™Cöucª±Ä¿ íídöÊÀ{ºÐ{â'8¸,1gUƾ³ÐD(n°¯Ì¡ššÒ…' æžqšE$–²ßn=ž¬9 ¬ç뛿‰-ÜS‡­ú¶ïq‚â}é‹N©é¿Õ‰‚¬TîáRžÜ„s÷œ…ãýDÄPrdoÏlRšRðí;u$%óÏžõzÉ’öyáùðéñTâú’Cç—rû܃é$ñ àn£öÁS‡3‹ƒ>Å á^LW€ŽÅ„´p؃’¾‘˜)Ò†™zc&VZ(ìaóhàX~¤¹Ó*T?)ñæ]Û?wfì§mÔljÑ´÷ó|›¬×c†ë”]É~fcRD¿Kè€î¶¬ÓM܈ã¿b¸%iÊ/AL…CÏ\N†öV¯ßkee*Pt ãv…¡¾¥¶í¹¢,Ô9«€±5–°#S©; Üé‰FGÿ>Àž)ª¶vy[eÔ¹Ýó¢5Ñðòa"Y`/ì¦V ©ð¿û:ä»=ºŽÄÔmÒlZ+1—?в……²sT&Ñüq=;“:ÔÙÚ•UˆªšŠÙíe¦SQOš[S¢ìäd 0R€%‡6‘Bë Hû£[ÞçUyˆCƒ°Uåb_@s~+P‘eFø›ÄŽq¡ÞÖÕæm›¸ln,·*Ò,¤t=qCs»[ñ„Ieò?JI™µV1.ü_YŽ[`TÔÛ71gÎY]Yéÿ-òzæ8y¬•÷©9³5À&;kž±ØHÕéß‹›– u‹´=GÓR© F#!Є7E‘áK”©] X?=gAkÒ<|6ñ{)ŽLu¼|ηô:ì…ȸÐeùE YeëÌ„§_ˆÇ¤*'(Y—¼¡Áå?Aÿ𼤥¸‚ÌÒ5wDi¼³Ò©!†×}c¶z/*µÉéRßFC÷{]t%›«‘#CÝÖµOt©ö±žôG‘'h‡¼´¼Œi]cþÞ_{wµXï£u-¨75¸Ä­ÛžT0ø”0¹´ZžHYD‹òEÚñm†`‚<>«±nºÙþ¿l§é}ÿt÷ÇLÞ/IœuüNØ»Æ XØ]­2r„_K%<¨úS¹(.@½ïðL$¨¸&6¥ó–(ÇÙ:Áluí°½X–û£{Ÿ$((þ¼7¹þ‰žÿÈÿx`3\Ïÿ×½ü–¥½RIâ‘Pºím@\Ïrrª25A³@^”dî5[´u;kÝߥäGÍømY<Ž.ÃN¢Ý "܆“¢šæÈWuˆÎLA_“¯Å³ù[­¿ ¥c¤ë?“ãi‹ J¾z,¹Xgç爦•¸ý8ó8Y6/ET¹¡ú”¨m©&R¦ÉJmqÀ÷Y¾‡…ëo‹¡<®Ä–ÌX >u„5G¤u 3ëæuµßIÈ7ž?—59Kw 4½5a„‚ÍÇ|DÙCdÌFøð â'­ˆþ5ͺhVn£UH£8 ¶G<ÄEÇ–×FlxÒdOŒ´ÐúŒ>ÓO®ºbL1 õaòàìH 1Ûþȇ3;<ÿ ’ç^NUOJ7oÏÎÞŠ ìÑjÓ{¸ÇãÝ/€¹`Lଚ¾–º—ºlùáaý½û°8yŠŸüFýÿŽ4ø?û¸«OÉY¾wÆi0qèVå`w*ì„ÈG“Û;xRæøŸ»²¿/Û·óñÛ‰þ«Ryuʽž ì‹Ûë~O©ª–ê ¾ÖÈSÀo«íÉÄà®Àiî´E¨zòNKý™ .™Ü:S½QãBW!‹¸È| ßsÕÔà k[‰æs$fÞª(qE¯†T7¥)Þ6brhWõþçð4§¬LTuñò‹µ@ÃSezœÉu¸.G ¹QA¬ûI%ÑOŠDKY‡Ä‹ô»LTÇóBn÷96¶Ë\p9çïÒÀ¼‘îlF“x™u¦s€ï4£[¤,\{£)âìÇëÊ»Cµý´ÙIDòÓý* |šõ娝>‚Æ×D{.eä¾À6=¿óNTVGOœGOrñQòNõc–ü± qtJ9#ÍÛ3ÛJ9;#¡QO6ÅŠ8 `¸qÚ™5%ŠñûbtP‘ù¶q#“.î‘Âöv³'JK0~ôÚ ´S¯ãGAݸßÁ>^kÉúŽ­®Â™îîQ8ï ©‡r¡¼V8ãF±Î oÀcô".µ¢C=DuàãŸ÷RÚt‘hþüé±"Ok¨Œï ؾè CÍŠ¢œ²àÎ,KQâ;íîn ò¿úsI“¿áÔYÇ‚`’€'S?LjÜ,¨]~¸»•Å“Á ŸSßÙ¦ÑCq£ðº—ðP`Ù©-MÔ ;Åì}/¾»>x+³u¦K÷gë:œ®hP°¦=™,ó‹¡M#;6C@$¿ôµË²„ÖÈŠõS?–©ÿý/Ö£q¢Ìx„ )à«MJöc7¹†Ò Q“K†áxû)ó„»FÕ$;‰×¦S¬C;pêØê_©ýéu¨îØœèʾ²i÷¦|Q’eÃñÛº¼e˜æï4ü}½%±Š‰Õ=Ä€@›ûÆû¶¶Ú™ âEšV{¼‚ó­ØÝ-4À‰³¸Ì!ªÉdYpÕW]wÔs½Çèe¡²Í82B;ЛƒãÚЄ¤Fö¨’ÇzZo¾ñÙÀÇ0ä/ëÁû?,Î_lŠe…ëÄ¡^›wŽ˜Hª0ÎŒ†I1Ñ#Jõ=„x©£P¹BôM&Oñª1´£ ˆ}’Y¦w~½Ø•ßöØ3`³¨!ÒV³\rMYkàSˆÅ@r'A#žê zþ3]Q¶Gå3Ÿr§’@•mï꣊_üÍND‹Ã x‚ú §ƒä J@ÕP6ÌÆ ¹"fûZn­™Ý( »ÏÚ}cŸu—Ð À¥ •ZNÛU}ÖjÑQ|鑘» {`ÑAsD+„ 6ZÛedJw€®®}åêe΃ÝÚG\ ÙÕ/ôdsëM .ÿïÙlR "f+Ð Ú¶=á2Ø¿J8ä­ún¯1!ú†OÊõ˜iâú¦7©‡Ec-X{×Á†bp5*¾tf^ìÄPVGINT"›I5¨§qô ‚o.ÂP„ÿ}”7?ÁÌ Ù{Ê/Ì*Ròuð½X¾kGÐä'ç“L;bÜTcŽ­Ððþjºe™‰FËNõƒK Êä.ØÝ•tåߤ!>,VHÕÞ  Òr€ü‹1µºm?åNQm¥øñâä†úgñê8&Œ,}‘{±Wkˆ`Ðjš<¸^t •ýã&H°`X !Òh o,ëpŠàÓÇáÆ¡_#`UZæîõ/íþðE(Ú6Òðj„(ÐUÃ!Uàn=eÂkp•cõ–öâð 7£+å⧃ü-›2(&SDÎá ­>5ÿÌ8% VꆾÝ.å¹r"žè›)¶ÑC¾t¹ßä´¶º+fM}…“Ú VG|jqª_ë>›6ßÔ¾‹(ä$ûAQŒßæ7àŒƒØ¸‡+H:Äíþÿ˜ÍÓòª¢j¶¹¤g; ‚àsç¶ÿ°™É°?‡ú#ÇÜœƒ‡/60Ü,ìåjæþ¿Ôk×ABÆÖtãôvˆ‘KÒ¸Ž–ÞYça_56/ã´ù{el¡kææÊ~¯:î$ÔS®Hg'@[ÎòPqX ÅÍ ­¢ÞØÝ§ÍÈX…Ö++ÇE}„õ[[1ã—í¸lg»bk´¡·g$¸ò[iûÞSǧ–"Yÿû‰Ñjý„ù÷« ®æýZQ]VpSr´ÿüëliñ1]Ìîá$Œ.Läß@,a(rãrÀ³ éŽy;33éÅ€·ZWm³»Æ“®ýŸ'¹ç²i,ô éxi‡øDõ-”ñ¯÷ @Åc†»*ïWó0û+ûaY ®×žZø–CÎÛJÈËoq‰5篗Ué÷NZí"Š«ø•$µ¹/.lžq<ÒܚƆþú¹ +¨ôˆ4õÇÞ¥ÏÒÅËe‰cYbøš0Ò"”=fbÚ,UÛwrWÖ&ohr¹Á;6”W‰"á>¡öû'¤ +[ZIÿÀ+¢š@4kÌb^':ÁÚTSí…«hïKEzsÿú±yÖHÜP‚+³#N’÷ oB—Jx½,ÕÀ? À:_:^U˜¾Å!žÍ4…Çð@ã­êï]zŠ>ÐÂÏO–aüÔ×M.þ”{zy%æç1­HfK‡ÄX ,v>H?Z(øŸåN¿ߎœÉJ{ "p_ίωÉîwÈþg|½ÚÆ]ülŽÈͯ¼;2€5Þ‹íŽGÝo`»iÙëUÄÄ âœ™Ù÷Eÿ~ëYg% ìŽP?Dä³ÇñõGJAñ_S a¬X\S òSÔI@%ï.½‰ÇYõhÎ3¦ž7E!SlÒÉ|<3€UG}‡0ÀþBª“trQ%ß•EØnÈïßs@"QhÊ;—p™Æ`8ÝÃß®L—r*ÄÙ‚—Â4C™/ÈÚ3¸>DK6v§mWÕˆV}tO!sá;ù`…à”†T‚)à{ø&Ê€\w["Ì R'¾ÊEðÅgI•¾ð°<èS¤ëÉó§”½»œ8Üðƒ‰“Tà(;LkʵËô\,gaÉ‚4ú/s·…¡ý @¥ÃrîÂׯa±MšŒ_Õ§SÕM1µO@ŽcY~»ý7ÐÛ»a‡4µC®C ®­{hõ¬RÏþØÉ¡£Cð]êË›YKÓž>:}/R‚H-¦ÿWuG Õ°)¦Z²Ø#û(§œ!*û^¼lšÙsÜšÍ-Žˆ+E¬þšy³ND€3«ýý¥í‰°à¶£ï]†Ù„>Ë¢ö‘aí² øjŽÔó_¬‚\zvÏ*«…¨n¦»–ÕÙ™­utÑ6äuúËæýÇX=jïå‹¡ D lÜ+­.ô@X-ýj£*õúÒ'•d,=Qv*ìÄÒ–ÌhV)þ[U#J™ïÅ6bŸ;ŸÁw-"è»2és¿øÖok}«Ðô Û ¡q5¦’ñ²€3(ð›w5Mð–¹ýAc <ÿ탧FŒø%@´ÛO–\üø# Å(¸­±ñŽ¢ªt_$2<}ˆ{þtDãÉgÖ,š î¤ý]™®!PýõQÄÝ»Pµ'[,ƒÜî©+Çf—‚(3YT{yï‹#ñðÁ;ÚñÑØŽ^„ boÖ¶'ˆ …kŠ×gu»K$ËȃõŽø š\– ð¬Cê‡å8ªÕzðW(º (T·#\Zv±T資½HÏc :£ÐhNÐ(½Ox@%p =ô |ÿÀÒêŸü‘£œ…1L<6ÄŸwPEoÕ2UR{ÀBY«5™´£ß©[ð,/ÈŸ0±²Z5NT‚òmŒ0ÛñË88 ™-Jzò_»—Œf›:– áåU4·B{ª¡ä›Ñ@@æl&è‚^}š‚õ90Rø™I#Tüã]Á„ÛŠÀ—õ™ˆb}ÜL›ï!¤mÎ¥a¯7iÍ7£þ¿!„ÿFˆ.ï×vÛèŠçRN % 3«þÉ’¦É–iK—f9Ñã°w\ë%¦ã%Ekèïî_O %£ç–‡U´^+;Øç+EÖÅÍ ¸h¹TÛAfô„õ$J¿“© a-@!^ÉŠh#à`'?FyŒIÇ`ú–¿ öEc)+eå4€|j¥6ý_J`|øO‡Rí¼ÿ-pø?Å–\Ö±K,‡9ˆ5$_Xë­W'GMj zÒìá(\½LJ~½_|°M ñ~jÐ)"ì")«udÏa²Q3MêÙÒ¼«F0Ì*ØIcV¼Ð:H`”í2ý$”ŽÆþ YËqsŒø!¬3˜Ë¢ŠµÌ€È¿r”º‡Jý—ƹáÈœWH3éL×Ô÷&ÅCò 6y@³}^EƒI§ÌÊr+õ#á@°æ!þÒ)L¶>‹ßT-?/Žô­ö§ÀèÝ傇uéÄè9µ¦jÄñsý®Gssöø€‹+Ðzµh%¡s b Ö'¶­©ºVOãüˆ1Q{5¾óª°|†ÚÈWÔ˜Â(|ªWGE Ñf.AÙùñ m´Ñ½5:Rž«¢å"‚Å·y,@$jKš™^†„»žFLkiüæÈ@²¸ç¾gˆ( Ê~Öo­ÆÁ<Úû§XÓ’·œA åÖiT¿…Ìg…ÕKÉ‚[¼¦^d½SúH3×3V7§e½c&¡b.J¦ž,u~†2’{ÒÆ‘í°[ Ö 2;}©Äx n›ï³ÛO²°›sÂÒ~oÕd_ëmÞå ®ù½?4ÿšêwwä>I­_¤¥˜h® ¨å¸5£~ êž›5)´Z† ¸Q#«À:Ì¢–Å“FöõçQŒì] ú$ÈÀ¸ZÈo åÒd°‡Yã3ùÕî»VúWg=ÅQEÈU1Þì„hfÙ*ÆÉBôßµcsLì†QLÛ­¾*×'¬1PÙ$©Còðd¢îJŸÚ±«ª|Ü´0þÜž°°Ó ±iÑ?Û<Ú;Ë2 £¡`q)ãUC®m@†Â8’¡ì¥ã­÷£¯Éï+Õ€ÖBÎWvúå€É®Pú©Í=^¨%Ô³ðš³ÐëÕG,ÊH¤q$„¡&)§[«29‡&oó“c æÂœÜ¡£Ï¬UØÏT=jjÙ·P~—Ç!ÿîÊ•Ñìçãï23áU“ÓÙ€1ï6SûvsØ1eÀb`?ë~ x9ù.›•ªì§ˆâÐ_×)#J(¿|‚Ó¯ Txµ÷®ÂbíOy>û›_¢ÿnHe豩”“ŽóîeÆòÍTX<¯µÊslª!õüºØtxò€ÏÔðMM¸¹}ÅÅã]k ¢B÷\½õ^5pÀ!˜žeÂu-·'wËNFdCP¦Sà`jŠù i«f¾A!Jkù‘aEëö ¡H…$Ü“r¦(½‰”žNþR ßX=y#)ÁÑÔʰµÈ Dÿ›¦ã, :t£Á’„?êÅâ7ÅXÖ¦•¤³rH§º²‚[íÄñ.—©º»iÜ <p¡dWV2¿zÓ7úÆñ­2+×kŠízÚ(6liÓZõ•{¯dæu½½Áw8~îo¥xåÂj8Ži—Ñ™`P«ç˜I\†‡x!HQåÊT1ƒö^PÖ`­à– ”vôqmÖÆëá¥÷ƒ·"õYØ[À¼»°aÑ!ÒgîHþ]z–¹*õ±¯dëÒe²ÿxàŽ–Xma§aFܱïÅ>Žž€AiÃQ.Ç¥Š«þg+gªŽ÷ýG¹Ù øÚc !4Qq§ÔÒà‡.ñøQ¬ìuwP`ØçÌ5ÄáÛ¶o‹šúða_ÃîùÂ+Îp”eÑ©±YT¹kÞÍõ€ïÖÚ{—êAÂ}“–¸xRÔ h?;³uH·ôõúN°õ‘ €GWä¡ß?âLFÂÖæÓ/MbWÜ9—wš+Æ7pTjðA–ôöˆNtÉ«¥(ÀCÔï¨/Ù쉳ª¨Ð©º €§?®!ŒÑk–4hXyriYe ³„ ú J}‰<—àrT̃ËOðCå|Ìk rû5W¬ãk®~`ˆøG® È£Ï|µÐz]‘Øl6®¢yÊÜTT™ì¨àgnZÿ\[‚zÜ$áˆ-š$"ôÀ¯Süuœ‚ºõ×àÌé¿ߪ¬°À®:Ïxx]"*¤ÑY÷`ŒÒ¿Þ€Š¡C+f4´‰MDÿ†fBN@Aè°ŽRf¦¡÷Üp»:Lr8çûšB¤ŽÑã L‰T°õ«‹(#ºqÁÃA§£»Îk ŽáÆ&RM©ÚpPB1]jî7cP4D}(UæûÄ”ŒœMä—æéA^Ò[ ߤÑfƒ²€p(=D(ᢠ©)!ù<~©v³ªèP¿5t|ÖtÞ¿%Lj8Õ+~Ç3ÌC›o¡ |Bfo«(ò%êNû<Ƀ†œnç³å·ˆ©?íÓ<(¨ÚàCsóñ6´Œ;FÏÞÛ¤C“¾²“[ÿµEè8܇ò!Ù^8(Í"Ünwj SJvÿúTc‘éê8B(:]"§)™·=QÅû¢0A„ø˜ÀÙM¦µÏcþ /ßA²Q KÁÇ}ç tn¢ŠÜûÜ>c;ã—ÓÆA¯ŒÑÍ¿ì0Ë'b嫟ºufW\VQ/qä¼'9s KuoRªøWÄÆ³¢5TW¾ôsŒ2éAxم鋌Ó'‘×,«þ‚Þ "Ê…Þ…RRéi¦ùñÿs³ë¨ÝýC2Ù<—ÁÛ°Oyó“—^B¨÷ >™¡ùï÷Ey)¢6eS=&½a]°0Íuž^YÜ&O·)d˜¡DÔ­ÍÊZ]¡†Œ‹ÕQ”%Ÿ3Ì^ƒ ³žD->r…iÚMÉ*(WïÑ'4ïB´ –úŒ?5n!ïnž,EdZ1ƒÊøµˆ¨ž·1´\Ç* ¥`ɧ;[þ¼o%i6ÌV:ÛK”¬g+÷¨d_ U S¯‰9´ÝO7E|!Ðˈ)&Á¥ç!bÆ%ˆ‚WЏ‚غç 4&v³ä \7hªúóvXÓ6›$À/Gè½åÈ–p=›Šdú~‡´Ç›;-9Á†ŽfîÙcê2»SÂ|§Éð0ª§8B\è·ž…Mʃt›ÖjtñƒA‚’¶2tÆ8×ð׎@ÙÝÌŒ—.®T½±¯£‰]`Ǹ}š_òäJŒz€FÄ¥ÅQ׿Âðtcˆ Ë–ÔAŒ¬ ŠÒ¸(©ÆåXê}3WÿIfîD2Ц•\½=E<úÂSI݆“òZ)ò•ÓŽ>}lèTŸ¶Ü}ü^!6ûÐŽDHŸ×ÁÊ¢†uÏü-ÑŸÉB›M\Ñ•O‚4µÇص6ÈŒ”‹˜d*NCwõõ;Û{}6‰KÒ¿‘ù”¿©âòLªsLØÝÀg mègs÷3X Ù+¯C³QÆ'ÌÈHˆ’õ.¡Ê¸?¾É_°˜ÒÍY-çÃB7XèÚ„ÆEŠoQ¤~×I^JQ¿¾:vy¦#ŽVØ övݲM±Øí& Ë’À³›1R ¢6ØM·ØìÆ€œµIÞ GöÅÁ;õïèSÿ™˜¼ “sÏÍÝjÐýaœ¿~õïb~ºƒ›Æ…Œ¯KJ·ÐC×èÛIbGŠ0ýNoèixð+¢iZßÏ.GM? z^íÎÓµÅèˆ(éÒ± K0‘hÖÈŠ*ßxš›C—–],Ô¾g29Ó“WÓ„gÝ󕌔‰±2Hʵë¢ë8,.Ÿ“ÒHDßä/c„‡Ë7vÞc»:üª[ö3[E ޏgºä×™„Ø?èÙ¨W¸•÷Ì¢x·¯_‚<ìÔ@ÖZ—i›=ù¡Bc%ßô4šÝÓçÃͰ^ ~l}VO”ƒaPÃ%~ð_ôMŸ4¡<‡LÍ0Ñ#"0qØA"§ËZLÌ „3ËÔWbõÞDʳ.o%~¢?¢–…Ë=1úסÎ$ÎrúBs×­E…Ì’ŒËO8(š¹Õ‘ûÈO|¿´;‚âuÃUÜ’E:b$EâõÑÖ´äU’ö£àœPTpõË[bN­öxÅðªI:¸þATº]0h$Jð«/Y—ÛºÂo¤R*ó“TÀÞv¾PÝÅ×Ͳì$²É„Åðñ•E;¶îÎô¯ŸÊGö8/ ßåì*–Ñ`e ä+T|†›œž1ëw£«aŽU·4ArsرB;(ÛÙ¢ïŠ.…·{ɱ'}ë÷Æza.{Ì…uƒU²%¹¶VI{˦“a%Ì´ãlï÷«æ+9(ì™ÈÌR7M’8½½åÒ,Ž4€AIZº:Ôæ,© ;XJã­áW”GÅÁß °ß¹aøƒ/ÍʯÕeW¤UKW’N»Ð_™9û¬ é$n×Ð ®ˆÉ<ì C¾#mÍ…#ä'õ·V›õt­drÊ Â)h^UF‡Ü—ªþ€²lu¤Ö°V é­FS6l×éû/.´!B¢og$×·evÛíÐûIswÞ*ÈÄålÁ½ª6TPHu°dA6˜pÞI²¾l9˜!þnã‘8î?L¨'&SœÌèÏôÌŸKPòo({Q&hiu&2/ k8´Mõ4”½dêÈ"TX–ä¾Ü´EßCITÛù‚°òä8 Œ˜m sx^-3þ¿A¸,Û#eÈâß ë†à:O Æ‹–÷ŠÛû桹°ã·…äÏ:e  :ÙÀ.¢5WgÖ^a-«ÐÝ*ˆ ¨ªTÓ®žWlí•·¹zl’4õ'œÝÆ~dìª{/švÜ÷b¼A¯“³óþŽI­%F#¢å/y‘âBG ÂÒ«EYžôôè2?¼œõui:;ÜqoH8[ûÍöBŠ1Ó]L€ Ó_½ˆF|šL:\áhï‘ûþ¯díCÖ«úGÿku0D °íª1þÅeª›åÿôa¥¿¶¹N!ƒÅáurì dÞãÓ^ûÈq.Ù= Îf*¿ÌÞˆhø~ ?‰t«î™ÒOUÿˆï=Œ)ˆ-(Ì¥‰ þb¬F"zÏ8wýx‘Ù)ÃάO:Wò1˜‹(è Té™ïUâUdzŒë‘Ð BÊc®E,7C½Ø\ÀZŠ®‘[Ù³oXVÁ~òÖN¢6&¥ËðÞ‰uü´Ï›µ±¯´P¯3Xô¥e6Û¢ÐÞüÙmOʆs ðžiâ¦r•SµGù°Î—¼|P­GÒ}Ч!ŒÖÿ'é´žné”ñ» `SÊIDAݺ¡ö²…ˆ0 äü¯²á€Áç·0¤€4ÈRákjéֻƒÜð§´D³A†Óµ6orUÍ#¯<Ï%_diŸ2Açp@)çó;µÛ¸SA-Ò ¤Yr£Lm …šž³p¼ÂVêòì§´V4FòÏ65mÆÄ\hPçÿ,‡WT[a1³ãwú(îÕîuL©í-¡—l2î ÞÂÞw Ù»ñ*›/ ¬ïáäG8ž6CØ¡Y“0i±í!I_ž =_-8Ž@!ôqóLOÆí[<ïc ÑäÏåª!]f?ý‹¦¦4ø0èkL ¤›õ(FPgtØ@õ„- äU×21l¹‰ãV‰+<µ ߣ­ñ¢ÁQŒ&lMÖ3jªÜEtŸJˆ¥¿SÍñSÛµ¦ÄÛ˽eEѱS> ·üxb›ŒÍz%ñàd~U,TÙ–'^ó+†—ÓU`23ΪÜh³Â«Þ_i¤úî/ÊÉŒA×+ºe3ÛskðTßÇÏ*´ïˆÞå—!©ÝTjh!§-Ô̱ע×ã•­].Ø3e¡xéìz]¸çk‚[{¤¢ñŠqÜÿºá-ÉåHóC_Ö0k‰A”}E÷㨕ºj‘BíþO²û¨d•YÍ$r.%º¥Á»‰ÏY º|ú1`Q‰¥„¼^k€ÄŠ÷øöB¦ÔP¼Î'Íe·WjŽ0 tVÂež”7™Uš¬ô%ÆbÙµÉNÉ1˜sÝrà6ü|È#ßÜíR·r¦zåQˆWgDê±öºŠ·+ÚÂi ÔÇÖÞšªùTv ëå îþZ+|?S… äwC霭oIÏ®daõžõ¾ÔäC:t©rº4¦_¸{üö5ÃzG¾ö{1â-з‡¥ ‚yý$iX¼®šJ ÞP±T&¿*Ÿø‚N­ü†Ô'Laȃ8Ó}ç2Uç°nÌ;÷ÔÛ–êØJáóbª|¼‹OßΟ’­k‡kd4ò®Ô=~ü¬k˜öÐwc8_Éü©Ô »zš…¨ž¾Žp|¨t˜>á9óÔŽímLܾÑŸöÕãNžƒ’MCÔn:Í€áœ+ij,±F?bÖ²gë»! ô0‹IVÿô ,ÆZò¿[yƒ.å*_‘üHÆ£Xq ñL^S–Æ_EítàezJ»ÑKƒ!D›#þ—sæ¿Lú-cU-%–äèÒõ/0˜oÜ€’܇K‘!@s6Ï‘(ðÖRW)«×u­@q“E °zÜ]÷g–®ãþþß.+­$EEw¾ú˜ƒÖË 6Ó}ËïDJ~ÎCO--4 AR‚HGÝ ÍNÀˆb$J£®ƒz’9lدÉY‚ùÁõäGJ¯ ºMË­IŸð§Wíq›ä$f½@ËÔ4i§s ñW‰Ô+PLèjRþ–õ[ ñ¨Põ¡‡éÀÔ$"•M jj ¶#EÆâÂíÖc¢…Þu‡1W<Ù¼ Òv“¸˜ÖÉ[Û(W&U ÍŸï›O±†Ì£b¥…¢·œ¯S0einzã‹…%'Rþœ}åŠxö&µ‚ÉìèŸàµ¸®g_Mp½:ô½-¥™Îè›`ñ\€g ^ñÖ9šô—ÈXÓ5’õ(¤™yòç”D/õuˆ¶Á Ò2 ®%1ØÔK0ºFŒ@o–™¨híúÇÒ ñ_ˆêèt¾EÛb'I2¾xÑÜÕtÑd&Ò5 ¡ÿYÂý¥è—r:ˆ7@âAj¥|Íg+Ã{ƒg:G³bé]>ðx¹_ƒÖ]%’9WÓB $e ±–S R킨¶N‘{tYqRï~92û˜Þ‚0zjÆW͆©"K  OWÕçþÙ lÏ »–gƒqèÌÓ–ê²,˜¤À -ŠéŸ§XAš£Ñ•ù4µ#‡¶Ül‡W%•˜±Jõ£*ÐjÈ™·#_ƒ€«Ý"À(Éž ÷ª­Iiz&`u #› ú‰ ¤£þJ36ŰNÌìË#½È”FØøÜ<ƒç'‚Äänã—†ùµÚnj!@¥ ÁÁx*®çÑQªYhh›J÷ÂG *òìÌ»Ä/#½°háß?ð„F«%‰S§ºÊ½;аQ³ó14plöxd £RŒã_ò8›^A2£×—0¸t¼wó=G…i¸tŒEî “ü¬«Œž[ŽÀœ;¾·ñ~}CïÖ©¶|ï%ÇÎŒA|sŽ¡ 2H›C(a|púcqiQl™zXÃÐB鹯û>½ƒG²1|‰JGª!VÌF,ºI\”rŠ7Ê=vžAWýŒ1®ŽBÇ7Uïú¯^Š– }{ßø0|ÃQ­O8`Ä‹LsÜCµ)„ÃË·« ÉÍíIIlÐ~ÇÕ3ã•|a¨K&¢¶©“}kÐyZÈÁ¬¶¹³;ñ¿XŽØYÏ«(½ñv”:Á²OEë‚ä°·Ó€ÆFÉÏô¼ºÃ?—Y%ÙZ¼ráÄ/Ô8g¢þTW:>n`‘(h‚E,B­ÂÏÇ6À¥þÌv>#Ò_=ð)Ï™lø²H–©b='ô ãrõ³8}ÿ;lîÄ´5õ†Ôнµé !Á¯Ú z•ž´¹ûµTXÄÉm?ÚõçãÆ0I­’—m¥8ƒ=çaÈÖ˜†äÕ>³6M&³Ô:Ù±ÆRWÐÖ=Ä–ýùf¸àÉ"­°\°MAž³ÁóÕs¬\n¸‘¶Æ ¯eJ¢ºdeà‡×#îùmZg|fˆ••S ±¦F€éu'‡†€“gO}°­îçê·z׫ty]ë m|îÝÆ[5~܆þÌù…ð»¾5Û·4«r‚^›{ÿ×—FoäȢνÙCî 2X°œ©u‚Þžûš!e¶cs®b²þ½û•뛺ºÊZu_bÂ/¾ùÎäIÅ>2ººÀcBñ1e<âe¥QtÚ,Ž˜IØI¡ßž-ÐÞ´Ý܇ǰ—WRÐ_6$d#ОdT^ï[Uʺô¦4ž^Ž—½ápŽöGÿG~Öƒ,šÅ3Ír®TG(¼ß<'ìZL‰¬cžÃßzKšùPl î{U-Þ+ÜAÀçÍzInú†¼âõ°úµ+âX¦9±¦$©ÌЦ6'ôñøê³íâÇ¥Í?Sç]Ç5‚ô´Ìü‚ÙOêBåmý(eºê;˜ëf¶Q)³1€5\gŽV% Î=µ¦2<ùWâþ)ž5ò&òÜofWSë†aÃGØa³ü0øÉ›cVö¢V€q ÊÊáZƒIXqðwŸoSáß<ᔇ„)©,¨ˆÒûø‡›Óà)Ei„¶D¯_¤…%ԛ΃X5£¨n@Œ·r±s¶í©â@ é:ŸÜÀx[¹°™<ˆ|vÇœ£‰øû”†Ôi“9èzÅ)ÀL+æ:UÐ òpþ<ƒw\îF¢Œ¿H滢,Óš·ºˆ< [C‚Kdçx;¼•bfqªaÜD‚„¤UÔ6ÄÛé‘Óû; £¼Œ<êÓLi7Œ'-1 K÷Ê—x.ÿ›Œ•a6ÄÙ9˜87}Öۻľ×t£{èUYÔÑäÀ:ºµýOyCŽº’9ðÌÕ—sbÆTn3Í`<½.ŸÇF8§1ê:ßVëXmE29•ùÝ'ÕÈæÝ‰„þHë?ôkE:Lw­Gp¡I†lê¼í~NPNô€é¨9-¬=Ùu&Jx76ÆìÇEÓZ[ £L\À»¢±Éû0tv±ŽÅ½Ý`Ãí}ÛË`±Ø¸-Ì·À\‡¥‰¶a¦‚}v# >f¸>\„8 ;{â0Ÿ÷ó ÿ¼C®äˆ,ïéÅ)ëâì1dæ¤Ð· PAœŒb0[눚¤PlåcXæËù ª<0ïjê©*þcü,ý¯¶¬lŒl[™u1¦¿\k4é¦Ù¯ceæ+Œêû‘/%4á÷¨È5¤Q‚XO~„cgš!ÝúÍÿ^}[Áó×2ÄØ¬®å*Ìë+•zòËeý¯å¸ÌÇÎ(ÀË­#tÞn[ÅÅZǤî®n.Xéž‹yÖësi=íGø”­ktþ5#¯_d^@o£:oÄ6ïËô  ‘ `ô£ì(v®­Óu9‹¿¶ÊÈü6ΖêºóÅ‘}8+&ðøpQjÎñ¶D¦j‘‹_ÙÛËš-ïÀcÏò>™€•rÚã“ÑšcéøæP ÃÚ{0Ðmö=·ÿ·Ez.¸kå³»>Dú!qlåákR 4%³Z‘©A;šEYÞ°y†BsÒV[³Tz ¶ìOdù‡Ô’ƒïEò· 'Ïÿ‚Ò²J­…a3“ž}¢¢^ªwò—PÃǪÄòòQ¼+±»—/‰» ûÞM AâÁ—`Rí*×+u09ÅUêÃoà·Ñ~qcg:ý ŠO}8\Ò k£Š†›L—Tçú&ž ²¡»U ª1ôçá[Æy´èbª³€}: ÿÿ5aïÒ#r f@‚-ª÷EÕêω@°…;¥®oaQŸîá$È>5ñ±»ŸP•>6ŒïËÓµî…çÎ áú<ôÇE豆 ™«î.‘¬žc^¬9$oð ºW™Uú9E®[>¿WB-/{ðÃXÞÉ®q4×;c%½e»Þóž,,ŽÿZ— ó‡Dm5¼‘‰~R—,|ÆÃ¬¼õ½Yàû³Â£¸öw£•A”C¶[º÷=Nwp¼5^Qo’Mü†¥¹Ú©`Jê7§pÛ[HØBÔNõå´ÛìwÑæìþn"«»Âtd-†”ì"(úÉÖ·a€æÓÏÇê¼ÚjÛøé]¾¾zI¹‡µÞú£¶ ,žéýRÌO›i¢'Üfp ÛÖ©°7jañOçD”~ UûÐiÖì’ðA8SþU l‡l÷0} †x *ðNÞ86ÕêTÂ)®äMkºi9äíù|†É*ÅV€7z~e‘Ï'‹ _s»¹~†0SEŸæ%ëxžÙR=—·ÉËéºK•Á×3ܼïcX`ly}x<^üüŒëOM=±RÏ„ ë[I D?íÈÎû%¤H¸*‰l«¶Zzµ¿Aåð¤&®\÷ÒÉQx$ßn”'5Àgˆ=Ý¢öbÆš²ýL+ú ²îZ‚ × <@GËÐ~6yF¬ aìê’ @KdxýxÐWê¹X}Žc\ªpž¦g~Î#Ï…†Vôä* †¬©ôWã–[û™=¢ä¸Ã¼Ë7*—¾Ó kFÉ52Ö(¥Ö™@×mk p³­ zòëþA©ÙÕ)AÚ¥Gƒœ{s¶ÿ[Y/3u 7i•bç¸ú Iä¾"–è,º~ŽXEVi²TC‹R!â LK" ¤è»ÏÆl¾mðûDÁþ@SŒì­ÐÄ^V¢‘a¤îвç' .Oռф\8‘'³2ºiA§;zÚAyFÁ(Z\†¬¼Wˆï¯¥‘˜ ú¶p ·Ÿ€aîcÑ­ŽÁD>/Ðßÿ¿*dWyØ9Âsò´'Dã"np‡I”‘1<,sǵ¨(Hr õN(ÈþŒd§Ê:j;»åÅßC.ÇÇÔ_£ðùšGçÈÏ•8`Jè„rïíh¿³6]AîÚ¯ü¢ªuC ¯a¸ÎXW~N×ÏÀ!H1Œ ÅÇŒ"ƒÿ‹â¾²¤R¬¥šPÊ45%ê'`™|¹~wÆ1ñ‰KH9*• xm©—×nBûzð¡_øÏöA˜Q~…ÞÝe{Ùgq.o3ǵkŒùAhÃF‰pKL[Ølæ6ÜGeÆe)ôn»þWÂt]¡YÖøû%Öòöiõlb¶•ö¿áÁs¯wJ¾¸CN!rƒGü§‹ƒýmO¼±!-Ù‚nëz(kº"JZ;É—r´Ä¡–¿'KêÇiPÇãØënî†çÅG¸  ÓØ# ³Ôù¡& <4ÛøBöìîassCïÈCU¹™éʼ´ U{¼0dúõX/'dmçÔ²V}¹ÄåüJ³²(,éÀ,é@– ¿qtåÚQ &‚æâôõˆ!]“§ å­ ÏÐ(”ñ´ †{ûÅ3¬]Aìƒ;«¿±Ë‰âù×&¹Xf‚x°éå[®ŠÕR8Lå÷7Mj. ËkÃhÐàY¨Â=ZƒxP,1Æ®z;ŒL®e„]·´ —P°DÍÖuíý¼ Тdü†È\¿ï⸽%$[QC]˜Št_èGŠ R7¦8tÆú^ži¹{Oö‡åÒ3—mÆÞçzZÏ›÷¬º2›@uJŠ:>ÍôŒDvø^Þ¿l»NjÔfLƒüDTüJdëŒ?1Xɉ¥_é–9?°ZX;(\ÝIÕïoÖ¸å=Ed¶79 T3‚<íïhÚˆÝ%°x;gŸÎ"êünµ~OÅy†°o[×ÌÌ¢ªw¦´û‹¼Ra NŒôq)sòîê‰%»Ì?¼7~c`é|Äð‹9Hê°Kd¶¯‰M®Œñ'j”ÈÖ#â3®Ë,@îY€9°;¹ µ­ƒçæÛ죹0÷×í §F_,3ÎyÅ:„Ö«s¢] Ê”“*LAHò#²-¿âY8š%»mu—›IKI±W êÉ1@hÒ wÊ+`ËvœÅ’;\a±¹}Êp¿5ï®Ö…™Y¬3ÿîF¬FÖ¬zl‘ A1ú½>ŠŽ‹ì:b Ò>|AR4 ¤{b~»Ì¹ìÓ õÔÿ$Ç)b?”ŸYà1}€T}ÊÍEr„0Ä(¹ê+ihŒ±`ÒsËÊqãç<—¾NÈÞo¼Œ¸»ˆÓÀ%÷l¶°ý£ÓwÁ„j—Œß‰ÜæüŒr¥DºJ`ªB'÷÷/OÝ®+á}E~¤7wRú7Ñý¡ýÛð[2&‚¢%G¿Ÿ‘€%G:”åßçË©q`‡ ^üÀ¼‚ûñOAÔlû‚uSOêÅqë™Û6ÉùyðšUÉ9à)mÊ·ºÜ’?[¡EKÕ™6Pu³x5Šoý"²èUE£»(3øF©Hþõ®;“IxµÃ·2Æ‘æXxbyÖØ²ç•Û‘÷–zÐ G‚aðÏtÎHñâVÅÝ–Ï¡æ&V©<^þBMËìD5i¤hÀå lÂÅ!š©w´Ãã´e¯s8*Æ&÷ˆnºaÈÂ2sSmHÔéj|‚ÿWƒ:;RÇëP>®l,à$Ý]ÁÎzÔ·Ø*Ý7–U¥ ›Ià¤áÝåRæ9ªâ¬Nñ\÷içØÙ„5’¨RG½²w©èØ;`Hþ)^N$ª^‹{ÂÄÙsãÆŽCùa=‚t©7&@oŽÞ.(|byø>kÜïKwúx¦ë¶qvv„_Tb+<«ÈòÇclrïçs1sU©~Jå|j6 è¢*Âs£~øßÄ5›&1_ÿƒØå ¸ç5´iÙqx|m'Ð8-½SDÌxq“I»[Y„FÚ¢ð½VІqȬœ²‹H!©ó5ˆÃÍúæ™­DÅ·Ê8?[§Ç (Œßf‚|r¶îWmô` c+}LÛR%E½¢+‰C¬ÁC_¤8W Њ˜[¯u~™`4„:~ŸYB)w2y ‡––QüÎ0`ÏìÞ¿5hâøáå‚á³­„%±,o{N}CÆ@ ©«€¡2‚©àûðžœ<À´Î69d+^Õÿ¯¥¨3œ×¸T=>×ÅítYUËVï÷ ÞhùªÆDš¸qRí>N{ù&ðÃæ¶Ñß@בÎ"÷ÝrÓd_.Z7‚Ó= qy$Ѽ?œµ‘ ÕÐÅú„[O L3÷ŸVâì.%øÞ”yHömìÕÅ==TíÏ9 ºßr|FŠ·-|#¦Më9¤UCcÿj½ÅÁJeá~`ÎÌ NeP±Ëër̦ „ð&H×zs#ÕªÐ˽×-ý8 {SøÞ© @º éÜÉTD5,Ð@*[9ÔÊ‚3ÕÆ¼Ÿd¯ÁO€ÁÝ&Íð¨—ÊZÀ6FBÎàTvmÁœÊG¬ÿ·QŠ$¿ épæÞWòð¡XxÓd›hLX %*|.7nu¸ öLU`M Þ¢;-ÃøiãÞSº Š*'îPîoøs“JKªX@ePK@ª­ô=ÃK••v,*鲪͚UU»®àÎeù 1ª³lØs~?ÔºÙ"©µþ>•¬rô*²ùW”Ì#ÕIœL©JŒüØ ª/p¦¯)$¿Xõd¥ÈF)3£Jû~hZ "žBU@*Kbš°¼8À³§(lYa9Ù.óo· $dXÅrŽ™àß@AÛkrw÷7d¯Im´ÓQJÁ‡ÚܳDveêà4–üEßT˜.ºW)ÌãÄ)V$WW$*AÉØnë[›ó.ÃÚ÷ äA¢¢aN b&iÚšþô€o=-©Éi±fÕ:šS\%j ­î ›{óØè .¢yÌ ‚ ¥ú%BU&{QP²¤å¶›d¾ŸPjPô7uíå[D®”¼´¯¦¢1Ý2x¹kªÖ>C|~o€ÆG‚#›Â:³pÇ'Ñð0³XK&Oà ÕÃYJ„µòäúŽø,w;fãzTÇÆ¿ËøÌ€NÒ€fªW©­þ¦ýaº.Éz#f¤PW ¾v 4)—{LýðTêxn~3Ju¯±P²qw üf±ùrúnÝz<>EÖ¥ûíÄÏ–,U·†µiM3nËrn0úPÅ¡æ)Q÷N4Dû½¡¨ª‹À±¤¬˜Jš¨‰Ztëä#”Û"r\”;HhƤÉ%_²™…u¼œ ®ƒqi éñè ÆT§,-|‘+¶VC‡i¹Ë¿–Ù­ïÁˬ°±Æ5k¡_E9_é›×Ž ·Ò{gÕÜ÷K<ÍêSô÷ÜNÁPO4À{®&ðѨS³¯gw¥‡vWgfÈÜÇ&dLÖ¦Ÿ¸]¶×ÞB.9ˆHIµ:ŠÊ2-7,š{l€¿Âú+t˜…ž|¨ÖeÙÃ+á‚Ö¸{L7OD¸w»@^¥Þh%a¥¡ 4\ùýâQ0¸‚Ë1¢Ù~#Á¤³KñDÊžÕüPÖ¥}eLÌÃâø¸bz¾‹ûK3ãl´sÙÑoæ+ËqÜA8=‚ì×Ïh*"%aÚÝ\ýöK0[ù®‘Ty³À¿CžXDp˜MN8É€»œ {Õ•ŒtνՋ¬ô‚Ìž^Í,5N®‹m. }*”1ŒóÎg„lP]n*UçšÑûwR¥ªó—Ô[h×åo$kó"°w?Ôcu;;)›ÆÀ*à¿Ù2ð!:§R?ö£1ÛWYͲÚÊïÈ ]{ó…×ð »9 ­&çyJH6mj\i97†pWªšœœ#¬ŸŽWf]²ÖxRfCew¾d–ÀVû”kâˆ_Ù3×ê3Ç 4œÅž LÙ(esÝýegŸŸªÒÍ“ØÉùèe…NÙµ–µ #Kž‚Iã•b¡5[²ÌB‰ 5À]ÛëâÒzg2‡[ÁúrÎ÷fé ¸ˆKñÏL÷ú™¨º08jÇcà ñ.©¤ÒUÒ±ÉYͽ.î{XÌ[äæÞ¥µïo`¬){(æËš(Ú+pëtº=.¢DaÀ´º¸MW:»Ëwó™i?ÖþE¯SJ@ªNçd7 …wÔ%‡{ª¸a£²‘þÎÎí8Wp²F¥dR1'¸ŸÊ&剡ØÐWÄÇè¹2ëä<ãÆ*š®üFݺØ’¡uÑKL9ÿÆ´b­9>"¡>kÃ21½O~ÆÙ~·<¬œ¶<ã\IC¶ÛUzúså®þ’šIµ†ðYBðY,§àX9›cºX–ûôÖ ª«¦@î]âªå&*},$ü®–ðsÔbW²@Žú¡K“±d¥µ[éØ8²øÚØ> EêRäí[!¡¦LVmvW#wr·n¹ö@wøå ì~4)âè» ¥§Àç‹•oöý›Ü>ÛmS>Þ{¿¨éºs*õUBÖìÞi­Æ¨-&R má**È—tnî€ hw a]õÍÇúòºr{äófdö¼n’lÅàXÄ‘“µ ûçbFÒ¾T/¸Q2é‘Ü êÎcVâVÄEL|Ý€ÍÈdi¸êGr¡¥ØÜr_löõì±ÊÕ’ÿ?1@‘Oý•h¢(ñý‡1>ÒS¾¼ècsr1iþøŒêÐn‡‡6ñ­t\_®íÎt€ëhE<Íq¶C<ËclyèéZ*­YäÕ’Ë%è,îÃù$œ Ki-k¶yë|Í´ä#¥,\Kk_kqMªÈ¯WÓOÆû2]fäšÝ¼FfsBªŠš˜q“'ÉOÇZÑuÓãü #†› KZ7 @D-&uês¥Fžj7}\š‡ÓGÁœÑ4JÃSšA`~^¯>_%1KÈa:Žð%rÂÝ®O×ãF8²Õ\FÁ“n!‡Ç{GËá;:)~õ“]±ØJD<‡ÍsZ'œQØ5ÕRÏ^2"?—^SLÎèØ@«>tÕÛÐ’Sûd¤/E7¼¾Äyš@=yX@`…ðå´xà3ò±rœ¿„Ä}.ÉD%—›%Ô^ÊÀ%ml_0œ¡K™2âÇï$Èí¶Ð@T~6*¹6.W8L4÷¼YÎHâõš â›ÐŒu½(n’F„Ô¶ŽdkàM¼«àòÛtúíkJÐ&áþYG ycúIúV‰G/§1úkæ+Âñ´S˜ÑgWž¿i÷4Ôú´H¼ìsp4ëú—u,ú×[mR‡@v¸~+žÈÔ¦oâaÔÙ ™j ã~°Q»ª7Êã1Æ‚°ôE%¡§ëpü»mÕºÁUQz÷t,jN¾…À#£aû<\þOÍqOѬ_Û°’ЦªAxËÀlÈ)èžÁQUÀSýÖLêÝØ¢o0F"ņT8 îAž1€.FªÝEw®²‚GÏÜÞá¡cF–süÄ(jKE–ZÞŠØ ©ª®Û\or©l͋ł® ­!y{uÖ4ƒ®Ñ´RJ~9!{!ì­KÂVSê´¬Ú6ó©lû* çÈ"wåßa˜¼ûAºcý\^ä¶ Ùè!áÀ‘X³2䡲Z›G$»o–—Ü…cÁø”R§*Íþ0E»ðaÊ#g›üˆÛæètµ0Q3k{Ö,3$õÇ[µX~•¦©0¶”³á°L¹Øòn,ž­ÅI<.øFaôU±°£òúð©G “×ÌÈ·Ì|ré¹vÙʬ‰ÀA%¤ØzÂÑû´Ön»ç!ï@$rƒ‹‰ˆ…¬–É™¢Æ©½w—íÐ8}>¨¢4+ªFêW×Ð ò ÐS¸Â‰:¶·µ£°ð·àBvª<üfß}â*u˜ŸÕ †ðP˜(˜CøÓ2 "S{Òtòi §×ÁæK æ”SóðãEÅQÙ†`Å[¸Imzr«•¤Œï‡Ñ"Rð·Á8&á ŽëTô+0¸YY+HpÈÝ‚7ì ïÖv5¿îd9X4Õ›apmÓ—ä\Ý4v¡SÒ?ˆÊqÂÕ¢£'z¦æ—íyU@VíŠÃï>1 9®ôñà âSEjV&!0±´=ø²‰En­Ç8þÇ:Ñ åœõ ›}°Ì1Â;ˆ5ö,ºXGWc óá;.ùCܬÚÔ‡pFS,´òåWx³ð:ñséò;;à8ðM),ÈD£K)'.6#%{ V؊ƼtúÈñ”›?FÜ‘Ë||½†Ä oÂ÷-ÕÒ&ùSpþ[ˬ{ˆrbì@:Þˆ›1æu[-Ap. tÞ|A‡×}Ù©˜1ëä0(âv’ök¶ù°eŠÖœ2å­V“#µ‹ùµ2ê+"vq‘¾Ï§Þ)øœ®ÿÊ|Úã=ýG<´à¶KQ³JÌ‘À·'éž-¸æ®’'hH:HØ‹²ÈY#b_˜Þ¬ƒ:˜ð³,J*-5ù.R"­Gt7h$þ1™ÉŒª1ù‚„z` ØPŠÕÕo䫲~±V¥±Á‹ê1}dHyk%jþcßZ·Ï#½þÝ”è™è37c4ƒUÏDRêžñɨ+¹°é8÷@v­Ç/ †þ 採aQ­ÀÚ ¦†ÈÎ3Vü ûc.N8ø™sª >ßË€¤ñà 0×H%‡x´ù¦53B”œnƒ@¢!Ó&©wÁ+Ì”ÖÉÜ#ø¶kXоÐGw5ЦÈÖ¶±5ÁCº$)U㤢°NÂëÿ›Ljý²RÅß„ÍËTp?¯³Ü¡W)á¨[7^ºuކú†%á ÞPq„½“cÃÍ@5ÄxI©ò·GdvuÏoÔqOá½µU¶vY]Ù¹eCèûhƒ®ó²ðáE³xÜ.1äŸQ>“ !ÜC F(MZ¢øP½h¤Ò÷"R—0–U–f¹zàí…ÈS¯u`õžÈƒv•ÞzöFMZhã@6ãìÄÒ½ᕽèñ²µÉîþÙ±õbVbC¸ÂØó«’½ÁÎVF(%´ˆBŸ‡[.Õïë ÅÓŸêÁ†…ìôEÞØÊ tü*ãÉÊo˜ø×°àé[xs.ƒhy&ëàyòïéεéiÍ•)ÛРG$|CàDH.T•¥Pÿ 8DÕºô44“\\³ã¿yXWå~ò½®ýñéÉóØvêQ+p/ŒùÛªçJ¹îi•s" ÊH§=›ä¼“ Žq«Ø6*Ø4(Ívß­Á P#`ÎEÃó ¦ÝÖU¥;šqsà¸wéÕˆÁÔ}@zmP9‚÷³Î,d ?h+¬Šµ#7 Šùã´rûpœ|Á® ¥ªî>0v¹3ØMÅÊ\®œîèp¸÷ °œšê^o`Úå­©[Mþ|$§¾Oèn4ä›@wwȃuÞÀ±²ï R [gÂi•5Æú티ņp’:áîÈ“Ç2OÚ ä‘µîáOœÄ4Ç`²åœ>‹&Mo€øéü£ß,‘o äîžünGŸìl!þ.Þ¶ÿ–;od¡ÓS<žO‹ˆóŒ"²}'¼5…mÁq-ÑÊ™=ÿYßpWû#Ú¶-dÞíÑŒ='#Ÿ5ƒ—¯2\>5ÔlN¯’Ý*ü J›ÂrwO•YŒ`ñ;èi•=¼LS4xÞÌ*SƒÂ@DKÔð¡•1¦LÁfUV³Ð yñÇl2­d€Nè>e¾©ÈBß €Òæ{È-uWÏeŠ®YÔ=x˜SNC.YE÷¡IQL °! $ä…73Õv7 2ëô£wøÜ‹Âã¶]å@M²ª˜÷òT«Kk¸ø/Q€¿ÞèíN/“À»Nbq'øP»oÌÎäÕÿú¦·“ƒ4&ÕêF°3zÙ]õ¯òk-"‘è™?—`w¬ÑŒ1üÔ[ëƒÃ•w>𘹚“MÈ7bŠ…n› ®„ÔLzº#W"a,R¥—ÆæX'“iùfðjŠW£êc‘õÞ. ÖáÌ· ²·æi­#q$?T°2xÒnOO9©3vF)\m ½®’¶³êššPÿÖà' lˆeWBúg@¯ÕYùÄíýŸ_wjªcPù ðlÇPùÏ5ç²sIëÖñ_ W’ƒ󖞇§^¡aR<õÔ×ÃÄ©òÏKó; ñYZÄØA  uõ$m¨jÀ0ØàgV$›¶û^‹¾bÜ®zË¿_hVUxRi^ ’Qê†(ظÑ\Ë‹VBB„=ŸžâÎJχZ݇óm2ðŒkª!ÿ˜ï®ð.GÂ`˜ñ;DCo:‡¸(”²Ø3fÛª{fÅAÔõ?—p³^¹>š9€îËé€=m<ŒŠU£=‘ÛMSºN̘‘.ƒb Žl­I¸ÊY)ÉÀp÷9ˆqRÉÒ/G,- TJ†¼™"+,œðAgÁï_Ÿ2Åìå ðw.練øÂ¶Hh]qþÁWƒŸ=YªˆÞòLI›ÉsΔçHª mêFpÊ9ŸþËs ‘äHXð(>D3J€Á%]è†n¤¤pY©ï‹*Ä&Å‘®ýe"¡ËάíÐV»YÝ¼Ž›¤Ž÷,á.•·¢òu¾cèyœFö‘ò|!€…[bˆóP¿¢ùe¯-5$höšýĦëdŽ×$~vö²Mœ¯$Ì¡!òVòæï‚—¹x”·C¶¥ÜGžâsÃ'…O*Ò8mœÓ.÷sf5ÀhàÖ¯×ùB+ñíJÇPµeì%ÿ‡(¤ ´„½¯Ì¬YÀä#D¡Ää.Ý«bÝ¿P°uËag±]Vö"\ T•”M•a7j—M7‡†®tÝ@Kˆ<¬&¼Ó‡K’|Ÿ1ßjy=»€ÙTÓô6k-èEÃÒ@Þ¾j^ÞìÎñL¥-ñ,¸`_ümÑü­lYš5`0ÚݰÀð> žÖp‹ž=ÛxV÷°É@’tÃ^(BÝ|•G,Øj‹´Æ:¹ çÔ©¾b•YåJG|œíM[DgLŠaëg»Õ‚U±píÈîÿ´î˸Âtðöp³ æh{”[Ž.YÆÎÔ÷Û¼ž$Q?õì¨/¼¦“…Š{4¯Y)ÉnDô~éàgɃâ"Šn?±ºº¼^R÷¾Ÿ"Õ+®Æ?Źà\Y. ΰ@$~_ H”í^Ø*MOhO& ›}ä7œpÛ`i…K»MÏœÜ~íuá´ùˆÎ÷C9TüënòÓâ£4 7¸# eF¢‘nÝÊŽÂæ©ø˜§æH·‘•þ·!€{ßjˆs{[W#ߨ²ÈÏÈãu’‰1sñ‚‘¯ÙòÑ$~W§lª¶þàI|ôæ0µ¾¸–òs80Òs#¬›d°æ; a¯ÎC+“â`·Áú)‹ 9\ßêÇÒN=»¥™‡kûûö¤žål½ãÀâœYÚà™2ˆ™ÊýÇUÀD XyRikíÄé:A‘õRfúiQŸ_±ucýÅsßvFF†ã̽’i­p‰ ‰éPúEÁaéF+<¿YÄSù_7éG1Ó_£ïŸøy2µj0­þd®â„ó/Ÿw•zìýC‰¼—b{†¹hÌSn¬qð× ?Éê_m.ú{È{x5ýÃé…>¸"©Æ ù‹éø·×Ü\¦jUþ`™Éå ˆ>J®–žZߣ}–åZlHïJ0(„G¼¹ˆ#³ù×iÍ!׺w´ FVqƒ¼N3ós´ÄÛ{ìðõhVñs—/v‚‰ØÑ“Ș€È>¬D÷9[)'4²˜õN~%&A¶|Eÿ3(w- žU.ß¼oNHÞ´/›µg=¾ÿWB›†’Gk©wá;ú85^ó‚&À·!Õyn jR˜ìîέÌ9Mà@‰¦YoðÓßä¥KÅ+¼{Ýì Ec¦Z¹’ÉïÄ¿ò“Æ4“H$:÷Úšé:lZ+ù'¸™nÎjÌ‚ÎIðáÜIâŬ(°E”HTd+´e „îHºÆ+·ìÈ…iò"ÔÚ‡y ½fE—P#n<¡ 5¸Áá7ñ(mE«óè0Ûždú4žnØD!s=½ JS“kâ°Å®ïäC䓃>S¬{ºtí©¤U7Ò£ ±ÐqNhÖ‚¤‰–¢20Š* pœ¨ÞVHÐ&<¥ôÀÆ -î;ðÒ°ÙÓïÞz‘½ÖݓLj]ˆÑXY¤Ë½›o½•tý<¿>øÚ_%<0¸­ÌïìùÛ(÷øZ@ o{ŸÒViÒ+ñþºôB”6脟ÞQ T8ŸÀxðŠðp1‚ec'±žÎàˆi \Ø¢¸VGLð63µdÖ„ŒO>¢€Z=(P5ø(|uŸ'f§‡^ΜÐfïrï®+‡7ÚŸ„cQ/ ÖÚµp“-ß1(ùy¹úJ"¤d —˜AaÈTOäzÕäÀ%h)Œ¾óRšÛQˆ¿‰±<óC—:U0q\gä /jñnœ3ûfIEkj·)SØþÎ3vuæDßU¾O¶Žó×€t»ç”§ŠéÌVŵz—'Eàˆ#Ô^íá`9ÕÊIŸ’ªÌh¬Ùb^ƘVèGæy׬ç>ñm1‘,Ú“½ÅS*w¾–Q Âpü[-RcfÇ9ƒ!3$kƒy k£÷ú™Ì³IABšŒí…å’$\ÜîQoà!ˆ(‡)U1}ùr² NíKȼ@#ôãÁFS¦¨{ñáñ´ Šðü<+"Ý)2ìZ£ˆ¡p&çŸ.ßOOåEÚTËÈ4“‹N,\´ÃønSj4KôèÛ;ðr)ä‚5Šf0"ÌÃ#Âa ’8æ¢sJܹ p¿trCÉzïìò]ŠwieL‹îÏì#åOî6å?ÿuÆßéàn ‹ ž$Ñ)†«Ì•!.k™ó½i1‡'ñ•W¥Pw—P´ÓÜ,DN;È”Iý'y÷=Kü°BUx±ºAVˆ’$ÿ‚>#bêO““êC]Ь¹æM¬1°öÿß"ó©~ˆgö¿ Ň’Zñ`}Þn=aTC‰=´NÙa¥£®^¯k Ó÷FüD_ "bòxAóúJ¢³i_\4vòˆI¿!®æXL<"EG/Z ðÆaWMz•±yæoµö™ xú¾“úhì| ©B¯³w߯O»·KÛAÛS*&áý½©]õ[T$&âÓWÖþ‘óÁœ¦ãÏû/WeG^.õkŽþ¹oj+ $‚åVèÑ`óQœÀo‘s¥,f”JYy)3DƦôfÝ/R¼®zߦ]jUëO1p0.”8D857ï]‘Ý&-Ìc\óø¼79ý›–Rà€ŽÀÑÔµH(žÝÉØ& VÈT}¢©¢9Ðò* ³ÖjÿÊ9<Ñ ç¤ô>}ÆauýS/y%  ÊðX®p¹ò\CÛûtdKE7êœg¡‚’©OÆWÞ’—òÍ3“C: ‹z¡¦¡ÏƨHäT[î VðË#‚3³)´Ú{S(éÀ®ú#ɰZ`]ŒT²Í§e±ÃNžëV6¹jPåsòòú|ï üf’|˜ûqÙ‹×a'ýΣ,WF7‰Å{‚«šŽZ¿dÞ±‰4óò¿õZ¿½ßö‡OPÝ öë•Óă꺫`Ï$)?ËÚaž¦eý)U5,jë^´j3ß—.Ó\´·w‰)Jø£ œh öËX8òã²@Y´Êså¾—¨ÜÙýþ¥ÿ°Hßâ \1nÚ4©¿ÚYd™¢)ø†ºÛÙÓß ù¯C T^‹èc}‘¹Í;f£’cQø…þ?k‹EDŠgJÃ2ð?^Žë…ß4Âöo†ä„ÓŠ (£)!ØG›¶|uxn ¸’+¨úó’Þý4õ”æ£é0çÿP ñ°—.å( Òr_bÿBt¶’ÒÙ|—'iR0í¤@>¤Ú&¬¥>OË30£2p½u®¹mÉÓ7÷:^]„„ƒ-X¬…YœhTˆ>\-sAs ~üª :ÁþIîZ%ƒOq À‡xš€õý2b™ïðPk_ÀÁlÐ ʤÞ%K‰Ç­† :ÐsˆåqÏR}½W†CßQ¸¹šFOÿ˜ÁµÀF± Îò¼–Õ[‹ÑG­¤:vžšà­ñÜ€•|a¸œCEU¥ïÊ1 ê.\–iÍ(n²7¥Ú£c°ë^Ì) Fx‚:|û¶/4¸Ÿ„ú`­'4ƒ(XLdغcå5ÆÖƒçìHgÞÈq“J»dp%¨•cù6˜Ãá)@´œp ”§êh¢yš‰Ä\z@|„Œ7«Î î]wŸ"ï?øÃa ·•>.87Çþn„+NŽð•2òÀÃúVÈëM?™Å¿·¹±ƒ@*ßë9ãå‘ö°K™P±¯ÓKKE~Õ’2•Ì;E=Éýs"o²"¯ X«i÷b\âcrǵ-.Q)øþ¶†¼0‹%w-4™FÀ0³@"Åùµº¦]ÁÀÍ·Ý cY¤}–Bs“0W‰O©G&,[ÅÁˆ¯ ¨Ñ^ űn4¢¦"Ë3­“êÔ¤#IsìØË¦4¨Ýzy®wÕ#a°5/ÌUy@@^BÄÇõjü¤û"ã-ÂS)" ûÉf„—p)Îf¥.£7´íPînò£7^ø{ß³ú“¨ e×F] E¨È’Ø‚ ”¥Ì‘¥}Z=ÑćÔÑáj×;ÎÛ]µÌþîƒÆI/ ,ï8çÖ)ö¯+€×ûP7lPE1R_xfüÔ²Až‹>;%Âòuû VUvgäÀÌÃZ'й1)øå™œ|Ç03ƒ~T1Éõ^¿Ù¯“ñç4)JÎíÀzv¹rz`Y(>ÒàÌΔvêî!ØÖLs\Céen×ÁæyÍbð­«;ºni¤ù& PŒ¡c0e)TüVŽ å‡ZoJA¡Šš¶}n½”™ÍG=Á.Q¾=)ÖIÍ&>ÓAùž‘Û í…ÇõŒ+ÞÄ×€iÕã*Á3– q}‹hT(a^ &ò(Øbéã4t»OšÈl¦î0R×65æàÄaò*B£ êG—½G9—2¿/à~Z Cíäçb’ÂxMÁNœ i›qŸ.ÈA½í ¹…þEDg}ªøÔcÜH ™Ëo!‘¬º4!x`ò ÆDû(o©Í8hzé±ÕõIL aÕýÚă{wEîåÿ'[ó1(³ÜaÁëö„ÃÜ{Ü ëÑùðÖß0ƒ/hú9-(ø”×SÊó`‘';xõ&Á0«ýšÈF{Ú–—¬ ñÑ"XáúE¡§—œþ÷½@´!Á§ÖÀÂN’»a²bu3Œ9;¼qÞÆ_žç£$É+ 6܇ŽïT’©&œ(j†7ÄÅ©­çD#ëoégõV&grJ%ض!ÙM&I¢ óF¨Ìž¨èS‰G§ ^`ÑFÉwmÀߢÓ*}ç&ó”¼–EÚ@[Ä4ôwJÀw°ÐǬðVnìªb2­N\Tôd÷”ñÆï8-Ób6Ä!«y›J³Ýã{¡çú¤‹_•ÔÕÅu\‰×1ðxe !K7t¹˜\5{Ÿ/*¼Ü`Ó+êj ÷_ásCS"¶ª,±kŒŠT3ò`@q²@ÉÑWx†ÇBó>µ‚³ ÊQç«§XÇ"Tò¥óz@G¢³˜®×[w»Ï¸©Oí3MŒÂ¦”Ó®êOB ÐLx { `ë8zF"åRqº¾¨jœó£‹r.0ѦÎ/w4Üá±jõaLæÒ/­ypUEPiG|ÞPQÜù§©= æuàâd³ÀqÁ˜ö*a(;ÜáâÊï¶(ºBÄC:ÐìavЇªË`*)IùsÛ œÒOвã–vÉ‘Z— ›j;ô«eÐg)o‡È¨Á-ÔÎï­^ÞLXÖîñ™ÎOæò$MdrµCÇü~ɉ”Ù'7ú ¹éÞ¸•ÉŒ¤çnÞ¬ÏåØ91ì |ŠÈTB¢øv@N Äø\¶ÆÇeHÇ>qzïÒß+£éW~r{…Ñ&Z:5–U]MKóþ‰)UR}çÒnþíJ.Ÿð}=I Ó—†”¦Nwe_aÈšÆ/½Aª¬û­ëB« êOŽr iQ(bï3ôt€‡¥Õt U‰áà fX~oÍÅ7;Õöd£‹6“ñAøØ+-C–¥3íå:xx™ó‰=;œ1õr¢éé¿“¯–|òKÅAÁa ýb¯8‹+qêÝñ?ޝd À†…6ÞÄ㼪ÜPŠ@„d—fc%YæŒÃ½ëƒ9*F€kì¯Íh_H>óÎfÈöÖ¿^«—³Úõ‡”—èå0 å ~6({e^j°•@3(DNذBi´n‡ ¨7ÀIjÁ0z4Üå ¢ëÊ1“µ\+‰äHSg?sÐÁÐ}šØ“-å·HÛ—¡lc¢f\Ö±Oîs¢*žƒ‚ý$uÓ™Ã耒V¶ß`)Q¶ÄÛÝ0@Ì%1FfÀˆ´DYU‡dÝÍg‰š§òšSŒ5P5'ôþV ÷Ís‰í¯ågŒ'§ &âè-s=K †Üu`íÏr«Ì¼é´zä Ï>¥de:{gÌr¾|h‰˜,Õè0xú”Ê$Aê @¢Ö&Î[®“a39áµD{Цée,ZZ Q²ÕVn®)Äk­·¶ 0U‚«ÿ úúœøíàd;ËÝȫտ(ï"V&"¾Ñ—±ù²ª)†«ao“* ‡èîÔf|0g®¶üœ%¹âïP  <&[–_ÙÆß%½ÒA2‘ÝX_öé"\mUãW œfÏKRÀͶɅDYŸŽ¿«'-ãÅ.Pi¿e¿‹¶¯<ˆ4‰¬äe»ƒÿ+˜!7|cPt#ŽBÒ·ïRåѶè¹,y£GãÚÔr óqž!߉(½ Žz0 H ¡BúÛs'ƒTD„·Û?\´TaÄ!ö¥m(eë ­†$j•äþî9èš‘fH`yÔV½¬8jXHÏûï9„9ôäkcÍÓ"A!8{J-n¯ò-b Óf϶4ªí¼Ø¥,‰nëdrî?­Içpoð—£-ì„À¢eæÄùnô"÷"ØB0m0Š¡.Þ­2©ànäâ£h"mß·«‡œ#;æÛC¶É¸ „«:$…°KYý+Ei&ž¥4e»®!p™¦_ÞNA V-WÒÃgÉ*9/µ:GÝ\.Qo†ªtJ²EÅQmN›×Üm Í“FˆæPÛQdï Çö}¿ÿX“8n’·k‚|rS }ýìAF—TÒ4 g& Û½aQ%è—ÅŸQ81È㦊£B*twhkááHÄriXè$ëðífZÿÈK#ú??ó‰?áçO:wŽúqµ`û¦žrnW q}%w'Ͱ©meÕ}áÛ·mHßß“Ap@Mϲòœ¬òŠêõÙg25ƒ$¹îÙÕî.-…1›²ê]j/•$''AÎmŒ2U6w9»í@ÌÎò£B®]Ú’§dï¿ÄÄGõº½_ÛakCny Ð'„Á\»áÃý† ί`¼b:ÈÓò¦š$4›BêP µo×»©©ÚB½¯Ùÿ70âûî.mO5ì諚P€¨¬0¦kï _.^E—W½ÈH™í˜¿*â/9%î[ÊDýÂ"täÍ|Ù4©©Aê»(;c|X“DG†uvJ)@E¦–%KFw³\½ ôœ$ ¸ÅžXÁuö1F¶'TkãY8yýÔø£WÒ©“è ò cÔµ#_*ÁÖdrÓÕc´Ü¦¦öÖDÀÐ5@~вP{ˆ4ZœÿYî‘oÚìä,÷ëúçºAí\/^C8.è «WÄZËzO×yÏN.LA³Q«i s`F¯ŽÇ¢8\Y™Ý ÏQmLÆA0]©ŠA€ñ°R8¤£#·kS˜znŒ›Ýlw_wÚ£L—ÊÜLÄWiV€VéfARÁ¨kgHXÄC>¢Ì%IýøBì7i`qö”UóU”ø™ßP6êe~¹žÖc0¸€F{^”—ÌØ·—YÃgÑgë%îßzr)¯H΢>Èrñ•srqô%N²…AßݪÉ$ߥ´ ïÅÇrÑAÂ>|[ùXpâr<+2d!ëÄ4W㤖3íeèMSŸTcOZí.¥ÜtËí‡8´kìY6OƒË-8¡èÝÇR"\žEõ3´Î¾ÕÌ?]½¯FÑdç'êáÛq»V»pŽz<~}›´”ºr|‰ ùé áñ’óŸå4.ôADïp¶8žþáCNØ¢.0s¥`"Â|,VÚ¦?æ0ÀêrªÙX “h…bým»q­Sg§'Ò‚òÖ–ŽLG4w`'¸o µ_-˜+Q;\3<@â¥H±—’}Ao%úŸ Ú_M÷‹è—mï¼>ùî+Aø˜¨ …¼K‚?Œ¾*‹*´(»ÅZ [É—{Ì@°ì QÔ'žlLÁ-Wãϳ™íSif„‹‹ž‡%#ò­€Ë¾òý} åèJ̨´¤(úAìS;ãÄ(Ђ¦‘CïÁý’*Ä @[N ]ÚŽ¡Qw“QÜÞùd3oËN¿ßØ9hФNèûyJ]&(o7민1w/ѵØh¾4˜¦JÚd6‡—„r‹`D||Ìɳ¤UÞ&þ–5ާlY©¯[é‰ÜK6ç6®w…5- Õh ÊT?ª~ø,Ú€Úžk¡3a/X¶÷Oz‡/µp†Ÿ‚bEŠ:sÆbKnÁ¯ˆË.𢵶WÄ•€ '£Õ%+ç¡•AŒ †7çê Wf‡ÓmÌ¿ָ×Ùí+Ì6ŒDǃ귫CtÛ}  šPæÛ¦¯båb‰ˆ™GhöæVW]=ù+tÇ›€q|à žwájæcˆWnk;@´28%<ÑèmÀ¯¾tz|Å‹O‡KÎu8"­'t¡ÎÉÿAÊŽ&]cÇæ¦bĸ†ƒÎîÄ®B rùÈàuïÝbÌç™O„Š6åüÕHÅa ØxÞ¦[6Cï` $ÿ þž}îŸ •I/ÐYvo(·µA…pªÇX±¾ŒFûl¡:´`à Æ PRêhz©kÞz²{Z¬KŒÏŒ Ø`#äDÎ瀰#¶+ј[røD¿ï$æ¥ þÚ‡}A¯îo¯iw·˜D³ÌH¾´zÎÒ&kÑ:Ì0ô…o“·©r”ax±Zq¦OWâ­¡.ŠÎýµ/ùöÈ8¢þÁ“/™×û¼,6š™Â²}T5Ë:$6ÖhS-ì`ÿq-?qMQå% G§ˆB¸ï%´/Ùìê¿­Ðÿ·±Â{“&Èñ¦,X>ÿhé6-``F»Ü¶Òè<™²0’E1¤Ó¡ä‰¨WižÓèëåb_‡Í¡ºPÇ Óå“b,É33c¸õDÅá9pù0Z´óSÿD.*V´û|¬aq<”™ok°ÓõnδžsKn¡ÉVGEN¦+iéŤì_ë_e§5Ókʦi »3¦ß8Á+GB’'©­9)ï[åúL‚ñ63¬ƒô”¾« Þ®}§ßùÂ+T¯–=AÖÈCÝ|¹ñm¹¯’ªQÕ®Q aFu¹<˜”ÐM>ȵU¥`/ˆËÕÌÄÊ:¹‰æKõœ^]z7‘(s»4õ¡,£2Äî'éR€ˆ+ßU0‹ÅÑÊ3U9ÎÌ_KÄ5ÛLÿ_“#üKÈ]¾€®8õÁ¶ËDÿ±ÑŠi biàò°Lœ6:»lÕXHÙ-mò}ÎYs¯øÔ"Ö›øá ý]iÃ*‚w§¦:„8õ®!AûK3oKMÂ"㥠Ä8n¡™«KÀf„´$ kG»×»Í 9I:[‡• ˜Yà”éƒÍÔ0Äv9ÖÄP^³ü•R¾\dѧ1rÈnùH& ”­# /  < ®¸uüNz¤Þþ(–²&<},­è!™Âù¸‡à›a'Ûðp§|‹±[®G}…Q®c%ž¤ßÏ­9f1ŽÜ3-2ÉÙø÷µV l–Öv‚Š*iF>?ˆGJu§áÔ2V›)îöÙ¯DS;!îW õê<†j® G´`6 È41rÒ âM™ê$Ä? Ù†ó \V *§ Ó€kÓuÉÉø®ÍZLün6pW-üv ¡E*‘·ÈNP¼mðÝ(ï‹æ„„€LÊÕ–8›Kb^ÚÉ­_§ŒMÔ~Ÿ#Èk4Åv«l®šÜWˆ{`³ ÄS±l#®rÓr—útâÅ”pæKœblã>C ’Û¬Ðø•°£‹µ ‡¤é°Q#JC—Y–ØL(+|l¿)4JrJK-°+q‡šÉgªÑÙÃ:­+b7oFqȽe\Ãaèlb¥:ˆ´S¥"']c#ÃÞõÛ£ŽÕÓñÈEž^b5õ´¸óòÈÖ¤·„‹\„w¦t÷À.WŸ±›bð{_6–9çÕB;t´ªˆA¶I‡º¶ÌÕ¿úœl¤daÞùΖf‚|¨^Ì·(ÁƒÓ/•ò’Þ|ùÔß¿ú……,4ó‡ÙÏôe‘U‚µt¦x)‘ˆ1³°`AììØT®•BÖ»Ïå2AgùR™d…ιG'‰ørjpEȾëxœù܆¬½UÏP;^þd‹¶+Z ܸqëÛ§¥³JʶñZ3#Êj¸=‰nðÁŠTÇ%ßZKÜn0±XWfSÕ a¢s”Ș%ˆ‹ºH㫲ãµ”K /X ›ôß´B^õo:Voݸü¤°uD35Þ§áÉùPˆÇAcdû…5äj›ê”¢»æé²jNNëÙÇÎgÛwVZ#/|v~È›¶¾ËÊ`¯ˆàˆï` !+øo‰(Í-½ZªLÀƒwõè€p™ÒxЊøß–ˈ(Èz{ @‘uƒé¾XßâVöóSÐëD]žX3Iì½9]]ÚŒ>ò(=‰$ÔõÎ|Ì`ßyPµñƒÕF˜XÄH#³~ÿº?8wË‚“™/ŠõÝ„#N0 |£Eÿ2˜T¦x ¨ŠåÈç¤UëÁmÄŸNÖš}H›šìビ΄±¯…$³ñt|Hý›k£òYX˜Å8XÑ5Ï ¾I×”QÚ6.Ôçª*«_bÍc“×tñ„|°r½+âÞ1nx¢ÊÛ¬?¤³2#¼àÿí…ñ@ÒO aœ§3ÌWÊ{@W)W#¼*Ò× ~¥THàÞUäÙÉWãxŽ9!JE<µ|¸Tâì@Âþн vü¶úм7~Í4#ìybV è¬Í–fV˜’ûHO#,‡nçñL´2‰Úœµ‡¾ TÓ˜°R ³2¦ˆ˜X÷…td›Ê2VPÛõ ÷1}i 2Ƀòʨ(°²sêhc­ ð$bÑÈ(­þ&y. P…Õ븭·¢úä«éo½­® ‰x´IBÝx5\&Ù óiÕ/ÚÖµG•5Úgfâ¸ú¥¶†­••âæMð2þÉ>“4í Zè†5®šË`»a‡½ÅnÉI*Ò¡ÓH-•"ðš:v„˜7Ó¬<‰Ãík4MÞüÇjÑŠÃÅó}‹ö~Ë—ÈlMûp6‚9c|õ0z3¸,@ļ±5Š!ÝŽGÍ£ÞžˆCI:Ì…|ø¨^•Õ÷°GÂň¢`E-ƒ†FðU˜çXÒ7˜¥ª¿_T ²ÒÏ|ì™™P‘ÊlÑÂÚ ¤•ÖÔÅî  ŒÇ%Év|pI…œ¦È}ïwåU’AW2GJÍð•ÇÓÎ6¢¯ ¡‹ypIáµ­Êbž½ªiÙ˜jo#à›®6'ý3FªÌ‡@š4`ª…!r.ÿ—pô2'è@èÆ= i«Ï56O¶Kп #µÉÍHÒêÜùµ}Ä~„¡ú5É;ÑŽ®M’1·Uçý H¢ž!˜xS¬aè¬xDˆó¦ùJÎmÖÊÑÿuI€´êÞ¬ S¨¦_ùóG׎Êå«aI Þaie ákÖSx iÜOÇr··˜?˜6$Èz«ÃZÙùÿ9Ñ57¿ÚíŽþÃT»b|’Ns }h< [µ¡2ø[EúË? s…Êvíå¹½(Ó‹]‚Q"íÁ†ÏC'Ö)çµ N™¨±D 4?`彜»¥kÑÿYâÇŠ©³ÚãªõÓE}õÈ2ÁäòõO ÝX^[¨EÃ'É.F÷K³f‡Îbä’Þµlìîñ‚tŽŽÚR[È(ÃhB^=¢Ž\þM5¹‹› Äщ2äêÙmÊUWF(2Œ*áÞø÷XÐYø…ï:ÅSê0]´yÕœsi?"âå<ýÚaÕYzH©k±žIÇ?Ñm¾CãqÃŒà;z\€Æ¬íÞ üHå&˜ôц­.P$§û½8³„O÷õ­Ï‡HoõQ»" Zc=u/»&Q¢T>,Ábak]ßKûò$F§ 5 ·‚üãƒÊQƒ±@éݨ>q?®ÁM|R”EÚWó;U•vèHÃ-Žc¾« ”mëœ×Ôì·³Ý9t¸ã8:U\Ë2âõ¯‡‡Fÿ½€ŒyÛÝ@ Ù”(N¥_=ý¡3¾SVÚÏi·ÎDTîý„ÀS-Ë|q½åÚÇ„_±ÒÃÏ¿¨ï¨ ï÷WW†_ÐUÓžEzÖË´ÿ<‰©ÀS}±€£U]Ul5#¬e~¨q{ÈÍäëÈ;wR¸•¹pV‚U@¢¥ÿ(¶cÁ5*hŽŽGsÖ[šž SAùŽ(»˜eÄ®6óQÀ«âž‡ŠÙîÚïÔÓæ:Z9µêÌÔÿ‰„ßý\„û<özäê‰¸ðØ ¼O?ØíC0ìŒÓ!I\Rº+ªEx¾ÊC’.<ÚW%û—z Iïûš{ ÈZºØÆ¿5ÒXM¨Å±~˜Ñobc@ª‰Ž¡Ðb}b2¯ î"‘Q.LØfbÆþ;j‘b~ªq{Vˆª°Ø½Õ¤æaOcZ¸Æc ?…›¨ûåo¡&®àF3792Ìú¯ Ÿ¦Ø){žú ½4d’·m‰ •7 ªå%e/¬%9 Y‹|)t-l¥š7ï}”¡50#±Ñ%¬¤é\E9>ÍØnxQá4‘HxÎþL½à1œÜ¸\;{èWM`&kMÅL/G­¥V„>¥·!xOU Œè´Žßã—©ÇyÝß“_²Ý ñ(ƒ¯eK/÷MóÒÄ1Ç>PddžW·õ±Ü=¨ßÝ:_WîM¯öv»¸n]=áÓ« $Ö¶”ÇœöuÅïêü[ÉÝM— ƒ˜Wß%xáCIE¬EG¹VY.öl £ïÿ™‹€&-`[yrGœˆ«+²´˜ëÓÕo=s ziÉ}Õ¦f‡ä'$_ùƒê¯bt„2æ‚›¶Ÿ‚`' ÅHpXy€#e7ß=wP ³oo Ÿªûz9ÄRjü¿†žÓX2t»Lú© «;ܾáþ)Q¤Y“°¶Ém01¼þ|PHvGL¥Ç~ö-~Ëjúô#½L*Ýʱ£ÿoòÑeV ä7*¹ÿPšáró† Ü4Ì|•X¿gy{òR¯ÎÍ‘[ºT iˆ91”+ï57VèŸÄBF…öo1Æ4$ÓÞ´çÆÒß‹«¦Ñ[<Qº7(±ÁIÙ;6T‚Çvm­ÎŠÿ E(¸LT ÐÐRªuØî*1²Ï»ÂÜT„]ôCÍÝI²rqAüÙO¹¶*ä3CÎ\×~˜+±Ñ÷@í…0©Wmr7c§‹cTq­NŸU1¥?õ%QI„úÈôŸÍ€)jï$€;ml°øq ëº=(X¹s%SdzbgXl1eÝblõ±¥a[ðÏÏ®U¢R=ÂðØzÙƒh’.p5ï[ö’Î* ‡›ÅwÏÇw+‚ßýƒÝês-<"éÿž#YJNÆü;8¦afÝä…ˆƒZÊö»3­Iœzl9 ¼Km ؘ=¤ªÓ âûȆ¬ljí2j+êfÆ”¨€Ñä~™ ¼_°¨‰ºØã^;Áª±-ݞʑO{±ÿø ëªâ¡f)ã*T¯u7V€5_Ÿ ‘º#¸†%%)«[…ù$`FÞbÍõ²8'YóÛªOjl…_»­„’‘»¢8E;<_» Gø‹Ÿ€HhÒé3üL˜ž«G“FHÊ7·‹OÞ|Ö€}ù¸^[:¡rÞ–Ã:~_nòƒ™þ鯿_Œ}]®@.¢Ï¸°Qwš0,ÒÃigA¸S_k›ÃÇv‰ ÞtKžà2Š&bÑn·ññÚÚ– —äQ£i#D+e–—)r ÅËž’.òfJ—}›Î˜!W/eªXepnçû!íþ?šñp5l\‰¦ÑÒ\·1dy}Þc=%ɘ<‚溿Xì±M¦Až ¸nmìl¤–²g,l’\-ý~¢ñ‹À‡‹=ɆþŸ>1ùÂ/WD ÝyQ™ŠtI¯¡~ô>tYšºôõ¡öOÜè°Å÷l‹A·À[ÍffYˆx"(ªA[¾QCsßÒM·€ÂÍœŒ)켚z¨=3ë´‘mV Ðlö[¨Óÿã#˜|ŸÚ’Å%Ú¦¿VDU±©@Ý GXÙ„|nOÛ À1ZŠ3SÚj|.²>AÕ²5ù>탰7¡¿u>[æ˜s+@FÄ`Ö&/ÅЗd˜Ädnq§úë~9•ƒ$­CnªƒüMM‰[ݳme+ì–9Æ&  ™.É·-JÚÞÿà¡ dºV8ýÆoÙSDp²|­E¼VÛäEo{;ÂϦÿ ¹5¢ÐS-ƒù D£Á5iØk•ZG*Œ!Ž%e'nDAxæËàZƒ‚Gm1OàÅá›Ãèëg}ý×õ?H]5šg '–Åx5&þUÌ«Vñh.÷ì¦QÌ0[ڬ륑<1[´‹8Ù 1]` QÖì¨%Óô+®ÙSù µYÉÞH~“°¸ ¬¹¢Îú9#_úØ_®âØË©Ò$Ö_¬žÈÝö•{! ˆºp¡éÝzà…Or/ –Í'^OгðZâ ×9÷#=Êê\ïùe\ºf)+ß{ýƒ Î«ª-70v½¦ÙœòjÜ–?‰¹-Ö¸„?œç2¥œöß|_UmÜmÇPà«D ŸçK=ÂÜIlž€•ÆPÅKè¡M’N+4)s†ÍU˜>¢ bxBùM±RÔ¶UPìçQ·)3jp]k]´Øž”»Ði>þ "iÇçïÅ~ÉäãÓ$Ò’3* ætz´×1/*1Æ­ŠlhB[Í¥ä?¤ôñï„ߘñj'6–;Y{±eÓ(¥]‡Ø•³¼-†½(š<çZú_è,éÆŸ”På’©˜£Sˣؗ†õNdG1ìP¼þGo/í`MEç2Ç]®èŒ•PG«*goþÞÆÊŒÎŽ Ïg­¥És?•ØúÑàœ^3~±ÌŠ…ü?§hw¤á8ÑsɵôHù?Zê=xñ%ov–cÙb{Òø íN匾8e# (ú—(õêÛÍ{üêFÖ&y[ ²Ï€Qøªuý´FYç¶Ó[ÅŠ ®Çj 7ÄñÄ]¤$£CXQ_ìP%·š¡à—1ÒµÀ̾øïˆ¥Š3zz ó\Þ]'OBKÇ+?%꾡˜OÏÞÞÝÜœ x^ñ¾`©v{'2ô á{ „ªn”ÌêÖ¥ëÛgf©F×<”æ*|ÌŸgú\1•o4ÞR«!B›HûáÚ–ÕÇq_eŠš|•5d!—·Kjå; ÂïD%ÕmJ¸ Ròùi1/C ?;e´l|å…÷˜£M!ËECÐdn7)HšDÑ@5lŒÂ“%c[bœôYiºÂoj§Ó»>»"ãÐwˆÇÁwº1¬'&4dp¥5•ÍÐé«”˜¨ò¨ÝûÚ½þ·Âb5Íç‡Cçk#\½‰=' ø$9‚¿-û»Ë”æŸBq2 Ïì7<Ž]`¿]¾a†äXÍ+»LºD6¡Í¤·™)(Þr¥ÅY¿Ö®A˜â80Ê&±gº´SÙt·^Ðî“¿@{<Z“€ÝPœm”m‡Úå—׏· ïÛ0Éœ®EHWZ…Pmž¸KcžÉ!E¯âSþÒ£ R3fFûA)šõÚEî Ÿ:3ªãD ” L¥R¯©ªðY#ŸOõ˜ÈöŠÓ œäoHž.Œ´MØ4bŠ:Ô»³ý‰ €¶“ºODšøBÞöjÄHsÝÊßôÁIÌ¥Jéð¢v<Åí= g¹…â7$1©úÍ©à™¦WÂù¼þÝ{ò7Mj^x‘G¾Át.$xÛë^³ mL¥ôXßýïÃ)¥‚žÃÜà‹ýŽ!æñ5|±¾aQ– €I$ºÊɪ5b_·vNºGL¨mîÎo‡l<´ÃæY¾¤#¡þ uì¨j–@¶ÉûëËܤjhÚý”ãk bK[Fo} ¶H4_±›±¹hç&öjÏCبø×Òƒn’©ð÷‹ÖÌ?*¦`w2æ4G³;/„¡íÿªÃeйx¾v±Ã:RòåDùû!ör íò?Јcs·+hû_ÔÚ½_z 5&4¡Ì7*$‹°1ÐcŸ¥ÓÓ¿9¼{m‚®,ê¹cÖ£ÇÝ -Digl#´ß g¤Å>ÈL…Wj#w ®Àð¢ÖIV±¯Ù°Õàè¡Ù–,rKÁ¸K¶¼XÜâ)±„_F˰å¸_|ÈüsÝÅÝÓq†šV&Ù]¦=’WÙ]m¦YhÎ\¹ˆí\1¦z0n—ߎFO0\_Ì÷Ü(’ˆÅ«8?õJ6­E…,q¹…¯öJ^ºãf`v;d‹bÛe7Âc‡µêQ ”â:>¥òHc›ãÀÑÒlûC‡G}œ50'`oÖ L@©§\±R|¯±Ü¶²ÅŠ5 Xp-Ö=Ö¤&#ïƒÝ/Þ‘ÇÚ(®Q¨Lfù6×…‘ÎÝwˆœM®[WÕ;7ÍGÔÂh9Õ~ìòBv‘çMò­Mr/”*ŠªÁì¨æm,êíù!『³Œ}ÖUÔÔ ¶1¨‹C ±USxO¬îÁKÈÏ¥ùnABMâ‰Ðy‡ÐËì¨|  hÔJˆÄɾDãûtYäø˜/"\N Ë—Ñ–¶ œÉ“ÉNŽóüÆ}aÉ»f3ÔúݳØÚz•‹xöýÂÓzµ}ß®”‘!Ìr_Ô =0<9 ‘P±H&ë$²p­Ÿ zl.‡ÁË:nD¡4†d1˜<=x¡¼KÙˆ@>Öò8dõOHDφÑ!Rs¥:ÉZ-¿N時×n/žx~‡)…=Ö´}ðª„BjMÉ)P¥M‰™2Nž°ÈÌš¬tü\rÞ™ú…òA8 Ý¢•€ÕÖ·D{÷Fîm‘>CŸ°ZéÅ>©ÕÈ@õ9ñ)¹¸(‘ëÂeÚ}—r½ëÊæÎ jož†Š…DÛiº¨ï¶@ërŸÕ ¦`¦`V(oNpPÐ)Ñš?í=‰õ-uÊÜ_ڳݰ> \M ãÄ*q²È³¤„Þ±ùÕdÀ%°}|¥êÖ 8Ë:Ã20:ŸÛyíõíò›/ú«ô÷I¡à™3îG>!¶¢ƒ_öû©02Ø&Ÿ¬ìhE#§Ž÷Ì+Ð=£þÊÅ|ÕìàÇb’¶’Žji‰4f©¹·ï­‰™´žý÷¹ˆœ£ç»¾Õcs‡[òÒRQzŠä ù\'ÐÉi?mR4Ôqß/PGuP…û—w¡gJüôãl6ôÓEÍ4c¡ˆïö`Ó3]Ä|€<¨—H(çcц.š‚ÈSO”$Úµ¢‹6à|^òh‚iªhÉë™2¾77¨£APx„&ë¹mM‡—´}àK€n` Ëó™ Æ5²²wS¥ˆž5J&{³Ê–‘ƒ˜Òl¿9‡l _tO\ꪧº³‡NA60¢*¹Gsÿ º…ziZÑq¦´Z­ê°ÀÝÿïg‡«HoMWãs29]7<±ÛÂö­—`•½S¾þ}—¬©üeá®,>Kâ`ÊÀ¼ÔOÖr‰Áú=VÌѼ{8g©d›¶ùFàƒ=1-ˆˆjêüÂIæÞìbåº!Pç¡ÂìàCP\yA|¢èæ¤ß¤3¹’s5àÁ–â ¡üJ·¶±BIx˜c»ON?³P•¨~ÃhYq2$û<ØeGõ1f$í‡K|gMŠ)¯±œ·)ÿOËjáa'Ùh¨Zë¤iNS³Qpa“|!àa…¹k¼@‡'ù3:Hà¸êÛ[ÆÀ{ÖÔ¯–àøöqκ9ϪU0DDàz¢9Š“®T9‘~‰·F½ $Ê´R£øt¯yæÙî¨÷¤Ö8æ˜>?%¢'CÂ<ƧP‚9ÜRðVûo± $^ÙÈýq‰ÁJ+v@`–°H9,Ê&¿ÇÝ&zÀˬ¿o©²ç—!ë£âV üâ›ð7­L˜;* ôÏ”H|éaY²ÞjNù´Ÿ€ú„âC£C;¿ÿ9Ô{q ÿiKÜ·åÿvÕ{’³4À˜ g(u_‚…R‘}û€c¸®v$3U;ñ_+¼Chàè€H<Ëë¬ýTNÌ (ºÚ k©zcD¡Å.ç<7˱‘¸#ÿš¢1B&1TÎÛ—Ïe>ü…ö9gÉCÁeœ¶ºêÈËѲdéõÀ’ ÷wµ[Øi¹Õј·“£Œc~l ^Ö]Söì 6h]kt¡C‘f0—Gè‡í¿»÷l(rC±_vë³l©ZP‡Õ建s”~×üsî&˜>¡Ñ7¥“™ô³6½ÿÌä§Âú×TOiü÷¶²—ÝëwË€ÿ(íäO“OÈf³Î¥| bÈÂÆ~×\e‚#EMƒú4Rt/O¯°ªã)zÄùH)~U¬¬Ž¿%žç>^ûžOS-xÝ’oß |nØšÔo¾zª}À€~šÄ#'¡jtÏó›gÒ Žzm”ád8˜}°8]Sn×h,_T ’”̇Î÷ÍJ_‡Í‘÷–ÈÕÂ`Ü  |Õþ"•äè'˜Áát›mÔϤI±¢¬ièbÒ|ËMNÆ;Úº¿ÄäL@’õ­¬Ìpw§Æ®M±Í*à’¨m^bÌçï{ ÚÞ¶©š¥ ×õâÖç|ö¾*U´=|Α™ÖÅZg ] õõ3›hÆõ0E[(OÓEf|¼×ÜOœPhzôH°ï$Æ·N43mŒV˜¶»æ!CÞÝÎ]zÞM¤^›‰]¯ €üàõŒ"Ä”*ÞìÑšýÖá۶ÅŒˆŸjf§}˜}¦[•\¶WÌ'ìWè^o97Ÿ=Ø)A(xš{ážGU£ÏØ”µ? ñûY_^†*?¦…‡“ͨÙç g¸½/ï£0íü‰6k•ñÐoåAå6qæëöcœ­|6‹Ýô®…Ezà`ã £K>;¶¨RTŽ >îÐèÃ{”ÜBŒ»IIEÞŽ\Äb`¡ü¾ÎÞ]È„Úúͳõ>/JŒ¢²"]³'oò&îˆwCPàà#æ‰V÷|ŠÞôý{í´õÆ¿lõy¸•j(T^€,ôë¨TŒÔEÀ@ ÏQg{Jûœ¿Ò¦Ë¨]þ(Y"ï“¶XtÚê:ú#K>cín½éÜÿo~ †àA%ì!ònkÉÒú±Ú?›ÚñõU.€êEZŒ4ôËs~®üÿg…„!I…ñ'Ç|é!µ½n yø¿†Iqh~~HGlè¿€USi\cFc˜= $£=·Í>áù¢7\Þ"yú~‹ß ¥­…ζÉj$Œ1ا\cÀÁÉV-wJyÌ£_Ý=w{ʵ.J?  Þ²VÒd•ž¿,ü‹î§æ&ëö,/K¦¥Ì³©û¿fRnú€b‚`©U‹Ø¯þlƒRPòq%õSýò_%/º#˜ju#t %@'|“ÛŸÞîiÉ?n}`´ýTäKßHiXm‡ë÷·ëò ,Î< ‰±y|ÛÙ ·[Ó­‰o¾¥ÉhOíµüòÚG)Ʋ÷ J46Åz ªh"U¿„¦á÷/ÙÔ‹°±nŠ8ÈbOíõPà8› Ř~Ænp¤ $ÚÁ7ˆ†2iÍAŽÑÄ6œâ×tdN¿Kn$Vu¼É02òüÓ¶öÊMQ;Žlô}:ƒ2V.ÞçmY+¿~ùÆvÔF©“ÕcT'£ž%åøöG¶_Œh»uP{´@ûj^rô ؘÏTšæ–Nõnõ3€øPÁÛ¼ûÿm]FLlÍX:ÅK¶‚î0BVr2 oÞù˜ Ž5ºÆÖÒù[ÄE‘8e –,Ù„\F¹ìØá_tzk€ª©þͤûBó²ÏÕ ÛpŸü?}ɦA_º%ûØáKÐ ý竳ÀŸþ„Ô†tüÚá˜-¼öÆ[î…è4 þ¯:_t€ý;çñîE´öñp*ˆŒÚí+1XÎ|4Æ.Vhœ}xÇJ¦ç!VÙ•}¿õð@oö—söÌ^Ì ò¸é¯CQü[/è H.ü㙕yï-œÒG9³•DùV R/0ÈÖÉÞ(¬7)ðR7»Iƒzöý#Kb›rÅP Ö S΂STHqB@%¥«ÊJ܈>L„ÛÝ|ùfѼzä&¬nâÖ3÷ùsªª-ÍD^·ÞŠ[Q|XÞ…æ•þ‚Q-9&QÔB.çŽ5’øì¶;Õx(3ͼ2„}®f´rèFwŸ"’•ý·àkÛüV|­½ Ò'vü]À?XR¯å"x.Tƒçþœx'_ŒÁ>Y^çŒ52Ôð¢®.Hœ4v!‰qRbrHáî—½7;ÑÛ¦êŽØ Ì]DõËjE 8|ÿtäÎ^‹Mø]›µ6ÜYäÉžËÂ"ÙT}¹ÕÉ÷`[Å3Õ•<’’×P÷¯õb£*„µi6<Î-jë¤Á#]=®²w9Aäá:P=‡”FO·64z t²?i@·ýòß=9"÷ÎÜ££A·¹<õj›Ü²U‡›È‚è/¾H’{˜ÐZ¿3ø˜]öeÂÒ`ÄŠï»Ñ37¥“°Ž>íjD,ù¬³P‚M|‰Èîo[ÛL?üÀÎø×G~ôËíN¥ÒùâÆL8>ÞÇP‘pPH,²¯Õ2Bçip =Žƒt0ÕdÌìÄ=2Œ@d“ІºøÁK ä=ø°³ €°@™¢4 tÔÝààÁ#±Gב$âÓ¡fLÊ@V°ûæ»hÆ1Ôum{’‚¬ÇšŽ7p¦ýÊÔ‹„ud¢ï>K °9Cm°@ïLÓþÔ^„oíþÙ™M§EWëßõ+þÈìKà• _ fðyA¿|«èÎL]ÛT¡è"1áÛÐ9)±0¾Ñ&¦¯íÿLRÕaÖŒ!á^žùcYNÈ K PuG«S.º>·ôVµ(zþto;Ÿ K€¶ˆ ~y•íÿØœ„yÑö9 ¯ÁâJÐuR÷ c›­€n‰êlOíƒeƒøœGÈÖáœÅ`8þôð†.ÿœ zð¾<áÈÒšª(^¿ â³úèÏKôƒ³çìÔ0÷)H »ý3<ùýRqz”âGr™Û ¸ñý\F˶ÍcOÞKbH•©ÀFTƒ+[{ÎÇ ‰”V>‰(±–Ê¿Üsé×O/Ým§’W:ñªX7’éÝS–˜Ø…x&nê݇i?†—R ,ÃØZ+àÑj¡ÇÆÖ·Ûnwï_tØ Œãk×ö;ƒœPnÌóhI|²g4*¨^öáP ôH”ýsÍ ÝàšbÛòw'ðý¸»É ñ"sUc_¡þDèkÜ[Lòa¯UÊzìa¦2¢âp>d,ªôf}6tiH²ç™¸"Í:Aþ»åïÃ$Õu†d¬sýjGçþ™"ëÎ,Ä•„A‰û»(ËòÙLa7ú{BnínøŽe¸˜)s—» èFβ?aÕ[µä}~UYv5èpPv§»á#È i<Øâ!œïɤ¨Ó¨ä¨v&Gp•p9ÂËQ£ãrŽzäêÅ©`ŸæŠØÝÆ!‹4dfZ*Oçf³[&Á©@y en•ÃeËDñ—Wc½Ž<öhß·p)ós¼Ü¬Ë«Ï”Y°ÛÒ',VÓ9¥†HAzN*U n E]ÙgÔX¯?R3¹Ka®M‡&m®<2v8•m[ wrZ/*1ÝÎêÄ’5Ë‘–Cë꟞÷Ãmƒ(× ,«|ôsÌ>ÓtÀ‹]¼I3—³fßG£&?G%´¯ûXha|DýžÔtÄN—¯Üà©¡ô÷¾rFe Ãiýfï £¥ᥕ05ÜKLø˜Ã+ïÆ*m.äÙ·¹d¥qÁ»"¾D¢gÛ}#Ôûa>U{zNÁ¯O&µ†‘"&ÁüˆÆÝ.×—ÏòÜ·!æŸÑÏH¿E"~ç »“‰ÀÊË'×¶^WL–Ï m7šÐÜMåpCï+x†¨°ËmR]%®O ’-Ê¡-ÇÛEß’J½{.—áÅæÇMŸp Å*?:‘ë3·Êv ÑsC¬ë._²=L6ù“uT¨ƒ:c;%9B©^ðõŸMz½üÈŸµ$JXMqÈÛBé¤AAátÁ¦‰ÂòÙ{â3Ø gàåo¡4œ ¹p85þ‘0Ÿ1ª5ãÉ€h§P˜¯³É5<²œ=ó·¥I¾µ‡0ø «;4îQ¾èv +ßVÎsù(Ð!@uBè$8âJöIò[7ìÑ<•&ò>*(ëÍ¿3-Ž©}IW¾uÝ¥²„ú>ÔbY©ýðB «^~PSäuÄÔÉI+Ô MÎMªù]¿®ÔÈÙÌhÚÓË\ƒGFýB¹\Lõèê¡ÂñÛ;y¥ Û-’³½2Û]pæBú¾ô¤§‹zÛ–ÊÂ2¹Î*|M â9Vyþâð:Ó'|ÈëÏ~ÿÌãl›dÄÀc# ‰\³º¹*.Ïú­'é‚|ùöxãø‡k 2¦Lw=Yü°NÍñ´Ï—:8Íá)1dµR¨k5d…Ó»cèyž¾&ìÈ‚ ×”Ouh²ï ‹q·ËÑ #ˆÛ<ŸDZs7¦[¨³dy3íß+™ß^Ý×2)0ñ”†k!¨Ì6{¿JÙ²âÿ‹Í xc‡.¨–Vã´á!àl§ö\êu°!RìE‡W€eN1 k“¨Þ»‰@r< v8¼]Öoz'Ÿ3áH@F#ò0´òßëéJÜä›™{0f+yGÃ9Ò}8è$ñÈÊfj™V·Ò(<¡~°Àm‰¾ŒixšEŽœ|òÏõˆ×Äf$LP#û%&Ò@iŸG}¢iL“#¤¬l6‰[ãíïwþ´ú· eükÀìï/è¡Iv±¶œääÖB•’â<»“H >£Ž0d®=íêÐiÞiå©6ŠÚS¥ó)™çUÀx¸“Å+&œÝï‘¿‡<» ã F]E±‘çÃávòÉ |¨ì~P=éu´ÉÊü©˜%Nƒ6¥åµ– EÊ4 Wwû¸H¿É´9¦. ñ1??&ØIjá *N/Èbª¸_r×ܤû%z ¤³k”R™Ã+Ü÷ï ýw¼Ë&3ö'l5…¢q¦,kº¹ÞoÙnE ¶=ߦMÈbìé7ªÞí¼¼”ÙâÕJ´,J$ß¼§ùcé‹ÞÔqÖÖš¸Û0A‰){)"\ Þ1´žŠÎ'#¾Ù.¬A–ôÔ®Lq[¾ éðOn«hfƯÂQz ÒbQ©±üHãÃÄòNCÅ¡ ì2ô9£‹´uí¾â*=Tƒrˆ[ìà) SÄס¯«°åþÕ:÷–ŸG Џ™±E¸¾Î@Bî'ý/æü}OØ3Ú'69ïæ­mø¶H°¹"™UÒ°5ÉÏ#GMµGqØÁüºº§“¶½­ÚÂ4Ç.ÎX™xa_u¶s`G»ÞPJÙLÜ2ž46ø›LR]g¨58€ú5þzäUô¦Ï¸ÆÁÿöÙS12à#ú•°Ü†’¾ÂW<ê‚6ôínÿ¯•p´‹ƒ't¬{ ­FÏ‚FJ1%cXÙçÅÚ®!-Mk0G‹ë.ñ¼:+—+&fuT&3ÆDš´Ó²«¡L0ðFüãs’`êxZ ­›S¡f&2*ÄzÜÃOÌíW> €c(Ðñ˜¯I9¬cR¦mp·é~hüLÎj„õÈÕ¹\·¦ ý%Wƒ@û,Jí|]½ÕHÅHGqåkUgPFw×õ™bûtF—yá[”Ó×@Œ¿ÝGE¿U®õ Ù;¦{c2íyk‚{ºi>‹_Òréå`ÿL¹Êê_aønŒé%ÓÍ€@<%Hª00´+™öc\‰S/ÙYÊÆ…%A.îÓJ‚G¸37ÀÈÇ/òRh.¨TLyWGV‡' fT½ßÏÀ€×pÙn×ë%SÐ3.ëúg~‚ epèó¸ ǯ£WÑ O¿êö+l³§øýŸ/+îM®³©MþWÐÂ*1âÄ®[uJ"±0" å½#•5öV½JËU¸‰aYs“=æxôf­¯‡áç™Ê£ˆW>ßNE lƒ‡V×d†ÃÂéJ|⺆\NhyÕ#ºÎ“?^Üü«´Ë,„·/õbúÊeØŸg˜ °hré™Z®¥Ü³Äñ©SÆÊ® ÍÐ0HÊ¿z O¼Kó84.lÖ?Àƒ mš1ž,_Å`…pêÇÕT\Ìå ÿÇQFáŒ/B¢“&zÕ-0Ò&Ësõ22háˆÃvñlòiK¾=‰–€ &úxfcì¨zOS­ RWÆoRo§ŠÀit‚Ö°BcS¾¬V€KS|ƒö¯£<ÛÈ#] ±Õü]1º²6ÆÊíBIŽ‹·Yúl—¯IlÎÒR} o>XÑÙ–µDÀ+ΊûC³VI×ïæ‹¿§»\Æ6E# B(OCuM9ˆCÙÇ…¢º6¬n¢«B}Ö%ÒŸC–øîå×`‹³Ýz§:z$ÅWLsΓMŽxzGSà.(EgT%Ãä体kùØ„_>ÃûÂ^ãw”c²WÜÁǰ¦F­¯W”‡d5Æ|=èÓTeÔbXÒ¨G„¤J-¾’þ¶f=–ô?‰6îú2.è~Ð3Wò?ON”Àa‡öOOäC_šÊ ÙB&‘ÌÏ{2*í5ÚWz¸³¼8ó(|¾}@êÃ8|ùW *¾ª¿Êƒ+мœÜ¼ÙJ]*Ðuµ\¿Ž²‡òÙ±HFèñI°|CA´" °Ä–c¢› ÌOÝ zšfÁÙBºE Õ>êÈæBììuýP£ðcl€÷/6T xî'‡gŽ_Pà 6²ßñ¨•ƒ0Øxf>»µnÙŸê_뮿U>Ƨô«š—Ä¿ïb{þÉÃîäR‘ð2õ=KµK ‡·¸‚i‘J eZîRÄXVe`—×®õXÃG”¨ƒ µ^§-,¬ÌôUÊ8säO)ßÍÖö( æº 4¶†sAªCNÕbŒ»fÖÖ¯®Ç[Ã7¹²ÍV¶É*×íæñü_Gc¼}¢Žˆf±0¶èÈ÷fÚ@RÓ3Ç#{¾&„ㇰN{Ó²Ú ²uîTR;&®ÌyÁ”­ý†ˆ$‹ÐÎøDà·Kî_p®Ý@ÊŠÛSt‡ÝÔÕ)—dê8KÜK.ü€þÒÝËL“°ÿH&kŸW\qS#ÐŒ*BÆhs@Ëcçæýø) QR1Œ¼`õYõø]™£&JØz4Zš¤X³/I‡3³+ÞpªG_º( ȆÇ&½P¤ReíÑLLŸÔÊÏ·ÎD¨H¬Wòø÷._낆(ÔŽ@&Š0lž)îúJ‰é&tT@#¬&Rùò2õ´qûÉbƒß¥ê„×'bĪ{ÀØ‚„0³ý“äDßôvÓêM¯±»Eõ“O¥÷«”_c­Ûº¶¯ù%E ئT“Z™’uìg8å*=0õuüѧíužÔÛ‹´ ãø Ój²êtq®;—p±ûpÛe- 8k‚ "w«Œ{öÁ¢ùÙ‡'5»¥˜w‡,t—p§QÝí×9}MÐÃ¥þ”>°§û5LŒüÊz·åpê ?è8xí LxºÐ[R€BërI©¬G€—@°òJL$Ì_vÈ@§ã3–øí&ÉqùŸ½¹$|Ü[õðBî';À”¶»`ÖäL2è¼èš=h…—ŸþÆ!‰ë'b´2ê’5B¨yn„fÕÝì'¶`ú¬7?¶¯;»¥âß*r¦‰§‘z•-¬]Xã@ÅÈ2‘é'á°;åmÖâößúÄëAákv³Ç´ÖwD gäPÑ©ß@¼p°›(ðÖµ{pÕæj7äñD3ßœ¤F€c´Åa*k@¥šYpãƒöߢæfd0h‡1¼Ö1ù,:+)v™ ÞX…+VËý¤›ü[F‚|h÷@¯g„]“„­•!v‰(6çŽÛ%ØäƒGù;=ÃOÁˆtp^ûÐ11J¨*3¢«<‹Ó41„à@vû{žOkë?£mnÂȃ-ZÔ€6Eo©ñ%VR).ªë4°î:?SšoX+´ íæä²¤Ši4­°‰âo$Ò1!ƒç¸$G®Ÿ—#ñrÃ}uôRI[»¼vi9,[€—FLhn—·øJqŒ^ÂŽ§åk' o³Ö–™:À^Æ3o…yãËuÌÑ ä-Òvaû§Lc÷Buúbµ|íÁ?p’ßÖ,ye9ß}Oââ!A J¾ë÷Oƒ_¨b;!‹}öO6ÛúýB†ó¥V”¾Á?Ùq9Õx¢rãÉ3ã6'Ñ\Õª¡¤«¿¿è½Tû×)V+‰®k’ÚÝÜîÄDØ£Ñiµw`ÿJÄAàá›0 äd¥Á\VІV­Ìþ…(P¸U ÿýÌ =£øÿü ûl4öRJì‡Vd;+íÀt¡á_×_H¨ÑG¸ë#GsH¿%»FeúƒVI#,SÍ׈É=íè½ ç(e³”ô%‚,èüP  Ì#ËdÜVi  /_£ä·Ð„µ.ÞÊòŒ B[%î»Èpf»7?…»bª\ÒLʉ*4ɹ¹½êC`ÅêpùoÉph èkìŽ)¥×EÎ)rn#±&EpY óÕQ±N—BØHôz' ùl_<›Žp´ÒvÎj,ItZÀ@(ŒÝÑæyXŽ_wDÖ:5¿‡'è/UÐbñUÂÒe³­3’aÖŸ±Ôœní .ÎGQQèÌ”…]¾ÑÀЃV~óaöæE¿€éëÛàuÛÞ.öõ``SüÉî›VÀ ÷¥{ ¸Û×¶.DÈ©ÕrÖ$”s?;ÆûO4$½B‘H‚Ϫw‰WÍS/HÁnÓ€m^·hÎ0&&’Q½¡„ ”`tÖÆQä!ž¶À´·~U?Ÿ´š®]‰…¯kŠÒï`'Ä”1)<‘ĉԿÔèèd]Ò«}e¹ÿ©#Ìþƒfa0œÁJœ"Ö»Ë¤ÛÆbá[Jèò-a ZÕµº Šx *‚6ùmº{ nSGƒb;ž ªGz¡´}Y3±ÓÒöü•;P.©¡{{7¸®Ü÷¦¡Gm‡Ü‘-O ä÷ª[á à¨8ƒ“™ëyø¥F‹[cò:qê¿zu·K'°ŒáJÈTÜ=i¸ò¥0†òðe|nº¤°,ÄkÃÛ0‰®û•£áà…n {þÚcu×= *o#ë˜ kÕÛÆ@ O‚0Y›M ™Ö$†²Õ Ê. íYÈ6þiŒVëqûÚØÅ „’|h²ñõ«ªoaaào}èÎ ¸ÐW­ñv ò±ðƒ ,^ò}Ë@%ìÄæËû»Ê¿OàdzS­†FÍØ•¸§ìùBlä×Ú ˆ+âI²Ø»IÇÂëNmù˜^@AóÌw¸ý ®C¼•q7p_’ÀŠlÐ<€¹%ûgE+ªá²¦ú»Ñ&4ÝÕtÚEm}–vèÅ(¡º¹Þù4|âOµE†&U-Á*ÿÇ$ˆ(mÒ¯OÉK4k޵Ÿ`-Ë:ÈeŒ×аɲ bsç—2`Ò†Ùq³±‹ìð3¼{:vOÄLžC5µ¾ª´°ILÙc9xEŠ«wt;¾|(üZÆÇÿ¢†¿7gW÷·¥€¦ ÛŽue Mz9g8ÙÅvl$¢y‹-Ý‘ø^öêw‡ˆZºÖb½µ’ŽÓ0Ò°eÄ3Ïkà[¨K…é_/áx¯012ËŒ¢,u°(]skta]yû?¶¼úàË'ÄîÓH‡ÏÑ|VYÀn™XîÚÈdl®{鯥€jf±]ÇiW-È‘MD6ÑK 6èã}–YÔyÿaòt t$Kò£üZС¥,€¶H‘âÀÔÂo$šmçn®e0ùÓám!~ÈQˆýt狊— ]¯®-›£ÍGûXSt ð5—!óG•ªï“*€CWJÕñ±¨£äÒ{öHi=•Ò#z ½!Ç`‚±©#lõ ­«§ÝdøV°w9cwç&%ëŰ{üU“ÚÛË¥*IñÌ‘Œo/¸ûÍ(.=Ýäû±ˆñZkÒTp/Q”ïtA¾ptåM»žñ:ñ¨wvi'¦'wxÌé¸Ä‹l.à¦+:Zý kŠ¢`FÁÚ&¼&ãlðu‚}šeêûõÄÏ—ÃýÛ*RL—ÕoºRk"Ë4Ù2&gILQfú!á¯õÛù®.<Ò2Kµ–¤[VZݱRã±0û‘º——Ä¡£¦*U¬ ‹""l=h‰]q ~žv.21“éxV“ÍõRà} À-hSÿ+'K~S"£T;Ô®b?¹P‡`÷¬,±\’e ¸å ć¨ãÛƒMDR«UyÆ Z$¦ÂíÄÆb"ꇾ«³ç_b«=‘ëAiy£”:ëðå\0Î'ê%óMð¾ç³CGÆ}sð ¼%}é~3jÜ‘îK¨ü&ïì2Iñ3¤‰}”Ç °W¶(åÂ3–c}÷ÿ ø>í¸Ë£j£9r`ÉAq¤ÜÚîG¦¶HÀ8Ëg[‘lKþ×5f°±“)ªÞg&Ê–¢ÿJæ.¾þvzñNl;õ]ñ¯Å:3‘ ¹oåò‡ÏLÙ™Í΀"«×©$úk“ȃ$y!V"ï¡T~@Žã¶Uf4—ûf×'àç×e—Q4¬Kî¤nˆúÒ9±T0çMI†¡&J ÑdáÏõÑ›­6*i´`œ!lÃB.L(ëèÂS…Ë$L(ÆÕƒ€ÃÊÆz#ëÖÎ…ý½õSå,c§@rNVJoÔ™³“ õꉧØÓ'®» POIsP¤ña¼$ÐyêñÏ>ÚO+cE€zK9œ’m4Mñ}â½øúi«m Œ†ltÌAÿà¾ìq›Œ C \á¬zü+*Ïä ]¹Œ’}é‹+`'£ ÷Dß/F­éụ„ Ðs:*ì~ϱÎ&$×"M»t†/zU/Î’ùqqí#kÐD=¶v·ä”tUžÙ=ÙÊ›À)@‰ ®ž7 ]ö&2µXë—@ˆŽ¿Ã#TêW!j‡”~P΃כ ½Å0bæjM7!æ ~Ü]ídÙ>'èÇ”Ki7µ ¼q®æ«ÛD3ÞþgíÔ=ŒŸ ˜g¿4 + Sâ(íQ¸Ž€´ªm'}êHÂSÝþ5Ò„X¸²ÊäÐáÁsæëfæÌCœ,]“XIg¤CUܦ÷€±µH|Ô{ÔQçØ9RÍkponñôG ZšÙ:fkpSÙÏé‰ (XÏš1ÔÞwxKyF¹}$"Ê?Æ_7º(ž4ï×]gÌýµA¯ MySIãÜCÐE&#â(W rzìÝeÝÈŠ¶ù©•°ª5æ‡Q i§¬î&é$Õß…ZZMvO$=ú㢸¿)§ôË­¼IO i¤owÊ…5xœHã_y¤êÕÉf‡@ìPñež7׿Q¾ñi±¤2ÌÎ(‰_+î#‘f ¤Óï=ù<[0’>¥£)ùîGN"ïÁYè'Ëÿ]Z”€÷j½¡´¢½É‘V€6¿wò5ý#N2ô`k;‚Øžßn<½­køšŠHà;æ8|}ÿ{>Ð%\×[YýúÑªÙØžIó¢ö ð4É ;>륗eë‚ܬ§Aޱ¸–‚_Sœ ý¦7¢V´Ué:—éº6ׯõ Àoªml/¨b€„áÛÙÉI”´L·¾§+qxôÝ A¬šˆ\ȳcŒ²ÏËéëN$eD>j ³¯ªá¦ï¢Ù­žæðQ3®!4•ê|ɥǶ!„ôÁ7tú×…ú6û¾?P䈹ïK‹“$_Eˆ úÉ`ðÏÙ6eä›jÆ& 7+Bñ ¬6µÍÇBñV¾a—„–§~HR>XeºØ‡ó\'8Sƒ¡w™~qÎδ÷¯üIë™ËÆ^ð^ªñK{ ä$~‹Œ7…¼•QÒ±u!nT`Îç Ü1 'sv¯Ž“B*{8[ÍMйm'  Í9Š–‚nžTd“ MêÖ°‰y2Æ£ka÷›ø¡žmïkoX}-8ð›|‚TNê²Õx¯p£Ùë)Ï! C{š¿™¶«õ:Ûåÿ¥7º³ !ð¬gxq:aëÍü±Ôe{> ]—ûWzÁtÃ)§N¬²'°¹ñH˜Qázß+8n)®i¿Ì2röÿÂA·㛘%”¨äã9GäøêO“¶ó^øI QÁ–hu™1X~dÙ?ß×XûtàŠp‰#©R~§á»Z„)q$íØ@s‚ƒQpÄ¥ôù©ç"ÿ*aC¯¬ š ™C+^À‡½ k&Z ' ®5hî˜]¦‰8 %<úvSÊò<šÏ ½Ihª-¨°MÃSp•4V;ת‡›®–ú¨Y¿Áz/GIIXyj…€²-Á ¬¢“Á +íR®#hÑ^ŒŠd7ÎAçg¤í%%˜sHPÛ&Ö*ýøF4 rŸñzF/‚òŸ§feBIÉ›4H‰‰äƒJésÁ«»ypÛ¦3”&èå[ÍktG1 óºìH‚Ó¿Â7ß[Z§¥œÇ8®ùéè´€É0C3˜¸&K`¸cî˪j;é9œ’uÝÀ˜&ö¬íÁ§ÍîÎ-ú0.VåDnàYœ> ¸dáEÇ{%`‡Î+`î*N0¸èWИßÐØLœÊ¬*é彑vrã5{îý— »®ã"éLæ.%è<+*¤&wÆOò¨ÛØ$d7Im°F¿3WæÚ–@˜jkŽ>Å!Ú:&rpù Ñ|áød%w×´êµ;\‡¬9chçwˆ9 vš{BsõÇ(‚=@.ÛÓ1ÓWaz cò 2QÊòfvº1ñL¿½ÏÀ>xĉaÔåÉ'ž0¾¹7ü#ýѶn û´Ä1¬†*Ê !Õ'ÜŠ·K÷lÔ‘`Dþüi&k¢S‘¿ñ„¼-c…Ûy\R«í9RòÝ\û XÌrÑ츲¸x|(áXЫ¤Œå‰³š§%PΛ´ß‹%6õh!…œéö°ÔñM1 ƒfF@ể¨ äñæ ×–}±4à®ëHå‘ýÐ9ø.•õ곫rbõû‹VqH\l¬Z{-LX}ˆQ@¹óÂ'Ë£v*…žëÝ»Xϲ–yD0à ÝRf+,ï/zØ®Á„`¯vØO7dŠöo‹ä3rÐÜ!\ ÿš@÷ö8Až‘êädüo'luûúðÀûŠ“sîË8'µª T˜ùS2¡áGfbÛ3üKÈQTÁ`âkù‚œeõqÛ>{v‚ß•*Ý÷$Ìä/¾Há.cǾÖý'ã¾jpVØZ×1ÿW¾FkÕ÷‰‚8Ã̃Â`Ó¾kèM#Ö‘}kû½È(!8—éBëá@ò&ªi±J)bµ{ÚM‰>!ÎP×Üžx¡ÝZW½”1µÑµ|Ë$ w±ŒÙ''ªwžeD*Űh)8§º0-âÈÑFÆ}™.Ÿœ¢µ›pö­müZÂøej» 6ÿ ¹‰cSÿK³¡1-20àpE#’¨¼ŒÈŠÍV!a @ˆ?•åG;©žO 8÷|Öi¾`ÐêQÎ~UÖñMÁÑFL>‚Œ#ðB'tð»@[Ó÷¢Ò†ç› ¤QÖç0Ùƒ/R.=Øÿ[òêð§ š“0|¿Z¯4½«îD~Îs7×y®òÑZ\`¾A´}ß?F…‹¤Bõ•LÑôNbæwšÂ¥c8$¡7%ÏX1²¢>îÒJ}×`â´@qš²2ñÌ™—®ëª™b2Á}‡¡á¨¶DT!·„ÃÑŠð»á•Y”`Œ±‚Ú÷è’bütÉk”]–r!.ŸUüðAát±d·®\Î7˨?¡-©O䢰9)ødH<Úæcu¯sº°¯¹UY¤Ä2aÕ{$je/MîURoŒ À—£Òñì` xŽã^]Y¨ýÔÅ2½žâ „\  eêR2 l¬g¿S‰wÛž¦y† ð›b¿Œ¢NKIp?ñ!yCß¡ŒÆî§Vk\,bð—≠*z¤õâ$Ò4%kr³YÏM\B Gɽr3QHQiÍ̉T{‰YùˆÓÙ»ŠSK'È£³MD]Ìfá/þT™xH®»¸õH •EÒ›9dm¶0Í×ÇI¦@nªV F…Nȶֽ•0ÂZ4{´ó¥„Ûë0±ék|v‚X•ïTg m÷ŠÒŠƒCgÕí|V‚jùmD0ƒ”ÚB¦Šéžâ*ÚûWnÅÁò4ÎÌÈzå:I2OÁg&¨%p©…*ëýô]¤ç›)’ 6Éltä,‹Æ¿ßDòرÿ*ëÆŠº÷×&.^Oá]Q` 6Ô•i ðö³Û«Üä‚„_2‡”lä:û[@ÄŒÐmm•\v‡¥ÝEÔ”ßeS±‰Eÿ æ>°{†iPÁÄ:Ìl¡LÌÃÛ… UD>^‡Ìg²°¹b¤6\O1k6£'cü<ɘƒ2ÌÊë­Ø  2ÛË>N^] êg’?õ“:m‘ëÿ| cóÄZ,ý jšÎÛ€º¶µŸˆ’à†xSU†"«)vBpTÅQ-xÄWe,[’#Ò¼C"ªZ‡=PæÔÑÏ´ŸxcásÖ«(|cFô‰Ø}ØîÅâŒî«èxRë­£×Iµáˆþ“¶˜s–qE žuB<÷µ[ Fâ5o•^ˆ;ª.7eŸûôÿhX§Ù‡ÿ£=Š7ø0º"X1°pKu?¬• ¨Jæ´;óA¡Ü‹ìÐïØ]C¡n àòÛEÁ'妊’=If_PìésEu©…Ûϱ–èÑû¶ÿ~œ¬š½À¹ª=.Qj7ZÈ7F3¶¤` Ãs¬âÜ„zŠ7]PÉé !ýAêVãJÄ…·Üié¾L&Yœ\‹&8ßìV0=µ´Q2»ÌúJWú‘7ñ’bƒƒ³«tu›ëÚ>C_oÐì¸å!p¤"¹(òq½k+öwd J›¿ÿáXÕ°6ñüäJ<ìÖ÷Fñy9Y®VΞÐWÁ—¾¢G¬­³½ƒ™4Cu5†PÜ™²#”ŠqÚʘGs)ÝSãf”Š“®¡mÁ¶À¾{ØNpWNm+ø.µöHË Â§>UZèS:,}GÍKRP+”$ÈÁ¯ºçšä\ÿÄ)îááÝÎwà 2‘O“´>éûõº PÐ){yúñd ƒÇ|p-¯Æ í ß:ød/‚-ãitÿ±ô.¸é¨õO¤ ŒŒ…âøË?Q­pëN¨çHXÈD/HvÜ·K’Ù#sAqoÀGà±,’&ý•ŽÜ«ê=ÁŒï„t¥øó0ªx¾þ’»Q²ÕUôÉ>¥_¡¸¼)’Ùñ.õ½7·›WfÍc/«VÂu¤m~ÍñqÑùR™_/5{’fÌvÛ‘¯Ñͺ6öHÖ¿ð¨Ÿ%G–…”2$E€—ËoY‚žJ‰¦ÁH¸ùá~§j¹\Ù£]$ÃÄ›˜§a½åº•†¥îi—eDæö3hqJh"q÷‚éB´²và‡=áÖîÜ¡1¾aþNµOŒ¥$˜ÖÝ·eó(­O¸ás9íÇãé=ÿnIIŤ'Ïþ?ž>g ‚¥LeG®üZ³±üla9Wq]Øcss‚ÒV&8ïÝ)iÒ_qàëb›n[Q·5øï—’ònƺšûÝ Ú/%Ï¡û™­ƒHú6Š‘·"Èõ™Z*ÀóÂÃãEb¬ÛäǪ훜–tû9Õ‡#ÁRÃèËŸ\7 øsqÕ¹§\NŸ½.~öa^˜&Öå™rÂÏÚšøñfEšÃuH e’/–»‘]žfLð4¾ý*fébw•ü¬$žB)‘Ål¾“êf ·«A¶µ¶y g³ p¿Àâºd Æ+~í• ï‡öùÂ;ѱ@Aý±üAHsŸë›[%+›úÒêrvÛ¬ƒYol÷ºÈ(Y`Ä÷”¥ÉÀ.b¹Ç˜ç÷æ3‡užË!Pp´¯ëú`A]GMœL`dF_§ÀYplØ00U·Skoë9T“m”5"Ð#C\¸}8lˆ.Ê„, &V!THÿZ¼u¢˜®~“,Ë9 é•ï$µCµ¨—¸ÇHOÍ¢+´:Z²oºLç °)%±çDäwát†DÏ>9~—8/nã4"dÀ¨G#>©EjóIWÒQ¡%° >ÁÅÒ³£¶ùˆùžA§ îÕçÞ.&YÝï úº’ûÓH¥›;ÄI„TúáÓ´U" lô+÷Ž£;Ìš­!{©*ŽÈ F:Gµ©Zð±—i"ü;˜QªÛzÛÜ;.ȵæ¨×6ìÿä&¬rÚÿ'Sþ3úIÀ)djŸK\­V3 ZÒ×Þö!Î3Så{0ŒN~^n‡&7bEÈvTÎH̆”/G{2ÄezZ2>³p+„¬¦6†LíÞ/f[‡+Á%]¥F¢]R€Xqô¯.\óS¿=÷2Å . ì äÚwÄ>qF^h'”Òpc‰;T»ƒH^þÑä¬_³€ ÌJ)zÙÞS®#ù<é‘e—É8p긑¿,à²1ü7~s‡˜N´AÄ´™>™ÖQ…ÛzSQ_ðeÏïv· IwßÄtn—’Æ· —뵡ΑI¢t÷"þIB΄ ÊÜ0d4Ç÷è,æI‚à¹FEâúîe¡K4dºXXå­úàÜó×ãYÂä€m5é‰`2.SˆÅeÉ]wF™ÖHWûk—×,^Ï®Œó—ûSf–^wuaú€PÖæc_îY--êÄáÏ%­Óܦ:j˜;^¾!üBëÞ^#ËÈ–µ±z‰õb—™<츣¼»Ý²ÿÑšãMj†o¿ÅU´JO_û„¯U˜4K(œöuÑʬ8n˜½³Id£uûã}4®› ²¡×C2ËÂ…åï|È&t˜u‰ð…ž'ë7ý¹‹‘Ëko)ä2 Ð#ÐZ!É\ 0;‘•a3?a±\ò»^TVF8Â!014c·Ò`ÅŽ½¸$DæóYc\íȨ¯k™-6Ú¿¡í_YªÕ=O•=îÅËl'E ”‚Óì³Ï€Ž‰ û©þHÈxý0®ðQ.io[Iøã;¹òˆ>#S96…öÜ ´uX|0ò#*™Á€×>Þ%E_ZÇw umÉVtûaÎ;›-Ž.ðêö}¬çÊøõ$tB×Ó`Î0?k»¼4'vH6÷r^‰Žæ_.k=ÂEÃ.‚«sÿxòÍß+ô{GœPƒ÷TÀ0¸ŒhëTï/Òâ·k“Ðv4ëåµ.“JJþ}Úý‰Ä.ðl Ôi²o þ ±òhïZñ ‰Šbb7WÇD¦`yC_=*=[w¤5ê±âÞ´a‹¿QÔ–þ¯{Z°¡Ôo4‘Ï‘g,,û#wjs! 5%U˜ŸHÖ†á¶45»1vž±0o¼!Î:6žãUö¼nÄ?Ø•J©%3J^þ \Χwb n À:#ïãÙ7RçHâáu¶U,8o7¥æs·y?m‚²•ð•5‘"|8¢ºj‚w?[¡zd–œn ×,O­,¬> Â+ùˆd“¬H'¦`ý gš…¢{/>ØÍwX}ü‰ò«™ìiˆ'Gù, nÚþîËaV‡&4X£Ò–üy6‘CDoÓWÊñ $«”UØ •|¨@FÈ*6Š…3ëÓ†$ªI~³Y b Õ„“ªqÙø—›V‘ÿÿ…®’Öî$'lo¶yÒbÉBËGÈ¥ñx6S02" ktòq ªNËMIØyF…¤Šþ1Êé©Q½‰æÈ•™3Qjs‰~í Ýzƺù8»Upþ­4´µÃÑpõÖ’+¦(„ #d@JVémÖ*Á£J‚oŸ% [‡>Íû²Kå†×-ƒÌC[Õ±pš™IÆ^±-ï•À¹*‹ì¥“M{P´¥5øÈ‰qìQ}f´wnœ­¢ªšÎÅð·ŽÕ´ñ|Wv'Aï\å)ß±±¥’Úÿû‚4ç*w¿7ºYñ&hô Cˆ HᲚ°¡)ÞÆ ºm 0®T|îVaí½øba§QÉ¥õÞÁ)€ý·¢ÕnN!Û³{ðVæŽ[À¹T ù±ÁÛšLýê\Êä}£8O_†ÑÖ€#ׂTb÷?†k|Ïç8\Ã+Ê^Ó/‹¨Æ+¥ÏÚ¼f ƒ©Ü2Àö˜ê<4Ž»~hΖݱ£ ùTLäfI¾ÍwöKMYQŠ:Ù¡/‰B3îÕŠÔM<×(l¤+]³Tè°¹ø¿F²s½@•ú,çõ(özøg w º–×·>IÎï OÊ-5ú¢É”êgŠŸK¸.HúG²ø©Aö»S9¬ê ¦Ç³×¸QlöûòØô÷ …¢ÚôÑ>†ù“´9t\8VÎ:6B1ZÃÅÀ+N£gy’ fúýÏP&Ô­ÊŸ–é…zLꬊ‹±w(ñÑPm™µeâÃÖ5‘ v݉J¨N²xܨ”‡Þ±î§_©ØcU¥Ý%T|@á751màY @]œD^gpª9¿ŽÚ¶—ÜØ¹á»ÑtPñ.$(ÿª›™°ï™çÌÔX°!ÌFÓfgté xô"©°à÷’MöžåQ ¾ñ6{?ÈY•'R UhzÍ¢†[ˆGÄ Tù‹XÕWÇB<•R¼ôÍBp™$žSwc:CÉÞvû܃Äa[GOaåX󛵦Ih }íf”²§b0˜yaP¼@yx®ÝŠ}¸µÇ‘0%{gƒ>|(ágï,w2·Ä–ï´3Ìì9'ã| Ü2‰N^ÔBÜì“'O8¥oQN±ëâ—;ûƒ\¿®¦éÂFˤÜëG¹Ñ'nôªV|²Íg†/®«;¹ 1¥Ç×Oàãw‰MŒˆÕS_›9PJ{lÄÞïi´Bv9ß)³é3YÓ‚QÉ_}4\äü6£À_¡k)TÕ“dW›ùw¿òö)÷e1¯Œ`Q:yËÅVV]õÙØpeÕ07€ÜiÅ`55m×À&Áö½±_¼¤y¶I¢5lt'¸€µÆ víœv§Ž«‚<–YƒœÓe¯&gkļV`=³Êö"™¹w‡ ©d$‰¾`PÝõE¶ß¼"¨×^ÙˆjIÞM9tžW1­BÀžHmpVjy5éMÌ߈$n㟷¤Zë;~’4cÒ¿ÇæüÎsª©:xBÅø‚ð<¬.L= ÷*0&Æ~ ‘œPªeJf¨Žeø„X_ä-IÆit®.ȉXäý*å63‚ YŠ;ñ_7ë…æ’bi~T7”I6£ÈŸ¿µ¾ï–|ß®9Wžå#äTûWHTçÆ·2´®-ñê;ßt6ð:‰‡c#r2¦IˆëK°²“@Â~…×ÿüZ Çq5·Z”µ×ó¬ÞÊÚ¨º±S§MÆ—ö>i;­?6R…vX% _Œ8§¥ÞM"±ZaƒúÒev¹_zÍÜIð)éJœqðÌÍ6“ªzÈb,r—Ëʰ›ßüýúl\4vСÉ'rx s|(Û,y-‰w³„RºUÖî|Ç2a‹Ò“ á)mt¤o—zû`Mä=©ršówÕÆüÇßoX“ ºC/„"­Qaw ëä°ÛfZL† %òìØyw#¤±ÊϾ»V«œµöžzå ï|\$íÇ®'^Øt[ൊëã äx­6 ‰ˆ·óþl&-ʇ°P‹÷Ö{?Z,B‡¨ŸQ .ÂfªÉ1Dw²‰KB½¥—»þ76±$˜¢þö5E^]¼.{š­˜¡"¡v4hášüLôoÜûôÄ4îÿä¥IÚŽgd¡Ûmp¥ #Ñ/œé$쌘9½ÕB?òŸƒ>6‘ˆ;Ê‘®ót2_òÏ—¸Ðw@³$íͼâWV¼ì7øs›n[xy,£¢ù²ó'ÖÞknb;2Ø í+NŸŽïÜvL ÖEÛPÁß»Xsöñ» Í8¥‡3iÙÿ¡L~xjHæG—æIÏx2€«J(€–Ï,J%:'÷â '‚W‡DfämBë¿ UC?ÒÑFŒœ‘Ý n[gj öÜ$ÇaOg«ˆ]f;GRe¶Q‘"ùƒûïÛÊìäf¦‹­7‘/‡eÍHâ^vïk8XÂJ^(ánz$—û¦Ô¸,ˆw…[>솞˜Xse{†Åyê{KºÐ~&ÆX²(£ Q(ã¼Á/1TéB6«3HÞ•¼9¤uÉù*¶c‡yÜÓR" Äúj´·i¢Òš²Òæ|„zˆRìÃF›cAc?ïe¿IEŽ®p1–.À$I™ý’Xî*~NŒ®ž‘Ï>%Æ!ÉìƒÈ¦o^&ÃBC¦éN¸N:±UÓÌT ù¤çÓþµ„únD©FEQñ,X”§,Å'L™+x»¯JKG~îʯÿb^xÔÎ7îR•åŒï‹Ä?2±n#ôcÅ×ñæM)ØÄ¶ÆÐ#L«VÀš-ùûiÛ‚i,`½¸†‹üÆ+Dp}}oµj¸ “œûÕv/ç]Wÿ"ýÿ‚<¢Ü1¯ƒ g©x‡fÏç s†ÿ@g+›Vü7N¤ª…q} ¡®j+n*—egIWOûÈ18úEÔgq3PnßýO°Å,ÏH6籦KjÛ[8®hw(ª­\ ¬r·´Ü樧”áÖ¡Çô©Ô9ÔT)ôÞàŽQú—­)¿¯)é ½€e=™jæìqó5¹BÄcMÌö‘×èž¼DšG£_Qiüò½™ƒá%â|ãâÆ8á‡*Ûd·¥ßãfJ~$Û fî?ÌF „’ì¡+«4Iñâ"_+O‹)G”ˆ³þÞfÉ—°ÊoŠã2¢%T¸*€g|d?'¥iŠÛÇ—àUÿâtºþ/»OÃxÁì½®©FÿóšfGŠZ¸¯ß' û<Ò÷I.±Yám>ó£tS¹HsNLc¡--‡³]Å™XÔÌòÎÚ09Y1£qŽ¢ˆ«†µdtc+š­xxfúr‰QOE6ߥ6J0øÞnAÖú­ÅòM[•6nÿ¤’­õ'T×5ÒÕJ0%«n‰´Ú\˱£¨dk5¸à‡ZY9xõ’ÊÏ!@òå•ön%zwC ?¤õ‡ÒO¦O~ðÛoÃÕ݆|æ|ϧØÅÓ9|Öèíwm‡Ù#†«H²Š ]N­q1X—míŽÝ\j°­¥{nCÏ~—x zh‹ô“ƒzƒâ ùî_A³ž™ƒÇ÷0ÙЗ„¥í§çq ?Wwü5œSÊ´ · dàÆ®ÀMw½äƒ‹k]®ï%¦r§ oMÇ–¤UàÍÆ"¨>YÀ¼r$Tɨ)WTü€»~=–yGöP¶FM¼†õ ö$Å$‡ð‡¼~¬aÏ‚|#- yß×ŽÉøìëTaŽÔÄyÁªB¦£A†ft4ƒ°.ßWMÔ©9gÌMòkrÕ¡ G-Ð ?Ø€£gê¹%u¹Ô ~S¡?ªwÉf4üT®# ÍŠÔ^‡výÚgØw¨Ù?Ì1j&ËXPx¹¾ä3 (ßiHö¢m{dHLI¢A<é䆌º๻Ð'…£‚BaÖTMéîD’+º;V˜jYÇ?ûÀaÆ¡IU~ˆÆBPº_.„ S[vuÀ4h¯>ÁñΓ‹ýÜMÓ^mf!ûN–¹UÄèo¾®§±ýýü‚̼µ a‡›Ú0´¯båL[Ã^u*ºìö§„ðǨÆÙõ"së;þÆí‡šåãê†iK=Ü'%9T¼D×ÅSÆ b (l®$ñ©Á9þNø*Ûñ¨:Ô1Œ TSHëÇ3I’ÓýKXÄ0·UÌrQ¸zz´óälOÓòcícõu^ ójõ‘ˆ tŠ#šœ[º Žêùƒ;J ÁÅhT³·áú`†Y; Ó剚²ÿÆ2|ç52“Œçl?¹Q×Â)¥¦²¬°‘éázÃÎSz¡ä*UFìc^Êíºfà94«®ÌuvD…%:®)|€ zÖ˜v§¨R•wvëÄ¥y`Š ºdÆhÛn›}'¼àã½0RÑA¹µ[V=‹mŸ]Ã…Ã bÜ]‰Q÷ò°…Vã´4âËZ7=±ø ^ÕÅ‚GÝ ¶ïUe³¬ð¶â›c…Ué[VŽ2y;¯ƒŽoýý¿„ÙŸ:gWãe¦Èw#åÉ"ѦBí[½÷0bÆÊÖ’«Óf˜dhO¿ç²ò¨hç4b:?ý%&ì kw7IHöâè ¾ÇÈ4ö¬ç•»i¨@¸ê&71ý0fÑœ¶ûµä0:w¬€Ý¢<>KygêÑ&;(E”nmIúš}Ll˜âW"Œ ’d ® ÐšºÍ3<תš&RïŽëþévŠ‚Íé°À*÷^š sò@ƒò^ëG”ÄÌ<Š×) ¢>öžþŠ~ÛÂù¿¿vèeì¿ùWjÊ„+vG F®á!@o„9í¯º+zÔ³3³¶3á}ù’Xxëï'{¶û…9™Å³›0®^QkMÖkØ04i—zãðLÓ[¨ú:ž5æj®R5ë$ xˆõàÍ*b?Ç èÇÒýii+éfàë/Q4NÊt,*»ñ»/'½äSÉÕºy"Õ#<[zµÝ â ĸgï•9J§#ÇhóÙóô÷˜cŽÀéì¯ œøÂÍÆf%SÃ2Èè|UIç‰>ZM„õ‰»-…5Ô(úe—mG4uŠo'7ŽžÒGš¿dŽý!² ¨“4Ûº’í£¼¢þ`mZ÷r˜>ŠþO˜ß=Hûùü—þ¨ÉΊ=t)—d›IUFœÐ20BÌÐCŸñÎÜm/©ñë¢é—ñN`OƒOL–32k–^7E Þ{&ó1n¹[‚a±aÎ<ú®4+×D¼1’­|€¸‘œ‡gì¶Ò ’3 ]•úl9ŸQ¸1wj°…»1Sµ]¹N¯¢*@ ᩚ„¥Wâ%Ò%¥E¿|ùŸ‘Þè½[®fÏ,àXÊÅ5ÅŠ‘]êA7 A›àÆd î#µ,¼¸;dö —ç3þ]þZìîÜЧW%QûR-{>’nÜî±²–Ú  Ä&‰+ènšø©i~‰Ñ_ž$ÝEãGÊ›¥Äš¹yƬÍTš|(rv²c€ É8z­!|Öî‘_®Ïúß M!7šÆ˜ˆœ¯ÌÝH•l¯oNÊÑ5ÂÄÊa#gvÏo8$bTêç©NÉÔf~¹¬¤@Fk4rá¶ „HãÜÆp8GÝÜÑѪîÐèOÈ0Ù_®)¦ïƒŸHôOR:ã3÷5Ÿ[¶ÐjO©Ù²³ª5 /§nP·°‹›‰Æè¶rœÖ|S£ó2/‰“ÆIrhUÆ;VFä*Di±tM?[Äê˜ã.`Å ô?•Ž›³Ÿ@ïsZn9¿^òdm8hD{¢AVÈh‹åÊaj˜„¤5ÝN/¡™$>yg%ü0a1¡tñÕÖLxíÝî«ëÔ=ÊñLÀB"}´p$ogBˆ9qy"†üSË€BeiÔ‚±«°}ü¼Ï«ã@±¶OҋƲµ] èl¸Ä®qþHì9;.Eÿ½²ëæ>ß)Û_(6Ýnx:Ä‹±˜§šgrÀ·lÕ+´Ãl\È[dp±O4#0æ>ˆó@d0ÏÀΓ ú>Zé˜H*«ïê [{W¯kºmÉ«~ÆÜS­u®3Wýž‘ n«ëÅÀåˆþüE:âs®Eì­PÇ_š j’ E÷oÎ#n4–±Æ&÷|>ƒðÏÙa%cÍõÍ“/Ä2á‹Y%3¤4š|èçm·³ÑªHÄŽÀcõ˜E¿÷ýCJŒ'í÷âúÁÌz¿Lž"ÏYÿtB½ü3Ñ«êΔÙS˜ˆB­¬¥ÝQoØS'3“ÉdêB,h;¸ÈÉ)èÎihº†n®*&Õ£`¯z’,ôç´õêñÕ¬cÍ>À ‹;z2²ë éê¢'/#’q¥)¥kÐ5E¶åÌÆ>Û>Ar‡êv6&ø)÷œ<ºô,/Ëc•b¹A gâ8†:Hç‚?^À§ÀêtLåöxÂ#à™BQØr¡r"Fçnˆ2½1€‡‚¯OÒq÷–Êvüé• x$‹¤ìœf=2§‰}„3Ž“îÎoÄéŽ@FHhÉ!ø‰ì§ô§rHb‹g­ˆRÆ©rZuaë8~7ܳâ›É±d{P¤<=8žÿ­µ%²ì6{7Ž@#â $½ËNVrBëPº ›,7ÅdŸtËÖ@ Ô'_^>-ü#1×B¤è!_#4Õë‰ ¯­¬}9ëxahÿ–òé"±Ç×I3ñÕö…Ó˜]íc2îdüAp:2¢pd§yñì*ù²¸T ù½ØùÏêÝv·ñ3X`‚“án Ii@å_B²¾v3CþD­sÉ5>o8Ïz¼€ÌÚØÝó±!i÷8KؤXõ=Dº¾LxI´œ·"Y+‚-AÓÿŒÖñ0¡ks½úz2°‹r÷¹ ¿[çìŠ{nÚF|l°‡N.ÊZ©€ˆüô]{#%ƒãÞ¹ »ös“3ò²Š   RéUݱG"¸öGqÞ¼Œ•g·ƒ¡¸I›Oû ±M_¾uÖuqÉrj&†’Üu?ÚÆžBÚn©H`ƒ^uŒÜaAóØVèK>)fS8ŸùSc Ê©Zp†Æ)mK¡õYjm«A‡6WÝ]i§-@F;Tà¤mƒ!±V ’ ù¡¬¼ùº+Bû;וxì¯Eáú‡\¡Q°6Šã .ÑrËIÛ~m…2š™,ôøoÈh,ôG‹TÁPîµj‹6W³g»ïc:#ï/¤F©8+é– _B ]ºœ@åï1#Õ=(‡p7FiæÄÎÊ«*í=1¼ <£…Ž$»4¡t%œ2t$2«BÊ2fõ,zû‰×o’Øü²bŽ­Ú­h¡¢ª§ˆEïq\pðÆGÊäkÀìku“з( N m妃žè¾'Þ™"H'­°í_<üÝ¡à `î!=&s¿3ð^E&`üÅdz>›‹ýV´x\E5-€0ÔHŸÃêþ1ÑîÛ!ù!W]t I£pÁöKžŠ[ìíµ›ÁªVíC«½•æ:³E_¦x¡Ü{&€«û[Æ?®žü 0¦)†¤ÏAü¹ìªiêŒG¯jU Ÿpx‘uвø–˪log¶þZ?íù»Ÿ†† (’Ÿ=²!g¬|pŠûø´º–ô—ÓT¶s¡¡+¹»ÿSÙ;ÃYb¾[¿ì®h‡] ì·?µSÀv.ñ²TA}Ò…ÀíSŸ´­ÄUðç^tÆkA狼Oì-¢eÐWöØj¾IÕ«ÐܨäcmÄòGhñ‘Ÿñ6ðsÛ—¬Iq¼`ý`ñÒ&e\m.›&‚BêÝò^*™'ÄX¯ÎϯÜè)õI.5.²þNòâÑÒWÒ5•1÷"eÁ©íñ…B‘yF‘€i½Ÿ£“RÀη.☠ò‡IŽQo‚ɽ‹É¡þ0_]•èK·Õÿ\Ũ„gXçL- gqPáŒ2‘Qpxqxúïí¸”ÏãE) d@„x|}lœÞs <Èß?óbÙ&ܬDÞ3Ú!³6oÁÔ^Ðe:¬6ÑòÆéòÆø¥ú\Ƨ“híª$ƒ|ïiè[âTýºD¾†K”­SíS,­Ž³ØGñŽÉ6ɉ»º!tù‡øЫÆÛÒ}!©AtUn½æfb&‡p†J“¹^*ïžìB 3bü"TAíCïWÙáS˜f “Õ}Ýêc'4¿"›ÅHáDy97az½¸Ûzs‰¶&— ¯¯X{P<ÚJu¥ü÷|‚lV¢¿oî¹RÊ?¸­`;ÜÕŽ’7ãCת†?1,¦Ôž˜£îà¿|iJ<<ÄÁ¬CðÇ™«ÉÄ j{-¬n?PÈÔ³6Ø9]ýðÂÜ IÔÌ=ê¾M1ç|•lƒç·¢ïzðì§×Õò §nªVÉœbTpÑÁoR/ø棉–NÃ~ËŸÅ‚ªCŽá ¿'?^ßhÏÅ_×¹iÈa¬Aä)’ !†T·…ŒQ—8ð á `€ieÕØ/‘/(º}/åÙ”×›æîýÊS úûõNH_x ¿Ð"æ°‚óG4…lúž£ K7£j|Ç’I3´ÙºÁHêH¢ÆÃ§¶È:GêÆV„;§† æ$~ ¼ ³×ØÝ0<Ùê¼ë @0eëûÈ‹è#wÄ Háì7¼&¯ èqÿëÍz8ôœ ;áážíMöê?ÕÈ)´KÛ£ð©Í!È„ÉvÎÿ†¿L3öÃÐz×lPc’Ô°‚¢iÃÛ:«äpÑ*9ÅnuŽª&{*CDôÚxh&zÎú`š¦óûS‚»m‡Õ4„¦•x#CEúôÆa0zE…½FX:_b,úÔø›ƒêæºs  rjFi4îj0¹Øæùÿë¨É¹[ÒÒáá]v¥ß¥ÊátZ”}|Ó-1®š¦pm½ˆTLPtÑW«Žùi¾j¤¥’\ÕàÊéa÷}°Ïc.šg0à†jè9vB¼˜MQVÿ'¸ Çèp½Ág*9èݵr÷{&‘¶ÁmûIu&༗Ûôù'm ÞŠB/.,¿0¯à-‚E=tn†êÎÔMUkXÄy³‹h×U¢7 é’”² yÇŽ^ãDã¼&‚:;;¥áÊÏþ.Ç}›®l‹a[L–´Öàz{U#ÛòBÕpûÔü?ys u³U¼r†s6u–š±û:މåèì¡i¢Éظ€­Ý/*OŽ ð] ôÚ¯(­ãT°yâ‹o»nî÷¡Ó†¿ókÖVpê%£*õЫ© ñ#„šñ5Ç ©Ê$&>ÌMËêr~È Uƒ– q@aá»#¡,é%-ãAtpÔÖ6†ÓÞ/Öqiìö-fî åqè-fMáïÝŠ®G7ñÓ«w±ô´B‡Î¶w`Ž1 ¡&£ÏQ(‚–Ðá‰bü´ÖûÊ=F0)ÏŽ ë´ìwÖóýUÉéúÑ¥k“˜Çý+ˆlMî,ã¯ÚtÄÓúÒÔœ¼Hnžc.É뙑™z¬6QeFÁJŒ³ªƒÐ6_ïQæ¥x sÃè>]؉óÆ¿ÀN=C¹ù¥ dßaBºGí.¡|C‘&–/Ce™!Ž"[â‘DÄÖJ¦(){S€ýgà6[nç7b¥.9딎Uj ßúRð¡ˉů/oÌÛMpúnèÐÚ>J_‘ Ù½%ÀÒFhxÆý7Ž>AÉà7ˆÀ¼'RüÒIjÝŒŒñÝ-rkªÕý‘@'ćËë’í žü*Ç…µH@ˆÌä,j”æ¿D‡¹)¬e‹o²>H  /Yö”É7€á0GëmŒUÅ’g9˜süòˆvÞ À5ÑÊí™Ïsѱ¶“‰s³=ºX:Ž¥ëþÙã£^­y· 6ú<®¿k\Ëx&·.`=ßk ÔMH¦*ô¡Ä–÷´³I«\ï¤74sŒ%§À—ÎîLS·sÿQ5*bûâroÄôo†kº£Ò8ÃÞ˰™Á÷¦™Œµ49kE~ô£„¼Üh£<ª³õC¼LJØë⌖ ãY(š1š·áaò~íóàÉ0«ÏCÕØJÁ¨Ùg˪l˜ÁÞÚ‘%þSK¤U]J0r¬À¬Ñdw7Jz®Zò~æ ¦05"_µ©¹¬û³ -´B ¾e8ZF•œ€16É»¹yrÀºõÅÒP½u’A?²hÀ4ÏÇ:¥¿‘Ì? õ. |$®S;Žb’Yëû¾0FM·kåÆòãPËÓø?x9±×ßŪ-5¤\''ƒº/Ȋ͇²ûÓDø ©—iæ-;¬-ëÔóIR4Ÿ)Ò®ª5XÀ¼@ýˆå&ÀZ¶‰Y!²)¥ˆ ›iW–‹‘p+’ØO8þ*wo­F[@‰±“i§ájHÙ€Š[ÖY;lÇ& “5R rb61<Ÿ½e(‰) àÌ¢ 2œ¸~°G«âân„–õû ^‡ãd±Å<§®–‡¸Cx%ŠqGJ6‡r½ÿ¯ñ•úBú[±îo›¯n„Èô Ú3}豋R^Xòv#±´ÿÏð%?\6<ügâeh \sX]r® ï嫌, 8ˆR¾!ÛQP-´7#Z¢…À€`l#ù+<ë¡öF(‡lZš}œ-¶ÿ3tO,2$)«Kââ™@nk+T¬Ñq*OΡ.DsCý&.å^ bÆQÝÐŒ!d‘4w<æLÄ]¢w#|4Ù`Hô¨ø¨Ì…ÔþSzà˜{Ô1`3=Ó çž¤´lÒ2k1ž6=2ÝúðD€Êï*À LYL:àH $˜ PÀYò»úiÕhé2ƒh£ŠäÙìø=µÑyÅÍÃ, ØjnVNò‚"mì ºHwÖ‚Z™/ÿã&> 7Ÿ:o-5†d¡A2J-fŽpªõùèp]U–ІN9÷˜‹z¡ª©FD!ßµšiYT­$i©–²ñ¤°þçÓۣ°¢ŸÓØ¡1%A“·%·˜Bt[‚_ˆD4]ëóþöIŠßü(U-±õe¡?s†~H±—<© E£Ž} †5`Ã;¬qåS„H¯ÄÃeüÊ«ù[îM‘#Íá¡F`ìï«*Òg/:ÏQ«Áwz!zÏ2?XËÿ`0¨àFNqsÇkÐÀhQ ^žÑ"ö^\ËpLæ® ˜f—¶Û|DîÞ’8‘öýЋ¹çñjnbµlCmÖ ÀzAè‚ц2(*KÀ¢·êÓZ²š¥¿äá%©¢'Í1RhØ_¼¬–¾ßŠoS «þGÑpLìûø«\“;ð ÎuqµÇ!|CÄ12:ÿѵ4ý¦Æ 6£HÎýúy¾gðÝ)_ÝÝ7ËÖ¥_šžíPSŠ/Í<¥½”̹7Vy'U:‹%ë£]lüÒ ×'}ÍNFÒG›O·½§XOA[œÒZ™• Á¬ c##ÐWŸðuñq €Ø£={-DÓØº3ûÚ‡ne|š—úï•–ò™\ýý:âz¤ìµèyj´7¡q¶ÌA+冲GŒlý×yA¯G;¸è²+ì½Û¢Ùà9Îý¥|¬ë Ò¡ œ{»ˆˆºgÕaÿqé9'Ü. µe'´½swД²ÖÌ~äa¢ËY]€´íDR–›?BðÚ+~~3ýVgZw›Q{P¤ gõª, Ú_—6øŸ»5¼â\ƒ¤šZmO uVÃGBÎ ¡Ç™VäûÐk¹‰Î—’ëe5a(Ñ cnI^­²‡  —¯ŸÇÀúk]’á PäÃÖY‚a¢÷{.Ž"±:]ž(b‘ ¬ï"ÍÂg˜‰yqýÒîK<˜K 5Ùe :ÿNÔüØûx˜ÂÙ¼ÿ?T>Ð"r*>Ibþf0ì‘^?ı²rå¬3´÷)‹î[Ö,©¾•¸=rU£ª~Ö(úŽÏ¨ Í›üÑÛ†»xÌ_„ÓE1Ø@ª2)Þ#’¤=|«r€Îœ bn+¶9 %—¸Ü­*IŒƒ¯—å=4°×N¬~tNO¦;¤-Á÷.KÄ‚Þׯ­fDuT†i úÏÕ+¸zzM6ïI æóO'ûÑNܼøVO`qPìÎò¹jŸTÁ £ax}ˆ×4вkýÃÔÞÂkäZZ½ÙÓº*ó,:UÃÎkÑ£‡âprIÃËV,ªÓ%Œ±à)êS`°¨I|¿lA»ö®À.«ç¾éG.ÞóÅ.!¦T»—kг‘U3ÞæŠDcïÇ"níñõÒ«‰@¯©’V¤Ê˽ *ÝG~# z.‡€VŽÜ÷#Y×59¡îÈ‚ÔMÄí3®J㊛›kg»†!TTÇÜä‹‚Åsûïg““ÌM°ÝS;­4²]Zçúûî©u¡ž7KJ¹Ð)ÍñËÖÍÕÕ†*T5ïVpªU{²kù©»/±é0¤LÑiùpÝ{O¥E¬Hqé:v`“f(»ídK—+‰¦§ø³ K¿Rv%§.‹ÈN”-Û:xrÃP]º+nP†&ÿ–ØÀ{©8èÈ%@DfP™ðŽ zJç–‘r}¨Þƒ)Â8Ÿ¸¼‚Éhë,ó´ß­¿\zhkp8d)"[ uŒ9tÐPùBLþüÇ¿8RÏ;¾ú?ì^IÄñG’ÅòUÚNyq@3Þƒ0|ÅWŠzY¨<.ˆ·ïž[œ¢0qS>[Ìüj•!ß³³˜+ä£Ã78u÷Óñ¥­‘­´`\$>ô¡ôºüÄÉÀiUëÈÙf½[±z|svãŒÄKªL`ƒ—Ÿ%vpDïÑ$µ0 Å©f²|„Ïw}k&ö(›i[#Ïâ G³)Îgäúô‡n$sZk•Èçü¾n‹ÑÑâøBVÆsVhyRh픬>µ¬væ÷QsNxÙG}n]‡øçj ä$è^–ôb4ÎŽ«» ’Ñà{¹¡ÇÒ'#Òv+Yìn:ÌæØJ øÖI#çz…„Á1BWþEöG»Ýz‡;¹Ì”dìD@¾_E‘JŸÜK‡|æ^zºë-îë›jãÄ:óØÈeo¡Ò`qw5$%8€µ¦wxðù tz\ØàbùEdP=ºL¢7®ûfx…pý,Œž¯ko*®ö•hBcY>­å±‡2’Uð’°„[ÇèNòx",8âAj<l* ³ö¤¨_ßûx£ß{ÆÃR\ìÖ•Ì3ÇíÑ3˜·ÐMRÚ Š<6†!˜”™)ŒÇðoov­¹©ûÁìÒâú[Ã3þ7p^žä!†×žq\‹h˜˜o2$À%—ê‰U’¼Ÿº<6ò¯x¯å\»yDÍþRË@Ëg°.l ÓOF`kñrAP-+÷L—§õ¨wJ«v ~TuƒvÑIòø´ÄÈ>Z†tÊÍjž,ãl÷¥ÞCŽöðúggx•…ÿuìjXS©XäfS``B… ϹÁOˆ`­_Òühn7ojoBÒÓýº@ñÍ  ¾¡cB’ð–#vDÕ‰ ïRƒ—µUÔ7¼œ«. ;\Á-_¹ò6„na¤³Ê#ís¢Qgrù”°F²Õ£<2P­5hÞ(œ­ãÐlÐ>M® jÞÇs£ëðÉ6¤3x±ÍmoÈ8ÞsIÌ{m˜Se6ä½KA³Ÿµö{^¦*ßHW.ºÿø±@›äXkmèáƒoÃÃ,…¿ðò(eähË€âç.77uØEˆV`Ò|U0ä[²NkU‚xUA>Û¶ ¾$ähäÂ@‚bHϨ@S¾¼ª©zÔéô›ùrÍ‹?þÁk Â^žúý”«~yƒíéZ=ò'GMßç ¹¢w«.rd@@Ø >À&£|l‡×?°Êí©ºâ°õTÌäªHMæ¥p‹FàÝû’¤Wn äêŠË©˜½ÏH ùµŽå.3¡÷+ÿ5ûΕµÍKÙ:x˜vôÆl È”„”Ñîä3ŽlH¨ %ì{" IMóQëÓÕê‚‹ÚI€BN‘#‰ë÷Û¼VÖí¿ÞÁ,(N)“Ï 'ŽÉÓIƒÿEyEm*ú¨‡Ÿ¿@…“Vç¥=’‚oÑ4H+í©ÆÂ’má´Œ57£É…Rs&}F?ŽmW,°ºÍw ‘¡h-ʨÃU–`ªµ›n2÷}ÛVØÉ€èäüSeŒ ÒÇ—Û#ß×`ppŒ¥)Éõdå“s®ž|vâ‹;¼)Àêè^ûÚ)ýûQLkîÔÌ’Û"ÓðOÑïZµ·NÆÁ÷1¸Ëf\MPêöÄ?kj7­e|GënâÈxÿ(†³º*B9.0[:¸ À<ÃÕc‹oË`Œ5#poÐËå‡ëo;M&,l®tEë1bóHV,Ñ;">ë\áW×ä¾ ;ÌT1a©+»ªyÝæÖþ¾<>‰fÐöœUSpJ€Ò$Ìï©ïüXýçrÞ•õ[O ²:~'eÄØ©‚Á™9])WÇ¢kÖ½·à„Œ–F1lªÊ+qm[¤Ýßï©C$Ov*¹è,e²ÜI½NeŽeíåôõ6ew3øP/nø,eÕõí{ÄvýÎÏMK/XÊ/ï¹Úí{Ä[=IIɲ[UlûÀ$ù!nêxð5&Þb¹èa'‘†‚ÒdÛsÛOÆ=Úuƒ‘Zåßô`ù.ŸÌÉaäžòÖ‹»'ÒðJ攊¯•íV8•TþÚ’‡ºüs¿úEýÔͻ׉¥iSÚI4‡$YòtÿF340E÷N)ô_´;X_oAƒXïÇ“ÌÝ‚E.Yä&Þ ^jYÏ6Ï*ÔM˜:“dGU/¹±ü+Mõà…mÿ·öÑœ@lÊyþ1Ia÷iQáò.ÒS  ¤µö"uC¶áQûÔ‹, <Îë+Ž„¦¹ëíPòÂ3èÜç:«V{IÀ•‡Ai˜ÍMDà÷ÊÀ7.bSÍGÿŒHÄqÏÍsÞí+*Љ>¾(kXV­H”îëÇÑ¿HÔ`¤W†ì Q¥»q™(¢Qw÷$sdŽÊ*¡ºÃÑí>.ùN«Àþü7\•SâäE[VM^7^<‘º(Æâ»˜òú䯧ÀÁûea³½-ó$šÌßes¸(ï™|Ó´­uNÞÍÎ÷®Fœü Ü”)|еrï­òôJû‘À!wiâÅì)|öšw1çP°:ÆÉ¡wd“(25«¤ŠTr­\Åßúõ ßš.ל6r ô[…9»å,8úóŒßU(žë$$}ŸO¹S?ŠÑ>ñK"°q œ6œé²W”¥}&#ê _a‘>º€†Þƒf3  ‚ÚC_”b†Ä´ jáï. Üwñ»0HÉs åøÂéþwkß>[-àPiVÜzÀ¿Ë[´ŠaÓLÍŸ³EÑ»È>{ÒÚ­U{º–ÓÌÁ¾±¨ Q"ïtçì…z)¸ƒÕâì!õtÍ¹Í„kxuy¥ëC§6‹ ÝÖªi—©öшk˜Çç"/Q&41þŸ†àó cÂu) àœ6Ùr¾ÞÙ¥FÚXíu¿afr?‘Éñ„Ù]…YMðÂò¤w¤#w¬Àgºá•ÓHl’ß›!öw÷ÈØã¼¸m¢œùuÉ („Ó‰ak×wjÜ$ÂnXÎç®í…IªP¥<ÊÑ^[6PÂŒšølÓÔ䟌B(dý£Q9Þ±Œ¦€’zÿ÷sJ¡á}±NÌÙrvá©|YË‹Uoº„ÜŸ2ièòjÀ!n½€çv(zî¡ýB‘Ñ…}jMò™ ÝÊjG›îÀ¦äMöeó› þÐxʆ³ÖÐiØ—ãÝ–>‡#ÎÎÙEA„Gw_—|½Âg”ú)ÓÉÄ"§ÝPÇr©bÖk ‡Óx«Ÿü-×B¹iƒtRÆ'ÆÑa=ÚÕv¹!¯B-ìó1òZ«v=›œ»øðÇÝ–)9Sîóå™eZtó)¹¯yû_Þ4³ìI ú Ìî ^B%þ¡—>wèû¶:Üq9ãAˆ&$#ŽâéÌž.ü¢pq1ÂL6ÔÕ¬ðˆ  "yцyfÅs˜<•bF”¯mô•3®Â-³@Âo‡Œ•!•yÛ¼”‹q–bb’¬!Ž[ …ý³ùzü‹Ió«56ËÑVDÝísŠÒkäë|c«†ÎW;hï"k¯Û$‡µ £žT_¹o­— ¶:Ø1¤E£ûðÁÈNNx›m=šz¸ÏØl~ä¡RâýuŸZIæï*8¡ÍaõtA,ÎB\+8;øzÄõ-¹}ß8™Õ&yÞ.ŸOJÞV·7d-l^©ye2™»³æ‚Ua¥­ä.À^8LÉ„Ðqª’±W~«Ü³DökŸ´5öêvÿ±Çqa(dx× þß:¨ä'‹ô… äè3‘ž„:ãÖdž4Ñ)æ†Hn¼ZpCá, æ1)õ¶é… zU!Òãbf1¾B°€0Ö²@ÆŸ­s Í7×ú}¨aJ“`AÙÈøç|:Þò j§Ë„uÓqT¨[àzêJ÷ö rã?ëBÈK=)í%îNsáÈnæXâtêm³‰Œ@‚雫Aš–Õ{ÒN½1••]zÑ×l«Á¯Þåþmgæ²S€_Ãb÷l|ånÔú¨„ÀÛJ¥X 'á®uÔÐÎ!t‚†YçÖ%F¤Nƒ93Ô³f‰û¾HOB0u‹Œ\d³À:O¼'£û>ïÙÙ¦Ó%@{”e°ÃgM®¬õÁåR=°¢K;hC4h~ÔÔž²s›á3èèA± ¨äºW ³iÊOd|§pOxP~¥›Nã@Š“žvý‘»ª¨wÏ|]xQº C´íýšÖv "»ÿÅ4RþßúÓQƒ°‡)¡YŽé¯²[%Q‰àþ Y ºâ¤Í} WyÁ›*ÚÁkïœí‹X9ˆïld4{\€Ýj¢ ‰™Œ’!W4‘L­³Ë×@Þ%´ôƒ‘l¶7¶1{2®µÒаù£æ°³‹ (/>Bÿ™šHZcq Q ’F˜öÛ¬ZÈL`¶p­j)­0cã³p¹Ùz¦c+NèlYçYóQö9"Y÷p°‚&}vyRfÎb[Ad°y‘Šôþ¢BJ!ƒQz*ª’±õÔ¦ î䪹NÄ*™3#ñûaQº¦CxÕõš'RA|\G^7ŠYuŸ' p‹6„"ÿɇ&º<I¤Ù¾ÿBɸ-¯ÄƒnóÆx-«­Ðu·*övOXØ¿ánËFQ‘¸Ü·ÿ—5z>ÀnÑÃÖd\a‹8N¸4ÚÿMí”Ñ÷OÐM<ìÆ¤À@ãeaÇ©}ƒKïs0æ ‘sß 42¤§™^®òÕæoðL^9ñ/ä€Êßë(Ãlé³OÔ6ò¢°YI®]T¯òª1ÐŽ<ün¨3’bDù*-Î2ø£”_ã5Sà¦3°&¸ÐÔ(Ò’z¨+Ø „x¦øÁZï¡§Õ„'·’9Ïx(Ù{–k ð \¬GôT}2Àã±ÂÂÿ3ÌXF4ãýuÝvȵÉUqXš¹î·ÔÞìßç'ÈdÿÖqj­§žnSÅ6”—ó8:2†!COŽEÏÓåié/h( ÏžõZš6ß1ôFFËp”Ÿ˜O’M]С4kmù¥µêý¹™l_À‘°Ê9^Ô” Xá­Zâ…îôÖ,:žÙT&iYô˜öƒTÎy†I*ëèï» ÛÙªSÞœÿüWS–GD¹øèïu§„8 Þîü~üŸŠ¼ÛDÚPǾ5Ó°z¾‚‚©2}?m«“‡ËJD:¶©D7C¢Ã…‡©þí+€R4×>aWF‘aý•Ôv1{Bnâvn@χöÑÐ>QÍ=lµ=®lŒ$¼ß¦ž¿+<^4©ŸÄ xÈ W@¼v“¼1l§R€`ÝŸÙÍ‚C‘Ÿî«8n§Or$¨,»sz;X÷­žŽÐ Sâ‚>Ÿ¶,(ºJšaÊr>ù¼>Fêõ‰ŒjK¨ÍÞ;‚ðÊï³; 7ãæˆŽ»¢é¨.ª@ú¹2pîÈëÓ iõ—±~ÿœXO` sc޹TºÊÐ(œK±{¸–‚µv= w?>¯ 8ÃWœV6V¶v+H¶·µÇÁl†Ð OU+ûzí”YÐò~Jn¶õÈ4£v° &n™+­ú„0‘~[æ€ôùæ»-qÒ2Œ7­ü¦ç̹å<:|è–¸ÏÛ øZ“HÀFؘ@ŽúYâ½+cr‹ Yb2JçÒÇäÏ yQ7tDà‚š'éz»q©U—•r¾']©ÞWu¬Ó—ê!@6i9Œ”Í ŒYë;‘†>©H›‹Q›xY§ï¢ÈµÎå‹ä£ÜŸ‘kŒÕV í±„±v?@Z÷iQAIÈTdõaf¤ak<¢Ð¯/´ÖEHš7h¥.ûw׋F`+)òo>o5ÅŽé‡Yvw2HݲuódÐè‘üe<+ÖÌ#„?7h<Ô‚‘û¯q\˜åBx=#ÖcFÈGé{o8Çdf›Áw}¡î¬Ï¤ú©çS²hSí³°HÕ^Ô±þÄÊ}0Od”ô6÷ÿS>)®´ zgö+ž|nÕ[~>j‰—³0í¿Kôù:ÑE½ŠCj5öÐkèŒ+t;gº—áaK·Þk¥ñ #³€Ò"Ø2WFÌùë#àf-cAò¨§äQ4š˜ú—×ÁÍ:œ6Ÿ•ñ䛄ñÒë»ËåYì,üˆ&zB™õZä‰%¢íéíNɇ'äàEÁ+Já#xG¥•¨—0fòÏ®â1.ïKªY —Žg ‚°ºYfg–¬rcû ç%ò¿³ýÞUz±|Æ#^)ò†¸Õ÷µZ0mƒ#Éì›ÓÐW vô}`¨çÓ»–TUOônô˜J¶Z>?Ó†Û›—\Áæ/¦âæë§œÀP¿˜+Ë€·É¢/* Î`x ñÉe?À< Ú±6ï".Z –&U¶ ¨Vóל¢d‘K»Å”EŒ–3…majnŸ9¡Þ¦ŒÔá ¼,êßñU£ºú9ó%䚬Îá]R~{§Î6(.k¨Å0/~#¬#mW½‡'3·gï1 ‡Ãj=‹?»-rx/ŠF¸5±ø¥ <Â|ûú=õrlÌû®ÂYµd {à=§a¿ò»ïTyyôv¦r9aH«ü´ƒÛókfó w]“‰¹ñÊ÷1î®ùÙb}£Ü/€‡mÍ#+{Ÿw |‡iJ‡¸‚Ñ/Å‚c7q'òtÚU‰CðôÞÌqÂ8ž?(º›0KXÒŽ @¯[Å÷ú§ß‘ÿ® ´ŸÆªf¿@ËÂ6«k¦E•GÛ«1A èÍz>¨`P Ùx™H¿/\nÕ|\îRK?ñÒ±¼-/w‘1ŪFE¬0m²övj<‹³»olÈ’#<:¢$Ü9haåä¢Ä„ô®·7/éGîýEJ6µaB¤}–Ý=jÌh•;`ènWÖ”\¥z^Æ5mÐNZr&Š)A] µr/Hù¥zŸ hÅRÀqç;œ€áù¾=¿N×ï©ã6ªpŽä¡'í4¼ã³pª°ÖÔAÌ>ß®yаõÁ|ÙOpŒÇ Ît†)’Íy¤ë¡O+tLÍŒpv­›‹sû·Æ³ïŠl—·UÓ™["û„Ö]ž ²åÇrRµ˜‡ô Šj®5Èßý™T‹"›ƒÊ0¢ý¹ŒÝ6øòlhÍ)"4ÐXì½EòôTë AéÒP*Ï;µ–Ó)ûÛ9Nbúü¼™³oÚwGÈ@¹…ã¿¡Ìį=f ¡×o#”äVÝ®M—+â—vsý4ø$QFøê½*9aôkVLW++ŸYßâr$¼‚ !¢<Âa@ÑYÿžqK©R¯X‡{ù” “ù ¯‘¤BÀÅ/÷Ž¥D‡ê“L¤ÝFA‡†¿<Í`Íášk›rËRï•ýdÆ“˜))¥˜I¢eÜô6/ìOÌI^âA·ò$Ó|;¥{§e½± ͘^<ÉÖÁ]Ö[-˜E‹+&oä;Ì"Õe\a~ÁÞ‡‹D¼²~[?ÓN»VîŸã¸.’½;Á]<£á{©‹$›Ñx•=/q©dr°øï! µçXÏÕÑðjaëþ‘7Ûˆli5 »Ãû½ùJ&ˆ±“îé-Ôz\ÂjF㯼Ó=¼`»-Ù§üø:J‹TŒC³JQØñs¤­Æª@Íÿ'íb‚vÉ3Å$/%±lûu!n‚ç¥hvSð9Ô”vp¼&Lżج€ò”•ì•0¡‡q¼Mïॱ‹ýïÂ;9¨Ba¡iˆ‹h™Pre9¹þ¨µÄj D;R*SÓ²²¬U@±)p61g ™Ô5¿ lðÁ€C.xÜ0¥Šåé@—8T#[MI×ûQ•-Âni‘?Ñÿ,ÔûWÊ÷xrÉ’'\áÑ•R¨*#£,2x£›:K\žG¿3™÷ÛaýÁ¹xŸ‡ùu@¸E ¥M¤mØÃô îø<²ÀHƒ0P€€êÒ/$î#R­a|nfš ¨ÿaÕ eÏke½Ùkmî§oBËÊéµßã\éâކ压â‘O:ñë7õb"Ô{M¡ %²“3’ß7ábrá=ñór5´µòs§“Ù€ 2Y‰'rt…µãEx+ôÓç(‰íQßQR(ŸšL« _ê´3® ìÍzSçc©µ¦© 4¼XÅW¼:]Ü…¯]û·î±FÓJÚQ Ñß;ƒ¡Ð†?M:ýzÒÜ¡† =öàz*Oë‚LÓ<²¨ûب[§ÿC¥Îä’ëž×1IÑ©Í2ìÛ|rfzË,KÖnùnuI à÷í+CÍòk‚@Š›r>™1î“Ù„AwÀÈ*—›Ó…QÜ`¥§Ej0º^Œ"0q–_Exº†(–„•Tß6 ÄP·˜CoDÐîòéóøÂ—6¯*Zøig †‡ÂI7ØÁ¥Q†p2åÜÌ/$“:—6ÇËNpëòÛÙREbÉÒÝG$x˜tµ®Ðîo±a…Y‘ØD8W =:Ú3ìjxö†Ë¡‡$ZÙ£EÈ\ΔK‚SéÞÄf[°K!Â`Ë÷ÙoƬ¢UêïH²Y2ûÝÔÔ[qƒ.œñ¥ú(ë5ØfþU½¤V/àéä¦ÇwgõZéó´;Ta®Ó2Ê}¸¯Ðá¼EúTùUu0O´jczî`Åæü=çêmNUíEiœ—ú±”'>O’ÃqT +‡„!ñ Â‚yöYMSwÝ|­‚^y¡Bg]ÃÂJ)×ײ³‹Ž|’·PõÁIß9ØÓC¤j‡·IÿΈ.ˆê€ë/ÃÛPa ûtOуÝk2¨2¤±s8áÄbì;éÁ6J‰qAE•ìASønb¦}uƒÅ¶rŽœ”Y»8{ óŸ¦.ÞÃ×¢üÒÐÛñÖ쎨Ƨ뛂­}”âã<]#$í_&¸¬1±¾@@‰Ûcϧdq+]‹eäðëÔåËâ„ÉX ë¶Xó„v¦Å8¢ÊD äïÉËC®wÊ:ÍAÄ!Øc í×Ý·Àp/ŠDfušõ¯±x*aðUê÷ g¹@6±&'dáÔ)FZ¨ÂùSo ¼ …Âë‰6Â.‰÷ÿ’)ˆbÅYÄIónˆaûâ‡+õ£ãpW_±<Ž9je»€ücÙ—ô8Ù4ú´iñÚ¹D¸6‚’Œ_h‰Šç=æ¨B<³UÚ“¹úá¿ú¿cº[¦«\–AÍß#Ä'„[]Û !©áÒPÂêï4ÎŽ J`þ¤'ͦÀWŸ(=Q´+Q3ó {!7WòýJ—6pI ÃÂ-3Ž<Ð]ƒØÊLÊÓ#±#]ZÇm½’Ýz‰¦Y:÷j©FÊ@ðˆ†46L¢G‹ŒE¼doA¹ëËß®lT›TêP€iåmöC‰²ï\JÍÖ{…Ýô‰Ù‡G[PöPkb”¢6·þ¼M¼¯˜R%Ä&Q°n)xÁäÌþ{oì­jèWb«Ì+¥@¬¨#dÞð>*ô#Ô¸ìSâÌD¯ÐUV2òži`5›„ÏøwÒ^gÎO/­¨L Ö‰¤YfÒC"ö}Ø©Ôyë0ý[¸[è?—.V\fºû—]¿9^.,9ýg‡½š‰(Ùt£„¦Ášâ& [ -BSÌ‚B>¸ùÑb&ç;=ö© Ÿ¿#Mä)‰•ºñU^Ùk2›^r(&lðЩÌyÐ׬Xå8¶~ƒWÍM³yÝÍÞåM%ª±!coèà¬Ý ·>—ĸzï&{|h¥U·Üu[EZsæ¤ï€­B¿cáYª°ºç³=Ä?î6³+÷²$cšêÇìAUãĘ…ˆÈ»Ð¨˜”Õƒmu9)£±Úý€½®¸5uîu^/w–›>Òc9ü].×ÑЩu!ú.ü“ŒÛÈÛ²NˆägÌ¿ÝDíyÝÛ¢e5<´¼…8øÂOîáð§úÅŽ$êT ÿ$«3ÿ "Èzêx@CßâX´9¦5L¿ R•¼òÀZêÚ»õYû$·;ñÃÊ8ø(0×âsønm•—?ª¿<’\Ö@-h ŠLùúÝ[wbÚø}‹ª x À_d ¸¾kÁ«ÈMò8ËA¤_°v7N®ôÌY­Î.ú¡¼U"탛€]Ä;îIhuWl7w¬¦^ 'dÃÃÂXýÄ9þ”6üïÎÊ[µEªµ|­Å$5%¯ÿ)ÛuÄÙs*)‚ÜTеOàõL[=ê¼há¾þÒKõ²||`wm?.¨Ã&þÃÁe(Í63ŠÍ$_Ëf˜Ì °"dr²e}6ä{ï|jG¬ýcNHkN„¸[žu„ 8“¦ËI:5cHñ‹óÝêl†›Èýø‡þSøÊËϪO”¹…NÃ)˜‘Ñ,Ïög]›ðUÏIcToD|O‡ƼëhQ‹7çœVÂÐT€2ÊÜÔ®´,œk÷)8`ùPÓ×&ÃyÅï¦3SI··í‡ô¢óìÈ TgUIU:Ž*q|wý[ÿ\²iL1‘H ƒ$h¤ýгñ‡àVFPhé—xÓ¾Ä7íÞjþ¤”³-‰ÛÔÙ+stlè ±Uî”±«ÜŸZ5©QHð¯šãE—PâÓtüÇM¼Ã7‚¶C¾.>˜T=WE ÅiÒ[!ª¨*•¿ÅI’v׎}NÙˆ÷ò¨ÈKd*Ò0@2q^ùÊ?ï³Ð3ˆª9¥S#[³mI× {9„ânÓBˆ«°I¿á­ý[ ¼ ‡nbQ¥±D{q®ëB³Áªv¦#á_ëGv/žãZl‹÷¾‘ 'âåz =Û?ü"¢JÿÙcξþÙMá‰V”•+r!@Êtü a2¿.‰mƒ y_š¹2ýó{»7RBzy;Sùå•¥g ×ÏKÑ@j·Ï„I¸ò¨ùÄ™>_‘`d GÈ,–M¡ëŒR>ž5’ÄJPsN£ŸÛœ©dùó+È«5»†#‚0µÙLQûê^Àhµ/´ˆÑ(„„FnœHU穲ລMÖ€° Š“[z®u¬K©FÅ‹,ó®•?:ªå+´wé.ó Á¼r¬¨m‰°}f*p,ÔŒÐ3HÔ|;Ûæd ä9ƒ¦Å€3—¢rC†!Cù/NŠÈÓxÉÅjð²y¹kIÞâ™çP…á,!k$°bÚ,-5,Y·œ¹­\‰{yG¯ñ ÿOu 0ÓÐ#˜sÎHAÒ4ª3µÍ!ãÀìv«mþ¶ÒƼ ¶«ÆÞk­à^ÿßB‘¹tËn4MÊk·:X»6»Š€o‡F¨d¯îçwÕk~~L˜w'–¶Å•¨ ™¨Ò'uç$+”l„ÔÂ]µÀ#T²z¢=¸|&<8ïT¯3— 6” $ÕtM)¹E Á½Î„~:Xüò®Ñö©Š®±è·W+ X1·{Lÿ.lÒ 8Ë.î4t;É_“y Çš¤Üs†ž`[a@!/È冽9r<à Á×£€0!þáö¾ÇmH°ŒäÖGªÿDP>¸ï᎒Ë+A¸ ÌË6h¡{7/Ã^}‰ìGÑ“¿XDšuœ˜ÛO|MQj†¦f+>H¿&bi•nŠ;VÆûGÛOÈóG[‡;³Q–Cû}-¿TÈiÞTï@,ééNÈT6ØMÛ§e/bØ)Ћј¢Ý… ê§Ì¹Ðe–ãýî(ÓjçƒboÐ}èwq5/”=ŒTö¦q“hÖ¶æÕpÄ“@wõûQ~Œ¨LèÒcc•¸J¼W—!¸²Ì@Ý *`Q똱‘›ºU@¨ù‡¸¸@¦ÿÒ¥¶*Üï§7­ô#o_“ŠJ“£1rb"¦Eߎ<ˆÿ¨äp¬ö‰ ÔN‹>‚O8wA q¸™oª±~ã£b>g…ŽçXO†ßϯâ€!±éh²*Däö~ΫoÃ¥¬I΄È°n•ŠJà¼üÎ)ªàö¼†óÓK/½P®¬ö€8Þv/ìÏKÔK äîØÝôPvF²Vèa¹Ñ6Z%1ܲÃì,ˆy´x^¯V7Uù¼}pÿ¬-¢†“[ù—XQÈÙ‚ƒøh$!`º±[FŽ„ðÚ.¾mêæãâ@Ñ%š÷hûóº?m«ÿƧ„úí¡6·T`8¨i <úºó:Þ§OC" °»§yŽ@g€Aógl4sl¶pr¯dÛÁÄ"™F b>ãD×6u2I„ÒºáÓÔÐ)ÉyCï§©Vïªó†3džëv¼#"ND’àлq±ßD1<Ä»§fHµµS‘øîeA µ\|%¬« zEPõ ?“®Ó½]ôõ¬°`æÍ·ò|8 ‘Hø*ç´K!‡Ã)|ûtßëqÖ÷û² Š=_',û¹fœbÈ÷Àu?¡Ê¥7¾8œp¸Z'9&Ûh»–X¾:ʘ¢O ¯‘BÅôßÔ™ ÕZ’Ø ü¼.Ÿ8Ê`‰B>÷….¬ÃvÇÀ‹þî1…D)ôm¾ûd›-®ª/éß`”ætzÅU }7‡¡åÛ”5zƒÀZÓ†Äé(Æ#•FÜPeîÚ‹þ¬y~´àNãl|͉·/;cy=ze¡„L ã5* U[Lº?…|Z·8\³çá¸ð_mpÿ1gv°Â1¯K÷¾øå””ØyP™œÃJ‹KpY oË–Ÿ£¤|¦×š-ÑCŠ_;D¶K9RXõiÈ1ç:!-ˆP¡A«2oç¸ÆÃ¼Ô¡jÃ:šº$2 R©}˵½ný퀚Nûé©XT½7§(…B(öê’5B¨sßÜDJuiZ^ïBÄ kúʆ¿è±#Þ)?6™'ljèq2ö`ànÅ<æP4±]õ~§Õq¨iFÞ¿û*»>¼ÂF í¨B?ÜilìmO‡~óü—øÑæ,^²#DË-v$|!9ˆ8¨^5‚УTEµù©Ðøì@±(gá ¶Ã$æTjÐý¦¨Î‰Šô´*ÊÕ©W_Þ­'J²X¶Þå­¨ªv÷\mñ5SêÏ ×n˜˜Éå_Øcîÿ´êBºEÝÏ;‘ öš™ÀwXùçW~:KghKÔ÷™A- ØícšèÖÙM¬§gøÁ;ZDÝg…œTÙΊqJµÜ ”+Êómñü’Íûl×Õf›:¾+5Ëäÿx5Åg¢÷±ØäRý<•µ…ø9ìÜß6ùQ…4Éß!úVì/Ím¢ƒ?Ü‹; º(j–Ç\T¦‡öØÑm»$^´~DzõÅŠçù,~ì"›9‰ ©Î_âggwQ¾²ŒµYgþL£MÆo®¯c#&eÜ1±†hIðâ%)ãMZ„â k«ž« då;àÃþ-(I®îö° ލ؇ã Éx/ÛPO¦…löŸÁÑá”`ñ/Éž+#ù¯)°o¶Ö÷ßý˺CÐIô#ÒÄiƒýV¨#±dî4¡À²™Sl‹d°¥´î¬’ð<© ¬ÈŸN îšG^G?cŠÐ¥fû*¦hͤMƒ’e_0*æ–E ^ÝŸöt€ôÙŠ‘Òøk7Ÿ»!Œyh¿å³ªžP"ZCTãjó®ò*„VÇg+$–NÚn=AR“Ž”ÐBx9Ø6¦¹Õ³æŒ¿žÔyÍo-–šÁ-QÆ•6µÐ•“v‹‚É.›Çö+™œïžíÚw÷&üS댮´C¼Ð=PjÉáâÜþpµÉVT–“ÝÒ™>‘:EJm4Ñ&%¿_p«À—gBeÌÛr¿úÖtM|,sU¿þåÙ× W†$ ÜÓ¾OAÃÿQøŸ)rpÝPˆþ €úákT[ÇE^ñ­X%8i·O'ãd¬·@t`Y=gßYGïll“ÁC}^FOTB*—IÜh3žkoæâñ¤ö@“yüÛ]]RÔ‹Š‹¼RË{èJzx¥g³Ÿßœ_DòsãR0‚Ó@åyßR©ð 0·Ì;ÈÑIÄ'3œ(d%þ©¹/''Éñ$¹zÀ–<ï¸tŽOŸÞ$*˜å ëÿq¥²›Ðmjy›Ý~8 'Å1~<Þ³£¹°Ð» ÂpF–BO„8ãIà'á™ © H¤_[ü¦4³Ì%$*9žŠXK~ÚôEi'Ò“F†ˆl‚G¿{}œÝÕâRþŒØÕ—:4îÌj½a®BØosý+‹gwÿ%nˆaßþÐÓ8¾Ç´È©…©>çä=bãÆ«ÐÊ›µlM 2ËLëäBJGãUj8lC£á–ža•­Ö;x#èŽ×Ïþœ‡xwŠpXÛ€bBà§&¡p¹aKdchµ ¦‰¢Üt ïæ¢ÿ#íÀÕ¯3ä0xålcóçRžVå¤K †˜‚ûžmÅReñÎ czª«cæs…–‡®s‡¤t §X8ÓäY‹ÛÓßlf;žŒûA*L^3Í«YÙË¥ãÜ$yŽò.šá9&3Ç‹'Æ>9h–¨0«ZyS)÷Òm•ÓÙÂ/{qþÛoÓvèõ#'´Fô6?$*’¯—OL2Ö߇@"c†QÍM<‚Ü{KJ™ãå½t3{Çû_ßYäKûfCM†þôNö€¦|„CD(yÚ?»\ rû7p=¼«ëÞÍúLÛÊ׺ÿÆœ_&˜„ï‡åë¶p;2æmîB¨ÝF‘…áa´ˆò7!i± ×ÐGHjgl)ì%—ÈÑ+Sá`¯ww«.£á{àZ¤ ^Ö2_–[„·‡Œÿäuú‚:eÕÖÉR=tžS%dÏ@ÑàwìÔ#xðsjÆéHΈû¨‹y¹ÞFEeZkSGC¿%²x‡ bQËöT¸ºýa¯{ÚR> œ=SÍAÍOë»Ò>Úéè¸ö^.qKVFsG¦ZÁ_¥nãFÛ«3kCUA ™“fÛÐ]Û;vhþ&ïï‰í¡Ó7Zˆ¡~ꔓú…X´¦¸³RêÞ/¼v%ç0$¶‰ô}e9Ú2Õ ÿr()´ïÎpÅK²?yªny§å¨²ÿa,Ã…U/~¡Æ@ 7®f†Î^v0RCõ”þwÖ²^ªAǦ ±°ïùêú8g+Ñ0ý™È@ÞKO(ü׬i/àûøm2NõõÎbžÀ£îmã50pý\¢]\âvk€Uˆù¹¡µiø~µGÞ|g  •*íäKJDzChøª9Ld”jñ£yKŽê‘ÕÄ.º ;t7]ÝÈ7þ'AÛUÉE¶«ÃvX»,‚ùç•aDjëĂΤçU5|8Ž(W™²ÒBè}÷c̲€ÎÖÎ+ÉáWðLñÛ)[]år±¤˜@Þd6­>²å6Qv "<Ý=¡Bšfô¥#D¡B¦mç;¢ÎÖ‰]ûo“¹€6%ýíô5–¿ÄA®·}JEe,7¦j|"³'«£ç^ÛzmMð>Ùóâ8Átv&aȪQÜt–z@[F2iUù5¼³/;ÿØß|õñˆx(2'·äÍàit* Ø0>EŸË^üû [qǵԧŸSèÁúdºp¯Ý¢ýóÕûíú¼+ØM‡ PÀ×ÁPK+/eZè™!!td0í-ŒÛ°¤¼?CøºþJ„LÿÑ ®Þ¨ÓKùÇQ#ìÄå X©qÿ)¡ðuG±9ݾmù\”tpº‡Õ¤+¡p¨§HÖhK´bŽ]«Ý4½>¿ ÔŒ1$a·IVL¹w[”“m[™1" ³™úŸŸ¶CéhýßzìúÚL¨!^—IjV€¤¨šæ°úàÄN(náûzÐð hóçÝ=J¬®'FU\õ¹OúpâB\ÊõÍÀ_Š¢-ü]õ{–À…ƒ€PUÙ@Lóâ&!Z–dYxFüzéñ}}#q ‰¼ ®Þ-MÓe ×òÒ¬Ãj+ÊÀìTjC+a,ÚrewF« S/à=°nE¹›­á40 „g‡Eí"Z ×7,+eÒ³ÿÞf¹åuu­\=mç=¶H›Òù ƒb¼Î†Ð—­w×vφR%(¿ýZîê¼âvxŸØIY›]†™|32Ný—b=dæïr»Æ)ÂեѸˆz‰µÂŒyT¹£°‡é?„‡Ñ³!MR¸ø»"I;lÑÃ&žò£ãûþ—<±8;qòÌû’d.7ÃKSBÌø·Æ†ìN€} Ü-xxpüÙ¡“€I`gµbïÓÁ·[¦Ë¢{}*êù$F   /)·Nv{v6 Åbzt½ª¬#É¢¿ ûï^yìÈ©eÿÐ0rz.ˆ²Öy”<_l¨òHŸmªÐ8ˆÕnÈÎëV4¼žòÝŽK}§+ÆMPŒ(V± ‰B™özç­’H½!¤NʼWãŲ/«^¦D-q¶Ùb!߉(½,»ZCp†ið[ã©§?”j×&ŽA1¸bNÛ­ŽÝ¶²gS ÛÚ¾D–KmHîCtƒ‚U'•v,ß4ÅÕYA“‚fÀþÑ!ùH_xJš3Á«2c`˜¸X†lLQâ<Â5Ö~Å¡²ÙŸ²Ä¡šgmKÏGRH#½Åäñ8„…2ø£è@ô¢†„®l¬V˜QÓ_usÓŠÎèøT@‡à† ²®ƒÈ™JtÑ9Dx‡Ï¬xãWÕ̾˜¨ FWnE’/;é¶ ´ûßÏ?½7ÛÒlìTù÷%` ¾ã^.?…¶gfí‚*$ÉËܶÚk&µY8#©Ø‰Á"ÓF`гé@QóÔÜ ˆW’¸,òròˆ7jÖ!ÊÕ_ÃÅ5¥í§ÆÙünŸIµú=†gò5€Ì>Nœ€È¡þÃkâêÒ;€õ'î7ÙÔÈЕÓç¿glPìÀ:Ì${°õ†ÐKR2d¥"Ò91ÓeÑ5¥›jÆ­_±2@)~U^5Ü»q,PD^–/P>4…Éj}77 nûôúw"I¼‹Á—JÈûU Ù¦.ûÁfzœêpØŽÜ â ÞŸxî¾éÆÅ¢w@Ð(P³Ã|ÿ^•¢.\‰×‘ WKW]ÙðnƒŒ@\p†úû£ àÔÆ¬U¹åfœLa §ê%ÈÎÇŽ…VÝ÷ÂG–Ž9"¥É/Wd XóE|qžV[ŠãºÈ‹¬| ó³åi‰àaÞ´•Æ÷×âFò{e!þÿº.òé'C¨ÓÉd£ÛD`ïï¬|ëÞ¬!íŸÖb³âagä·kT”©ûÄôqŸƒùfÕœú¼*&רkqâF)éŒvÒ>g¯fÇ€³íU Z~eW‹°¾¯jêã’\Á·ÝFBbM´öCŸ»d¼è«⯭@9‘5~5tp·€,<ÎìÎ C犸½IÑ «ï‹›!Hæwá¼²ÛŠ]I æžv>6×K~-1]CuŸúBÌ^öšŸ0ßV¥œíjh«¢åð{iúþ³%(«Üñðì vϽÑÞÈ‹Z°GV‹ŽÎúÀÞÜeb5ãú¼Äƒþ3@ÏQúmuTw¨*PË5O‰Ðh‡¢Ò2ÚF§—5¦Øê­À_ AÉF~{ê÷/n*ò`]¯çëHG “ xìmœ^$åÌÒÛ“L ?3 àAÊ’º-q6ÑJ ›?ôU­.Kù ·Åjtâ<`ÐLó¡“™”Ã(ᘆ0ꤿÀœMÉp§T)‡ZO¹S«»X¦ÁCŽ)æ£]>vH ±…{qÒ“DYJèµvÈo0ÚFzÕ_æ_º0 d ãnvè€yÖ[/Zæ(̈ÅñõvîHOföÿïä4•,ÏÌêyÑ% Fè„}žgØc•ËîZ¢»67y¯ »k¨¾³†p^}ýI{;oˤX>çþF>¥ëE8›P ~–1e¼ÎƦ믱PÈKÖ³Ï/‡!—ί׸½+¦&ß|÷\Öu! u!r¡÷õëgXpö´ ÈÙ·ýÕײG‚ç”c2„Åv½õýœ)4}ò V"1}€½5Í©çÈJžwº:#xîõÜ$­ÏÔvšƒ#ù#™÷’à0 à†%ùåcãË“Qwļ´¨óì#•œÔ½K‰ãÜ£³Ã¤ J®m8¯øÈ@‹ßhŠÔHNßškªÑˆ ;1ÁëQÔuº•Úú°Lœù ØÞ‰O8›¥Æ&òêh ßð>ZÖªú 뚢éqšù ¨¦T‚»>bÝY¢L"¶0bJÉÞ–çëç ¯iKqö°Ð<#/¿øÏÁï@¯f:¹½9þßfæ$½bUø51ù…5ˆý½›c[–B‘N¢k¶,Ï%@ùYK?3“ *øŒv»‚Ø_íêž”›´ã£žÇô4l¬¡´ùÍæ¼ºÎÕ)#«î³òÇ P[L.šïÞop|]a èòjñì3â¾Ìˆ‹S*íè_ß]EÝéva±2P³ëï,ÔâPD@NžØõœîW~Çí Þç¬E¼\H6MfVÔ0<™xš-·€D²$“”M¦ßä—ÛTfXÕÑ 1!-_w¿ Þïž2•^oi7†~s­ÈàŸóȵ,¬›øP£¡ÓfTxM|‚$L‰o'ì­BPHÜ­yF†ÇFöT‚å˜{H¿Z%!<ôÿ=ÿìXt“e~@^~Š_òÎT‚¨÷Ð, \$xR½€O@ æRû[†ä¤•ž2qÜqEúÓªq$ÁÍog³Ú>Qî6¨üI5[ŒÝœÚÔ)×-4Ô¡ahYVîÞ“¥H‘M¤˜º–¥åmYNŸT:,àah÷ôûÎEÝ­«k‡bWw4ñ.&ÌÂY÷J9òŸŒ©ñCPõ0@.•je D G°Óû 4<Ìñr¨iZwYfº¯¥\Î>‹^Ýâ«íS¨FÕ”ÅüéÈËá3DdÚ^]Í“ˆC8"Õ¢QGÅO ÷aüÄHwP§2[ˆ¼nþÓɯA µ=úUîŽðkE¯Œ¼‘åäþ˹( È4p/²ETÿ¡“Q<‡›2ßåŽàÍû\;—ò§¤Íég>ÿ Vµ®ÂL.$«æcÀl@õR;2;cknêˆæ¥ãpš†×p9SŽÎ”?QoaP[PmÖªî°rµwr¿R|_‰R2¸ëm,/3™Uö¯— bDQ‹i†„Éäeì¨à*•WÂWÞ•FÞ»o¨ªˆÿD:5ß þŒ~',Åf%ÎÙ”ñ“ñôO5Uû¡ Ø”€Ð„jÇŠÆ‚ŽR36:ö>b˜sÅQÛJá2i²iÊ‹ébÎ=#…L ¤H¬ "Òç7òµ#o™Û0ýkMº>Ž>¾Ã†Šo~büö‰Š=àÏu$M̳-¶0µËuo7íæá:[2‹‰…uÕM*IØé™À x %´¥#|{…›b(Áqel³KFÜ>IfÏf™óÁ¾|Lʹ^û¬±~ÊdÐ(¸´|39/Tz-b c‡"×Í’•Nf öl»CÈ0»+DýuÓkçîºìÛŒŸçÔ¥Ì×k®¬Êy _{Þ¬^{÷ ·fhø€#·æ3o§JóóËŽ¤§›t‚;1›.ƽë˯­ÅGò²p,~Q_lW%¹ŸÈª%‡Sž”Ô“'jøyL 0ð¹l*Ò4õfß,A<Ÿä#4Â@ÐÍz_«’ìÛ«ß²·xw‹ñïHóoŸ¼˜Ð}©„e,y…JÊ“jåÒ %x“·ZéŸDàèÛ›Ãg™vù•,÷B4ȼ{lÞÑtô¯w/ ”<us»MC¼4” md¬H¿”p^±7"Íè™h æFmÃ鮎””  ô¬±½*Õ2ôE*¥Ù<îA~H='04æXOÞ¼ä›H¬s8ìFI?9x@I·­²¢æ\AžÜ;ó'}I|:Á%‰Oos(§0·ëˆ¯´'iíóóƒÝ£%ÿ™ ”Ê%Љå#!ðøâñb’¥ ‰®‚ó2Š:»¥W+ DXØ@ò—‘Ú—*Ó¸ “ 7¤í€ÊΫbHƒa}äú$"™´ÎÐ󨡾´öͲ mG Í“í. eÇÜbuú7ÎÖ]¿Ãó ²æ‚©uŸè'~®˜n©Mž‚€t™êJÆJи¢ÖÐ7*šqr٣ٱñ?@­­_ƒÕó¶ 3«}U½zPLOíJ3ìGV{n9•oNÖ!„r®”€]ÔÑ»©äyU¡d€€"#³AÕ¦(ݤ^ùƒo  ¤+Ÿƒö®Ü…ö¯T΃R ݈ÛWܸ¶”¹è—Ái𑚄Za6Í|ÄtÓÁvÄGFåê³çŒ` aDtò&Sj]©ÉéÄÝÊÿ}¢þšfd¿›¸?Æ×|&pWOŸÿÐ-®¬ŽÿÌrþÅÆv—O•‚d]Ò¢tVðoGk2¿‚‰DR¸ó3¹üþ¨]ãZ*+(¿¾h§ÅþaM ± "¡rVüÅÐJÞkï°j.k¼a9®Ó©¿£Ê‡\4Ù,íž.çß;«lÞÄ„B4Mqd/áÕœ÷q+Îõ$käŽSäÅcIxÖøò˜‘pÆ´¢¥ƒËÝŒ,Ž¿Ÿ÷÷O0(ò ã\_ÉÐúQºü6âFÏ6N¨‡Áà4Øœ@üªÿÚ˜]wõ©g=Í+;4<–ù5•åH,ftŽü½d²$±ÉޤeØìMfµl‰Æbþ‹¥1m·¾ùa6T„>¸ ´zXbrè&ÁGð2ÁÆ;¹­)ÁR ÁIE…}÷yí²Od$„Ð0J¨®fŒÎ®ÓÙ álnwÄGÛCäOÑ1ùqEsÿ“Á3œ|—Xú‡a¸m~ûòCˆ±ùΧ‚FŸ¾Í Œ ­ÿvlü·Ñ ‰/ z£…òÏôðBˆl¡xéZP!€ê¯¬(…ÙStÈ{cX}M[kÈÄ3ïú'ýx!MG¤ºpî$ÊJ‚uW:î`Òä¾W7 R…ÿA ÖZ}U-qí¥Ð]n’ëâ×Õ­Z Ì.egÿÙ{_*"JÜØ‹n¨}ñgXÐ0þ°©šï]®V<@X¸iõ£+s@1ÄýÛÂæ›Nþ-±§?ïÌç¿ÿ÷IgŽ}o;±wàA•xânnÿî@äãdñoâçë_ÞhµÛ|ÐË,ñ•‰œÉrý¸Q“ éVšCK ÎÔðÈöôíjåÒ…±÷ÓþJÚ´sXƒÙuªŽîæïáÐ8Ä–ižM$^Z{pß<2Ør­8øÍSät¾Ð1)z¹J 0`>S¸÷ý”4_« | 1I ¯EÁ=ùíä €àÍV`“9‘ó™(}*Ø»l*Yløzˆ›ÿÄÌôÂãL6žëX2PwÐõë ÁnIEƒWÔí™êÓóþkt7°+l9VÊŸªÖP„DU7_XîW¸‚â÷JܱH’NõhãÒœm £ÍF'}ïkÿùê<㾴ȧ‚òœÑ‚_«÷_èªE…ëVs‹­èXCp:\ÜÍÓ=F¡Í‰´¤úî”tcxúTnøt ÷ø•¶ü—Ôïx›{Ê<œLz¡R“7·¤2ã?‡e'? 2–‘AÕOe¨!e0fñŠc=‚eŽj fáœ6 c€Å¨6ŸãGåT@ !TÂKŽ‹NéG3öZàäö½E zðÛçÇ O |/µs½ž—z§qU†4æ„–|nqø{¤BUu;‰=€Ì*xJ`œïÒ2­G¢¤»¼ MÔaá )/`˜Ûyn6äz‚¬ã HaE€ròºˆ¬¼=%|û™™vî¼d”W­–OüMºçF”}xí·¬„™hµ˪ÌÚMèk¯( €,FEž‚¦8ñ¢Åìb~±uŠçzƲ&dBIŸã8XJrmñ[§ÉÛ½$3ËNT—Oþׄ6ì3O ÕRÜ+þ¶‰rˆ¡×“©Nõž”þŒ’ÄB:ˆ€q©³ÏªèVˆóš)`SÆkàitÒc'z±•!B¯ÍX. bö¸œ* Ç«X5ÎA tªòšÿ˜èT*¶!×OÀØmK©NÍ*×áÖW¼Ëøe¯á˽’y²ò$ ,b3ÆQJ†HIåEÿcúQsEŒÕÞ¹ ¡ª¤ ÆX«\63¾9;°ÆÙ…92훢)!³®Œ&ÍßäNB`’ä €ÓÃvÌqB iŒŸð%ŠºUkCt(×îwÐòw”¬YFÉšksçúzµÜhc–ÒIX4ÓÞ¹ß>%õaá 㱫þ½~¨é*¤ÍnT•˜M.tÓƒ ní |ý#ÅG¶cxîýÕ˜üÓÇÞ.Óõ«‹ûØ镼d©~9§ 1JQ-:€KÄG˜èÊ4ÖR `Çž<M§:&HÉÿ‰2† Ž(¯éoWf+‚Ò^ ·$þP§Ù¸xñA@;£øÙXRË´$ürLñ±¨;‡42+…aàš2 ¨Ë¡%úÎ3dí‡P¹"±'^Z 6—ÝGæÁÏ.¸«üÞdÐV-ßd…@û\J !3¡S“î³éá–ìTþ®ì_hù·AçªãP„Ö@'¬Š‡“ÌxLÔ™<&A§]3Æ,WíLÀ¹›ålXø6l)%Œ+jhW¤‘ùK8ÅÑÊÓžXQ¼¸‘„yY^‚j¾‹}J¬yÎþš'R;íþ…—ÊŠHsóû{Çg…G« ÊÕ.,ˆ×¨»ú%ít²— |ì€Z ,ÍÌEŽt~g4_© —g/KC^f¯…)í¯—1&§%E!`(à¹~ Àwp÷}åÖ2~CüˆÛhYþVoa’1ÉÅ #𮼵C¡E­ß6•Þˆ"mÝ™KÏ6œDã™oŸ¦Â{}«‹ƒÕÜ)"¸óQãÚshR%lkw«ñ«ÉÜ?T­Uâ=敪Ÿe§í¯(ªØ‡IV럇§;Û‚}´ŒY‰•€zì™mäð˜–y„ᆠG¹ês¦þò'élôñz×8x3–ù”øËBÛ]0L%^x}w M±¬HCGØx¡Yõ >!+%ß±mî¶ô7Ø”S Yô>Ÿ.qûjA ª‘g DpKÍh™%ð´Íó™“\¾&»m¹$4ò»k.¾a*H'3L<Ú¢d€u;Â?„’F76'¢Å=œÝ̉Ÿ6h¡¯,¾…ÕXÏßÚ1š/Nc[†ê·0üzøb¢™e;CÂÎcP„ Õ“^pÍk9¨|ªóÙ6a 9¶½xH 4]Äqf¥ª0®ö@*PO .C¾Z¶“üW*$¸©B>ê›è)uB|Â¥…°ªF:*¦+Ú‚„VNîœ/Y…#ôߟL’=¡iµA×zŠã,Ŕ§t¸|´áOb®÷[õ¡ìà³0 Ê—z™Ab¥7ì 7¾_ã‹DbHyE‘ó%h=$‰ ù_nñ² ›;K*ŸrJÊ͆/ÚZg»DxÃ2 8Åg§ßp;?ºEcRç²Ò3Þ]†9+GZÎÔôB¼¿†À8|ÊnÅÉjfÐNf‡K—[0سNN#Z)–@8‰ÿåÜ!œ‘Ãç­³y•õ)—µ&Äî_gqN~ ¦ñÓñÁip;—ˆ4ÀÍÛ¾ƒkp¼âÍ{Û°b-¢v;ÿµØ•uñç¹Î-,Žó=ÉZ±Ý·ª{µ;dé™ÖI’g°ëoö„ÔÐ2q¦(Q[“ÕYÙÉVEE'œ×øSo'Èа; ) Žá¿ß6‡Ä‰j^¿ä®$ý ë|¢'¬ÿÁ¤“ ‹-‰³ËÉT“,Oö¢’ؘ9JуÇâô™Ê*AAÆ|ÑÔÓ°×Û¸Ô?Ž«aÒ¾Þb`¾æˆÑsh>{46 }ÈmвFu`±p¿Z†©|ôU÷£p©‹_"GLée‡¼·§oEJCF³Àå–f¡ÝÙ¤9âÌ×­vöÖ„s7­‘A‰À¡uÀ?Dî Ÿn[ñé¡ AºnýÚƒ¢¹ÂgP|c¥öC=²$–jÿ˜FFg5ƒ3—–bÐ<›iüQ8d€ ™ö×Ä´”SØÉê¤< –í0ýc×IügÛt±èÔkj/B3¬²¿n cí`t&„…I~`¯$Õmà4×ÛCΟã[´Dt\˜oÜðarfêþ%âMy?´Eö/Œ´6]t÷-øó|h].º šAoB—;L±2ˆ·ü•tÿ²êh}U÷|}Ê‘ÑK :ûÏA€¬[W™ÓN‰Áp)gv‰“I#aÖ )Žì*LÈH‚‘=D—ÐôCh+9Ÿ…HT$¬ŸZ½N2û#[ë¹e4õ®þ*öÒe­1ÝñÜ;/c ÇêA@9‹l'¦E¬õ¦…J6pàë’üÂ+Æȯëºõ9ÀX 9Éûéñ¥±†\H=h¦k¼>u÷k ÉNÿU&tL36n2ÞB„s6>zCóþû›þe~9³-¥à)¼…¸tNù”¶1Uœ €ìD‡Šksáê³i¶“SFÚ.ºàÌ¥¥{#™W÷(ã¾fÔ3)ÕN˜-õ?ÿ¡ÿIP9Ñ~Ò©j&9iü€ýÂØéñ(\OKq ûšägÛøäUÕrWú8V€¢þM·º;y'*ÑÔž!*B¯>•8ÆÀÕHR&W0ÿ|µªÖ¹o±µ_L©*,´™ÌBóï?I×°*gÌËò’]mrRÆ·y_ÉÆ`\$p²?…ú1 ¿­2}wªéŒT4Ç–•úó3êá7úª1Ÿþ“#¾©hç飶\Bè¸úÝöEá¶çŽÞvÅ õ?œ¿¤B»6§ÀwcÆÁéÝr^8åuãKIÕÆeÌRí?·f!ÂÔ2{BšV†ê¹=ÐoŸ”ʳò؆p_ÈoýGƒ/Œrne¬Ë—áüW‹;Ò;J‚šQÖþ¤üXǬZ@˜xEÑbÌ5Бð84ÈIæû+.1•ïÏiÜŒZ<¶N•¼¶¤,2Ax£hÆ!,iݲwò³yö¼ÊLƒÛq-b:Õ‚f/¬jý!,;:QÆÐƒòGxìS¸7Øøc:Ï¿2S{€ü\W£À+°C…éøœy |â]D3¼~u9jvJQÀòk»“‰Å«ƒÁ+ÃúÌÖŠÕÉÆ¸¬‘±öu ]ÄE_¢pg%²Œ@³€· ù1“…*¤2ÍÅ‚Gýv›F[UAï'æ "Ú·4YVàð_¦ Ê hÇÇQ?ÑWÍÖøÔ”OÈk ËjG-¡´W-â2¿dÍj„8YáÉœóñâü¤&Î2ȪâÙŰü(Y©®HPíÌžAm¤F!»ÌçGŽLËÆ]W(CyK{CòeMÃAeŸ¦ç0?µ Æ2_cÙLË$¡¥%dÛO##<·¨K©Rk¡¶@1€h±OÚƒfÃø¢ÑÃþ€À¬p{e¤Šü„ÍHˆ×ý ø²|‰UMåÙUûO}w¢˜Áo­n]HJ4uy ÌBx_¹êcб­êt™6Q» ¬kAÃE% °ËB¶n=÷uFq!ªžmó(.ö±qMÁä¹Ç wߢ³¨ ²<ðV ©î·Þ/ÑÏpñÎŽù¨Õrm/–ߦ–¸ ©¾r ?S-î+è”;² ¨ÔÀZ'¿’±Äšr¥ÿ‚hS7˜=ƒàåU‚+ÉNÝ_÷¨Vy6 ÖU,纱 ÑÌþa©^ÞÇlÃýó8¾ñ@*€r‡NگܤU†ôŠbôœÂ°x_9URO<ïÊA®½ãM~s}/ @6ð}º­H ­í! Q^NVñøhàD#ôø9æ Lþkü•þ'Þ:^UŸfÁß8@ÆPqñ[Crá8KÚ°œ9ßvõ«ú¥!]g‘3¾§n/òáØ»å!^¹?•Ávt™!q"Úß¿ÍO$»˜(0ÏÞ SäKމB£iãÌlh–™£+®_ ÈOk½É/˜%ÀU/k­¿#:WÙûÍ»Õ|in…¶Ô<í§‡ 1É žòã‚õ³²$Wr{øúÚo@døÄ«ÌŽŸˆ˜PýRàØ{ŽývIKol³ ­þº÷¢Ã$ tIÁOüð|Xµ°#1­Ýcí­p¥^Ô(Òþá>ÔËA®ñn«ºôWÆ—€dîc?wÌ@#ìøí–7alÓ*™%˜$wÊTðºšï,j;Û(tˆ|¯–oDÆÙVßãÊ}•ëD;Èå;LrC€4¦f+,:v$îÚ“Ç÷­“ŠˆÎÒï—]Sª2°¤}M–n·§SªŽòB”v.iâ=œ% =䪻®{¦Zž‘Q¸!Ä–¨9!× eÁd5¨µž©*Ë™`ï×m¶M¬â%1 '¶Ïß›Ù(4èŽ$ž¶ÜöÖ2ŠO†âÆÅŒÄ*êú‹€Ä!˜–ä3djøG£ÍMuIºŠÉžpXmSÞøÚ‘§â-+^iû”?-¼ƒE>¥-ÔöoÉäæ*é²wéê ;ZJ$ýŸÄéf~Ç+ûQq”(ƒ®6íX„®%v²$ƒ@ÈAž¤WÌì6Ï4·|uéƒÆy+ý+1…;gD5º<‡¶´Â}pTÔ¿_Ø<ûïÙ“ÏSk˜¡÷D¸/j`‘²Â|yüÃC Wôè"•ZH7Qzú[Ç wO8A>QSßkPy§AÇ®b TOœÎ,òm-ÎùRB9!=»‚h¯& |¤böß“|V¹šD2§tj±¿ÊŽù¢!œÚÛù,ãêãá{GÕˆÒ8(S°Ñ“Ø}$î=«©â¯ŸM]²þ]©Õ¿Œvr1Œ.ŒDÍO„åý ç"–í\^媋uÎÜ[Ͷc4éõ{ öJÔbSò`OnÅhϽÖ'7üµY+þù¶€¹Ä/ôĠ˽w¨¤‰ÅÞxÌ<¬T°»ç/Q=~gÑÃ6ÿ¡l¬Ë˜å½‰rKêfiÒ äÕ¼~T48;*Š“CÐ 9°IMš×L¤@s‹16zEaÏ+¶  Æ@ÜÝñ•¤FèÃîˆqûSʵÄýºäêüœ¸ÁàÊ‘„ƒjê7\;uÓ¯,}4ÁSÞÀçÐv:K@3z—êß¢ÚÆ®€*xX¸/±ÖÀkãSÔJ®Û˜¹2i‹-´r“Ï?OÝ™GTá9µ©‚Ô•€Uà~A‚.Và±Zø¡Go´©å4£ÿÌOñ IFÞÿÕ4Ì„#1„ÅÞÏ£ “P¨1R²Ø;©³JÈÚTþf[®!é˜q—ËŽÀD™6™½‡|Ki®×UÊuüY™8Ú+ Á¥ç­)fññðL±÷*q2/+oSäUŒ…©¯u‘ö­P} '"bQ®ŠúæÚL–Í>¡rƒT9×;+ùf]%ê#aa ÷êÀ³"ÅŠÛp#ÁüºÏâúFÑÖUÿñëÇ £’Š;ÉB#ó×ÏmD梂Cë5GãXb­Q'–ædäÐaJ¥Ò'tÞV"„D5®7UT˜!ðNh¼ ª>Ö[Žò H¿«PÂaâ8Ç…Õ$Ql|£T’1ÞüËÁG¨$ì”+*‰bî|¸Q¿ý±…ñD© Œ /;®­Ÿ“mcî^1üiaŸÅriÅUŸíŸ3d­Ì­{£geF#ÆZ0—Óý‡°«\#ì ÛݙĹ«ôf[lŠ Lk®ç­û4iý- ÛÀ{ÑÃ~Js¿y¸†ÐÃ@Æ$‘îT5&Çg €7:Cìµlß“q˜Nú[»<Œó±æ )ž(ßðœoCvs‘\B•üðàùQeŠFëvÊa•ãÕ1¬ë›ô[¬ Ïß: Uwìb¯¹oË¡N>RÚ:­1lšZÛÈ 4¼ÿvÙÜSÔbx„y—=² OB GŽr $Þ‰b[åÒ—QéM “<Ÿ`?Œ°²\A=$Mòø+â)ôäî/©)¹‘Tÿ) ¦M’ jðn…ƒ4tº~5 ‡U½3 Sa¯}~i©Šåˆ„û2•¨õ#d¼8-ZlE°’áK-㬗™Êi6÷‡ð–˜…øœXÅTyÿ¢®P\ü2M›SøÂ«õŠ9yÚyeQCâC”"©jW‘òê?fÝ<ÞÅÈÝ^.g.–~Î.¥ÖMJ¨SnJš´u¼‹í=¾Óè‰*µx#ãÓÙ´ 7#>FÈé½›Òe„s4 Áªàùôå2¨¸þ¥ ½€}Èû5*fà6(ÇÀŒ Ïkœì°²:YOCà0DçžÕÚqîÆ™c³•Cœ½Ö!O2'îuç`Tº¨+Á*‰öNÅ|ÆZQ­J|*Gë¶@ı(¼ÇŒšd­™Þr†ªxåg±´m´t]ÿ—]*ŸQV‚û¢Ã\˜³ ]$Ìp’>×”ìçΞ øWïjÆ‚ç=ª,£¼[å$¬Sfóž1þS,8| ØÔßWRhÄ:}4ÃiâìîliY‹ÔèÌš-7kºeÏð ?ØëFšd19M§5^f³pã:™ …1sH¢q‡ð"Ÿ_Áq<®‘»Ô*R×¹<5ýãJ*®î“H3vMÀ—'þ\ ËŽ}X¸ÁxÃäžMEm1vÏcPùÅð¹(ãì÷H< Àr2k‚£Yy¦p‡WHÕ)X!€‘£ €‘ëYÉû›÷# Î i8•êQÆ4˜O鈾¶í˜ãHš‹ ê$ëÓvõÉíTÈp™¦×‹aT ã™(«•1¬Ñ);À½}Pœv…û7NOÉ„Bïe¬> ç){syõÍZ*§¦kßÝgàÕÒÆÌYjv8@ÜêHœ™&Ó‹:œ,¢É?ínkûµ¡|pWw *rÊÞå¤p±Ê ,Å5Gžê"™î¡¥-^|ÉÉGÁ=¸âìЛÅÓÅí(í&¨íÞc´{ÂÌŒ"ö§«~é?\ M¾}ú)ôÑ»ÏUuo‚-`ù<ßrr““KøçÀ×—æôá“é>ÊI=ª¼–»§¶$IË¥î§ôšu$Dçû†ýå&úÖ,é7²„j)Gtsë;”\ îÐww"¾aîÝ Œ$¹ïsyÖË}îÇÚŸf$ì v‘úVtž°,Í´û£Sç1É"ž/¦ÑÀp¹1Îk ‡¸&ÆÓņáÍi·–dA€ÍFröU¢@l¡Z·{Æ=zäç}ïš0P¹Äh9šg÷¾åMÕ¤î±4ÜvV' ê— *£9÷ÌYåˆP?_eR „+ è=þxtž«¨K ›)#x@¡/‹ˆòóCÔŒ¸“ƒ3“é1¸½çƒê1o_j?ÒÁy?Óð–Æí3—Äo©´K'þ¨¨&S%W>/O2“wä¬0̵®{«á¸Œ™‘³êhÑAŠçWÖlMü dÙ<Ñ9×…Îs/}¬êVÔ[V°Þ Š@×!N+8þ¤ãEøWÑèHÓ«2Ëc€ÇŠu>TpÛC¤M=êrÊJï8&‡«'UÕe¹Ó„üá”õ/Ò@`Ø. uã+ÖGf-Âî‰>ƒÙÚ ÃÝ5²@íÃk>¯ÙÇÞEøšjþ¼¼µ6 Eºç\®ÎŸšrç’¯ïŒÍEÄ ¶ˆË@r´‹o[›ÀRZF<±:·ÎYO·Ϋ_‡A9ÁÈïrô>+N‰0'éÌ$@°Ç~©ƒçb_ 8&©@|s³ŠA#oÃýâ9¦12zó¿šæF3Eí‰Øí¢•Á‰õJvE媆÷‹µŒ®ÝxÜáÁsuGÃ@•Ñ <'v'WÂtÃ'#Œ£ÉÕç~÷´ ìsÑ»)èPœ½×GNžPØŽÅ?壮‰'‡q—D Í4.*©§uBÈäâ|e÷mÛ+æÇ:—³¬FZÄ@‘8Árq]Ü÷Ü+‚] š\yÛ`p)¿'è ˆ9„ðÄ• 7Ÿ2]­T‘ªà«dwK¨|µnŸ¦.>œ3¬­_‡ÙôÐÓ:*×/ñÛ%—™zÝæ†ñÁÿx,9Žˆu…¶Ú<8E¤Xèf¿¹AV®¯a«+|;çÉšÈÈ®l#’š½)Óæ™¡.a ¦g"¨!?[÷ÍfvÎÛ&¢ó(UU’Ã~¹!ȾÈ!3FJRL¨~ a\ÏS…"”‚ÔËš'GE*H÷fÄÒc³³çR¿ê7¦â¥#&e‘Kج©ìö½tónÙ8üðúïq°/"çäy&öe›z[.½vº;éÂz*Eª“Ö7LÎÃaÓh%¥évÍÇ®g8f˜@-!,_XŠ$”­üýÑ-û®{,Ùü‚Eh¨¿æ©€æíð´cé ©{䝢ޡqÔsí^Üti-w£(’ËF¸!4ì‰ÕõoÅâ,ßô.F$9›ÛªûpÀ²ÚÏ_ż…/Øÿ®;SÊ¡y5xC=ö¯ÄLŠ&ÔUOÏîÙÏ6µbÍ~n%[g¯Š/ü®é¨ îmXýŸ5ºÎ.šWÛýªˆ 8ž¸ä¯è4-(M.Iki¢ñºý|›ÀZ ˆ¨siƒq3Ì›j¶ é /$»õñanÖ¨|Aa´=qLÆÑ€&LU~Á¹ó0û^\¹!©bYéµx)ä0ÐI"ÐŒõRí‚vè×õÀcëÕ™ \Œn¾È¬âþŒÇ“¦ì Ï콘„º«ÂM_Ù`Ë«'aÄõÎÛÀ·ÍéðûlÆp·.ÿ}™M¦çÏ;èð¤ËÒù#pŠa³a”Œä-€ÿ¾†_KFT>ñ¦d,€»Ñ’3a¬Q¨LËJÌMvß!Ê[w2‰mµ==þQóZ`KV¥ùi<§ ž®X|Æ{1™Fü‚®Î«TîÑûwF¾48Y ÃKÞÔ‘y-„•ÓªœÐî‘ÈO/5&£Ø¹‡ZP·ÉªùÝ)@æsPr$>èkÛø:î/OÁ×ö±¬éõUGš™è³lâËxß³.`¼D‚Ø[šþ[æZ”CræVŽ‘vVi´û"§_jcqÜ#zÿu†×zls/4?| hЯ/wq’N †™^²:×êÃÿF¸†êIŠ˜B 8õÊ ¢‰ L6É}l­ã-æ^:oE’É[.Å,Äš+x®³K7¥)ƒ¾¥Î¼»èÓ¡¬³Yبäs3#à0w‰"¢†Æˆpøå12bí_ƒÅ{cç-ÆEÐ8Çß™_?èœ#ÖúðCþy—Ÿ =ÐÕ™O5ÖMë£Þßt¹ÐX˜¨LG:¡ŸêD8º¥ôÏËÿó˜~¶“Z]lÅT7³•:L~8?Ôü83Z^3=ÎæÈYÕ„áñêÚ’O ϸ—8x”þ‹-+¸sP¥îÌeÙµ~q,½€Bfn„Þßj>Hbơ֡ðO:lØlw Ÿ›×Œ4¼ñÇG{°F6Xꕜv$àá%üø‘‹Ø>§ GÜÔa­Üæxu˜žPà2:¼Ê3f¹%ýí}3ÆÉÃØÂ×ßס¥Ó¡bç´ i¡öûOK‘*üx ¹`‚?®\íM·$ÅB‚ˆÂæ…¼›©ºlG DßàiÖ%¦@k‘ñ4½ÓÝOÅ0­0Â¥ý:…ŽKJËV¡ù6ÕsHßxjþû †€ö¤lG Û–Ìì`-—NõVI.}}–~¡ ÆÉ/L¬òèI|g½Üh僽o\¥EU¡zíò½¾‹÷ÁÀ«§LB›Òwª¹J'{SðßÑn ¬”ÕdÏ•E[+í –â‡ 6#³ÃßAKjzh¸ ·â(ÜÒP·ÅJÃý÷Y-Yq [gãè*Züxƒ(œA ØßÌ ˆZ‹N‡Ži’À\Òt+Z?iÐS3·“WçvDœœ›çº²èoèhWz¢ ÕA@@V(¯ˆ›‹(„›•SaþKfáIÔ9ñ_¨‡²3{çŸkï]áôÞçêÆ À¿ti sþÓþitœá&ú—|•8V8Ðóki™â™(Á¬°‡òˆL€½Ç«Æmðÿ´¹öaö`˨³¶»â”­wG æIÖû°3öÄÑBÜ-ÊD:å Ø†+Dl”’âžúP´¬¾Oi‰«B4—ü’Š"1ã†%´'0ÖÍqíoêã¦ó+wÇÒ7žõÓ1úò‰öëP'Á€ÿð°'ä·/µ!æR–˜J.d#;¼ÃªpËŒçM¢*8Ó/hïÊ'KIÏ“Ã0'ÄÔÑ16D×Î|“PY M "OÎIâzQ²\©îì,«ÿf Ä8sºèPG…:bBÿ¯'«xÇ¢ ¦’(4‚ròEn^\äFöÔ ž —•Ø¢Þ›ôÏ¡—ê.ÿ›Ï®Ü€†ú Å®Ü~ZéŸK#ü¹š 4{Á·äøÙ¦}A‹­ûAü[γ ÇNLqЀ $a2‚ï î)˜¯ÙÈßè ÅÕ´+çEà[§ªdËÏwÝ/ÐRz¸øsvv”rkºƒðäòy§Í%®ÐAðk 'üŸ¼ëùmœ*jÇ…·>=,Q* ÜÐÿQÉï7K»| øð™g´7ô5Tuï~ø&y0D¢zÚ£–§j2…äûøØ'ébÉ#.Íâ¿Ú ÆEÛ8û>Ëáô uuOµ /Èy˜,ºÂ®F9Œz²uQ‰Ö™Ú#…ze´Yßú—>ªáp3Ø5põ7Øc<#Q'â=êCÈ:‹ÌOi1ËûïmhDÛõ.c¿ï‡ÿ_¿Ì‘­Ž8CŠ¿} ~¸ôGúo„rÐW€¤J|A­¯µÚ¾!Dƒ±Ø_Ö­¡êÒ0yA`Þ>ª&Ó\ ÿÙ—ÊHæºà¹ŽéÍq5Wd<AfSbëÇKÒèÄP{=Úz¨Ê“!¼J)½uác* ýamé´ Œ'~Gë™ÁÄ }¬ú¼:/UëIPE/Š[¡ËÍÙiï*ïĉýX`« §¦Ø3[Žý·yÄ—ïq¼@c¿Hí–¨"ÄÌïK1éV”¼=RöÄ’ìæ”rþ¤§õÖPÕ ”ÁÉæ2ÆÞcèyÀžvqωW‰Íê§ôª ¾cÉðw3Ï % Hjñ+-?|ªÀ©ÚÊÓûñi%g»|ÿNFÛ•éCaUi“è㉈0gä’ J—v`yËM+îRqˆ†p»ãÓ²Í|h„”Ê8,í$I/ØL ŸUEÜÃÒÊÎpËæ´0à‚Æ ”ãÆÔiFC ÌÔŒDÊHâ•âyû•-Ѱ…œOfI[lå²¾ýˬÙòØÁC*}‚jìI†ÕÈp}¸k‰ˆNR%"ÐìoG8Rï=wö½Èø5pSøZ@t›¥êBQ?Xá…i×û•óRڹ¶.Ö¤—޽zlIV'jW0Z.ÏÐÿæbP®T†‹î§q¸4/¡ãIŽ÷ÂN–˜€y\Ó¨åšâäFKÖlà6¼ÃBZûþÇýáoé{m04Ø‚6—Ò庴Ý^ɬGs«d¡kÞ{¯7ÅVɨÝ:`¯Ï£ –£dÖ#7ìTíX÷¾ Äå.ïɤay^hªÿí¼ñcàI}âÈî(,”ïk`f £h¼§Ÿ*Yމѽ3ÄB$“íbG}ª®‘9gÛÞI”VB|èSBYâí$çq­Ò¬9jbvšfíKÇý=Fÿk=bã!Í^ ð­[:ð‚4±çd ­ŸAV¯ßNP [N^ãû—jaø$\&˜,NážÌ(¢ ñT¦{—¹òfãÙë9<?oMöÙ¼Lž{ÖrEý7|®×É,˜ÓðéŒVHÚCÆëœëüÎyÐʶY-¤ä·@£ÿÉpÑù|g‡†€“iÒåA¾4ò#º@§H¾Ø1Bp­Õ.)Úyrl¾Åì7{C®ˆŠ3×t´CÐ8z“"y¬’WW/"«àÖJ®è·lÛ!”ÙEUÄDBý–Á·L-`ãÜ),ÿKj ToÂYQOê¡Mp3âÂÜ"£5oÅ–‚žêÈôð$Éà¢vIVÍtö*K.÷ì4¨øA-¡qÔ¸‰L 0|(ªeÃxÁ¼T±ò€ rÔHY“šhò¿K¥¡¶Éo« ÒvWD©ÊõãÆÑqúï¨E[¬Dq¦·º¤Ïm)-ÏùîëƒP;¥GNê&¹€“ÿ²Ñºˆ´¨©þ·‡zÏ˪ޛâWÒÌ¡ãýŠÍ‘ ?ý¬Œz0ÌÊñGY0¿¼ÜX SÀ?cqü³åÉÂU®Ñ;~ÍPX^oñ¶ÆA¸LÒ£U2Éòº[º·Œƒ©?¦B‚b¬=þ dWœUhÜá–Õ4×q±zFLêQ×ÐÜ5Uñ#Ý“H’pø¿ÁFÒù긡ª"áÞËÞa‹;“N–ùð‰}ó ¶Ž"k½·‹mí<ÓɺÅñU*†)ñ0E2˜O4Z–—õK´ôyæ’ôd€› §ÿ¼ôn1øßQùµßé´Ë÷ÖîD0í£<âd¨€í‘“à•¼_ƺ´µö‘6B%fÛ\ˆìe‰ìBãl߸r'h *93ÊÏEÙæUsQ‹B¸Ë{¾ÄÑ̓Թ Ýìzï! ð.A¢Ã¯3µÀM†ᦜ½zô‹ÇÓKm§Ô,1µyÎÉXN°ÄfD-¼¹¸%®uTi¹ë2áàÎÃñƒ†¦fåÖ¼ÍZ‡Îû±ÅºçÌý N»5ʸþ Õω¯VÿSð²¥+Í„vEøs×eÿ†TÃÁ÷†¸{®ë¥yÉ’:ÉdhŠŸ78}j+3T'›x÷N#RÓÙÞ•lOƒÄ=d†Çï/mÁÆ=æ9ìSͱ¿Ð¹ ˆŠÑ:ÔC4JæÃÞ¢"* ?Ðèä”õ$ãuOdÄ(ÁÕõ¾‹‘ðt7xßO?Žþ*+O#nYbÅþˆjý96Ýu$è·ÌèûlÊ+gÞ`mÍžp¶Msž¨]ˆ›«trÔðSÇ×;ÀEõScç„È#!Wý±°”à…Ds93¤ ⢶A#o Ò®ß-I[&ÒJV@Áz~kÄ‹Ìa0©ƒ³:VÞÖt]v@®†J†—âÁ½Q‘ÛüaR“ O" Wñæ;ØnÑ¥Î×™†þLEÖdÜoz4OŽ3Kÿf)a)Pe÷ÞJä® ôËÐËw:š{ÚG>DÙ™ÌFìOdˆ®QýÊÏ~4jKp´¦Ci²‹;(Žén§¡”FPb¦°Õ3ØcH{©Úf°A<} Àäè YcÒÿ^p·É%‚nó”áÆfͪ%/7~ÚŸæÆ}ÆSÓèe’G'U³Y*¥ÇJ×8^e©UL͆˟l—VÒº’Þ£–/b°Xµ)ûA1SšA×z’$ ôXh&‹Ð/ÙcwÅ ÿÖxôW}u"t£¬o[w‡dzVW¬ÃF-wT¹ðg ¿HÁ`ŒnûU8ä”)U•;em«:§¯»9îºW—Å–m|3‚Ê\‰Â@!¹º|¶{g. t€‹uY yÝW£È.mcG¯P(‰}5±wAwj«õ†sAO䛢ƒsmήìãý<."Å} |¥:nÐЪØø}Ÿ›öAb–¤›4Ì4u ’.P…ä77n=\õ[N%ÿF—-T>ac^ÚûÅDõšúÔ Ä[F>Wä¿£Ñ'´ê«Ôò¢•dlMgEôó¢e#z;¯ÈkWðº’IJÔ?à†E(¥j )ëV|ÓÂFEôب/zL„Ýn þxIAÃAáoå÷[ AkÂXm*þ‘O¢°–’ñhra80ƒæ,ÝtVÅá`BgI2•kÓ…žr;¬n?@eÐX1Þ‰ìÙÊòùòiâ©›šQ#iv”/žU"š$íùA"2/=××ʼ¥6,iÒœ(­vŽ pANõ­AJ2 ”¿Ü„DGo=ü/}&׌ô†^ÇÐ-8òèù:›Þi(ŠÀí(ß1eŽ»oòLAh x‘xižÏž1:†jÊ^oÅú"Å·G÷TÚ¾÷‘åºmnvÓúº%úf"ò¨ ›N\Z6ËŸ’=Zèfu Ü”Âá»$»r‹¬)ðÌxÂ$[K×Ï‘« m\µ ²Ipœp³ÚQëÉš|²Ö‘FoÜV JLŸÔZSòìæû>u°•²ŒÝ- bZ{±ÇDh†ï{ƒ‹\qÅør6Yñ>.è;×Óš½B(¯‘Kл´ÈfÍX€ÉjLuCs6z5XK¶V®Ì8€8å °wbÖv‹wܶžLQè±öƒª{Þÿ‰aîq~”ìÃråÍJPrz²›`Ýö3L¨£^Q˜f(¢¦»]¸ZÞ‘éXa«r„£ÄÍìÕÏC–T:Ò©}•~ü ,“ÈLŒ£àÊRè ÞÕb5—ý÷èöURá#9 UE×<,¤+‘Nfr½xjÙžç9É’Þ/&$ŠÉÜm)­TÙŽ_è¨g£ƒà@‚9˜ZØyЙœÓ)Pû·E°þ-ó Í.ëŠÈ4ÆeÙã„`s—s¦@> µù¾NÔõýlÐQÍA‚‡3PÔ&A¹Í=tá¿M©l,Nô¶‚#0<_Ÿ$ûÜ¥Œ¨„ ý×Àz»¾äé¥W m/­‰Log¼s¢ùZÊÉPÜß™NtÁ*°-`“¬íuu&†Í| ûÆ–öëPofê³xÆYMlažòKæ)—Qº¬Ã@ÏåÿdïäõH“¾ßôã:x*pÛk&‡CsTÂs|Z\×q©âÕÃ8ܦÊêÖm}Iæ³ÿ ä¡lˆ„ŒÔʵöW8n7gõ ”f.‰5+$ˆÛèÝ&žo·oÀ@gñYñk]ÚxoÃ!Öt}3Dꮄ%k¹Xª1<1–‚ø„Gúâf<¯7ƒìÁ8uzNšs‹µ5´z»Lq©ŒsÌî™m£›l]`ðáõ´| )kZxÐxe ƒK-ÆÚ‰>¢Dô”ŒfPã¨6Ѻ;Ýß©Möñ}¹'B·G3_Cɬ,s-Àάà´]ÕSbQ§àv`Åg"ÙùÇ“…œÌ4æXÙ‹ÂfåÖ”Ñôz,õ횪1Ç.ÇÝšÐæF*^•qMÏÔÿfši‰ zãÞ Š¿×¶+ )[»k#•¨m1ÛÕ7ºƒ_t  ÏÊ2Whm(;±t…XW^Üyίdóçå‚K¥­rX|Û æÖÂ}ß ’ïFªÔ¯)¦:ã–×Ô@™¿¾¯PО=¡ïœEñžqfK!?Õc}(§/1ƒTÌ€Ý-lÁ¦¯ôÔ¥¨çºÈ–~þ‹beÂQ äÕy^€œ”+Þ¼§¹K$Ú~y%<…ÐâtÓù ‰ßûúË×Tx0ëF&¢ÛmãoÚ¥øáéþAeÊÞÄ\]‘sy[™À´"?èøå2Å$rÏ!“âž(ŸÕ'HФMûYë-,ÑÂ=xÉ´µ­añZÿTrØþÖ!”íM$V‡¿`hfɹÕîh•®tÊ©)¬1›ycàÎ\•…ÀÁjì=ZTË}2öwì¯d°1Æ­7é§Íd½Ó¶'áÙEP¿&ECªžT{¨ ä°yb}`ÕÞ+,²¶®3íèÃŒNŸëlÁYäwj—9ç;uˆ·KžüŠ.¬ ³§Ïs]å¸þ ´jáªÆZšC³Z ßg$A®ˆÕ$~Þ žÜ"šÇ÷»—œ$&ŽC] 2¾§I©>¼ñ¨Ï¹IAç\”À™Ô¦ŠFÀŒ2*Iª®5­â †á§ºx‚懔ómÓ¥¸LËÊyÓöýÛeQo6Š&/ î:µ(qùèN¿\Âi¡¯šV% ™a)œDÅÒpÿ¿ãAßþqåÀ鮋&hÅFX¶ä:¿[¼éjÖR‚­`áPBZõ @C VyõªW\ʉÉWËI$Yލ¦¦2*šwo[RÞNÔ,lÕ[ØÔŒÔ°èãñ·.¿‚}"Ê_#ã1É2Û>?cQ ñ‹ÊÏÆø®—Oø°ïÜî„;]ƒ òNtm ªÞq‘¡k®--¶.ù­Ï:¿¥…;ÊO›Öáj;3$A5¦±=Z˜ÁÇ{ BÏ0A €Œ©cй›EÃèxÍ¥`^P+Æq¤ÞR9)y‚69àm¼f4MJÞç× )SõVx5Z>˜û ò¢@ ZüuÚw®øF˜4PkR€3›)°é"tcg6e¦>Æ'b¼ù[~ ±˜½Os a»è­¶¸,yÚeJÈéÐ;8{„ó I… qo_ëŒ@]´q§d»öB:„w#J GbÈ78D•Ÿ=·ÊÍC@xY9—(_ªaÔRÑÜ%g¬(DZ¶”;—§IRtÈ1#¬6Ö'Ðu´@¤ñÓ…†š‡U*{¨®åà–÷‰õn4î‚ÐPp æµýäH ÂÅà~°¦LA*L ;ÏO–»™ÚÐÜÔØA›¤L'ðq…JkJè`BN៟æA±ëB1©<ýÀôã\ ûÛØ—‰fkts5=8)åvo6åÓš_P]g}é?úºòµpq÷3¢¹ìÂã%ÚTê=Œ‘¶û!Ôbû SŽ€Ô‘[juó`h ? #€v»'7Ÿý™8®~[½®Ò&lM¥È/Ÿ¨m§* vY*Ön œà5 r(º=eÛò´/æ SböLšƒÅÛ.Ú ˜ÖTd4¦—G™8NÂy1¢Â‚áõúѦV–áoÝ¥¤¯>¾*LDz7 ÙŒºÿ#¦€e¢t¡MûVp+,ÿä”àöŽº‡í W&N8í *ý'ÞIÒjûçòËCèÂ'šV€Ó\µäP·VóNç:¥GDk¿µ#Ÿˆ§ žÍC.¢‹ß÷Wé¯÷dT㕦â©ÍJóS†g”0À–öœ_<ióu!‰¼¢ AÊñvê âEf£ð¤®Ýú/ þ‘k@~W[ïÇÜc1 ûv]¨Ú3Õ†¥“z–"­›Xþ®^SDõìm w?²aDÅ–0@^9w©S»R ÐÕ<Ù‹§äBÓä_O¦|)1øÄ>ˆ6¤„¦_e°£Ÿ ŠA Ó³§D‘#'f¸Ng¦I6Q¦`ß}¼ÏÈÁôU–èÂøeÃÌ%=êù»ì""”{N‘@¥¶†K Ée¨2¦Â ¸È(&:Ûn)¯¶£E8º„9¯›”µ“ÓÛ©çN/?:8 eþF³çu:èn®ªá~ŠFØòfvÐaÕÌÞvµÆº˜Ó™à2M+(„¼¦ØiFRU¦¤ÀõºkÍÉ@-MBœ©³÷±É*¶íªÀ2?ºS˜{(®FÄd.j‘¤üƒŸ™%]vYð?ñ‹‰´oAx-“KÞñßdŒV¤Ó¡ršîá¡ê,JV¢V„Ã62YÁ(Ó©²áˆI1Igˆî5lOYwîÝ~ŸlË”øû,ÇìèHH2¬Gû«@ …ïó0ñ`F%ÚØàQ #âÈ}yÑî>IÎNœqØ+–ƒò Чf€Ð_Ì£ªGÝ% ¤îŽØ©'ëí@xõ”N£V—é|ídõˆMe²ë:Vê^PzA3«»9VÝó¹tŸm½-¤j‡Ôìþ²^4Ê»ˆÈ¢ØÔ¼–}:˜äLyœá÷­œ%%Z³ülå ñƨNý‰$UPGAO×~æ‹ÈÛ)8WKÜü–ª®ÒÇáÕ}ŸUGGŽe"» Ž9x¢¿6ºuZQpb9åé{~’ÕJˆ¸Q-¿½Þo WİCY¦²}è&…+º6KǰRv¦4läKWÔ1˜éåvªù'}†9ÿÁqk |Õçá²Pȡܽ6.ÊA•ô¹8è‘ÒruàP‡ÐáˆÝé¾+¼»¬!æÈËk©nÛUA'žQcjæ8 £”xÎç4n–Ã&Ã>^bQ óݪ9ƒºwÖûo‘»DIÙ¶’Ò„ÿŒcÔWò2¢ ÛÊ“ÅÂ0ùå%¶,î° =eÐÞ@%LìÉ å/×b­ Êí=tC¤ZõnFÅr‹à¿:×@¨Ÿ/Ij žqª¿GRža‹6n!qŽ:J5f:RÌ×â,æÿ†•‘ö-ø~0VEbúYJ‹Œmßþµý.$b0Ux‡¨õ1§©~ar »hÓ´ùVMùëøË•d3øÃ&¹ÞëÑ#_cháã(ÚgW(ÔÁ{.êå—ßງÇQ"!¿?ð³o$Úáç:˜ZP!KÀÞ¬€ú2f„–Žq¹¯d$z¬ì³ÿ¯[.+¿”#ø(”<è­Ë†-šõ5Hê'ŽË4Ûãê^dx™’º¯áb‹~g⮓RE­›^»Ò¡JçC mrݺœ©sËœR7”„* Xñù‡ ¾‚tfb¦T¸‚ä–C¤€¾¥M‚XI^80e]EôÔjž‚7¦@’¬uŸitÝȹ”§¥ÈL;CÓc”–e8v0OÕi€8Õ»?ôíAš•äeøL¥á ,TL4DîNj^mª>{;hÖª_O’~icMxž X>D _±Ö£.2•twZ"Í­ð™÷yx”Ø™V·Ë{YÕÖˬ‘cB˜Ãrì`-¡H™nåªCЫÎC`f!4 Å‹)beÙÎènÚ·ßãÑž€s0r•”ë=—†SÌE™¾*:>g2Riˆ‰a;ÀËh99 _­8`(·oK‹âåþEá¶IÑ'á.VKïµë¦ êzÚâ÷¨U”jŒÒÞi[“;õ`øÁž‰….b”ôh­±;2Áá\¡?«©>eµú_Öû5èßÇŠ–ƒçyÂÅœ÷qµº²â“o~‰\Ñ+¦7uârËœO¦ïú¬.EKkÈ#é~;Di/®ÿÒ¿þ®-8ˆL7Ÿh¥„°É¾ Aˆ8k¸¤2Å÷;x{§C %3:w!&àsN¸½÷D)3’ ô°ç(UZª~ ¾Öåù¡¥¨ÄXD3 ï‘Y‰‘õ‡kÐÆðx4³Ç_5ÎØC4-3>1ýÍ#­Q#iÔt3ƒÀ¿É*¥»…&½¦'¬ì¨û8ë¾¾ýöì:‘’n¡»×…÷×õ+Æc•üzÅê«»ìŤ–Iˆlp¡ ÷®øµ‰’Pñ‚:±ÈÛ‰íÂbD„ÊVÐÙ€?FpÙÄç;lž9k;¢W³íPH”€³K‹§XW ½?Ó\’µšîÊ­ØÛÚýd«¥¼URÝÈyG)~ΙVó |ž¸Óúü¯û»ùÿP—§ûÕF¤·pJQ…ï¯ìSõ#àfÇ7V›¼}ž3øx¥Æ™«Vl\(ØAOÒOCôäž3[Úæ¡ ‚< sÁQ§£”¡HÈ5PÓ*;+ üÌ¡_?in›¸„°»ä‡ŒƒËܪ‡ùœ€mùK Ka.òŸ ¯Léªv®bTa¥jÝæÜâ‚&››òÄŽ§CdZNÝêÈ ´¯öe £NÅ.¸š®›C|À°·u7 eH’Þbô€o‡îû¨¥¶äÔÒ‚´À‚¥f–¤h`ÖN ’;AÕËà¿u<ìgQ RêüsÐ/Bß²õÝÎöh+;˜«mB}¤Ì3mã«f%Gìöι–îĆÚ>õoŠ‘ÚÈßàöÃt®÷r¢ùW6Faš2°™¨êe&ÓÚ°%æ#o$&òÖgïÐ^ÏCÏp jÅhß3ß_X&‹Àáð¥4 "c·;ØÀ†Å™?ß{µ\ÌÑ óò‘Ú¶cè2J$¶ÐyHq%Ý9?Ñ€½tqY%Ó ·øê(SbU~HeZ¹h))Ì åG›|¹+õÜΰ$(›÷vh¶d˜ÒÍ6-3öpONØP€\žŒŸäòB@Ì87t «†êoÈ5®³›rÊhÄDê”à—Ÿ36Tœ–]hŽÒTo8zákuG}`Õ`ørA8•ƒíˆì_^°&«½;è— úJ÷”Žhç™;Ê Ò†åÊèorÆ)E•º¥Ø·é‹ ói_ËVu™bÁâ´$ÿÇÚQÚÁ£½Y¡2Í×ÝÍJøV÷Ûß /Ûõn¡Øc´ÇF €çÑûkÍ®¨`Är.¨'Ö?ß’†~-ñ:vz2è\ mr¿«ï[ôã ™±¦”}?­Ѱìt ZN!¶†IoÜÇŠ)SQÇ·x…Nƒ8€2×ÁÝ+ëµî˜m*#l'ÃýYúo»|HE&#¾ÈÖ&ç~’솧³œîÀÞ¼tœœû°Îä@â $&´­ü.ýJ ïQ~ü?-#³»¨:‚mÍfßgïÕöJº»´´ }82Ì¢…=Èl©«pçXM ÜÀ•c¦W^¶®‚‘)ë”q8 À34Íq²þ¢gB;Ó$0ÙÞëÅb¨ßh¨ÃÇQ‡µYTÕºøeF]ÙoÚchÞG§~¥È–«5ÔàJWæZ@—0Éy¨ƒ¶…¥]½AÃzpAâr5èT©ìÓgvAÛà´`ÈãhMí›Z ÐýWJ–…’ýº§O_ø:Ĩàl“ߘFDVºF[YQï_ôH?<Ñ쎩=ZÓc6ÙÓÀžMÅvÚàÒÉÛz¯ ÆÆ'•PïÃøfñý²ï¸ž½ü6hiÝáQN·h›mæÛ±ˆ´7¦»gõê²Àxˆ!v=Fi€ëô2"àU #¦r€í. © ÖÚúùg)Aï‰]ïÓßE1‚¡µ”$•ïõkH96Ö¨›†ž~pg• í5ï5Øî_tŽÈëúª;õ,*Ë1å,†N{x—MÚ ûYðèJÅç!PäÝNˆãÔ> ÄŽ±Ã×Ú>Ýë“ä}„ÿ¾¼PPŒzÙwY.yZ@°CÊ'¼Ï–®®Q<Õíù=bû¨ªŠVeš{/t}‘‚ã× «È3¼Zu5­]ãPA(«†g‘v,Ëò6åðžÑwëö«@æî¹Š;ƒ¼_Ó›”;Ù hˆñ·Pjïpo›TV½2=ªf¸ëêGKL]/syMÐv%øø£T'ãõ„o-¦U)fØ‘œY°N4<—ʈ¢Çñ-ùóÙâýŒH”äƒ-ƒwv€ÓT¿"„€§;ªçøÛ€àt·?ï?èÍ,T{ã²’Õòu—›YP¹V}!Oæ7¾{8xkf½—jiœÀ€ÑçvÈVm¡^TõÖ3œ¢?l“Ûè–„G" ™Syå…Cg`V„×öw¸–c„Rô«-‰Ð‚AÄÛš„´¾ág‡ÀIÆ ²÷@.¢ ]pªˆþIÝåQi,"ºØek%“;FÌZ` 0\ÆÌõÌ‚†Øÿ3ða’© åp†èƒ¿Kv¯l;¬© }‡­{ª…/N ÊsC¶£ëjàÁ MK§Ú,ŽÇõÉMs÷tÓÆñ–ºø3aºðûž”Rê#t”X Î<á€4Ã5 àvMã\ÌDæçT-ˆGÒIÍæ/„“n¡ODAÇn‚Rà*­ŸM7èhÞzAH÷ñˆ×u²ÄiíQqÈ..´|ø[¶ÆMì¹-#*îXœ½¤áº­ÈL‚°F ±U3¢F¥#òŸÐ&i¤¸‚í{:–¡,"™G±à¶¥ìbÝ×AdŸ=Ula•B6w¤µæúa¨%y¦’™P ¬$ª•K[ªñÞG“Ö=I++¤£`’„-¸™|þµfD¸;:èd É;È®„Ìåè¹æû;3Fh.árq„zJ–¿4ôJÁÃKÃÒJË6*ÃÀÑ)Áv¨è>àÁxwïk¦€ríÙ`>âÖßk°N®5—ˆ­e áÅ%yðÛnê“ÊŸ)•,·¬C:yñž—Ê!2Rëq³Àðñ7˜òVßXßdpPÏjhMªã<ÉFutÂOž—D ó%o â°°ÔÐ’wÃÇ7)^‰ÒËQ*çauN´zQÍØ%©¿q¾­P¶Y+7S-V—CjÃ6 ‹+3áP© Pß8T‚ ý‡X"‘ ”IðM*HG§]{­Ç¨_þ³ƒp~(ÏBú´CëòÎ}HÊh*›˜vibºbkøtîZ¬Œ(öîh4¥5²fÛ¿FÑËÒ,3ƒÈb†#…ÂURf>5!ãº@¼ÂíqwX!â㘦ÖÁzK?'ió"R/ /M&®}ÁGj¾›jq]tò8±, Ô¯.q·²ÞiK…ó:YÁÿR[Ž©ÕSÈ’zµ}œÍÝqÊ9à¤xÐ_Á>˜»‹Xb]â¥`´ibÁf‡‡-ù¬ bÐn4ñŸX„`|Å_¦pWé熇 ¦ÈbH±®›ÔC9yá™OÚ™ì„ K•ÄT™ÔÆÔÃ_3åëG;AG\Èy¡áЩfÆXõ\œ¾ Ï²Á” ïQ ‚ØŸŒ|pPi§]•‘/ñ¡Ø¸ÞûAØLµ["°)Éý8YO—ÃêÒ<ÃFv q¢*ãÿ…6ÚÖµrX->ó‘¼õ‡üAZ0¬޼ªF‹ì±ïá<È ´\±‚å4ͽÕâ‰ýg£òtÃË®ã)UP&a-{w)˜=$«[‡0«ëSìÈ1Ï¡£5Š_z1Šr–5„¬è-ÞTQÆPw:kn6É€«_ØÜçŽ&šlάv° CN…íáN7ÛóîúÌ‘×Ühƒîî­4i'y~œº'\¢‰7ä›ûrF,óIXG':ÝE`­r[еÀ½?;£dý£ ”Èã&N~dßÌpÒÏL¾ 5˜ÿ@.H¦c <Ëð¸ÔL›ûþÁŒ}fPÕój~œLƒxJBÒjÔ·DƒÓudKz`GT'(‘}gÝ®.@?éÀ'Ä"йBô†<ë`Ÿ·ô^r˜ù³*ÞºÁ|²‰g¿Ò´Aád]¢)é÷†aj$šŽ}Z ­ßØÎ¿¾€XOÒ»xçîe7lG™®\«êàð‰êú:¦ñ¥¯tEf1 }á6Ó´,ý`ý»Á6vïV¯¦]© °f¤Ï7çg=ѯ*‚Ü, SAa±3…?é‹Yì,•ááÔp6ªõÖ„8“È6 ¯o]);k£Û샗CzJ2Ú'>—€‘!õ¤ö&[ú«HqdÑ_ÂôþH‘jüÍ9#Ö\úWPSÆÄ–ÍK­-oô}`V”3°c¬ƒ¬ìluù,Ú^ˆef$ d4Ôéìà‰”'¾„*:òQ`× ôû1ߌï«§8ãˆLÝ“ Î/V™4 -ÿjVZžH@PdyÂc­,ˆÄYeÔ^CõÈÚ Û÷—Ž {ƒC=¥Ê&&iàܼ2¬UPf>HXpŒœp ÿ§%C™X ¸Wª:åÿ‡ºòÉîN2¹Ì¤tÒ㯒â6•Uøªr¤%-±Ôܰzáÿ3Ò¿÷ÇÜ-æD¶~ à[ª'X^¡T‹c^  PÜ[‘ùÇx|Ó¶Ô°üïÐõWdŒY&#Ê ">ƒXOf©Òc:ÝMëCËæ2ý@vdÆÜ(wÔd  _‰[_ÜsöOÞpØNju4L öRE@¸ðIÖ•jMÃTúMÀ@¿KÚøfXcºliaÆ÷pµ‰ÿ²ÎzížkÍçËŽSH‹Í!üG.D d;:çh`ÐÓj,öjçèUÐsç÷Û¹™úG:b´¼¡€¦iC‘DJ`W Z¶y¢Ì¤…¬œÛÇ—†4|éã%§×Ijúj-ª×çÒ< üQY8C°¯$\° `Wî [½wð71Á9#ÝôhBl²þ™^ËõjBvË×mOKE9š—ehƒ°/Ñ»w—PÁ–Þi"t—Q÷W‘é§Ó›Ÿ‡û9:ÝúÖ´Í¢¢Fœ/‰ #1 â¿ù„›`¨ƒ ɰ ÏBm{Éû+V¯HºYóxÈ¥hêÁ †'×(û*uµÈÄBË7ðˆó‘„âR«œ¢œØ®ø³V|F¥DêúV˜¥I¯š!{ {†Ï?5‘® zlÛIf^Ž:°²ø‹’ç×YÚ?ð´»’•N$l.ŒhøÜX²3¤ºÝ=2þøÒ êO0fØ]æÏ‹çç|C{çIÐà4}Žº<v”ýúŸ‘°kTÛŽÑ -Rª¢†4/ÎðóŸ½Lcp´#Í^½X/)v5ÃÍlêµX”Þm¹‚Ñ-jY‘ˆÌÁÛ#0NsaíÆ¼-σ¬©û3,cnŠ+ù­Š_Í~åçó·nŸ,M&®æÉIøàÁyD?€w«B)¸ z›(tKК5·™T¸ZüÖâÃ4yA·,¶¤Y'Wœc±+TË›¾Ü×RôUnÌÞqãõæáŽ·âP1AØ0\QÕ=ÇDXÚÛäÓ 7Uš4bKß8Ð6óNÿAîßç Ôìbl@57È“Œ8à0O¾úD(pÿ”Í2FP¿¨k íƒó;Œ½ÕŽÑYs¾þ,5(“8âú¹ò7h‡ÂBs~Æõï0~Qó˜C8 )¾ôyE†­áÜÔIX­[þ ѹ±§ñç²?W«œ– þÏŽuš¬x:M¥}\£;wZ«nK9ߺàdÐãÎ!¶§g`­­™¤õïD8zY¯1J9ÿÆ|óΫDétk^u~ õôm§¶¦¼b3yóU‡ôÇ‘65«¶‚ŒïÄFx½á·MÂt0fiݱŽ_š»(¨#³nºˆEÏm³ü¶3i0dÄ@ÆÀ=|_£ó«¯ÔEƒ TŸ. ;ìÃç ·òØÙ¼OÅâ\ÀˆkÐÛ2ƒíT2fêìj•‘¸€q]¼fÁZ…rçê;•±&ÓÕ‡px,~†žšwŒæT´¿ü`önøÅŒ‰h(¢âAˆ’ߥºM××µ-µ£l\a¬Üw»Q < +Ã4òÒ'@´`ýž º°)åûn½˜5õÓÇp;¥³?¿^ ƒªÝ¨çUaçÊgcÅM¤äÊ8ˆŸùÛ­‹;+ƒ†ÌKã°\ì!Ÿ\jx'‹ ‘ð¡UÓáHÜô²u½Ÿýú‚óÞ瓟wëDšµÚÛ² ™XL–—)ÊO&?Yð w»Àmç×pgGdA$0öÙË+ꬦ / ˜þ;ò–âšžmñ®`Xq\ÿè÷±ýQ¦»˜þqï,‘âô_#õÐãËYa3ç…ð+o¼…i‹hè“£ôn÷´ó¼(ýwU$XkdTàÙÍXã&pyÇ 9ô÷†f7KwÀQE.įváf\Zà ’빉ÑK…µA—]¤%Ö¾Ú"\Cã¦)q¾‡­Ön.CT‘wTù~Û1¯Ã̪ԲZ]êç¬*ñï†.ϾŸõ2Ûå&¤2ƒ4 ~mâÖH&²t•*wö‡jÈ#v¥+¡m˜xMë)t÷~«ÌwGgûŽh5›E–%2³ž¾:ÓáÌ™ãcÛ¬Sý¸<ßΤ,ÛpÀYÊà8Ø ©àŒa½¯Õ‚+´ ©å¾$ù¯¼)Ï0î|MhwÝÖ—U9úK0·šŽ¨þº9LÈí4äÄ'L¡?ŸÇèØÞØVðÂŽôŸ…‰×ß»*Qµ¦>ÅÏ\@&E Øß;sqØ@Æ›Ç6áÓËX¶’bŸ>­tJ^5ZÛð)Šö¼=2ŠÓpñó…«8‘»ÒªlÔrcrÞøSÊP#`¼{jSBH¨¿C7qtˆ`¶…ò•½ås û&/4ô€Ôˆ¶Vá³±èÇbjAwx~Ýgp©—¦!Ës;ëÂÐ]@_˜ŒI,?äoØ­Ü1¬¤j>BѬthf]„ĸQ…#”!_Ã]ÐÔuY•m1 I6‚´¡(62Nã–­ŒŸ„’Qûû¸Îx,ÿ¹V j— \M÷Fdïb“®­Iý$}.8lU6¨›Ð?{82Éœûωé¿å[«ùÓô&„ØŠ†óòvíD:3dÄ$b岉¦ŒâU»zµ¶2ÅΕí㤠c`ÍÊ¢®£¨Ëÿ“TªÂÀ5îEíÐ&ÎûZÍÜ…äìf:ë©Ñ·:>Ùû¡3mmLbÐ;ùýµï8ö¬cz/‡,OoŒ;æó ›kžÔ3±O"€â <¨*~èp|Õœñ¿Ë#²Ð¯¯àò1 ¶W_h‘JøúiÝÐÌ_Gæáäo4å]x¯f¹ÐlãÄJf­C«øÈ‡xν«D]J.…•N$sÒ?𧲨¼DYpãÐÚ  Y&áèà·õŠ í÷8°sä‡V!+ÒL¡–&„_.÷pýÉT zY’D¹¡Ò)2ž º‚­¼©* dhô5r]v]²hš¨X‹&êŠu¹b[½íÍUÂÉ×–”³P:"ÅW°Å,À„}tדÄ„…±Ïjß ŠÌ-SœMOn†„ÛÇTÍódLmÌåú …$´ KT{à~èÍËÊø`^üeh]3F𠜪l+ž¿±  å}ž×añ—“Ö|éýÆ.fë#[‹@‡ ô´ñ|ƒ> …ÉvYZ)^ÒÓMJ"‰'žëA”ªtèïJŸÃTÝö>H{m‡¡@Ç#¶ ABôÚ¯vB±ËÀú)¸ïò’öû®ê8ê"2è7Î0A£?/\e¯në‘§…6—rìA³­Ó‰Õ3˜€¥5ø]†ãöeßîei=Ž>ôcêSù/ÆpâI#ZÀ(«[ÝÍÍ}ÅBQe XÙ^Ó;.ÓH¬šÀ,È/× ¾ž&é£ ÚR~P•Þç29  ?{Smym=ç¶NwÐ&âFpÿe».ù©k|Aª{iY[R ‡m³Íü!ìGJ—òX®M÷‰‡‰¾óe~¥Þç³øÒx)K˜De²[r p²ßuE¬³~²NÀ-T¬ûÎæ£cM'h #bpÂø§Þ¦ÿÔCE¼I'úpÀ§ªEÙ­YÐG¯ ¸'À±±qÕ±—Óe®^C´üñnþè·rAf•û¤$ƒ»£‹èm·ôñƒ™}£î ºpIð!Íâ—e±áÃìÈQ@ÛcjX­M3Ë\âñü¤ûj©Ê°–RËä²6?Èlæ´çùÈ]/µ€…‚»üêƒv,Š|odð¦_À{ªÿB¥fÕž8[þ(  -¬(‘L+¤ïÈîX®{ž°çt¹ S¡4•Ø;¯&2§Ë@ Æ¢M…8oÛ@DÜÀ׬Ü냱äkÁ ¼¯€µlO’œï…ï:URáó[±K|Û-Íã pŬ¼µÅy‘­È|JY¡Øiä`’b Ì1ñ9ÌkºäÂ$;÷†{y)_S¼s½Häk ™^›Ž:¬àÖjå©+oiñ:©t}ƒpfq³úS“ÇÙ7‰©òãùì$sàUOæ°Ó£þת5-˜É&&¼ÄºmFƒ-O·°L_Õ×H­ñÀ4î3Å÷ÕiÁ¯ Ça05XFéÚ{£ˆ/ìCNY5ЉjÀ'ŸÂ(sðù ÷Ïï‚[‰H)²]÷òa×:¸‰ýâüÓPV‘ªç1ßÐqñ­qÉ©ÏC“@Œe¯bøâ¸*lQ”ƤŽfµ¬k +‚q'Þ!9•U¶ ËÝѹŠÿâCïÈzîT2¡™åÃ_îB> à«R¨SŒÉž>Úþ¨z¤oæËÒ€=Õ4›¼ËÙ„ZÍôç1ø™JYÀ1ÈÞÔí$=LûbËKwÆ=M#uv¶)^„™?wÝDx’ƒõ1:Ï3ó§ó9« _訛1dQ®~ˆ‹Ç_<«˜­|LåÆJîôã¹®ÎÊæ'›€–?p:Í0sdp 8Á× ²2¾­ùw§žÌŽ; òÓwn‘¦/‡4½,7§êéÒ ÄÔÄ' †ÃuŠcø­…º{—iºÜé°Þ¹U¦ Ú“bëQ…ÂÎUƒñýÒZÜÊ…õWn±¶_Ñ–Ä ?¢¯w¼Ý%ºÝã  ò³D3Àši±@îëP–³:ä7hNÖð*tó d³‹€ vèCVAéxhÛá©TàxM´Tl6’8›‚v0P”êƒõpC_!¦ „àò€·¨ô?*vÁ*£Eïêõÿœ]t1šB×—ÞJ‹-;µæb}R6Œs6ôß½=!+%çéÈ›²lÁMƶF£[Ò)¤93íãé¥×Ø.Ê 74—×ÐÉ4\TûÙ¨éàóU/‘øy?˜`]úDölç Þ8âŸ]˜ì‚¦£#òõ>ÖÙGÛ£¢òÎ)ÈM; B,„tI +=hTé(µîo ÓKÊot=\¦äÄqÍ‘²©U5•5tÅM8šg¥0åDOAÖÔM²>òó%Ô† ÚnMº’•6ôbU’ÔpBy²-/ƒãpUNôpœr 1gO¸º÷mól š×éÔ%*b -qP„×ê9÷4N4I£XäÞO“úRºá`àtJùñԜ޹R¿ŠÝèôÀË>Š5ÔÃ\\…:Do9sX+5ÐèJË%ÞŠmé×0¾Fûöb^9V l–Õú =g±¼½TPH×[¨ã•ÁöÒ5e‚ŒUÀáèè¤u®OÚ2ƒµúUîΟòÆ;­…‘õtXtÏ%RÆ9Xž8ý¢õç€m‚ÌVœ~^yÿGtýÛcÆLš›€:h™ùQk¯>›VQ˜‘C¼¿/y z˜1ý݉d¬#Y‹¾å‰2‹ñ¨¾aP¸ûÔ!`Ý–©h¿š’Ü%CÅ[Ü*núz|"%b„Ô2±À›rcn$^ høñŠcù'Âô7{ìÚU܃$cHò¢¶\iæ‡ua"`d4ªÏ¿22ë—äIY„‡büÜÄ?V_ˆVÙuú“Ûö€‹'PõCùl£Ä‰uùÅpcÇ!V·6âjÑþaúKE/Zo’­{²ßAù&œD¤1…¨‡@nA¨Ž†K<èÊZÒ¾ÂvmÍþS<>ò’>Àû=‹£²Ù€ž…/,ð% (èíš &‚e .ÆúT"11/,y%ÌcrÞ¸`ôÇyK²ð^-´*¬ò+í÷%Aéz$-4½W_ƒÀãhMícaµ†°‰c9ôH„°Üˆý™»E ì™G}eÂí–ÄÉ"öž»'Ù¡^Ùµ{?vãO9g~Öû2ÁýÒ>…˜ð/Ú%(Þ¦K¤µ3æï{¬±ƒç!"‡:´î(eýÙÝFî6j,¢ëÑ@¯\,ŒA²iç‡z1]ú³¨‹7Ê4À$­e͹4T(+Φ·:t“e |có'¾ðÚ¨o7±È•øžgècïŽv1§B“9¯Rá‰ïAˆ™V¬þØ©éã3˜mG‡¸Úy¿f|!u# :i€¤ˆ‘õxÒºè1–.eK¨¦Ùý¢é¸m¸0Œ–b€‡¡«HÎÁ‰MŽíÈw{‹£Î ¨è/¦m{BV(yޏ!ÿçߘ]Òcû}ŒZ0’À™Ñ÷ÃYÍGà…Øk #-ߦƒU > 2s+‡ `Ö†ÓK¿’#ÛÒ21ÈQìfÄd†nR{ÜÍN QªMàò·!éU,(ý‚°ÕÜLzs1³×ï¶µÙÛñÆ(ìüWÌNË7~ â›ö æRA™JzSm*Õ®0+sÒÄ4ÊeÆ/ë1¹¢Ì™F}[‚(+–QÃèxY¶øH¤äGNó ñ¦ìݳõ¯•>ªªæRÖi±¾S95–òIÜ·¦v;ÒýrXÄ.'Xýþ3é°ã´nÈ:¡O|cÿhÒ4Eiö6|pEm#¶Ôã¢wÒ· *À&JA}†Bxúj0qÄ;NÚëG½ùí¾QjV›[ä-þt0À¹d<{­°™þYØIñµÛ†fz’Õÿýk“WƒÅY“gãÉÏH ØON©’¶ )cúÎV:ÆWw¸‡*ËL[y§ì~‚õ.ï:ÖˆC6KéØçÉø“«BfKs$E!œ-{?b˜½[鞨mˆœË9ë‘Ϩq˜ôû§_Zúp%€³[0M¸¯Ó ë/·öÖyo këŽm‡ðVm0Õãú^®ßô>üoƒ¢þÏI.ãM,ù';D0ù®„xˆ:Me3F5òVÚ+\‚z›¾ÓÇ ò0Ã[@„=Sqî ‘“~P1x§ý¤ª¿œŠw× óâ1®²dØŠ_Gm Õú ç¿:°W§ ÖO®—úl›ßpž+0JšC/ø»~a+˜c8TŽJxa ™:ž@N ©1 Þ9ðZ?û*ex«â'–âÃ8÷ØTyäRÙYßåï`Ê÷“]¿¶ Èœ³–9ô±Ìf e#%üÅe¶¼ËO»5âÂ"õ3°m®J†B¹'„š1+øT[š`æw«Cb^ÕƒÀÖ9)ZóŸ›” N?N•Üöí™ã*-‘+= Õ’ÇÚ;äë…¿BMjáûÔã}~¹(û&Uù†TÜÞ€ÀœEønÓ9Ûó[…éåÍmmì‚¶0z]o8¤êgk0ž`6Ïø/Ld¾¸ï¢[Z¯í¯9ŽdüέO†ÍÊwÓÛÃuj %ÛjÿÚÿz¼'‘‰ú‹(hu»Åô å Óñ»«†Gÿc ƗɇUˆDC| å‘„Pýåmä6cO§•nm=rø‚5‹‰F<Ѫ¬†¼Kl{6.#É, iý¤…yå°—}°} (g»^ÓmËãÖV?äžÔGÀ*X}òLBy B*…‘”ºÛÝ’¤aG£HtÐ/UZŒGEGù…MŒµ¡pðú£R¶Xg$‹Ãrjù€A^‹dºÍ+"®+§™/µÿ¥ƒ˜ÏMÎ4åKýã>R€ÕW‚Úÿ9ª~—jÐòúFÿ[¤.í§>DªÚEg.Û:½¼LúÓ|X/Wòç³HÓÕ¯u&`ù~·!'†ß|À<¡âÚa×ÌfÅx]Š^&7ð8ïRøÏÄÁÏÐ?Ùž@éÊ|¦ËÖL„3¹1]H"ÉXcM•,à;ÂÄ&g«. …œA”5G`"¦„­*8ºq­ÞhPòç1Žßÿ¬ðŠÊ±õ!O¹¨ëŠOÜkŒqäZ‚;…«O”–u¿ÏFŒ5Dº°ltÇ€ÆÔF-'t!XMãØB'û†>:ÒàÂÝOm}ﯤkžæ×©»ù3²¿K.²/†í¨Ç¾ð Ib웬m§‹%pSùJlóEkðY¦®Ã\9\–]G›âÀßHT&ÒÙR»úV¸.;´9àžiЋîBÜõHzÔϱÂÿÅäÔ|/c¿šñ"Œ‡##(VË3dúðtO¼ûÇ_ãh&ÞyÖ„_±GÀx·í/”ñQûIE»[°¶”û¬ ð™WðàA k“š ¬Û¾ç–¯2 æ‚~ÏË?S*Ö€æOw%ÐÀý|J®`­õ+£H’ìbˆ¸_‘2> ü.7!åÇl™ ½Þü7FŸv¬Õ[t~öWl&ºj/8ÔÅ}ô™º¸Éè@‚ÊÁY2§ï”3}Fç add3ûZ h–bøˆ"s­úÈzÌá ^k-”õM@(FÑ©Â*I!ufvÔÌ+Q#¹l‚/ä· 1FÎf“+ªb; á å;[n*GèÄö¬w¨¾váoÎ¥·AÓ1‹z+WiúqÀ P:¸±U[âÃÑ™ ¸°JZ-·¸¦êFÇ“šýÔáÛýLá©«·¦e¾ú|ßbžÃwº:å=Ýòû@×ί¨`0œ0è\â…iIÇáGÐFïòâ”ç‹"1O@„8`8î®Ç¾ç½T‰ÝipØpñuE/ÒK9(ÆÎãm¦doH‚ DÖ~+”Ej—pÁ®oeº7 UîÄý^_J<ô©ØÁÜ£^ˆSÿÖ!åž‚·*"¹Œ=C~KñíÄàöZJuéç,`ëEÑ…K,nÖuk°ö$2w0R^ëœçÓ¥¤{ØW% ÈmËw¥(˜ Ÿõìê dÉ—Mïr’ÿx-™3“ÿ²*@ÐÙèp5ºÁ®1Ê|5E€ÐBørè&e3¸  Y~éT2]IÍg.6l>rORtúÜ|qÁÓB •€yRa\g¿3€ã´ô x ±ƒ”uöØJíðžPèToj[yà´qgžlÐІ'yïl¼ú;K&à]I7ã%ÊÔB¡#“vtòɧ9ÏÇýµÁdìEÄô<CìOɇVË –оˆ;Ù+k‡EB©µãºóåá ·àöÿµ%´áožÊ‚C¬éÏWà³´¤ÛxÖ]GÉåB70Ý9ù.:Íd¡ü£æ"ÈE£`·‰Ü †•ªÙ?ʳ'µx¦ReƒM;Žó({Ôw¤Æš: У§¯ªöwÆ#.Í OÙH'Á•²µ$‚óó·>Ü0@$¹¾´B3=½xÈ‘ö%4fP@±%0N²|"8\9 š4¾~ŒB6‡#WóãVj5³WÈÓ®üœ°8!v~ïÄ3‘»VÏŠDŽP¬¨rù‚ÃØ“玥\8ª*ÙùŽÈs¨0/Ó1U®‘ðþ%Ö®¹iª„~SÓð{¿²‘—ܼ ©ÁÃüŸÌò†.—#/~ÐÒT?á)&›ÍÉòsá„12îc,'ž¼bôo[]ð‰ŸA ú¡’«s³Ü‹dàÜIðýkN:æÐ€öW£Ñµ¢;îNë§.þÐÓÅÔÛÿ¥®6þqÏñµ³’R Ÿå]éÊ1Z­i©“ÜDz3L·Ï‡šÏ›]üpº™@%!•[‘z{éÏF [_/q‹:D)VØ )“Xµç§{ˆÏ*<úR;”IêR(Ìñ¨[Or!Ed³q{ªeù¯ÑИvy´ê¼½½=|$=‚†ñ )½Ÿ®é¡¬3!'4Ajd]C …>»;€;,0)1Þ•üf+†}ÑH Ól–Z/)“i¹ˆ´ Žgj@ º\Ó‰b‘š²ìp‡‚3¸J‚k ®ªM È×ÐÓAU’Ú‚.‡…Y¶ø§cºÜÉÌ&Ùúp{â ÒÖṲ̈ÃÂnÂæc} Â7ù­‡çÍáÉNÁx/@ ô &%Mÿïð_àÝK ¿UE¶¼GÄ® ÀTÚÈõy¯&:‡lƒ®TûÿËÍ ÿ¼>ù’;öª¶ßs‡¾0ULIu˜í¦ å+YµãKùGOOßé[\—$:™'³D½áw­Á/‚Îc Ô\Op„_PÏ£ykO~8iÁÍÃå,ß¼B‰¦Hd¯Í1yä°cã1£¢°^Šæ¸ÞÜÖe_ó—å>”5Ï7ÕS~ô8aU¤+˜>q±Aqì Q¤ÍÛ8¨ØÚèb+KNõôr¡Ê_pøÏ.ÂÊàß7³ji³7"¼tŸÁ(FÁ#¦¿/a(ØÊtZÀNi8°X€9šÊU‹â²ÌÏ~Ù6ßD#zï¨oÁ,h)c•¯l>¡]m@X˜%tg" ç&2Ö"yû-cD`D B²`K® ç`]*ei§yª³æìgªK•«¶€hÃ@P´‡c¢úæ©ïí°´]b“:ðÓ. @Ä®æE¤KPÐé‡aAÿpT;>ÑÇR¯„ìib€öº~åx”öKxR 뜚Z#E=‘g9yMÑö®ÒŒÏi$yœîý‚å’ -»Â‡])¸&üÓ3²2>°I5 $&ÜB3]Œ "“¤mAYêx‡î {¾D¬uð~{8šŒbaÜÆÔF¶¸ÐeÓ1‡­¬£UéõÕP[9Ùa½=*–S`ŸmžÁ·%„X¹ìs݇ö€”Ãõ›ýþ¥‰œYOc§*Œ_À‘@ÈiÓçIšv<ÕÁ’PÇÖ#€Üä"±T>‹ßBkyVêPÐR2ÈÇò›ÌIøøîlÊÆ=fÒ¶@Ô1êë*Üþ †ÜSÇ(úHoˉ²[ò¯Îº‰…“d×ÐÄNó “›`Q/¡è4ÞÀw¡0¥‰ä~Í߀0·ð«ª ß’–wo$—.eÉñ¬–šjCÍ ûƃãwT9·0å±®¡Õâ"äÕ¢o—ÿ(ú ï¡Ð°·®¨Þsuøz´N(#8”;Æ-tíS/ËjäoËä®5+þ:a>~-mª³"ÒO~ŒV±É¦;àwmÇB¾éwTéB‚uW»—Ô‹sÝ[(á4Nu 1K0¾Ò=#Ç©ˆ!kô8½Á·˜¬GŽÇò@ËLöiŠ JJ‹Î{ë]èeìM(ýH|F²%ë‘naØc¼|&Õ¿~šEDMA !}åD—u—hT<ᮃY?xÕkÛe”e¢PÔ;ÓúÅÊ’…Ä€86ùЏôkf\!Üa«øM™EĦkœÅ1@*j£9X f®4Œ$u =Ÿoˆfèü)ÈãA5FÑÔ®qÍ®ÆïìruIrÌ>ê¨H2°n¥0 ¤Ý·p4S‚ž9Jp`…{çÉ÷ò¹ªÂu¯ lªÔšÑOÈ@Òª "-Øc‘±ZÐJc1¸3ίéÓ«ÍXRÃEš™wÙŠI””p.” aãOT£Ã-دÃq°—Ö£”ù|Ezü“/]ÌF žJLM´kÆâ^fîðÝ(y©á»ïþì—îv%ÓDßä’>¨¨:"Öp1l6!­¬®­÷ GnXÀ™%X»öVݳ´=zÅ$.`¨pçÕ¥  y˜[ß+¦‘“®Ö;äc¹Wґɤ]A8„ɼœƒ;}v v_ú‰Ç­<›7ƒ„ÕR }¡ÆKI£™­¯ kI\"©zÞ:”ÊMu¤DŸmĹ úÎö²O7ÀMåH¥Ã+Kádí)ÍìQü.n´²õscc­!6*ÏùR)c¹û܇!™&ØkÖÌŸNÿkƬ=ã6ej¤Ž–Õó¦•Éê>îÀÄÃØÝ‡óÝqm+8¢¨Â¼9ÉZÈ=‚»‘‰oéѲ' °‰¢ꂳa î½5ð…C´Ä0Þ˜­æ>rèO»X®¥#ë”›H`Jùu0îÝ»¹Bñä¼êbm©áV†d{”Ü!e7!St,çðÌdÔ9.\ûCn\ˆsÁùõl“ÌõÕEW=J÷)2ÁŽݸ+”á‰:ý›H”y½¾ù€(ŽÉ’Õ|¼÷IòÜ .‘ð[˜7~W¡4¤ãÖ1œî½|Gž†ùÞ¤ñÊrÖÀ­N%¾öÕò·ÔøŠH§æÓÄðÇ¢©ªÙÔ(pŠ­Y5–§H`e \"oܱáÖëÂöH¸·‹+cNò Õ©uÁ|a_\%÷ W) \*ìÊÇ®ý„óŒ'ˆ@¹gÔ™Mr}ü™©Ù5^jPÔÕ?jž,½cd·5¥& =ŸKå*z•Híxš³ÅïüÙd,ö_É$ÐàÏxfÜ4µªDE\ȼÞ8¬gyË]¨$¦ô*Q(ëgöÊø–¯ŸTêzÚ®…—æCÖ&\b.¼œ’z£1K.ÂÃ)ã?äè =×iqIà,œ‚Û<=flR”]¸i"3øç:ÅP»e«, QH ŒÇÄéü›EDÆ1,H] Åê«8LÜ´ñ^7A5i·Í½~>«>Šu Œ/çHNu`¢ÿ¢ õTŽÙ—c uß_QŠ‘è=ŽùKxBïâÇkl²Oo7$R™ŸâI£^Å&ÅŒ˜©;t©Ÿô¦I<û èß—+Ó¿  ÄGÌÇ¿4˜¡ÂJ@¸,zstÄ#Áx£0ªrÌÜ3UCï³k»Œé°¤)› š…Î ”PêyÉêú‘oŸ9±&øE ‹x§ˆ1%ì¾»–4ýÔšó)ý+³gWö÷u?×ÖÒpJ­ñ☾ψ¦‘xkþGÿ’ó*„ƒ ÙäCÆŸ²( Üczã—ÇEÖYg’ª«n¢™J£#‘™,ë×+*sæËÐj¿mÒZŠh¯<_ØëT}VŠúy\$³œ]¼µÌ¾0F^MÕ×é’¯SêF¬Y¦(¥Ôbv‚\)%®™†ÿ]p|+{¡¤J^^ãÃ…KÆŒùö©—'—haÍ8ó:Õð;ÈxÓŒÌèÙ¨ýEô4ë.ì%…Häç‰Ù°êIƒµØ™â¹aÇÇ|,à¨æmqáÅ,é±­òÂq Uy†„zòV¶í¬3,Ó Þ¤gªÛ÷ž¼>ƒC1÷ÿ2{m„õ-«8Aòȯ•Ž)éXÈáJïTì¨^,å ¾{R‚i´ªÝ°RtB@Õ,î¥ûUƒýË‹= SõÄX‰ T6øpÔTŽÚ5œß*6ðè=óãæ:jŒÍ¥Ds#‰jcàléóYjïÊmkSƒÛo›dÑÆ£À—828È/Ú„e£uPÓöHWÔað 2ç[ÚWwó`ÐjÙOqƒAòã÷óEjTÜ=·9E3†3ÕçËLÃôx—’1w×xŽ¢ürh¥°/§`]ÍáŸiŸ˜ÊÈU¬Å¿}ò[T="à”df$ˆÊA^ä¡9ØÆíõÈ›nX˜ÎëpÛ7¯€¡òa¢¥ä~<ÜÃÎÓã‰ôÆSgËœ CK„fšaºˆ·7q¶riGd^'¿þø?ïëõ‰!Z`ذ9¤â#ìæmƒìæÑ}+ðºQücCRÜ M ³Á5|4À*Ù¿=d“º5¤ÑSk h»\hV¿åóÐÐhýâûÔ³‘Ù§~êÎ ÚU ˜Ô„ÿwî.î”$X_ĤÄû‘2<ðLG/#ðja9¬É”ÙéÓÛ¾?]v£llg×)»Z2ý´Ï£Ò2©É‡³c‡Ï€-1tô¸Ðqßnœ¾ZpÝ¥‘ZØé”Ðs†PH·œ¬¥s .Ž;SNV»Pc…:GÐÝFÿŽ 3®i8Ê·#³Y€&¾½ó |AB.ÂxdÖéÛaž’!„÷6­]Š~»ä›q¾m6¦$Ì«gÑÙé_ä{Ë#{BÄÃÑV’}î³€mV´{Óç÷Wi‘5cˆár ÈaìøñI™—CâUêç“ñ]²ºéœØEŽxþ½T:ÆÐ“4² Ð WÄ·‡' ‡ù„4øÍ•r w«UåÜ)]…Ëf¶²É©´ÀLjҭšVÈ3‰ÇË7²J.d2XÓñ2Î}œ¢M|”]—#¤á¶(|ØVÙÀI&ÖÝpU†VdcK8ÿdÂuZ$:Õª[ü]”aïÝfß•¥²d~qÉÂŽP2wÏkò©×H@éš=õ^á¸ãÜOÿÕëÙ!!gf}½v)o®‚mòÞž0èO]ÄGí÷×4xYrç‹evÙ ‡f*^g²ÇΧ3qÎO ‘H-ø, ¾ýàз©NfK )"µèP],±>o…é¼ ËÅ…Itß[ç̯é5ûôÆÿ&s*Ö2ÿï´;sÚô&výŒÜ~‹TM>Ý*Ô©[ñ DKrû\ÕxÈ@Z»³:äbÊW·‚9 tÄÿ™^3†6íIæôø@ñµ²QV,£5’fÄÐO½,¹äï«9›ì ˜”ú™ú÷¸3ÁZÍ)„.&›& ø»¢³è ÑííÝ‹¼NU^®æâSV#ôà,<ŸÆKº}³»44pHš¹³Q‹ú´æhÃFM»õ¹R×´ÓßÄH^ÏoU£x™‹ñ®âD5¦@ŸsCyæ>¸6.”ZÚÓ„¤‹Kr”ªz¬øV”N§ÀZ®Ìg;TؤŽÜXcÄxì NÀßí73©åä÷vªòq|~±!vÅfËÓ9ÔF‹Ò>WI9ãÓ©Š'CLK™ ·kW«¤§`š(dW{³/Àü¢ž\Ö97 ™)­;Âè§Áð‘ë€U'?®ÈE“CÞ%ÿ޲9J½ÝIðR‹|H*ƒlõÃl£Þ´ÓÄEˆ†_±½5¿×÷Ü_‰QÔPv?*`à€Ü,Ë LéNò||MÚåâçÖQêrþ¼a&W6zç÷Òn+Ï{Ðú"H\œWì¥/ÕÆÅ(/R) üãã §7QÍ‚’Ë«l›>»ë>«ÂŸã¸3½¢½'#(›NóŸ/"(‘'Gdy"‰ha\XÕ~`Ÿ_¾¹³˜dñMïÊôð’D‚ 5íÃWp &zˆÈ ®†Í®ôfµl'éTÎ’þ–çºs¡áÆŠð–5ŠPj¡î]mØÃlæDäÒ˜I¡¬9_÷ÙZ«àvС/{M*¼' ‘x7¾˜¯¯p¨Zž«5+b»t4!,ÍaË›5¬+äÅØ×÷ü$Âã Zn‡²L#Ÿ]þD¼2XÔXDómŽ™m8€ ιWS[¡»±Eâ~¬¸8^ëFå–.âu¡âý*̘c›LKé+Òb›Šq”†AUòÙ/™s¬)CüÞÃÅ£¬h®DD³jmN·4å yJ—>wŠW*I.Ô!«/ÔÂ^ŽHŒ. õ;¼•g“à“Ø(Á†BHÁäØz1˜‡ÚE¹§%.Ô™…ºàç‘b‚¤f¬óaΗݎfHQÈâ 4ûXFÑ‘ä¼ðgm/¡5øÌâ× ¼ÀÙ®#BËЉ Ћ@{úhqn,;I"¦WÎЬ˜NÞ¨¸]Ëmô1‡dÄLí›A•õ÷Ó©6ãD†aTkÏß-¦4…öY†Ñ|?±ñ mòå²M¨!•s5R¶Çag«¥æˆŠí¶¯Øn;nºÍ`¯ ÍzPôËpïl¤Ê¤„áCæ sýÀµ©Ïku ¥‡^1õzk÷¾š‡kÀtHšuúGÎîÆŒUÉÖµ …#Û&!Ñ+íÍׯ‚Þ_ë#W0¬¸NØò½& 'ʵHé>­ò›Áø ý)rÓ‘à`;æàÃÇRëOÕ«´û¾Ï{fJÚu€ ¶ó¸ÅÑKÎMSQDⵯDErÅAвÃÂ莢G»ýÎ3ÃŽ !–ˆwž¨åPœu¿wU‘^4)wèZ½f…;]ÜT¢åæ§‹§¦Í¢E è„¿fí¾3“Ñ­8`qLñè[væŽUØG×$l÷ÍB; ‚ Žgƒñþ–ädxÐ*g^ÁI{Q³'Ÿgô$fÄ„†«¿VùÎÂ%5g4ÿQ$°ÂlÙ\°nöýOº?Òm,Kç&=x¿g˜4T•vPD ^›1Ö¯KcÆ!åëâ¬ÈM5¨ja¬1Ëû^Ù&•&ÅPü2 rwÈ¥®¾Ɖô¤rбãØò3p¦ózQÏk„Pnõåý&º±Ìž6|IÙLìc›|ýŠé´KÚ—RîR@É pßzãRh?ø ÉaŠ\Mê…[#(¹©4t ›°Æï/…påNº|£ÌÎ휘ÍG÷ÆQ@­B¢z»_æÖ#OALåÉ-2I½ú`|& †…+ó oæ¡;Bx§×ù§ZáWBS&Ķ©nÏöSW˜:ùq~ÍM D9ºÐàOh÷¥k@~2äu²Æ‰eÉñØð¦¤—ºk/ «B|rpFQJ1~8›DM‰w²¿+a‡Nž"ÐýÊŠ\ÁŽºê3 'Uå#°¦:Ñ™j^¡t'ÒN¿¡£F˜‹è*‚=t5L£ saSëEAD[î‰aüÈŠJÚωHW½]ì $poÍô*_Ðò1kvH ¹ÔÜ®ÃÜÁ×f0x™ó£úNÂ{3ðƒ—ÎFÞ‡½¬…œÜ® 6±u4 ÓÙRèdÜ¢EâqçWRWõNF 6SŸ¼^¬Èø•€ 9ã¼"IZöäêäÙÝÌ!¢÷ž§0TM0y®_›üY D»nxÁì|†°rͪÃ×g0ä 7 eÊq6ÏR™5C0 Qf2Å0¡ÏYÖ•ಒ ‹¾°ÓÙìHPÇØÝÍàªfÅ ÓvóÁ¸·dye'vçP#C'¦¤PŽñº«j (/Ÿ)Ê è˜A×7Bp ¬ p õd-¥–ÁÊ ŒwN¿ÉeõDÊëlðOßì#‰GY"‘þßR¸Yˆ¼ÙCBŸLñàĹ·€¼èRÎ6›%ɸ| «ÊŒ éPÒ9^­­2u@äJ•±`+`É`ôè§¶»ð,°ÏúW£ä]É 6v²¼ "žãâ]·f²#èàz©1k˜²Q Eû¶æ²•Ç<‰—ŠDJàÌvŽP „O²r¡\›+¨Ìø\O”e& 9FTE°;³y¶0&¶.è=Zcæl¨ÄЖÉÂþÎ|:7pp]5XÔ´{sžóÍl\-áêôœ„]Œ)Í ý•öœ…sÙt–«<Ò­_ª–/’U½Ò’Ýî$Ww„“Ÿ\`ÖÁv©Eo™Ç¨ˆ ü‰(&üS"VDÓM¸mÜr¯DqÓÛEÆÛÚ;ðþ´X 8ýÝw}5Ô¿u1Ôg$YÙ‘©‚,0Ü>äû„yy_¼À­?±ë@Áë³3tÿÐ(¢ó†R\VÚ•¿K’òËÔ½Þtµ~)¥àÑ;qD"$(ªÒ~-ñ¼ø”RƒU³·ë.ñÜØ¾Q)u¤â ¶vj$6\TqttîÕ+”Hº7@ÔÕèTûs8Ø]uXXks¨µ„´´ãâÝþ¢Ý5‰ýP¥Ä1’bÕ[«Ìƒ‚ÕµÎå)žt·)ŽðžPÜzÙI qÞÁæ‚õÄ\€ñ˜S]y¢8z·ŽZ sÇGç#£ àŠîÔˆ ׂ2p'2š]Ç„×cí[ÜÙn﨩O ›;j{FבG}¤C‰›F<†·ˆLT¹}'Í­âÎh 3dN}ë‹•fšï÷®—Њð} )K\¤°ñýôÎEÐ Ц\©µ¾¦[7×À“ùì"fjƒ6`T1mî:P\šFzPÍ ØÒµ»0àGšµŒä\Žú¾µÁ¹¼?åÕÑҺ筲¾rÖ>ü„èO¡õ–׉NŒ}7‘¼M/ά*¼²`’ÒJÁ‡WZâ_?U`ÅX­˜V§­Ñ…õü"5Ä^&P÷øµ1ø·‰iõ~­1Ý‚S ˜ôLrºvHT¥!ø„4¶qµw¾‹*¹aGû’µ··‘€|¬®©ÃÑ„qæâWƒf:‘5eКjëj‘ÙVdáB·ånø^õi/Õè÷Bš•Y]ü‚I•ÎÖ¬€8‹L›z—/ѽXdÌÏQìðÏNÕ¿óü‘*Yr—oÈU(÷ùwQLK’ábÄJÖ÷ ;$¹JôñE’EŒyDÍP(;*=ðã£òßhhÌ €ÀÙ2Î@’‹Ãøx8ŸFEEá¡{uñ­·&?$cÎ+F«F’´Ð>†J]yÖÇÐS µÒ„dœtjŒŒóíîbx›g ÅJv×*q(:”ÄvØ{ -³yE½OŸyöÍÑg´·T‚Éf¥URÝô•rŠ­%ü%Ðé¨n`^ö…‹ãÒz Ï‘w¥ß¶óÆÞUg¤‚nì5­SÊ´«(¶G2u®…=ÑêÔDé·ó¥l£ÆT÷Â6<ÿg=3’ghwÇ9VT°„s”EÊ-fŒ˜¼ wiG¨¬bIz %EñRáÐO¡L†³nÒNÒ}óv‹° â)·zJ†trÇËæñ|3YœŽ”ö^êªéSÍP*²ÍÚû*]¯RËh¼'m¶á…ýuÛ™ª‰0. M³ú7H2ÙSü`›ŽÏ-i¢tÎNù×*·ö$Ûu“Í;­OÞR0 í䧉nõ¹.¬HvxÖ~‡Ž®­ê]Ð#]%6â¢þ—&´iBj·ŠH¶EBr® #êeÈ9j–ôV÷mª±ò3ßJ8¥PÈ8ö›ÑFŠzÛ”“>tIoª«ù¬ÐZÏ:dh™¡ÃäãÛ¥Õ‹¨B(­´­ nƒê²I1­æ›úžÎú3#© ¦ ƒ/<¨Ïý=ž÷ÒœDP$5E‡:­N¦™p`*t6ÜÍ)ÛwbXÚó'þô[Iö>°Gæ“\<©š:ý¨/O‡{WrÏÆ efÛ;ÀA[?¦¨CW¡È0?=­©qCŒg³p‘T»íöǦî†nõ¼zåÁA‹ö§èä¶2ÄW-HÝ`Ôt*µÐªû‹°tAéÒOø9çaèÇ*"- ­óÂ<Õ®vMÅ"70P(XÙO´PâÆ}§Fã‚:ÈC"}ä9¼ìJpý‹v6¹·ÂQÃöˆ_Aº1Q}_¶ü¬ìÄ`‹'u¯x-×áÆ‘Ê®,Û‰¶ªéæ<¹ÖŸ`P½âqÿ¥¹ IÝãјã+µÝ ¿¤Ò5ò/ÞmŒ ®®t~pÇî²äIÅø pBãœÿɼ†½Ã½ÝÉj"‡sÈ(Í©°iˆ¬òWç£~¸9×ýÑ*;!5iÞ®4s˜‰¥(ì&²åvµ#ÝÛ )³òÃ:£×&¤¸¼fYn±b¾<ô\¼› Ì‚jÑ”ˆèµ< Á©RJp‘‹ÊFmš¼úÛÊöÂù埻ë•É9Rõéµ2긾P'P™~Ì V‹znç¸hD–sî"ÏRZ¬ N/ vÙîç¾[ÿÈ qeÇvª¶¼œ'±Š9E}NºÃ¬D°¬Ö0¥}À©Š+<€¡—ä\Î \J׿¬_v»—Lö“p ²@¹è53§·0 ñd T9á’ÒN6^¢r)*âYÚÒjQ'ø~ž(ñGW×PµØ›1ÈÕH†Þñ%¬µkšøöÝÅõÉ8%k£imüp/{w%Áå¡2òg 3ÚêÈóGBõx€iRLÿÙ¡1«4>Ó%LíÝÌæ3O™Å!Â:‡€¬r°ÁZ–²€a(XÞŸÁbza R›¸òmë¨øõ½oT fX¬i$öÉù Ïë={ÏM<³Üó|R‹¾VrË~cš#ÁUõ¯Óo¤ ÐÑC ä-Ó|‰÷cUÆ•âÕÓ²ç6ú;¥'<  åû£Åø¥ …i“ éÕIfn£€ø…Bÿ} 7 ™ÿâ=ì=+#V¥ÅñL®=MˆzÖõ;M' \ìC3|«hÜ辞ï)î㓽Ø>“%csA·“¶ç,=v_ø$8- SÑiw7މˆß¬{²j k¥"%=+hé:à ;1ÞÓèüÉš²ZÈa€Ä‹Ñ6AåÄãy£ØYIÈ f‘kxßd*ýiåD’r€”˜®2ˆè‚µ¢ÛJ¾ÓcŽEÌvJMn÷¤׫TÞ†)ôõÓ G-¶C¶V>m—AF=Å.ÿæW fÓxêi¤*ƒýF[¯uW^|™kŸÖ7µù=óU£ªd’’4çÒéïj<|Ž¢ððÆŸdîWX%Iy¤È âb?>µÖÛä&³±ìœk}kÀƒþq#¯²ÏÕÞÁó·ÈD£#þóJ¹èÙô‹nt¢é>Ÿ¤K!Ëf#èÁPVY9Øòí®›0ŸÀ9éüékFÀæóÒ)M£‹'£GD"qYÆè³Ö«|E©y5~Ólg$Òûƒ]Oqä¼­<ñ|ç8ƒMGV¼%Ë Å–Ä^-9>³ÿØ‚|͉Å1ÅF·ì~#߬íí_Bf[†©ñd»ñ%ÏVêB2^ÏK' x$u×óé[lË8Áî[t5YÓÆVñæ‰,~¸J}ÞÒ¹Ùó(ψ/ƒ=Ïñ§†tÈ캎ê‚ÑkYrT’N(·Îv«¤çßÅ ÀYe˃üµÈjp¦ïàPö7‰öæNö(@ùžŠÄÐKºÏ— °@Èq·ÉÒ-uZ*Nsýœ$@Ý`[ ×’Æ 8T§çR’2!Ë%_=¾Îð±ºz_ˆ¥ñø•Õ†i%Ò?¾€aeò8n£‡(¡±HÅ7È'È{`×$s¦‡,Á£šà¾0Y”v쳜—XF-E™b—4á0C^´ ˜›uf:óé8Zœ~ˆ*ämœ‘ßI vñˆi*]õߌ²Ÿ;NŸ ‡^!Ûç§±î(vi­åE{¼Ë¦ ¤aešÛt·nà¬ìÝUZyƒ ÎÜnê?³‰P£ym¤éýŽÂ)Û#ƒö™,½€y¨3؇=Ëño÷Åò ì—ÔÃCâ†)×ÉâCr%j¸$~;YÔË]©c¨yÍ– ”u½Ïâ­t#àCðQÚíãç‰-3Iàv6V"öÑÅ&arÚ”ûcÞµrè4<Õã ÐÐ~Íõ?]mÇ0†ÓwIIª²á÷K£¬Da$.VžõÀ&©kH#þ×ðM[~ÕÜ'úV:æo‹ „óp+Ãþ*µKØp€ 4jån÷ºÚÅäi?ÚNo³4Rm§-Pô"ñÀ&0Ó7LQõw1ôhêPàjYjžT·t®gúå'Ø—&¸[’žnW/±w?k§”"UP(z¨%¤"iܳ¥óù§§ÌA¢±4t¶ïô^(wCöœN®|ÖÔ´Þ’¿‘}qL·}Ψˆá]`\ AɯOÈŠP'××n­Dªû‰ûú“öØ›cÞnKklŽÝ|‹G:\‘&n~í¾Ü–vÖ-jÙBÉüxhŽ\¶!TÏÕšrŠËÃÃ]¢|ŽE°šaè„pËÞ¨G˜BÁeßôK¥\ï2ƒ "Øî+];Ìž[¼o< dœÿEÍLò—{„“ñ)Úãa¬-êð k`®ÝÖÒÙ Šž¬>‡€¢µˆ›nd!¦Ž€âà`lõñ‡bn'øüÝ›M¼ë>v¦r™)º^,aâºITLf&ßtâÌ^ a~%}H…èÅní.|ö”mR!ën· ëˆ–¥ùŒÒ›„nƒ?Ÿºâ÷'°8Og<Ân›¶ríi’‚ëŸ,çm‡¬€žË/üUßn‚¸ äM³›y½ š/›Ã-al%]¾I`Ê÷¤ûY®0€/™Ô(ës@x‡Ûñ¢ûôš‚HC…Ù•À´©îxnø?øo1«!ãebq{×þLÊ&ÓÝ0åÁJ]½£ `ÓdøÅ-3/ÔôÆ:õwâ;ˆƒ|7ç<7ñs¼+¶Ç@F/0 ½ ¹àmXûlâŽûý²«ˆÜXß{‡Ñ8G(Yzå^ìD¿Ù= B¾ ×x[b±&Õ85$žoXpØ®¿ÛÃØ³n xÿÖ¨¶ŽhJŠÍC )—¹eµP.Õ3òòI™²5Ñc¶Ø,rØj·»öØÑô”©éM¦ô+‘^¹"ùëÔÂJÅWùvW «²gÅ`ÊÊœŽË™‹Þ$¥* ìïðKZ|×´¡’úAÈÌiÖžàßé|Ês~S¼¹•‡}uµ¼<þß´9|TV›J(‡[úÔ§@V¸%÷ÞébU‚ßìÁc³©³Ü IíY‡‚”È ÖKÑß²~2]19TóŒñH»þÞˆê »àµ\42zK¾þ”–ñÿæ^'¦f+›îÙ[0Š .‚®¶—µ ùÑGFÿM{»‘ƈ(±q…;2Ô ÅôjËø+T Ì©¹ûÒa«û6¼"*¶i¥û~~¸õ,üAvñ¼{g+M:øq¾níÓ‚Œ)õ‡˜dÞŒÑÄ©K½†ÌÞ‹$"§o£þÒ²þëí²o•áÊÅ—º>:ÝÛXZ€!{*ì¡q`IuÄ5µ¨^³0“Ô´œ‡ABš®Û4ëo5·˜ÃõQë§@Bm`:¼\Q)šð_‘ž“F¦mRFËúߌ)¢ôJúçáñ¼wén%®® Ö®îN।ԀÌ)DNbÑ|žݦ¦I€¦Í]©ë_¯ÛÍhÀ–n¶áî#Ÿ×a„1Œ1ŠòÒaáXÔº€Éè÷­¯— p&AÒBèFÿF8=%rE°¨¦ŸœP¹²¬ØC0.ùGŽ¢YG„#ržžôZ¹ÖM˜Õ „®¹q©6Äîº>ö¿¶ª¤„¸‘b™ۨjaI!kKkÂ¢ÈæéYIJJFF5™{ ׎Y$Ø ã2Y!Þ«`1¡•…'¼Uk½ZI8t<Èl >šÒ´"¶ð94J ¢ÏÇ\êë8[[ÌêRà¡Ó¶gæÂ”Ô æ:ÐEBÕDø}NOÜ(€Ã‹Š×‹ye´¾ÛNÄ9´–íÓiÁ’Äs‡ØTÊÌgGœÔ ‚Õít´g³ßw_€@º³‚§Ý£AÜùöÆýðÄUûVaN ¬ŸÀ6ÓÎ_®8Õòš¸GRÅr f¥â7å>t+£¸n6ô=äÞÿ"÷¤™ûF^œò&éÑÆ8ãš+ŒX¶q¾ÿ_¦L)"IBè~8c@ÊE7z®>-Y¥!%kîì¿PpÎèå\y`JFLœÐ& 5_ûzèwÓä?ÙÓ‰†Ù&émk1îfL(¿P`p˜óÅCÔÖ¥ žk)á'äU7r#µ«õ{”píjõVð™Iõ“åP›egjWå”+:âè?PG»o–Ã*Á…ÞW¥z;â±Ùw§¬cÑZšd=·e”Âîª~º‡Ì¸;¤…½ŒP«ÞAõA¿(¶É++s'e6£¡òŽ^¦õñ;¢}Í/Hb8fÎ…•ݵ *å&þˆNœ‰2ž¬ lTUôM^·¸mˆ‘ħ¦¬[ú ]øy)=|tºD#©)‚þö÷a÷1)urj€0ð`îs —ðW ÀN÷«ÉgzÓqë¬mh_¼ÕtYAÓ#ND$wô^øKkñz‚$§ïÂx $´ÃîÒÔç#…-q5jûÿ¶7냳üKÚE—•óÞšC¥ak/¥¾C¤Æ“òóf|&_ Å&‰¸¿–Öîà üá¬BdÒe6x$ äa$7Ø$>þr2%XA¬N3P#³—½aÂãy’×'$$–³?DŒžE¾8m~=º3&§]†E„é ’åJÁ.P˜ài­Œgš¬’2ë#Ñ©‚œt†3»)ŸvS>¯ Õz’[ á`K¾h—Ù6ºžß&2YÍ0©z7}x»Â…°CÀÓ_tx£à""ÐpãÝòVzdè›xñ)HØ}!‹7˜## ¿Â»~ ‘A/ðËo ‘Åk·¿ÞB…»àÕ®¾š·ýF4¿eL+ywËUoLA•@°H–sa¢^âk$"éô½ãÀÖ»àŸÃUÏsÍàçÿ ƒ¶YÞÔBÙ©1ŒÀǧ´}ô<ꇗûmG0*¹)¬Ö.°ÿhØ 0ŽlgþÍíÉ" Ê϶ ò±×âÿn^fk¼î8õ¨IB'AM²¤Ö é'ELøuUB©,µe÷ )ýjDLoñð¬DµÆ¤<¬záð¹÷â|êšÃÃÃÌ„Vš?éçy-Ò[ÏæÀ(†eŽ¿Ç8¨€M‡ºŒe1@Û¡]0oóÍ`q1ÕéךÍÖg½(íFÆä¶mmž5È?(3ð;ëK L)H[ëb¯ëóÒZ²è£z|$ÌŒ"ꉊ.Ár›¯V·~8øßœÛügµ^ LÌ©r„àÐîhSöZzŠE~{ð­n_÷uT€)œ*o5{Rë |2ã–ûlâ+6®–æÂõu5˜8PTHÇC2©½…_$;ÉŒe…Åi0ê8j&VÝ‘ï>ò®æŽh¡à ÉÓ®Lõý懲Ë5ÒÔëp{1Ky±€­¥£Á.œr!†@Fì#=ZIÄ×9cŠ"3 Xo±Ãq`Nùé¹í€3Àiû £] u$H%!×%è8²Ôz>° !l™²g,l”>$®Â^1O΂Lé>¾˜‡mn"êV®·ÝƒÐÜbœNÏê€`UO²¡ÂjĬÅök]i\ вôkeáÐæeš{qß ›fS;7·r6e§"1÷kÑ ÜëÒ¯[¡Êèá£ñÑÁÑ´Ë’oY¼¿ä0@/¶µ²5q¡»„qCÙWJtÿV¨Cã%‡'h˜¦4?ôôÄ» œ+»+xuó¨lá~ƒécô5`@¡#u,F4ËaÔ"ÒtZBP¶!C ³CÒÍf7Aƒ­BR˜Ñ_Õ±½B4C_õµ×¹(ËñOVt Š©rQ`× V쨢mTâÎ>J(¢ª L»v­Y°Â~« Œh$õ½wO_êghKÔûÜŽRñCSn±“ÿ À—W˘ü½R[iQдtbôyºˆ½nÞ8>ñ‰`´ö]æ¯ÃªFP°’Q¤WÙ€ð0¯Ÿ˜ š|]kWžÎÅ©À%`ͤ6™èïŸâ“Ñÿ5[AÖl«Т¿Û÷¿Z˜o1pˬ‰¼sõ½«¯úlªŒþ¼Ô+»™ž5?<òÍ|óÃ…å)ŠÊøàÀ¸×;µ—”¾4ÌØØV¨iÛÈ8.QhÞÇL`¿Fùÿ*˜‡¡¶8µÆeücZÖWTL.müœžËf& éÔgV>ó@S¾lmÛûE±÷·Ñ1²ýè!ƒ’Dj”¼ ¦r/9ìyU‰E[Vè Pì×R”Ò™7kmdœ>3¬%6ŒR¼~b5iPòAUP%£@òÕQá+£‰á…¬Ïµ`Æ[\ì“qìRY:÷O])T8‡ÚÔ;Ð}¥.Ì-Ý~ÁòÀV/hSBx‹¡§E@Ä–VˆËJâT—ÓP»¶æèäš»—¶³ÅÙ‚ƒضÞ´¬Ž³Ù¹ÝálÛ¡E÷E‡‹¥)Ø#¹Ëâ.ϰòpÅ3<œŽÚ8–^½¦ÀRþ©Î˜¨yZ^èŒßcìòù<¿’ƒÁ7Íë‚ãÙØüŒnã?µÜ»Ï}y½‚)½.ö!®ÅœlÉT¥é„îñ­JBþÙÈ6'Wº ì Ààp~2võØÌÄö¨þ€MõÒEøÁBé|yŸ8WÿZ‚À‡bóna¦Mò>,U™¹¹s³×¿[¡Ï ™ô(á»Qô±ÅÆN†›,¢ç›híœ,àM;býx4M=‹$äZá‰2#Ù5žŠ1ȤGí_ˆõ=V1+xY÷ê­PhÙϯm9“¦×`zq‰’}EùÑìÄ`½êˆÃñq¡£Æ/ V @îëÜ *Š%mföñ¢Zdè0J½,6ÌÀ>Ïq”Ë%fFXµ…ú‘r[K.A|ߺ:\Ö)DÎ’7\|©"Òéu5Ï ““›~²K­‡$¥ËB*MMrú)1žð·yžZÕÚ†A²}Á´¶-4(æìîYÛ4ãfR̰Þ}MCš²Œ(€@aUîu|ÆËd=<ú¦Ù×Nß3›¨¥o{Ôi†òOò5Ybêº2T™N²'Iw®¨‰Ý?5ù”ìÇÜà]"ŸóO˜D Ÿ‚P\ý4¨¼pHP®‡êî’²/cÙEšŒo½‡–ö;,ÙÓwªF¾pBæ‰ m¹÷]þjs^¦ÿÝË!´!‚¸ï ¢†Z—ü¯.Z ¥RQÓ÷Oq('2w°øMãò±Ü*‰'mU QûšûŠaRA¼ülE^¿óeú òRP†Ò +÷ãÛeÞC†P9Ž€æ”P­CN¼ñìØ%ÏÝòÈž`Q[.C™ª¸7+HŽw‡…é¥7;„C¿ J2ÁUÁ·(R«v_Œ¸•ݜ§½]o0~Ó1…,Î×ü™¨CÍ~=©ÑÍ[Sù±`=ú…z|5ÉúXrUøÿmEoC˜D˜ >ó3ö£&…c…ç®Dê/œ¿;¦Ý7é¬ís“d/éŒA P=wGD¢¼ù˜7Ú×½îw¯˜0åðbâëxMT^ ø±lÄ‚WîÄk[ÕŸ¤¶“¹åE[™¦×ݺ»ö56q*¢'Ì!|ˆnû“9ØÅ¦„`©vºµ’ J€Ã2¹3›XHîR™u¬?µBeñð|餒`,î@wKe€+‡`ê!$§O‡ýöMH‹0g3-ûµûú½(ìjt W×Ö² /Õ¿ùö×l€Ùžt ¥ÚB×»Œ&ÕM‰Ö Ó貺ìyYæ É=cÔ˜׫1”¡ðå”[v14P4áˆÖ!ˆ*íÖaÆ ´7Up‡8bê›–Æ“½XÊÿV¸Wz±û^þ^:~›ý"¥Œ©ÅÝôcе<^ŠÅ¬ñ…ËItáÄøÑàÏœ?HJ¨Î1݇¬‰z±¸z[6 ®\¬˜Ûϸ«/™‘oÎF“ª²í‰jSál亅º3L.9eØZ%(ËÔJ]’HšؼÖ7ä ž®6jCPÎ>ü£]YâëÎÇ·HhØSz#Üw0£¿Aå¬Ét Ó¹*,¿rSW•®âùÄeÀµd6” ­Òæð–&Ri³}¼{QÚµ"ÆØÕ¢¦ìcåÑ@kxÒ¸.C­&bËvüà]§HÛ½…£Ï£‚4ÉUM‰|·Ò¢òï IÏïÇ'CÁÐ#]Aæ ï=xàAGÃÛc XÀqQ“QÈuðåEšÝ`U)q¬·^­"‹”Sô< pâ™ÓëÛHC ÑC ƒ§:Zˆ©å`TÆÛ0OwÊ×üä}á)NÇ«ÔSåÿU8ž3 A¹Ù—-§ˆ._ò!Ô—tš/W²vÚנíIž(âÂfM²]°ÞJ˜%7Éy}1žt¿úIÝôâØHÔ¹ä‘:ICkT?1ŸçE­Ð :Ù ˜š£ó`b¦˜¬{ë´G?°9W`.ý9 ú«þYp°‘*Ýí²ºìÜø ƒ¾Hø ÅêóeßN¹ËÅq^ö4aÑËðCO¾ÉÂS±Í(²/ΜÓ3G‘jt¨ŠýdÈl·qD"åu "ŽhŒ§sÚêµ!Ðè1/Å««¿Œskçã’G8ÍWC ïÔ—Ê©cZI(oGD¶ø7õ)6K{§…äBñü)qHñG¦¼v€»g³ÞyMÄ»ƒ’á&ì×y‚d¿ƒý±aŒÙõÕØýtØNCb ?Â}³”Že²þ' ñ¦°¨îÖ#¯>£Ö@̶§±&§G¼N;^¹6¡_Χ³ù·õu† ÀKõüaF-õV­+±mÓ~e•„'«1¬ž®F›œØÿ5Q*¤´fQ{3djEN·ùÅüÞºêH;ÇG´nѽþ•Å*º„]dlü£¨ªþÛM0k"~/sPŒL1Ó`ú„97jŒ<ý«ÃŹºÎ‚ÑÅ 8…GÃÙõ•z“³ œ’Bˆ‘¨£´ì¹š\ÏÎaÛ¿Bx$BoŒI·«‰°v%fHØE†ôèòÉœ-ŸóÂ=èˆIÿh S &kõ‹©½—"Ú‘§¹½!Ûá àiŽ@‡´üQѧ“•ÑmÌß`ÞŸ%yˆðÞúab8¿xÁºäÊüAæãPXž!œ,æ©öa[ãú—rš<Ðÿœ’(mökû­EŸÂ¼Ùý䃨ßÓèE&¡²i9æ¹FÏ~b ¿r 0ÒÄ{&ü¶/=4>W”Ü‘eôqwúÛª"ÊØ=vƒ;+è-8S4÷‚P†õ\®Á(ƒ}SbBë$#M¾ÍCJÀH`daoÏvKŸŸM—¬n*‰ÇÓþBº¹»û­nÊ1:mefçîmfóoñ Ù3£´àÞ¨lË ðøç÷eÀ9a-ÆS~f÷ ;©RÇɯGYš@]&¢M†0º‚½¬)›¤ÜIæP38ê”`’b*y †ãhJEã£óäß ëŒP5[£^á.lŒì°^޳jÅÌe úåÅx¾Q<žþ -ךîphš&òaF4J±É8„ŸX ­CÜóëÐKö|‚1Š‚#xDÝ@ZøºÂQ%¿õZ7…±¨ó\RÒ¸ˆÏžˆÌ1ÔAÊ|Ð÷;üËc WŒßè½"mI„ È3Ç[àþÚQFË‹xJgï¿qµß‡ˆrÄ$ÞýQðyšŠ]–Nw»4ý¬kÏ}s‚h¿¿s=-ÒÔ DO3t9s£'©âß:”2‡Ma!)Xàp|ë+Ç(¶ÓíØHŽD¡©~êÃkಙÇòñÝ#¡­ŸœA «$Á¨^”%sRªýL_@™\âí\Í<íZ ˆ¬Ë)ª 'd® >.bá¼sm¦´ƒD-\²T}úçoï™'©;2"h0Ũ65…k@¶ƒBatSóNÔfÔË–µý x È­S³vcH ²1 A¶¡dPK¤ïf¦A|ŒŸ;×î»K_V€w«° 0¦Ç1uÙÀ!¸§£Ô“ƒ³ÄËr¹½e€Ã$x=XUÁ­Ýs¡Ÿ˜Ú/ÞôMÁ$Ð Z# fØ}LîcÚŸq/=Î~V±ùä­jY Ûm‡næ-5ª^Ø»sZì(ql”2ÚÙD†fÎNšaP'å«TXÈö`0}”gJZÐíÈʤ¹— ?§äÓë ^"âãïÈ®Ûè¥ËFž`X›òþØåéX^,DmÄÚèª7ÛàLÔ¤7"#ç‚`¶)Ö‚Ý’bð-É¿>3 (£V~®–œ†Àæ%j ‘‹úõ}Ÿù[DSí)§ÎÞÅæœ)[Ø*ØÙ3F:{^×½Ÿ³® =BÖúªš÷*Ü>¸1df&zÉù¡ÀØ5,\ Zåp éhÜ{ðJaàüõx¡½äΕ!±îL Ÿû°˜”­Åù…5 j·X,ˆÚ\[ëY`£{ýè^íª©¹áÝŸẄ.º˜wbÀ#ÔÈ· êäÉ_â°RÜ$,tÈÏ<Ùÿ H$óO[D™^Ûö@],×DÆáýWÆÅL|rèáºCÅ—O§²¸ê¼9 ¦ÌXÖ¬dLà/®sC„³ØÜD×ôbïçÓ£+”.M¿Z¡ÿ5 ÝàЄ*h6Mí_e6èšÄФ¯æJâÃA%$No;›YÜJÎÈN:Õ®êµpØ C)ýÊ•ö×ÌÉ],p¯i%}ïÉA5B`ŸPxDÀfyÊxᯄ¢SÔÀ)(¦ã$6#Òª%ÔáDÅïm4 ˜#»9E”öYÂt—FŸk ÇʹãH>/<]}„Ç·àB»‹ý2Y1]<¿öåš&T f׿¶„k¢“ÓÐÍtÓ/þ¯1u„ÆVǬ0^E;µ-fÄ1fiòr>Íò_Â%Kz„Ík•ɘ=ÉéþBŸe?‡y^H3QHÁŒâʧuÖít3‡iÞ Aytã†a¾ârSØßýÅ m?ÃiOñ@tt4‡ø÷T9;²MóHç=±*.jÜüœÐ‘Mêãǵ*‚c€1û$âær“”%ÎÚº·ÊÚºÖ¶åàžì“¹ŸºOÈö, èæ§äÅ›1f¡ ^ÅÔ×Ç“O&åÛþ0zA]D *«mdž19o¿Ó’kcN[+q O*ª3(¯&óõ/K’÷îú^Eªm.AYÉ1þÈr­)âg“uj1¿ZFx¼{[à ½A}U³/éQ©i‘CIG àfÃx\ºØæ>Íá«\µã^ÁKÿŸœÅÑý} ø}¼’Vø§ËÙ‹ŸëGì/1®¢?爥6EÔÁ÷~`ÏsI™í‚Í4rßúÛhX³;eâàS–BÉ`±×>#+݇œÐä;ÿa?ŠüF"b%öã$[ÏÙ¢v¬‘31?ñ£ëÒq^ç&¶uѰ°s«VfÿP²·ˆÑZ>‰FòuçeŸ|É»Ïulפèã™™Œ»U¨gvû2bÞeõ‹›^`!{H¹”¿“‚¥Š§±·Ýû|†o_VêyxÎY4†<œžHÖ†ïÊÅz†\¸¯\ç~‰K™ì|¦iƒ]QO±å üßyÉoçv°5WB˘Ûëh‡ü¤«Udš¸ÞtRÖ¥@6ÌC´FSêm*ò¾¬$,v±vÿA6^Ø+Ķń¦îçf›íPÒ „Š®ˆLøJTÇ~Á ¶¬R¥sêïÛ1k謼ûYÚÆk5weòy-Ÿ _`¿‡qŸâT4ã¶ÿ¥·E ?ÁcóLKn6ÙÑ]Yõ1ÂßRiTï‡îwÃb¬C&µ¦½&@«„‰ÊþúЉ•UüÉ´<¨)õ¢OJÈ\Y’Ò“›la|[¿¤m¸•¥ÂÐí#cÜÕF;ÀŠ¡ufد{:y„󓎦ª×þHÒ1‚”0¨ÏZE})CÁHù–ÎÔMøð “Œv;ª•Ör®d1,¼‹ê—íQ´ðP;æQgêŸv—ÜP®WÍ_¼Ü¦éZ0ªž›êýià AõÇWè·þ”˜ûÜÁ¥g¶ã/ÒŠ²¬©±ê¼7Ih¤c‹fá{-@DÜ8£Tâ[¡¼Ÿ¡"¯(ó4¿ü »­þÞK{‘Q"3¾ÿq=„¨puD}a?kp’ Fý#xÃ3Öøö~…Ú„ñFÜ(¦F+nNÅ ~;n#£ðéˆÖó¸æ ºB(ßbÔÔö ]T<Ì´ÌÆBŽÃ@ÿBQÁåÇ ;‹¾ åÜ‹µ'¶·úN+ð.•þ„‡•AÎX­ZoVÖ}Rz‰‡­.*Ùù¤¿ÜéŽd§!ŸÃsA‹,_3µ¶QIqÇ›eœbÐtI¡Ü— ¯x»€¥õÊS{“*ÔW™¬ÅïCÿš’ÐçCÝ«‘pk®,üiÖú)ľ„PÓ§”¡‡›vÅ¿ÂzðÅeZ[O ÒºÞ0ºåÅ¢¿óï Ýi¢Uæ$á€ÿæ Á»t¼ßlIÚ0PF9b"^¿Õ­3fl®5PÙ©òx:÷Ø:¶ \eÆLÉ&n˜bº¦ E R«8|‡‰è~·Ô`G¬´i2—³ç›4ßWÏÌ.‰ ÞU=ŠàEUyyãQûÛó|}§˜¤Šj!Õ-ÛL °ÙDŸÚè™ÃvtÏþ~FâUjxçTq\¸R¶G­°ˆ9#m*?÷fçÆJÁÚRd•€0æµw¹™KlëÊŒ%ö·Ob¯$o£LŠx*Üí ?—é&!â×ð¾¶Ô¬rx\’²ˆþû4£ üŒC·\ÔY`1ïœuw-wü}ÕZ;yz£³¹4/Lâ7øpäK»vy_ÎD˜:†cž{»\wD¹lè5ÌòvØa`Ùœ =™/®v@¯øîSNþ?§‰…<åÓ®zWdK\agYAÅ=Ô™ƒåú×Ï_,ñs½Ú…Ù:&ª ØGÙÙæ RYEÄS®AÑ6ËƃÁo¸…ÚµuYak¸ƒ'% A[ÉŒìOžï§‘Gƒ¢vÞLé6ã¶Òô²Ö5w—´r}Ž~§Ó äay4‘µÒÒ;×úg2K KÒÁs†4o´a(–æ7¿0Ç?,“óÉÆËé¬ã ŽÊŸ˜í/5ÁgX²qí­6'êD/ÁÜ)  iÏy2£H@åÙÂ¥@ä »7Ѷgä¼ykB›k'“+ó‹‹¡Ó)CŒÙJ"š˜x×+ÀÁc%¯] ðáLǛӯBmH^ ¦û›TÜÝé_]¬™Ïo’²·ŠúÖ¯±&0¢O9D'-¡IÿÄÁù†t£HáÑ«¨ˆú—Mç X¢ÛI“BE•/! §7Ã3‡"+úeYÜI#Ï®Þ_¢·aOBè÷t*äÙ‚Ÿ$AcKË-×㼕Ñ“Ns êâ¦,§Íˆ œ"œQÖÀ›1§ÁY&Ј„Ó-ÈbɈêvK!*2¦R*Ä ò ¡uN¶³z‚[wTËgƒ£eC1‹ù¡PªV¬næR€Q„¸µÍ”~naêQ}”Sæ¿[\qOéj¤ïIÅ÷@äñJS޾\=£n¢œ}NÔÂÜY6ò¡Œc†)aI1_DCE×ë8‚&ħLy°›*T£b„¸ˆ0Àt>•Ñf@Áë0†ÍÇMb$«9øœá"$³Õmï—]YIaè1r¶]¤ÏAv£(¼+³6mM…Ø2Zb%F­sM-rÉÞ²Glü«æÅr|ëM¨}%J.÷ëEœÂ9QŽž3ªÃF ÷)«µ p kº ÷hD‡3Ìü°ÜQ©ÿ:X/EÀÜÚäHˆöúhf;An3½Ø˜ rŽÿ̤Êo*Ò ¾ ˆvþÌþÎCú7bBT·ú¿¤óql^Ú‰ây{ÂJ¹KñŤØd,©#«0ú:«¦“?Hk‚Ôm’íX³Péu€:+ ›ß;ý§“®þUýÂ_^}ÝY¨«[~gß÷Œí¸YZü ào‘ÀRI8H3ÅãÆ³,8¹"ù ~:’ûEÎÎzŸ4pU›zºõÕŽ_XÛÐT[*킊B4m|÷HwÉÑgL:¯ë-Ñʨ+N\4\x=:y{Ïð¦Ñ á¸s Æi6ˆì<{æ¶D*áþ˜k`ô––JdjG£rÐäõõåÔ÷e§’ ­Ä†9Òš IQIO F–>ðm‘£è6Œ GJ!ÀìSl<»ãV™–ÌÚË‚%åìtXÉŽGm¸†gf¿”XÄ'l,STéÞñÖæÖ[Ú¹ÒµŒk~amâ"H¦ÄÙQ7ØC6§ª›h$Ò¶ô‹âkÚ<Áû[Ó cÜtIt/„4Ý @> ±™Xï0|'É™Cû8÷rÊ(ù"©?俣‘“È_Äãbfå–ÒÈqÚ:¯óF¥oW&ûÃñkã8mTH CÔè˜`÷·µ¯ÅhŠî‡ã}FðI|ÑŠCÀIh”s k[‡H1lÆ+‹:˜¼ÛTä»V´!M½ú‚J¨~4_wmíØôFn=Ú{Ÿëì^¥¼AEÎ%¨º(wG,eOã­Ë‚’j0SèÁ2ºÜcð¾ö'ºÁÞÖ6»*žÒ͘–ì¼fH{€XõÑ9£Ü };™iR“¯Oµá?P”b<éí…|CsÆŸŸg©2˯‚ÓˆýTMT©` ‘þ9mäÆ¨Ð¯sÕB@ã“îÚœXUÂù@¥3ösÅ(0[ç¼PF‹µGM¹Ù.}ƒ ñô_—‹êÿ£EêÌ^ÏOP¬ðnb&ÙµN¿5¥ýRyæøØôTC× £û¡Žëñ,ËÛ~‰Ó×eÉ š3ªËrjÞ„„Α¹D*ʈFbã“4AŸöбeV–i™ˆy[}Ä1^ô]ð'ùž³ÆL{ÀÛ5Ås±)šžîxz-_ˆ äOÆ!¼ÇV­š®œxœôŽØÙóÞÃF6®7 _i“[Ãz~þïV&L­Ô*À»‡wïWUU|àar££˜þJ±™Ó>ò˜²›‘ÖçR5&½Au/å~ƒÖÙÎ:*0žƒŠxzÿÝÌ{d²Ò}ìTƒdFN{«…åÀ!kfÓ=ø’c^¶4v¤˜åþ¤¾<Ïl àýÔ+îSÐVã-¥¾]lGÎJÀhóqq¨®ÿüŠHt¬£ÔpiˆQÔ竱‡ÃîÁÓÑ¥‚zS˜Ú‚ Ðï 5À”­q½kÈ[e<6ø Q ;^~è‰Û[×aùAìJOG£Š¨¿ t‚?¿À³/ÆÀâYÈ’ÉŽ®X‘ /|¨þ•ÛêfŽC |²®O7ú›4˜íëD­†èò¢:ˆ$æ1Aqª.|Ö¹ØæI%)o›‘ÿVFÛé·ö±)q:ÃÇ8<“æöÝ1™âäÉâø· êûAÆ~‚ùû÷H~tI)êºÚ«z1ætœ„hêÅKw‹Qƒ‚þE…‘^™^G£R]6,§0S‰÷”9òŒ Àùf:Ác$‡o”o<# -´œz Ú4á#óGPàÈd’ÒEhîSŸ‘g=°,èdUÕw7mçLÅ?è¤ÿ‰>c•Sc[P¡™eBûwø¨Ð²„nìãé¼Á7k¿IE@JúXã8ü_*%ŸSø кiÛgõ‹âÒÞ4Ö»!œÂœïU5~>‡d«!\ÈJ,• ì4{Þ$»½!‘€èzÄô _‘ Y=õ(f-HBoªíR<§.“TBÈÝeÅøê’Áþ4ímY×™_rÌSQK¤tãèu‡é³®øGiÓL«)_Â5¨KåM‘ ”$Œù9ùò2pnTâÕpÈ´ó5{›”–aXéê+Î÷*+ê•S<‚ä,bÆïoÊç›üðÎd”„sM|ÙA¬žñ«H ÑÞ?d²: Áî~Kd£zÚ=ŒÔš–ºª¹ÒkîWÔ*,˜ô¿Ÿè\áZ߇pè>“Eß2Î4âiÈWxp˜:SD„¾pK4‹*¨ä¾ëÑï 콿Npµ]ÌÅå~K:‡v¼Måê¤$‚0#sÀÁ>Ði7¨P-ª¼ûVJ|#|BS\LZ€Ô1^§2xi›ÔfAÿ¿æ75G£ÁÑØ4x²²Æ}^k@UAŒz²u^R7`RHÛgä~x”Ú{`=µ€FOºqPˆˆÂÿ^,pßÔ³¸…k‚‰íúê4/¨æåahX*paá½YÏ,‹¨¶Ï 4Êg|·oçÉyÙîġ€û Ï ð@ÃõŸû¾'Ý|jÆŒïp×½ª_I˪pÏbF‘žà…_>𮊠£&^Ù ó+|¯&Yü8¤ÕIê»ZzþW‡'“NÓHõÛ«©šø{‰„µxÏ0Û£±™÷¨¸Ÿ¿ü*Ñqû.\|ÝÏ¥®¬çø.ù  UÜ¢;“å³YßCú,"]ýç‚à¢]sq¯êüÙ¦•Ÿž‹Ë¦ cå+Þ ÷áaOc½Å OìWÚn‹Ù²ôZ^ ªk#îó{£@ZÈÆØZA—Êü§[œòfJd­šÔèð®Ÿ&R“UÜ Y]…o´OŽ–(ª4­ ßäÔ„ÐyÝ¿¼§†È †o•¡Ôbaœ|Ž~ÕN9q#;~ ¬zB‚/¢T/±šwºS±äd âCDâþdó i:ù蜂úæ¯ýVÄXö#­—ÑD˜LÒKlzkÿ®æuŸR¾sY)±Ôl%áòÐ'ùÿmÚú¬SâY<âI»«‡+† C¬ð„¹)ÁùXžoÞ¢ësæ,ЦIË잯©5s¨"ËWÁoÍwÊÄvÈå(VIºË–m0õ4üƒ~œ.—§ÒbÚï=ïuÁ8ŽÝƒÈ€°N&­1S **ž¤–FV£æûÌ$M?Êô*€€ ãâ÷úWùµgÒÃ÷rEl4dÛ¿Yd:n>Ð}Œ¥Æ2r÷’áTýT¢•3xU›ËtðeÖñF1½æ¢Óûò}X…8¹”±t-1¹XÜÕ´y*Þ_TiöÍr%åÛn'™ n7‚ÁžàBš¡J”ÄfԵלž7¼AŒã•§èQ³ sWӽċJ› v€)õó»÷uÉôóI]ŠŸÊu:Öü#¤ùˆ´/W3PžæE/?Œ§T´Ík®2tû€“‹û'‚EIÇ{ÀŠ À<¸MÌÖ¹É ]g•Cá.ÙôοKQó¸] –ˆæ!/¤ _ÿé”zØzõuE-}”!p}&ÉÏj3Ec»öÌl§ÚZJ¤z ú¨_¾­¢J9ŒWcqL*²ÏÞȲåÑSö]ïúÚI–?xš$Å94¾ŒH5“}ùë3¼³ä”Ô›`ET]Üb§òìè]©'F}‘õ;/ïkƒâ–6¹@Ê¢wïTR¶ÑºlÌzªÿö.&Ú/c-çiR3õê/èâ‹DIPÌc’²ºÅù¡ ‹4kšsŸtëç¾)ÿÀÈ\ÝŒéôéØýë[_ìÛŽÞ0Úo=ÞBÙ”wdŠpFâ_Ú™”ŽoíRO² Cæ’f3ÃádR<ëþé «tDѯèÞëxÞ3[ûó±x TŽÝd Žž3]^º÷KÜâ+ì‡Ýä=,Ðgñí×UÀ(²E[·jÕ•ê'tF0|mªc±XŸ±ôŒÇø2ì.œºŠ~:Srúø+gÏùÿéX¹ñÏ`[:#]ÖoÖ‹ë?¥ë¥Ù\E-ì~T°%¯gÙ± íÝ`e½Y¡FfB97!t0©½~Š nx=9¤³®‡®ê®T„,†€e6ÏÝÝ™ÎØt”ÀÖ`QÏéÕ5ö yº›Pš9íýyjôãÖX®+9hW…”˜•‚”^H‚O•ä¨ÀŒ}Ù;ÌA”3wÁ]ÌÃ4@UØ÷c(€45¡âðÔ¥'°cÉ<÷¼Fð‚¥fhí&ˆ÷8ÄçeÙ´­¬tÚF©ÿ !çBzI?/úüYrsi鶇jÿûÚЂà_›rŽ}ܧãÈcÀŽÎ}“OE8•w}.>žtL™ãnÑÅO$êÅjed*þ\dÑîlÀ\f§ÏÓ_DdÆ-Õ&Ë,ŽèF þ«Û¡×ªûVØgŽ:`¥Ñ:Êì˰sÏøCLÉÉüyœ¨›ŠùkvÀ½¦Ž¤K@s±ùŒ(ƒ£Ü'b ÌC°Sî &_˜ØÅ|¹…Œ§Õ¾ñsñ½YÞ5mØóê¦zп¨ömTIÁ q¶ö i‚Ðy# [FÊ»˜N>¥Â¬’HÏ2‘ñƒUûïü:¸Ìxò0«ˆÍˆÏ±C×ÿ"[Émh˜¦ •FÐ^=¢úŽ~…õe?ð@Ì,FTórÿZâ´’¯øän`i)àíkX-{“.>¨fö'㺓Ñš^Ž?ùÚΩŠù—5 "€&ýT™¸VÜOqÞîý¨1Ë{MoÊpÜãŸÃI Ä ‚p%:âÌœ°^ÿú±ˆÞíXË)"cÙH5Hö‡ä[PqZ7 +Aû±ÐÒ†8A×úçU)Å:Ù_R&ì™S ‰;`VyxýGÙé}í§}úf{«ºë6²kžû¼Dó]†ÿxß6W4Û±k²×‚¹'ïÛÑ$ql²Ê#þt±4„t ¤'å@¿4×!Ï™§ù„¦¸£ë[ÀFòs­º¥D§ŽN«nxœíÉ;iÄ‚;©#Æ`ð¯ÝªhÊciRÛ"l ºv±‹Â8ò`<›¼À|çsÑif&Í« ùã«.­ßÐÞÑS ¦ù7²m·VG”ºIOx0êòCèŠV+GUP\6¾ OÁ ¬uò6co5#*FÉ/>"ïξØH.Tý®øx²¥2¥!òÖ”‘&~Œ%‰TÈ' 6 †ˆÃÅ,¦B¢å¤, –uàƒÂW×ë¶Æ -Ãa^!•ßâŸ1•Ä8ŸS,ìšÆf[C9øL¡Î‚‰¨…H"SLÞl^ œOÕ×TøÿÄ%6tD‚þ®©1.´LVùÃU>ç{~Ž…Óâç¾*,&÷ú˜š¼ù|IJž‚ágx{ðχ %vaAF“[i·<ë!÷r°7Á¦Å²á×4ö'Éô«”25e—s›§ÜÇ—Hq±fÄÀXKÈ~ŒÇì«NWÄJß‹L=Í6ïHuXì¶%nUºODb‡}=È3¦Ø€LŠ<””ûÓ ÒÖZ/yaF‹l¬]m4Qs²Xe [!5ö§OØÛûŸ`’)Žß‡‘¬Ä!|1P/·5vZÝâx¸€&€ÃÏ2œ÷€ˆG襌f‘Õ¹žñQ …ã²ýZC+÷’1ý&SÕoUÇmWÈxo¾ÐÃAïÔ•Ó¦Ee,„½AÏÌ•EÈ‚d|ÍäbÔ¯ôf:ûr,Ð#A*,ƒ¯C5Lê 8£µö»òXU¸q:Œ–[ŒÅŸ‘ †\Á>Ý/÷võ¾]¶^=‹+çR‚‹­ëËioC¥—¦{ÖCü±)õ{Úgì¼Gƒ´m׳™Í<ž¼ð'©í¡™5#ä×ÿöÑ+K¢º&Ð?„ß(³pãC2Ë{‹ ¨{iœ•¾w¯«5þÖðtKäèü¼ ¬¢òû„¹ˆ‡lGnLÕGJÄhù-ô—¼È¾—wM5“Œjø”µ¼tøêóê­ðG£}KÊQÓ>ÒñÁ¶‹.™±š²Œ]aðT ôQLOŠºøKé®<‚©ÁêEÚ{ªŽ“:IÙ‚ƒ·Óõ©“Þ¦còlp1ÔJœï8mÕÖ|`XwI¨—YCRx>^.½|p 1ó î,Ù–øH6^r,)cŽ8é·Ê­°æ4úªÅð<ïù¼›ú!h#(x7=ºâCR<ˆµƒPÿòÖ@}éWEähÝ>>Ž>Ûî»\P,Ò¥ :`ª…n/1ÃØˆ+’þMX̵ƨåthzfë¤vFDz·”] ‘kþ¦Ta¯d0U.üݯ¢ò<¬Èiƒ$@MÙ¦€Ô!ÞßÙ´ÿr¼:NúS¤w›½Š‘ÀZ‰¼5§RÓ[¿÷·–’À"°’’›¸‰ßâm™(8|x®<œ|í_„³ÈI´d¨Àu©`èÉh–ë'ÿ„(°2é½ì·ñ_Pt?kcµäÒ!BO4A¤4%\³*¤#bJf•7yBË"¹ŒµCÉ8ZÞý¸äPsbpÉrû_ö%Vç'M8èŠçBÍb©ü‡RŸEc¨~"´79[þÎ%U8þÓî5pYµYp 7aîq0äG(¯e’(È:8Á!€=ïKTÙýÂMèöGqÀù÷º ²˜@J¬"ÑK(Mä|ÈZj’êú¦¶ˆ¥Gð»§µ™ÐWÑÌ÷¢TXµ4 RI¼v>ä¨ßLÝ©Z“{ˆWN[ŠaŠtÚ"4Ÿ°GŸ`g¯‚´HTJÌñ¹º´a/brQ瀼¢@H ƒŽN*Èa©hv¶öuWDM»R›zBXÀÀ$ã–j(kTP.ÖrµUO“Ö®äü€1ªõCnÖt&ãN…–ß*ŽVI»f§-­Óð"süC^®Ä`hL/¿$KÑݤcZ¤aÞªµ£Rýë\µçª9SÏ.û0Ù 21ªN]{Ïj\ªó u«a­‡Öôy¯û¬ûï¤è3š },¥ùƒH%¾dÿ @ÙvLB1kžÀ6?§Õ˜5ž!kÂùZ9 VÐ?|<5È𨲢…hš‘ˆƒX¦Tìë]Ç3ë†î 3-9 ìùzi‚ã©-ÙeФäë•ÃýnÎ…îXö¶ˆYÀ„fúRl²?ÉØ~E§Œµ¥ ; Œ:hJ 9[@VÝÚǦJ'%@ÿIZŘÔ‚ñY>ƒjŽF‚0«+ h.”‹™ø.+¾v!ÉntùxÒœÁ$øÊ®Ó´‹2GÊÕŽ}ƒÔ÷s'&jd¾aË›Ví@9°Â§P„Å‚ýÅ*ªÂ`0”vX&î_ÿv(y©³NlÅŒxZ{µßSF ¿RMUó€ç vy͵À¢e1Œ™üôtBT›S¤CÊŒCk&±¡zÙèdⵊö%#Ò—%‚-i?±_Ì+Ý=JŒ0½õDh«ÂUÔ3nš4„F™dsÂ…®q™XˆI*NS¸ß¾ÝÄ+GœTc ç.Mû™´Jà5†ÏÁͨAdbÇŸÊ^ü¬Iû'õøb߈é[„¸4ÖlŒüPŸÌ£²ÃΛCÀŠèC ñÍ1N)ݨ®Okî½ÔÈMðd¬ˆ@Ϙ²íY]_Í=šyÁÓÙC³—;Än–Rae¯ “1ÑÉëÂ’€ÝÆh’Çv/Ýëå„,d ã¢]&×m‰{õ«—Òöt6ÑF ‰)±äÿ3]7ºÇ­BÒBÈ Q÷GdQDLÕp™=ûºß^î‰JNMnž?ÎO¢#ã´a7t&dœpÅ_×6µ8aÿ×È’cpŸoõOà`&s+e¼0Y¿Ñ]﬉9Îg†»0+A¾,>áïöX¥mD¦ lÊeƒª¥ÛìrK£bØ.:XÏkÅÞak«f…Öâ„þÒx~Zo+€´ž´¼€.ÚÂz­ÊeÜã‚– A5œ¬¤úö q•níÕèïŒ9ù[¹ÅÒ¦¬€bmJuâøÌÁjh°ý¯'®ŸÒ4ÿoHÈ}µA ”¶´€›7Ö\lŠó)à{atªº½6DZr) »ï©j“ "˜KfÚ¹öŸ7†ÜD«$¶´¶ÚŽ4†î’†ÑÉÁ‚ä,ò,§rK£÷²¦hVÌ”þ7q 3ÇGõ”y {K¦n¶5kj¯3³pq’“îq‘1’³¿r¢h`f'0 xØybz@(«¥´L¾Œù-I†è ¸~s*‘4A NTj·‹¶¬˜·AWÛüw Ž¶Ì³r;'ÒÐe×#%%ÄðYóШŽön`˜`sa2~Ù¡Ô’¯Y/xdŒŒ®øÕ®*—JSƒ‘?V¡²i.T¹CÒä”'¤ñÍÊìðk—X²Ï¤¦ÛѺî> ÚÅ^Óâ¢ICŠUGøjç[ ¦ùE„—ÓòåªÉHŸh}ßVg“&zÔë¾ ©6@1’ õü˜\˜Ä6J›É ò(ÛóuŽ"Plã˜â¨‚cØÍ,¨+F…]?‰›EwÜXÀã“‹Cšìb{”¶•*ÚãÂ9¾þÊ#‡iÔÞúËK×a訋Å5ÎMÒtñt&rŸ2Á_º~»á¸¿´óžÙ>zM÷¡ã·KÇw"zx‰nÓ ÕÛßmÒù: cùñä—$?V¢×W"Õ%"¸ˆüP[Owæï"l– ´öLþ l†ÛÔÝðÆŽt×–ˆJÝ9k£”÷\åÕ»>­ÛQDkÖAì(5³9'¶üA.<Œüa”œßÒc°ã®è@ $¶³Ÿ„š…XX„z~~cÆë»$*»C˜}¬µyHšæŠßöW2ÙA,EóiC‚'œâ´`7Ü΄²L®øÎXZ¿LÜÃkŠ„¿7Ö·]wR0µCQ­ÞAQ¢Á{£iû]™0/û“Œ ‡@<–áÑÅ®?D™r¿l_²˜]8Óº ÆR?›>YÛ5#=0HT†Y3#F½íZ›`P KÆ-H™EÇc\.(CüÝKb´ƒ7ÝÌà£GV? Ùn½/Å\e¬ wA¸$‡¿Ó–²Ysš÷ý´Š2³®çIO*)fßM8ÃͲ–Ðâ(WcjérÒ ÒMãl¡ãõnKõRîN,æéXU›óŸ¸ ò±f@Zzk‡,¿¯Bì©Jî×=ªËG@$à"îª~ Eíñ„óøIµŽ©§›ÑÆÀ!‘Hj2~” éœÚ~½‡À8}ür“µæ·^Fù¹’çäà•¥Kì Þòø ªË‹lÚ6½Š°5 íGmW¶ŸâØ&P,«ë; éâ]FêòÓ-Y¥ª²Önxu.çÀ¬BÈtªÑÈqt¬$"zY_’óÕ Ÿw¡w䀬-Þl^ùk{SHŽM”+Ø¡âL€‹ä(®W^ ï:D¿² q¥VU9v4wu1K‡£Àšm]|ÓLš~ÚµãªËÑå“Yö ß2wXÍTK¤®¦0ª¯V2hVü‰’E8#‹%ðGÄ´²Ÿ.αäåöbÙr¸û4)÷ØNŠô­‚çkeX“ÄÛE»f9<Ôp™ER*ÜQ¬ ÂÚeD÷-F|F,Gv½ˆüÿ|–Æ„E|œ¡ f0ˆ…mnjëî%TYµ^þ…}úªˆáÚ°=ÞîŠ+Á ÊØ*™xòÕ˜^Ÿ~¶c9E½ÕýLóÓ92x„Ô}&ì[F¥¦L¥ÌóMÕ,¾+ŠãŒ½^ð@ÔvôO¿¤R‹.‹ÚKèDšÔwä¡ìÆ{çòðÇ®xç`ëhü¥B¼Ë€4 ^çdÀÓyQDjy1MÍgr+úÍUiÈÙµ÷×)ЄœP2Ÿ?óì7Ì4ØÆ³1«?fžÀU‡’ ÏÊ8ô6L-{ŽŒ†£Z]t&§ÈItÊ&SÚ¦·Ùž©º$ŸoAc¬AÓWÝúþh}‹=¤ #¨é0…P±6>¦Ùë;)Ì» `4ꄎ‚pÐ<›\0ÞàÀ+dÈ?òÊ„²Å?½ßÔÈ ¢ÛJ‘–a½£ç„àØô£…ë±ö­24! îùk­ÔÀTB e{jÕÓçdì ªOŒ!^^VÐs´ ç2jšùÒa€Uø)wù`ÏûHF,•l¢ïZš b!<+çEà[¥*–œ+9ë¤gáÈ¢ŠŒxŠ% ¶ú•*ïè·ÀIÙ¦c™îÙA±Êˆ%ÒWà¢|k©VtSpg Z5[O^vIêgÌéo®&`uGŸµÖS“\"nC?–ûmÑ3;¯óSßÔ†7½÷£ †PÜZD(Ìï] ]b©MÐOš\ûzo½øTWSoõ÷Ø?ÙêµYõnik--—+íLÄ2ôEÂjhò;§2æÀÚÔEõî[*²!úãMÒ!oÃúÿ¶NF}C¤rB˜“^AVå3ßVÀ¸ Ò׈/5y¸õ9 èT²Õ ¹ŒÄût,Àv0ÓùÌÙF²¦å·¡¤¦® †Ùx“f7£ôCKþüH1ƒÒlû„ÁÝH’ÖYK Ã-÷‹Ÿü¹Éy`å ‹Ñë ¼ÿ‚ni/YXrUbÒß0¢xëÔÃjŬ!92ÈL‘”àMêX8so·ÁQ«O_¦Ò%ÒaÓŽ“ ⾈ɞšÈ`Ê«ð¹†.róÑùà7W1ͪxjÄŒíõVI˜bJ„dËÖ Üà‚£ÌtYzõv®@!}ØÂ­Ý y÷‹¹¶ù ™ÃaÃáUÜ«–tíy´•< º* |/Ì(Êž(±^1û \?^ÜbMµK+#¢½Á¸*O7Ù¼1Ȁ¯5p…©P9®Ï†ô—Ö{¬q«ÆîN³äòÍX'Dz®‹Nè€J×tØ÷IéC1ãeÉCYDG­‚½ºB©I(©p|n|J^<<ÁƒÛ;”œ­;ØÏn“$Å–ÔþC¬Š­nóÓ«ž·œ…Mƒ·¾oÝÏÿïŽOé’zÕŒ’_›™3ŒF;&¦ãk¶çÓ¿;ë1êeM3U<3'"õᬰ´PŽ9éÉ4{%_¢å³>³jâP•DÐyEKø1 ÚŸŽ‰BÅŠ×={Ø[„Òafaöf³FMòš° üdG°ò±Dv";ùÔZ!Ö>s¸´&Fv5K@ýƒl9]6©ïE-çð{ºv,,Õ{ãšê؈wâNDþ—‰/BJ6‘Ûàâ^Ú}1¯*& ´m>(À™5þdÞ¥0Ŧ²M|û Ò\Á¿ï¾‚VIÁa9ú ÐÉC­@ËÊ2]AoœipÍ'$cƒx¾/'ÁE³G¯b·“‹€"pk–!W–þð6h7ƒñ Ok¾S€pæb|œÍÿ*}Ht[•¤#èãáS·\··={º’¨i ß]R»0¾øêó$“Ý ^èë¾HOaÇŸëA{/œ®ÁåžÀn´í#Ê¡^-ÐýÑ»ø·$öûõf ø /Ý`u€]ÁêðÕáèëз }Ü0PÛ——ú¸ ¬½™æá86^Zêq[ʬMŸSo‡ü/xî†nCp²$²áú\Ó“ûÔ7EÙ^ØÙ2 U-†±Û w¿y¨Á4ϱÆ/;ФDí~‚ kÆ5ÆŒ2PÀLìã§Ð[µÔïÅT§ƒK:" «HsºîÎîÜ]j%Þ´ €.¼XM>ײÒØ4Žš+‡u"ÂÚû<Íæ· {Zò¬›„ÙÔmè¹—GüìŸð§xA}¹B{ š]ú÷e÷I÷Ï¥@eÛUÃ$íòRÞS°*m©ŽÐô[t‘*‘H‘ußG£§ð ­c=áJlD]H÷[dâ·b, ¢V–À0ÍÄùqS¢¤þA„éì.¬Ý¹ \’XÎl@äóÉ ääÖªj-‰xLáÃ%¼íœ'ÎK£J`Q7­(úõ| ÕPÓf§ Ø‚¶mÇ—<ððwŒwJVÚæå°dŒ=d‹å¬¢oƒ÷nƒíAŸ~Õtø ¼6†ÐÚY´'g¤ëÀ= MLîoÇqAωɛ«Œ†ÝinÀRèêí}GIoáAo5Ðî¨nµzóýLhsÀ{7Cz(ªÆâƒ˜3±ð"°2 |<™’¿s ]Ëç—§KQÛY<ΦÑѪAX NÃyîæghŒ¹óPžqÁ^õçvʧú(¸—ðˆ¸J1­ëéT3ÿ~%NÁXýDèìiôIX6ÒZœªu†#Wf½ vF”qmÅ¥Á—9°;=ÞÚy ý;wâó0ž=φ{+K›>ÛæY+Ãv†%c1Þ¾°Ê*ãG,~’ˆ¤¾«„øUëû)”SÀzL:ʼzXÁYfÂæî©æ óñÅöSù.©«ËéûÆ-䄹{ÝàTHfC|X‹ó&ð@õ»å¾FЬ/úbèHÑd lœCK»°i‹jO¿ê¼Õ\Póˆ”êöš¿qLfp§~1ÀhKœ1¼¼Îë:…Ý»Ìj&] còÛp]Î>Ææß!nÙ´ÑÐäÀ£‘3†7Ÿ,‰Wq €ö…'CKY:8'ï¦]²@¾ì¹rS¥¡#°lhænpjÍvÍ<Î~ˆ^MÓn¥ôĹ)zs~Y.­Ü¼}Up´éœzpäÎôÿâÿ”XùuÚm|¼:ÿ)Ê[ru”ÁwŒõ”Õ¼…8/»M8om0Îcq“˜°.9€bù]:—ìÊ']#™mV”³qÑÞþ‰ïSuL=£•‡q*Z3•CK%^ íœ6;âc‚Ú6Õž „²™­ë®F(ÀÅÃ]":D¥nµg#•¯ö€E§¥Yš÷ÆÙÄ^–?kÝAÅUðóVe’™Ž³ÙÔ3;}/Äbb­•‘\“í«Ÿ®9l%ⲃÐzñÜ—7L¡ ¦Üjl%#“À+‚Yß¿Ê#Ñ/ÇÐ/aåZµÕtXxºR.Õª'¹Aƒ@XíƒH€\ÒlÅvrx|ÿc=V"ØC\›N;¯{n«Ð¶³¥ÖV™ î‰Qq¸ÌõÃÆ`Êd’›cö°‰vë93—ã zS*ƒÝKôcqÿâªÚ¬‹¬ÞýÆûìØØ¤pÝ!e ×­uì§ÒÉÆ‘j©½©jýôSs”ÉÌxýFÍF/êÓ‰ûfýRh~‚®þàÙÉr†9P+j Ô¸ÎÊ}Ñ» Ð’ 6S×eüw×1g¤*bˆ: Pwo3pBä®Ñô‹ oݑثîç·¶œ^a€n œF÷Üĸñ’k}“z+^ÌË£„£M_†âN˜û_5æb‚:†ä¤õx]‰a‰bV—m¤Ä:ÈP/d#]ûšÞ²Íâ;QªçM„öÑï&Œ‹[н£!A›ßT:\º§L{aß^²üÖ‘©%Ù±i•2ˆànÖ%ºÜ5|:¦”È–|¼ 3Rà×Ã;ãÙÑÉ Êùèï§‹R¡†¨w¿»›3MQc’§lŽúèè]dÒ/š §g—f?¹ñ®˜˜UC-¬Šì¢©P8É:’î²(zg+*×e‚GUYYÒÆ\c†£ °}~˜Îme·R•Evé…¾—‡›“Ò— Ç~ .ùÈpá/ž¯Ô¬u¢.@(}{ŠJ¢q//hQ¤±œD}ØQãµ§SIÆY4n#ߎZ\›|Mn&?róøEàˆÕÐÌT*AJ’?ܨôÕùø°‘´öÒúietè­Å‚QÊ,¦Èhý¹iã.tºüÿÊŠž·½Žó€hRmÉ[—áá(áÓ Î´Ÿ6öõ”¹ž*]*]ïBvWûŒu˜Gv.=~IÕ¹æ¬påq2 ‘—Â÷ß{~Í0‚•+Ô̺ëÖ™dˆLʶZN sÌ’%ï5ØÚGÛ¼Žz4 *šÎ~RÝ#­*ZKz¬EÞ‘#R±>‹,F( ˆ?sðö²EƆ­ù;î[e=“˘6[mî&—tÁ7TXx½€…æõSé¡ܳpMÂc¨á¶Xf§³|¸oíî•ö9%•=-?]¡°þ|Ž˜­Àã¸IþM¢5LLrs¨–8 “¯tkßö< "`·¦™$_Ÿ7À˜P6øß¼*ïo’&ÁÒм[ÁÈ]JŸ¨€WlšT]Ýûåæm03±ýÒHú*g÷Å6ñ¦L ­Úža*s_µƒ¾FNzÜòtéúØ—B9–çAµ Æ ¥Lþÿ¬‘·aäÜ‚Qæ—}UÑ’”¤Ÿõ‹þf4©sï"Õáq#¨]¨ÎDmïþj¢è~Ãkk5KQ¡“9¶ëÙköðjfj¯]á——§G±H±ÒÑQ©èä®­íyU²^”¹G¼×Xæeà‰r‹ž¡x0†@Û<ˆä¼&‡tÏ¥ ÒbaÇ2SkmÉ©AªWÆÍ‰*Éjÿ)u£ËÁb}®ú×u–°IÝÚN‹áޤYS‘f\-=B(=Íeðá‰ÛÇ=Î=Ý yZìŽ9*yâ%`É®úœ,»ØˆÃ&yaÁý ®ïöå|Ÿ°Æg7„›jZklõ`1BkÕ ¼kEEh×õbúª¥Mñ±KwNÂxpª@Éd` gÛ`8 í³˜ë¥ ò„°Xa2ðÑáñt(NG¦||]»ÞúXâhZ Ç©-DÁ×,™+"NÂôX¶û„~3¸˜æOÄcXP§À®î@ÞfÿÒš{bž$ŸáŸwõôåþÂ.'åÖl¦5áu§îTÇõ„ÁÖäå8õúGP;ý8Ý‚²@OníÎöFyù98Ìamþ ¤Q¤°¯h‰|žó¦6£îZ¦Ÿ‹–Ë}Q¶rBYÕ¥è£n9e° 8mM˜Hk~ê™*§ú*³Sv«.‚V4¼Xì‹Ú£ãL8æ‚sÔRóDFjÆüg]õ¼9Ü`ŰWÒ­Ò|A¥À•vPÂ=B€¸þÚ;Ë¥‘ëÕq±µÁq¥É·u®Ç‹½|k²¸´°óâqÚjØKJPi"O¯&‘¤t-ŪÎUgï+ŠT5P½Ëã·D >ÙŽ}qÊ3»l‡*ò/²„?l¼SRìwd’ÿTÉ^”ÐÕ°^·mˆZRÿH—mñÓ±ªøß"{KÆ/Yúud:ཚät&Ñaˆ+.¥ägAÔ‰øãŸ¿J$Æ#Šmè°»hǾñ‡Î…Š›) \õZ»Oüb¦œXwZ§Ù¸ Ä©õéß_þú›Ú?–.¿Ñr7žô©ÁA“Mü7ŒÉ–vw \õg„3(¶„vãVÀ,£Ã“> a€§Ùê–)pÛ˜*Üû:dž|Õ%ðèS(AÄÈ~Rb)ò«¬bfMi¹~cMߦrݸéŽB—VxS…ŠÐCUným ™Ôã¨+y· ìŠÏ¼åз ê¦`!ðÛþÏ –š“°3Nh¾~¯e™Ìßê¢[Ç~¶í4`kaäöN$¥Â»ÁýÃ)~aך„Ì”Al§G‘ÑêƒyI6çg®»;À0=—Œ`Ê ä¹QSlÇ!<Ķ…‘ìøûìz ùW'¿oˆälì¡z?Óˆ?$Gòv,4~uZs¸Iž‚ñ*³àÎÞ³Û,pü ÛðÄË!ˆÑï]Ľ-¾útXØO{B‘S)¼Ï@Ç=ñÚ®v¨BÙѲZ}ó” Ôï˜ø9¯U‘¢ý cL>æ7ÞÛ°Â9rDO «ÑÏô(f}„UÞD¨ü·u½X ÇÁ/ÒŒ¾–ºV{d¶jù͸¡ÑÔ*o¨8:5;á¶Ñ‰ý‚'ºÕâAÞ/Ng¼‰QþÏ33E=ÂP=ë8çö-âB”aø“átb<\äv÷6h™ñž»Õ£‘FŒò(Tjl×SMãp “qÎ@£¾“‡óJf]î.ÍøÛ p~H€> Tu=ò"›¾oæÙ™Ïû\”¹¡]cánoÐ{c€E¹`sÝÀç?¦X5"á×'qYí’žkFï9¢ÝAO³ ‰ àg®… w±F™æÕÉ+™Í_/߇°9Ú¿ûè`ò¿”L2c" aù0Aä\íM%ãSÔŒÑEž_\Íæ«%.#ZGd“Xä;4:k*.oàn¯8KeD!"ÁµWª1@TPQ08dŒ3Z ¤L/í¼è ã q±•Ð: ­»µ¤3$wiò …Z÷Ì¿Vj‘XÞ™þXÄ\ž`5^Ôö®ö_°6n.ï„Ú?ë`\)Ó ý5£¬wì rÉ9d»ŽåéZûJÀ¨¾hÐ+§Ga@.˜~§õ@Zá 4†qýæõÌ,RÌt/ʉZÙB;[ô(]·Ž$gР_û¿`: ¥[‹ˆ˜SŽwˆ°y…W‘õ˜‹¡.Qð'Pîé?ÿ?”½Æ‘y°sònÈ(€uSa}Õ%k°.|;¬ @^nDØfä#·ñAóÕ\ }·JͤhسiÚI·9¯ë†rÑ!{ʨ¶ãé,–úIjâš·ýLvÓ8%#Ð[VÍÓI~dØt½ÆMñhïpW®Œ1ŠK¾G¾„BR¬Ã^y”ʼn§I«¯nµ2@®ßQ®˜¸ zÌ[DwÙðn›'QÒ}œì©_Žðs âÁ§Ø^Ÿõ´,(Æ þUéžÙ0"gœ£ mFc::%÷[@Ú•m ò¡†Ä„@#2Âftg­qŽ@íÆ«Ò6q§ãt"£NŽt§›d‰U°EíÉ•iî÷N½ñ¤3™•;. Ýy©4Ã*ëåJ-¯Æ·áèlв¯Õpñæ8*üAàÝh"dªÓª¹„(^Ptÿã5‡¼L€2ŒÜ”¸,Ý_Ù„÷’ù.4§ãJLøˆæ\o%órIb´“ŸÑ.œÍPæ Bt|À#W•U f;¹/Øo²2&.bFá ù"?áN’wƒUÒ¥¹0²½ÂSülè_J½œd!¤” c¯•W‘ž èýðä]þex9k{¾à÷8Y¨‘º«pÓÔy¥¸=­pÂÚ˜?¤FÿU';[ÇU}ëàûcbp:¹-šÝ°ˆœÈ¦×eUïUoKí¥©Àv\'»šXÇp¸0: èÛ:Ïwãü=ÁY¯ºvv ¬ÅâܦQpnž”™1`mƒxKuÊå ÚhÖ¾I…,iŽ4ÄDÓØ`ä·'únÞE¥¥ð1ñî:0ç·‘Ú{*ô¢Ã}¼ó™F¯¼XåXÉP‹Uv£wa 3N©¶ÓšíQÓtšM#’Ü5ªÔ˜ÆctHÑT»Ï²ÿ×sÈ>ûò\¯÷êýI&)­œãÞÇËšx ©ZßàÜ_m1å´r°ãeôI1'¹–•É7x6Å»E „½”­>³ÔQ@ Q@Yuݺ–›~ÓëGåyB«Ìàcað :’Ä6²¦@ä@yÃÿR´XŽjXû ¦Ðd@ûʽÁhFç00â»Z=n" 2UŒáp³A°e×6Ëó6)0„þ×®¼o, EU‘i”¾ê f«SÃ|ýÂ.¦g?³è˜p»m½1€¤Qb¿·Ä›«ýÉê*º—,£“ßÔ;Lž‡a~¤'xðñ–Ñpó>'$ž!óX—¶‹ïB\VâÝÙÞˆ:xM2t7Ö´ »}$ÊñÓ°¶b/ÄÌiåä?ÙŠ·_aWÆ:…ŸÙšgÄ}ý$sI.í¼€L¿éÕÙ¤ÍâçžçÁÑô,±äàëïŽÙ—¢˜Ø-ØßÜ¡ÕâýŠZ¨.ÜNÃî%×|»6$F55¯=4Sèò-A:PÕÞS9#­f÷e'¶!¥„‡vàëür=˜—J—7mN5Fx×QÖæ;†T•32SÈ(|˜ß=pÆ`Œõ¦i‰"ö»#³;ɺÄj4 \Kê‚5ÏDt-5ä£FÂÈàü…Pö„ÚÇçcHjÕ¿i­2nžµ±À¹éö”“>£ïÕ‘ÝÄ1ìI´.ÕØ5çêaÚ3›´kÅ´©®ÇäšÏ¾ˆ2û¾Ãâ19iÖï3_¤àØRþ}¦‘²ôw«êèÔòãš~¢[€qvsûB+wG›¯Ô…ÿ®W e½·žh®>-m«rÉjÝÿ´L29ÔwqÌ©ëfQ¾e÷§yd*oÁ*Š vxî¦Å¹zìl"A„Cäkñ²= ‘L†ž5Þ=‚k½v4»(DoæþCo'fO`‘+ÎipÕÏc˜æÞ‚c}Ë£.ë¹&$Žò¬59ÇzƒÐ#> ŠT –×幡]iF_mq>¦3ïçzóÄlÕ1t%9(Q£uÌû€5¬Õ=[íƒúyŠ5‚U‹ÀC&,›ªõ£M=m Ö‰ËKMâôºQUaÍV£MÖ-­0ýÿ&29o•ß\àGº¯αì…âŒ_8Ü3¶»™aؽõŠò_X4Ÿ<þ ÊJÛÒ¯.üwäö˜«ÙÜ_qŸãC¤D“|A D@B„È 7µtvLnœ»“EeêþŸèwƒëå%Äö„æ’e5i)i¦÷³ô4° m—æ\üš5V),s >ÒÙªvÒãNj‰Éµw9ƒ\ÞgŠÑc_r©§-™á¯(Ãä)–ø +‡òÓCM8 å":ÔH¡¾}v£ÔJ@9À¡_{/ø²Õ”ƒ•£µá²¸¯zÿêǶ앱5Y3ÝÚ.8aÀÊD7T¿’Në¹ÔÁ2k.aÓ´ùî9q…|†³@ WXögd.ì"Úc‹þïQ+3¿ßÔv„UyçJJí­ ô?pM˜W*ØZx9ÿ­á0> ƒU²zXM±ÉUm‡ð†aܸ~)xýC»hƒO ²\RŒo–ŒL]²|šÎ'À)WXs –“d¯ôIÈ%H9bWH[«²)!/6ŸQ7`Û?ÍÏÓ %Öãâ‹Ôˆàõ_#Hj4ûÇ-+’ ̺*À9öü, áºCƒæ—ÌU*]T2ù†aârIô8™ò…"ßQÐ>†—é½]ÃbóŠl¨hX>ZÓEaæÀ Ñ=רë<ÏÀ¢IeÜo3¥c+Þr'Ǥ¢h'ˆ²µg4\·0f·„uT·ù¡[cÕØKñ Í -@3c5¬1\x &‹¡Û¤ûÛF>}—S›'h½wãóÔìÈEþ GѵšÕ[bðgžºæÐH#šk\  <²®Ú£ŠATBMt rŒg'¶RŒ ‚Ðk—èÔ;~œÙÓ“`Š\x³±4ûJ”ÜÕé~(¦#L)œÑ‘b¡ã*¥¾-“O4¬22£ÂBEÉÉŒc‘åõ$y‰­ì,çšNF‚ÅXIÎì05Y2=q­Mi°Óm˃fÙ¤§ H¡îÜîD™.U3™n|.Îįî‡Á¦Õ´¬4¢Á t¸Œ1Œ–…E—iqçkŽœ†ëã—öe\Mi|¹Aiïiþ{M ¥¼†F²ê$."€p˜ÝKÖ*ì¡íCqG}ºÝó½ &" Øj* H|^w/×(\ü±Pâ{3œ¤UÂ9#D)˸‰ž{ùDÈ.žýÀåñ^Ub Kh½ÿKþŒB¹œùà PpÐrÿî€Rp´H’½(É„h° dCË@ÑØÅÚÞÜhoÀ+'Õ­ Ì¿ìf˜)À®Zñ‘bd<øÛ©ÇLYÓå“âÕVR£Ç³«ç»Ï âó|ãû-°«„óæXãìˆVèæXuˆDˆQ-è–hAS§øœ–ï°‰"„µkÚ:Üæ•眸^Gܳî?L}‘Óxšç›ý˪òAÍc›b1„Þðß”-f ÎøW¶•’ºú5¯aÕÊC©%hŽMZ~Øn¥ßIGo܉Ù,ŒÖ“C-J½¨¾CžtÚ0¯o“AJ›ÕÿÌ{˼ýU½óÊìz¬ï7ü¸ïÛ€ÇÐEÿy=Ÿ"Â1[í“—à4äÖ?þvÊ[2ÂÆ•ê1Øëy‹0²'3&¾œbo“âAK~º{ó†ÅU³ƒNŽqáb±E/±A±²¸U£†Ç4&ò+µ ÿBÇt C}ÉI ³°{Ö«fûJ€†ûJ§È¶$s¦æ+{-Ús¶ÇÊë=ý « È­xÌpøVµ8Îc~-€Gßn&’šÁŠv8¼V$jSL7럂 ÷éÅE çYê+"(g‹u(û@º¼ ÌŠw•œd¹½Õj˜‹¦Õ862d?›±è`UÎ2¹³\õ#Ænïa ¬$¢ÆO;Úµ#°žS\E¤Ékïk†Ó Ûjð^Zô&js‘H3ra¡7¨wíKp‡jÀh7¦’†ô"ù2q.‘»Uy8ÀG_—¦r±Ædš­xS7ÖÄõ•k¶ÀÓ™9ÃUzÝ„glòÈî^õÛcÚ{7<¯m …]— ë¤ìŒí7 ¿òx¾ ÈÚË©ƒ ½`R5 PmÙèú7‚ô6>ÒÝTÎìÖ.Nìá±é»ÊXxB`!Íà‹ß)åÆÜ%‰ƒv>)\“Ê~6èYÂ’“óÌÕµpaÒ*"ðIN8ÏŒ5x¾zœy-6LÒ…ì&Þ³ øúᇿ$NïØ%¿ée¤YØÊÞ"ež›¡ò…7Y á YóUXƒ) »|aË&*à.°=÷ì|ݺ„ÿëÝÛ‚¶˜K¹A–ˆŠ—œ™ÈÎáŠø}mð)Wß„£¡—E6ÕÓGÝ#R`@Áå"ÈÝÅHÌýàš«UFĵ[/zÏ’ïBWŸ½i^®·£~P[‰*f²º›ò´°Ã­¶È¦qG, XÁøë^¹zâib70ßÍâÍ~ÜäW¨#wø1 „1`MÜ€æ@=ؘSXˉò;‘\~TÄëê²_öÑ'hÄÎü¶ ÷É5E`Îa½2@vtEwml²E¬º|l$¿rÞÇ:F²²YÓkÉŒxé¸Å–Þ4.9]MS]OÓÞ@LÅ\x{Y. (qnõK@:aW6¹ùÆgaÏÖš_õh0 AQµ¦‚›ã“nG‡y|/O¨“–`‘h †(õ Õn³çýà]a𧵉ۦYŽ Œ¬S4• ÜRàà0í} •‚ü.ZDÚúN¨Þ:™jqè ©ë'Õdœ‰3)oŠGg6™RÀû&æÒ*\U›ßíÛr„[/ }7—Cqˆ¸½Y¸Î7ÞçŒp¥h®V|é\\ÍMõ(øRfÙw©•c¼#T1ózoGnáÛ"ÇmÅì7øäÜÌØV¤.ö|‘^&²±#<¼Ñ{ÖêI÷îòëåäœy(+E)2÷æMÚ d+ 6Í„nE¶ƒºÔà‚¡ïÛdä]¤$ﳿAy@0(-¥,oG«ÙW•{^°Ä«† šˆ5.hn¶%üÓc¤¨Ç‘mÊÐHäLŠ—¢:2Q«ASó¤Â\†­èêøßõç};ira~[Ï%¬é´¹ P# ~4‡Dí2Æ|«¼”.,À"§íƒmc ’êØ/Ý,.áÄÐ, „^ÉââÇ}í@óìÎx>@:š¢®hž¸êÃú:!U¤êDd[³ÒdŽ šV:ù§’@9F™È`' !TÌ”8& e¶6âñÔñ̽>ôrkgQ"l =aTéK}¦â¡ 18@]™,"PôùY¢[R«rüªu¥Åv6,ÅÌx¡–¿Ë«ŒK~sÎh*@s/Ö÷¬FÈÛö Î>«© ÃFßúœÒR¨’W=̺Sе›Ëh¸VF}|ç=F ðÈŸr“EçÝá¬õ&ªϵítÜ]òûÆ\[ 3V»Çö=^Š]û ‘žá4® ìÍ€Y &wí!.+Øäܓꬫޓ7̨φ•Èû)‡K†n¯´Êsÿ¼Y)WV@YùŽ ÅëÌB2dáF{>lìÖÿ«pz^y˜G™ís½^¼˜eDx±`‚!5î$^´M)x¿†'”0£ˆa•\G6s ÄGò>¦BꦢÓD;d J–¾HfÇÑ^h Šˆ˜ÖQóÂO÷UK¯ÞÙ±N<Çs dˆ)u-·ÕÊ&Í'$ÊgH»ÈP:¢IÔrcÝL¼ &¸>¬‘TÕ¸ TY`Æþ£gc¤üP,#M)Þ÷¡oS›/™„?` ¹²ßLÎ(Ð#…‡r>ùâ¤Q'Ds8™ƒ?„c§Y5ûTZ°‘+RßI@ßgÖõ@å7F¥p²¬ôvikÎ\ž¾&Á:#9ÑeÀÆ»+óÓEÖ6¥°}§#êg­7ÖìñKá!²û¿íß À†-f ½¹ÌŽÁÆ–ø½Dy \0h[Åþ>ð­ÄÀ·~·²Í·j‘,̺gõÍIêÊT°ì´&Øáø¢Mâ;2Ül‘öôݼ¦®Jɰƒ¾û´ýó‰ß¢¬iY!/m;ÿ·ÈŒD„>z›*Ž·SÁ2ƒ 7Féf¢•ÐVúѥРB“ŠÇ9Kâó¥O6Œ4¡³v†0•)v=‚dxóáîÄ“xû'| T÷r á³Nq¹·ó/ý£ÈGÁã\ïaùϦ>Ï}Ï6ƒ©±WjÖ´k!4ö'Šô°È.¼A(N ‘êeÁ=áöNøÕ磷ü+QÞÿƒÀ–w˜FvA°L"‹©ÁŒz

General-purpose Structure-from-Motion & Multi-View Stereo

COLMAP is a general-purpose Structure-from-Motion (SfM) and Multi-View Stereo (MVS) pipeline with a graphical and command-line interface. It offers a wide range of features for reconstruction of ordered and unordered image collections, and is free and open source.

.. figure:: images/sparse.webp :class: hero__image :figclass: hero-figure :alt: Sparse reconstruction of central Rome. Sparse model of central Rome using 21K photos produced by COLMAP's SfM pipeline. Install COLMAP -------------- Select your platform below to get the recommended install command or download. .. raw:: html
For all installation options and build-from-source instructions, see the :ref:`installation guide `. Features -------- .. grid:: 1 2 2 3 :gutter: 3 .. grid-item-card:: Structure-from-Motion :link: tutorial :link-type: doc Robust incremental SfM to recover camera poses and sparse 3D structure from ordered or unordered image collections. .. grid-item-card:: Multi-View Stereo :link: tutorial :link-type: doc Dense reconstruction with PatchMatch stereo and stereo fusion to produce detailed dense point clouds and meshes. .. grid-item-card:: Graphical & Command-Line :link: gui :link-type: doc A full-featured GUI for interactive reconstruction plus a scriptable command-line interface for automated pipelines. .. grid-item-card:: PyCOLMAP :link: pycolmap/index :link-type: doc Python bindings exposing most of COLMAP's functionality, from the reconstruction pipeline to robust geometric estimators. .. grid-item-card:: Camera Models & Rigs :link: cameras :link-type: doc A wide range of camera models and multi-camera rig support for diverse capture setups. .. grid-item-card:: Datasets & Formats :link: datasets :link-type: doc Ready-to-use sample datasets and well-documented input/output formats for easy integration. Getting Started --------------- 1. Install COLMAP using the selector above, download the `pre-built binaries `_, or build from `source `_ (see :ref:`Installation `). 2. Download one of the provided datasets (see :ref:`Datasets `) or use your own images. 3. Use the **automatic reconstruction** to easily build models with a single click (see :ref:`Quickstart `). Support ------- Please, use `GitHub Discussions `_ for questions and the `GitHub issue tracker `_ for bug reports, feature requests/additions, etc. Citation -------- If you use this project for your research, please cite:: @inproceedings{schoenberger2016sfm, author={Sch\"{o}nberger, Johannes Lutz and Frahm, Jan-Michael}, title={Structure-from-Motion Revisited}, booktitle={Conference on Computer Vision and Pattern Recognition (CVPR)}, year={2016}, } @inproceedings{schoenberger2016mvs, author={Sch\"{o}nberger, Johannes Lutz and Zheng, Enliang and Pollefeys, Marc and Frahm, Jan-Michael}, title={Pixelwise View Selection for Unstructured Multi-View Stereo}, booktitle={European Conference on Computer Vision (ECCV)}, year={2016}, } If you use the global SfM pipeline (GLOMAP), please cite:: @inproceedings{pan2024glomap, author={Pan, Linfei and Barath, Daniel and Pollefeys, Marc and Sch\"{o}nberger, Johannes Lutz}, title={{Global Structure-from-Motion Revisited}}, booktitle={European Conference on Computer Vision (ECCV)}, year={2024}, } If you use the image retrieval / vocabulary tree engine, please cite:: @inproceedings{schoenberger2016vote, author={Sch\"{o}nberger, Johannes Lutz and Price, True and Sattler, Torsten and Frahm, Jan-Michael and Pollefeys, Marc}, title={A Vote-and-Verify Strategy for Fast Spatial Verification in Image Retrieval}, booktitle={Asian Conference on Computer Vision (ACCV)}, year={2016}, } Acknowledgments --------------- COLMAP was originally written by `Johannes Schönberger `__ with funding provided by his PhD advisors Jan-Michael Frahm and Marc Pollefeys. The team of core project maintainers currently includes `Johannes Schönberger `__, `Paul-Edouard Sarlin `_, and `Shaohui Liu `_. The Python bindings in PyCOLMAP were originally added by `Mihai Dusmanu `_, `Philipp Lindenberger `_, and `Paul-Edouard Sarlin `_. The project has also benefitted from countless community contributions, including bug fixes, improvements, new features, third-party tooling, and community support (special credits to `Torsten Sattler `_). .. toctree:: :hidden: :maxdepth: 2 install tutorial concepts viewer features database cameras rigs format datasets gui cli pycolmap/index faq changelog contribution license bibliography legacy colmap-4.2.0/doc/install.rst000077500000000000000000000433201524536416500157550ustar00rootroot00000000000000.. _installation: Installation ============ You can either download one of the pre-built binaries or build the source code manually. Pre-built binaries and other resources can be downloaded from https://demuc.de/colmap/. An overview of system packages for Linux/Unix/BSD distributions are available at https://repology.org/metapackage/colmap/versions. Note that the COLMAP packages in the default repositories for Linux/Unix/BSD do not come with CUDA or HIP/ROCm support, which requires a manual build from source, as explained further below. For Mac users, `Homebrew `__ provides a formula for COLMAP with pre-compiled binaries or the option to build from source. After installing homebrew, installing COLMAP is as easy as running ``brew install colmap``. COLMAP can be used as an independent application through the command-line or graphical user interface. Alternatively, COLMAP is also built as a reusable library, i.e., you can include and link COLMAP against your own C++ source code, as described further below. Furthermore, you can use most of COLMAP's functionality with :ref:`PyCOLMAP ` in Python. ------------------ Pre-built Binaries ------------------ Windows ------- For convenience, the pre-built binaries for Windows contain both the graphical and command-line interface executables. To start the COLMAP GUI, you can simply double-click the ``COLMAP.bat`` batch script or alternatively run it from the Windows command shell or Powershell. The command-line interface is also accessible through this batch script, which automatically sets the necessary library paths. To list the available COLMAP commands, run ``COLMAP.bat -h`` in the command shell ``cmd.exe`` or in Powershell. The first time you run COLMAP, Windows defender may prompt you with a security warning, because the binaries are not officially signed. The provided COLMAP binaries are automatically built from GitHub Actions CI machines. If you do not trust them, you can build from source as described below. Docker ------ COLMAP provides a pre-built Docker image with CUDA support. For detailed instructions on how to build and run COLMAP using Docker, please refer to the `Docker documentation `__. ----------------- Build from Source ----------------- COLMAP builds on all major platforms (Linux, Mac, Windows) with little effort. First, checkout the latest source code:: git clone https://github.com/colmap/colmap Under Linux and Mac, it is generally recommended to follow the installation instructions below, which use the respective system package managers to install the required dependencies. Alternatively, the instructions for VCPKG can be used to compile the required dependencies from scratch on more exotic systems with limited system packages. The VCPKG approach is also the method of choice under Windows, compute clusters, or if you do not have root access under Linux or Mac. Debian/Ubuntu ------------- *Recommended dependencies:* CUDA (at least version 11.X) Dependencies from the default Ubuntu repositories:: sudo apt-get install \ git \ cmake \ ninja-build \ build-essential \ libboost-program-options-dev \ libboost-graph-dev \ libboost-system-dev \ libeigen3-dev \ libopenimageio-dev \ openimageio-tools \ libmetis-dev \ libgoogle-glog-dev \ libgtest-dev \ libgmock-dev \ libsqlite3-dev \ libglew-dev \ qt6-base-dev \ libqt6opengl6-dev \ libqt6openglwidgets6 \ qt6-svg-dev \ libcgal-dev \ libceres-dev \ libsuitesparse-dev \ libcurl4-openssl-dev \ libssl-dev \ libmkl-full-dev # Fix issue in Ubuntu's openimageio CMake config. # We don't depend on any of openimageio's OpenCV functionality, # but it still requires the OpenCV include directory to exist. sudo mkdir -p /usr/include/opencv4 Alternatively, you can also build against Qt 5 instead of Qt 6 using:: qtbase5-dev libqt5opengl5-dev libqt5svg5-dev To compile with **CUDA support**, also install Ubuntu's default CUDA package:: sudo apt-get install -y \ nvidia-cuda-toolkit \ nvidia-cuda-toolkit-gcc Or, manually install the latest CUDA from NVIDIA's homepage. During CMake configuration, specify ``-DCMAKE_CUDA_ARCHITECTURES=native``, if you want to run COLMAP only on your current machine (default), "all"/"all-major" to be able to distribute to other machines, or a specific CUDA architecture like "75", etc. To compile with **HIP / ROCm support** instead of CUDA (for AMD GPUs), install ROCm following the `AMD ROCm installation guide `__ and ensure the ``hip``, ``hiprand``, and ``rocrand`` packages are present (default location ``/opt/rocm``). Then pass the following flags at configure time:: cmake .. -GNinja \ -DCUDA_ENABLED=OFF \ -DHIP_ENABLED=ON \ -DCMAKE_HIP_ARCHITECTURES=gfx90a \ -DCMAKE_HIP_COMPILER=/opt/rocm/llvm/bin/clang++ Set ``CMAKE_HIP_ARCHITECTURES`` to match the target AMD GPU (``gfx90a`` for MI200/MI250, ``gfx942`` for MI300, ``gfx1030`` for RDNA2, ``gfx1100`` for RDNA3, etc.; multiple values can be passed as a semicolon-separated list). ``CUDA_ENABLED`` and ``HIP_ENABLED`` are mutually exclusive. CMake 3.21 or newer is required for the HIP backend. On RDNA3 consumer parts where ROCm only officially supports a subset of architectures, you may also need ``HSA_OVERRIDE_GFX_VERSION=11.0.0`` in the runtime environment. The HIP backend currently accelerates dense reconstruction (``patch_match_stereo``); see the changelog for ongoing coverage. If ROCm is installed through a Python wheel / virtualenv (for example AMD's TheRock packaging, which puts a ``rocm-sdk`` command on the ``PATH``), the install root and target architectures are detected automatically from ``rocm-sdk path --root`` and ``rocm-sdk targets``, so ``ROCM_PATH`` and ``CMAKE_HIP_ARCHITECTURES`` need not be set by hand. An explicit ``-DROCM_PATH`` or a ``ROCM_PATH`` environment variable still takes precedence, and CMake's own architecture autodetection sets ``CMAKE_HIP_ARCHITECTURES`` when a target GPU is visible at configure time. Configure and compile COLMAP:: git clone https://github.com/colmap/colmap.git cd colmap mkdir build cd build cmake .. -GNinja -DBLA_VENDOR=Intel10_64lp ninja sudo ninja install .. note:: COLMAP can use ``boost::unordered`` flat/node hash maps for the performance-critical scene and SfM containers, selected via ``-DCOLMAP_HASH_MAP_BACKEND=BOOST|STD`` (default: auto). Auto selects ``BOOST`` when Boost is recent enough (``boost::unordered_node_map`` requires **Boost >= 1.84**) and falls back to ``STD`` (``std::unordered_map``) otherwise. Ubuntu's default Boost is older than 1.84, so apt-based builds use ``STD``; to use the faster ``BOOST`` backend, build against a newer Boost (e.g. via vcpkg, which installs ``boost-unordered`` automatically) or install Boost >= 1.84 manually. Explicitly requesting ``-DCOLMAP_HASH_MAP_BACKEND=BOOST`` with an older Boost is a configuration error. Run COLMAP:: colmap -h colmap gui Under **Ubuntu 22.04**, there is a problem when compiling with Ubuntu's default CUDA package and GCC, and you must compile against GCC 10:: sudo apt-get install gcc-10 g++-10 export CC=/usr/bin/gcc-10 export CXX=/usr/bin/g++-10 export CUDAHOSTCXX=/usr/bin/g++-10 # ... and then run CMake against COLMAP's sources. Notice that the ``BLA_VENDOR=Intel10_64lp`` option tells CMake to find Intel's MKL implementation of BLAS. If you decide to compile against OpenBLAS instead of MKL, you must install and select the OpenMP version under Debian/Ubuntu because of `this issue `__. Fedora ------ *Recommended dependencies:* CUDA (at least version 11.X) Dependencies from the default Fedora repositories:: sudo dnf install -y \ git \ cmake \ ninja-build \ gcc-c++ \ boost-devel \ eigen3-devel \ OpenImageIO-devel \ OpenImageIO-utils \ metis-devel \ glog-devel \ gtest-devel \ gmock-devel \ sqlite-devel \ glew-devel \ qt6-qtbase-devel \ qt6-qtsvg-devel \ CGAL-devel \ ceres-solver-devel \ suitesparse-devel \ suitesparse-static \ libcurl-devel \ openssl-devel \ openblas-devel # suitesparse-static is required even for a dynamic build, because Fedora's # SuiteSparse CMake config references the static targets file regardless of # link type. This can be dropped once Fedora ships SuiteSparse >= 7.11.0 with # its separated static config. Alternatively, you can also build against Qt 5 instead of Qt 6 using:: qt5-qtbase-devel qt5-qtsvg-devel To compile with **CUDA support**, install the CUDA toolkit from NVIDIA's official Fedora repository, which (unlike the plain ``cuda`` meta-package) preserves an existing NVIDIA driver. Replace ```` with your Fedora release, e.g. ``43``:: sudo dnf config-manager addrepo --from-repofile=https://developer.download.nvidia.com/compute/cuda/repos/fedora/x86_64/cuda-fedora.repo sudo dnf install -y cuda-toolkit During CMake configuration, specify ``-DCMAKE_CUDA_ARCHITECTURES=native``, if you want to run COLMAP only on your current machine (default), "all"/"all-major" to be able to distribute to other machines, or a specific CUDA architecture like "89", etc. Configure and compile COLMAP:: git clone https://github.com/colmap/colmap.git cd colmap mkdir build cd build cmake .. -GNinja -DCMAKE_CUDA_ARCHITECTURES=native ninja sudo ninja install Run COLMAP:: colmap -h colmap gui Mac --- Dependencies from `Homebrew `__:: brew install \ cmake \ ninja \ boost \ eigen \ openimageio \ curl \ libomp \ metis \ glog \ googletest \ ceres-solver \ suitesparse \ qt \ glew \ cgal \ sqlite3 brew link --force libomp Configure and compile COLMAP:: git clone https://github.com/colmap/colmap.git cd colmap mkdir build cd build cmake .. -GNinja ninja sudo ninja install If you have Qt 5 installed on your system as well, you might have to temporarily link your Qt 5 installation while configuring CMake:: brew unlink qt && brew link --force qt cmake ... Run COLMAP:: colmap -h colmap gui Windows ------- *Recommended dependencies:* CUDA (at least version 11.X), Visual Studio 2019 or newer On Windows, the recommended way is to build COLMAP using VCPKG:: git clone https://github.com/microsoft/vcpkg cd vcpkg .\bootstrap-vcpkg.bat .\vcpkg install colmap[cuda,tests]:x64-windows To compile CUDA for multiple compute architectures, please use:: .\vcpkg install colmap[cuda-redist]:x64-windows Please refer to the next section for more details. VCPKG ----- COLMAP ships as part of the VCPKG distribution. This enables to conveniently build COLMAP and all of its dependencies from scratch under different platforms. Note that VCPKG requires you to install CUDA manually in the standard way on your platform. To compile COLMAP using VCPKG, you run:: git clone https://github.com/microsoft/vcpkg cd vcpkg ./bootstrap-vcpkg.sh ./vcpkg install colmap:x64-linux VCPKG ships with support for various other platforms (e.g., x64-osx, x64-windows, etc.). To compile with CUDA support and to build all tests:: ./vcpkg install colmap[cuda,tests]:x64-linux The above commands will build the latest release version of COLMAP. To compile the latest commit in the dev branch, you can use the following options:: ./vcpkg install colmap:x64-linux --head To modify the source code, you can further add ``--editable --no-downloads``. Or, if you want to build from another folder and use the dependencies from vcpkg, first run ``./vcpkg integrate install`` (under Windows use pwsh and ``./scripts/shell/enter_vs_dev_shell.ps1``) and then configure COLMAP as:: cd path/to/colmap mkdir build cd build cmake .. -DCMAKE_TOOLCHAIN_FILE=path/to/vcpkg/scripts/buildsystems/vcpkg.cmake -DCMAKE_BUILD_TYPE=Release cmake --build . --config release --target colmap --parallel 24 Anaconda/Mamba -------------- Install miniconda and run the following commands. You can replace ``conda`` with ``mamba`` for faster package installation:: conda create -n colmap python=3.12 conda config --add channels conda-forge conda config --set channel_priority strict conda install \ cmake \ ninja \ boost \ ccache \ eigen \ openimageio \ curl \ metis \ glog \ gtest \ ceres-solver \ suitesparse \ qt \ glew \ sqlite \ cgal-cpp \ mesa-libgl-devel-cos7-x86_64 \ cuda-compiler==12.6.2 \ cuda-cudart-dev \ cuda-nvrtc-dev \ libcurand-dev git clone https://github.com/colmap/colmap.git cd colmap mkdir build cd build cmake .. -GNinja ninja .. _installation-library: ------- Library ------- If you want to include and link COLMAP against your own library, the easiest way is to use CMake as a build configuration tool. After configuring the COLMAP build and running ``ninja/make install``, COLMAP automatically installs all headers to ``${CMAKE_INSTALL_PREFIX}/include/colmap``, all libraries to ``${CMAKE_INSTALL_PREFIX}/lib/colmap``, and the CMake configuration to ``${CMAKE_INSTALL_PREFIX}/share/colmap``. For example, compiling your own source code against COLMAP is as simple as using the following ``CMakeLists.txt``:: cmake_minimum_required(VERSION 3.10) project(SampleProject) find_package(colmap REQUIRED) # or to require a specific version: find_package(colmap 3.4 REQUIRED) add_executable(hello_world hello_world.cc) target_link_libraries(hello_world colmap::colmap) with the source code ``hello_world.cc``:: #include #include #include #include int main(int argc, char** argv) { colmap::InitializeGlog(argv); std::string message; colmap::OptionManager options; options.AddRequiredOption("message", &message); if (!options.Parse(argc, argv)) { return EXIT_FAILURE; } std::cout << colmap::StringPrintf("Hello %s!\n", message.c_str()); return EXIT_SUCCESS; } Then compile and run your code as:: mkdir build cd build export colmap_DIR=${CMAKE_INSTALL_PREFIX}/share/colmap cmake .. -GNinja ninja ./hello_world --message "world" The sources of this example are stored under ``doc/sample-project``. ---------------- Shared Libraries ---------------- By default, COLMAP builds static libraries. To build shared/dynamic libraries instead, enable the ``BUILD_SHARED_LIBS`` option:: cmake .. -GNinja -DBUILD_SHARED_LIBS=ON Trade-offs compared to static libraries: - **Faster incremental linking**: Only the changed shared library needs to be re-linked during development, rather than all executables. - **Reduced disk usage**: Multiple executables share the same library files on disk and in memory. - **No cross-library optimization**: The compiler cannot inline or apply link-time optimization (LTO/IPO) across shared library boundaries, which reduces runtime performance. - **Symbol resolution overhead**: The dynamic linker resolves symbols at load time, adding minor startup cost and indirect call overhead. For development workflows, shared libraries can significantly speed up edit-compile-test cycles. For production or benchmarking, static libraries are recommended. ---------------- AddressSanitizer ---------------- If you want to build COLMAP with address sanitizer flags enabled, you need to use a recent compiler with ASan support. For example, you can manually install a recent clang version on your Ubuntu machine and invoke CMake as follows:: CC=/usr/bin/clang CXX=/usr/bin/clang++ cmake .. \ -DASAN_ENABLED=ON \ -DTESTS_ENABLED=ON \ -DCMAKE_BUILD_TYPE=RelWithDebInfo Note that it is generally useful to combine ASan with debug symbols to get meaningful traces for reported issues. ------------- Documentation ------------- 1. Install latest pycolmap for up-to-date pycolmap API documentation. 2. Build the documentation:: cd path/to/colmap/doc pip install -r requirements.txt make html open _build/html/index.html # preview results Alternatively, you can build the documentation as PDF, EPUB, etc.:: make latexpdf open _build/pdf/COLMAP.pdf Publishing to the website (`colmap.github.io `__) is automated: whenever documentation-relevant files change on ``main``, the CI pipeline builds these docs and pushes the result to the ``master`` branch of the `colmap/colmap.github.io `__ repository. Pull requests that touch the docs build them too and upload the generated HTML as a downloadable ``docs-preview`` artifact for review, without publishing. The manual steps above are therefore only needed to preview the docs locally. For a main release, still copy the previous release as legacy to the "legacy" folder in the website repository, under a folder with the release number (`see here `__). The automated deploy preserves the existing ``legacy`` folder. colmap-4.2.0/doc/legacy.rst000066400000000000000000000010751524536416500155510ustar00rootroot00000000000000Legacy Documentations ===================== .. toctree:: :maxdepth: 1 v4.0 (2026-03-15) v3.13 (2025-11-07) v3.12 (2025-06-30) v3.11 (2024-11-28) v3.10 (2024-07-23) v3.9 (2024-01-06) v3.8 (2023-01-31) colmap-4.2.0/doc/license.rst000066400000000000000000000036471524536416500157360ustar00rootroot00000000000000License ======= The COLMAP library is licensed under the new BSD license. Note that this text refers only to the license for COLMAP itself, independent of its thirdparty dependencies, which are separately licensed. Building COLMAP with these dependencies may affect the resulting COLMAP license. .. code-block:: text Copyright (c), ETH Zurich and UNC Chapel Hill. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * Neither the name of ETH Zurich and UNC Chapel Hill nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. colmap-4.2.0/doc/make.bat000077500000000000000000000156161524536416500151710ustar00rootroot00000000000000@ECHO OFF REM Command file for Sphinx documentation if "%SPHINXBUILD%" == "" ( set SPHINXBUILD=sphinx-build ) set BUILDDIR=_build set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% . set I18NSPHINXOPTS=%SPHINXOPTS% . if NOT "%PAPER%" == "" ( set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS% set I18NSPHINXOPTS=-D latex_paper_size=%PAPER% %I18NSPHINXOPTS% ) if "%1" == "" goto help if "%1" == "help" ( :help echo.Please use `make ^` where ^ is one of echo. html to make standalone HTML files echo. dirhtml to make HTML files named index.html in directories echo. singlehtml to make a single large HTML file echo. pickle to make pickle files echo. json to make JSON files echo. htmlhelp to make HTML files and a HTML help project echo. qthelp to make HTML files and a qthelp project echo. devhelp to make HTML files and a Devhelp project echo. epub to make an epub echo. latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter echo. text to make text files echo. man to make manual pages echo. texinfo to make Texinfo files echo. gettext to make PO message catalogs echo. changes to make an overview over all changed/added/deprecated items echo. xml to make Docutils-native XML files echo. pseudoxml to make pseudoxml-XML files for display purposes echo. linkcheck to check all external links for integrity echo. doctest to run all doctests embedded in the documentation if enabled goto end ) if "%1" == "clean" ( for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i del /q /s %BUILDDIR%\* if exist _static\viewer rmdir /q /s _static\viewer goto end ) %SPHINXBUILD% 2> nul if errorlevel 9009 ( echo. echo.The 'sphinx-build' command was not found. Make sure you have Sphinx echo.installed, then set the SPHINXBUILD environment variable to point echo.to the full path of the 'sphinx-build' executable. Alternatively you echo.may add the Sphinx directory to PATH. echo. echo.If you don't have Sphinx installed, grab it from echo.http://sphinx-doc.org/ exit /b 1 ) if "%1" == "html" ( call :viewer-assets if errorlevel 1 exit /b 1 %SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html if errorlevel 1 exit /b 1 echo. echo.Build finished. The HTML pages are in %BUILDDIR%/html. goto end ) if "%1" == "dirhtml" ( call :viewer-assets if errorlevel 1 exit /b 1 %SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml if errorlevel 1 exit /b 1 echo. echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml. goto end ) if "%1" == "singlehtml" ( call :viewer-assets if errorlevel 1 exit /b 1 %SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml if errorlevel 1 exit /b 1 echo. echo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml. goto end ) if "%1" == "pickle" ( %SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle if errorlevel 1 exit /b 1 echo. echo.Build finished; now you can process the pickle files. goto end ) if "%1" == "json" ( %SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json if errorlevel 1 exit /b 1 echo. echo.Build finished; now you can process the JSON files. goto end ) if "%1" == "htmlhelp" ( %SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp if errorlevel 1 exit /b 1 echo. echo.Build finished; now you can run HTML Help Workshop with the ^ .hhp project file in %BUILDDIR%/htmlhelp. goto end ) if "%1" == "qthelp" ( %SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp if errorlevel 1 exit /b 1 echo. echo.Build finished; now you can run "qcollectiongenerator" with the ^ .qhcp project file in %BUILDDIR%/qthelp, like this: echo.^> qcollectiongenerator %BUILDDIR%\qthelp\COLMAP.qhcp echo.To view the help file: echo.^> assistant -collectionFile %BUILDDIR%\qthelp\COLMAP.ghc goto end ) if "%1" == "devhelp" ( %SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp if errorlevel 1 exit /b 1 echo. echo.Build finished. goto end ) if "%1" == "epub" ( %SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub if errorlevel 1 exit /b 1 echo. echo.Build finished. The epub file is in %BUILDDIR%/epub. goto end ) if "%1" == "latex" ( %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex if errorlevel 1 exit /b 1 echo. echo.Build finished; the LaTeX files are in %BUILDDIR%/latex. goto end ) if "%1" == "latexpdf" ( %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex cd %BUILDDIR%/latex make all-pdf cd %BUILDDIR%/.. echo. echo.Build finished; the PDF files are in %BUILDDIR%/latex. goto end ) if "%1" == "latexpdfja" ( %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex cd %BUILDDIR%/latex make all-pdf-ja cd %BUILDDIR%/.. echo. echo.Build finished; the PDF files are in %BUILDDIR%/latex. goto end ) if "%1" == "text" ( %SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text if errorlevel 1 exit /b 1 echo. echo.Build finished. The text files are in %BUILDDIR%/text. goto end ) if "%1" == "man" ( %SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man if errorlevel 1 exit /b 1 echo. echo.Build finished. The manual pages are in %BUILDDIR%/man. goto end ) if "%1" == "texinfo" ( %SPHINXBUILD% -b texinfo %ALLSPHINXOPTS% %BUILDDIR%/texinfo if errorlevel 1 exit /b 1 echo. echo.Build finished. The Texinfo files are in %BUILDDIR%/texinfo. goto end ) if "%1" == "gettext" ( %SPHINXBUILD% -b gettext %I18NSPHINXOPTS% %BUILDDIR%/locale if errorlevel 1 exit /b 1 echo. echo.Build finished. The message catalogs are in %BUILDDIR%/locale. goto end ) if "%1" == "changes" ( %SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes if errorlevel 1 exit /b 1 echo. echo.The overview file is in %BUILDDIR%/changes. goto end ) if "%1" == "linkcheck" ( %SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck if errorlevel 1 exit /b 1 echo. echo.Link check complete; look for any errors in the above output ^ or in %BUILDDIR%/linkcheck/output.txt. goto end ) if "%1" == "doctest" ( %SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest if errorlevel 1 exit /b 1 echo. echo.Testing of doctests in the sources finished, look at the ^ results in %BUILDDIR%/doctest/output.txt. goto end ) if "%1" == "xml" ( %SPHINXBUILD% -b xml %ALLSPHINXOPTS% %BUILDDIR%/xml if errorlevel 1 exit /b 1 echo. echo.Build finished. The XML files are in %BUILDDIR%/xml. goto end ) if "%1" == "pseudoxml" ( %SPHINXBUILD% -b pseudoxml %ALLSPHINXOPTS% %BUILDDIR%/pseudoxml if errorlevel 1 exit /b 1 echo. echo.Build finished. The pseudo-XML files are in %BUILDDIR%/pseudoxml. goto end ) :end goto :eof :viewer-assets where npm > nul 2> nul if errorlevel 1 goto viewer-assets-missing if not exist node_modules goto viewer-assets-missing npm run build exit /b %errorlevel% :viewer-assets-missing if "%STRICT_VIEWER%" == "1" ( echo.npm and doc\node_modules are required when STRICT_VIEWER=1 exit /b 1 ) echo.WARNING: skipping 3D viewer build ^(npm or doc\node_modules not found^) exit /b 0 colmap-4.2.0/doc/package-lock.json000066400000000000000000001647051524536416500170010ustar00rootroot00000000000000{ "name": "colmap-documentation-viewer", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "colmap-documentation-viewer", "dependencies": { "three": "0.185.1" }, "devDependencies": { "@playwright/test": "1.61.1", "@types/node": "26.1.1", "@types/three": "0.185.1", "typescript": "7.0.2", "vite": "8.1.5", "vitest": "4.1.10" } }, "node_modules/@dimforge/rapier3d-compat": { "version": "0.12.0", "resolved": "https://registry.npmjs.org/@dimforge/rapier3d-compat/-/rapier3d-compat-0.12.0.tgz", "integrity": "sha512-uekIGetywIgopfD97oDL5PfeezkFpNhwlzlaEYNOA0N6ghdsOvh/HYjSMek5Q2O1PYvRSDFcqFVJl4r4ZBwOow==", "dev": true, "license": "Apache-2.0" }, "node_modules/@emnapi/core": { "version": "1.11.1", "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.11.1.tgz", "integrity": "sha512-RSvbQmHzdKzNsLYa/wHrbc3KN4sYLKAdPZxqiM2HATqv/SBk2/ENSHpvXGaLOMcsAyz0poEGqkmmKYG3OWiJEQ==", "dev": true, "license": "MIT", "optional": true, "dependencies": { "@emnapi/wasi-threads": "1.2.2", "tslib": "^2.4.0" } }, "node_modules/@emnapi/runtime": { "version": "1.11.1", "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.11.1.tgz", "integrity": "sha512-vgj7R3y3Wgx24IQaGPA/R6YFXLHVMOZ0uVEyIQPaWs+rd1AzfEMXlAC22FYwO1XkKR6NPsq7mUandH8oIRdZFw==", "dev": true, "license": "MIT", "optional": true, "dependencies": { "tslib": "^2.4.0" } }, "node_modules/@emnapi/wasi-threads": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.2.tgz", "integrity": "sha512-c95qOXkHdydNKhscBTebqEC1CVAZpyqOfVfBzQ1qgzyl3gfeldUjIggDbIZgDKsHLgnsM+igH7TJ/eAasaVuMA==", "dev": true, "license": "MIT", "optional": true, "dependencies": { "tslib": "^2.4.0" } }, "node_modules/@jridgewell/sourcemap-codec": { "version": "1.5.5", "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", "dev": true, "license": "MIT" }, "node_modules/@napi-rs/wasm-runtime": { "version": "1.1.6", "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.6.tgz", "integrity": "sha512-ZLv/JdUfkvOy9eCnnBaGfiO+XimbjebAeO+MRQqD/B+FR1tnRN0tpKSJHRbE8sFfS6aqsXZ67TQjfwfsxULVbg==", "dev": true, "license": "MIT", "optional": true, "dependencies": { "@tybys/wasm-util": "^0.10.3" }, "funding": { "type": "github", "url": "https://github.com/sponsors/Brooooooklyn" }, "peerDependencies": { "@emnapi/core": "^1.7.1", "@emnapi/runtime": "^1.7.1" } }, "node_modules/@oxc-project/types": { "version": "0.139.0", "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.139.0.tgz", "integrity": "sha512-r9gHphtCs+1M7J0pw6Sn/hh/Wpa/iQrOOkrNAlVLF/gHq+/CJmHIWKKUUhdWjcD6CIa8idarspCsASiXCXvFUw==", "dev": true, "license": "MIT", "funding": { "url": "https://github.com/sponsors/Boshen" } }, "node_modules/@playwright/test": { "version": "1.61.1", "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.61.1.tgz", "integrity": "sha512-8nKv6+0RJSL9FE4jYOEGXnPeM/Hg12qZpmqzZjRh3qM0Y7c3z1mrOTfFLids72RDQYVh9WpLEfR5WdpNX4fkig==", "dev": true, "license": "Apache-2.0", "dependencies": { "playwright": "1.61.1" }, "bin": { "playwright": "cli.js" }, "engines": { "node": ">=18" } }, "node_modules/@rolldown/binding-android-arm64": { "version": "1.1.5", "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.1.5.tgz", "integrity": "sha512-lZg8fqIv2v7FF237bwMgzGZEJvGL79/s5knJ/i6FmsGF4XXlzccZ4jb+TrFIxtSSxFtIpdsgrPZeMk1I9AFcyQ==", "cpu": [ "arm64" ], "dev": true, "license": "MIT", "optional": true, "os": [ "android" ], "engines": { "node": "^20.19.0 || >=22.12.0" } }, "node_modules/@rolldown/binding-darwin-arm64": { "version": "1.1.5", "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.1.5.tgz", "integrity": "sha512-51Bnx9pNiMRKSUNtBfySkNJ9vMU9Hh3I1ozDd6gyPPYzaXCfnptUcEZxXGYFn+ul2dtcMUiqGR1Yai2K10uoTw==", "cpu": [ "arm64" ], "dev": true, "license": "MIT", "optional": true, "os": [ "darwin" ], "engines": { "node": "^20.19.0 || >=22.12.0" } }, "node_modules/@rolldown/binding-darwin-x64": { "version": "1.1.5", "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.1.5.tgz", "integrity": "sha512-Tm+gbfC0aHu1tBA/JvKQh32S0K6YgCHkiAF4/W6xX0K0RmNuc94VeK419dJoE65R5aRxmo+noZQSWrAMF6yb6g==", "cpu": [ "x64" ], "dev": true, "license": "MIT", "optional": true, "os": [ "darwin" ], "engines": { "node": "^20.19.0 || >=22.12.0" } }, "node_modules/@rolldown/binding-freebsd-x64": { "version": "1.1.5", "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.1.5.tgz", "integrity": "sha512-JMzDKCCXq93YccG5gz3hvOs1oXRKAf0XYpfOS88e+wZrC8Iugj6j68867vrYZkvpDDpKn/KoKORThmchMpF6TA==", "cpu": [ "x64" ], "dev": true, "license": "MIT", "optional": true, "os": [ "freebsd" ], "engines": { "node": "^20.19.0 || >=22.12.0" } }, "node_modules/@rolldown/binding-linux-arm-gnueabihf": { "version": "1.1.5", "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.1.5.tgz", "integrity": "sha512-uML21j2K5TfPGutKxub+M+nLjZIrWjXQ5Grx4lCe/nimTj9B4L63zHpjXLl4y0L3mcm2htEQIb06oCG/szerNw==", "cpu": [ "arm" ], "dev": true, "license": "MIT", "optional": true, "os": [ "linux" ], "engines": { "node": "^20.19.0 || >=22.12.0" } }, "node_modules/@rolldown/binding-linux-arm64-gnu": { "version": "1.1.5", "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.1.5.tgz", "integrity": "sha512-navSiuTMogvnQoZoM/v+l3ZWo50/NTwSHSzheABx/RCnmUPaKwq9qSo4Br2OYRs21+Fz8uFqITZM3H4opOB0/Q==", "cpu": [ "arm64" ], "dev": true, "libc": [ "glibc" ], "license": "MIT", "optional": true, "os": [ "linux" ], "engines": { "node": "^20.19.0 || >=22.12.0" } }, "node_modules/@rolldown/binding-linux-arm64-musl": { "version": "1.1.5", "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.1.5.tgz", "integrity": "sha512-lAryqH7IteztmCXQXk0etKj4wBQ7Gx5S6LjKhsgp9zb8I5bsuvU/2llH1hDQcjsFeqIsovMVN339/8pUDDBXxA==", "cpu": [ "arm64" ], "dev": true, "libc": [ "musl" ], "license": "MIT", "optional": true, "os": [ "linux" ], "engines": { "node": "^20.19.0 || >=22.12.0" } }, "node_modules/@rolldown/binding-linux-ppc64-gnu": { "version": "1.1.5", "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.1.5.tgz", "integrity": "sha512-fsK/sNBnxzBlL4O1JNrZakVQxPspqpED5dLtNsZS9oOKmtSpdNIzxH2kkol5HYTWJN47sE20ztMJPxfZ89qGOg==", "cpu": [ "ppc64" ], "dev": true, "libc": [ "glibc" ], "license": "MIT", "optional": true, "os": [ "linux" ], "engines": { "node": "^20.19.0 || >=22.12.0" } }, "node_modules/@rolldown/binding-linux-s390x-gnu": { "version": "1.1.5", "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.1.5.tgz", "integrity": "sha512-gLYb4BIadlfTOYT5gO503n8zQjXflgzpD0FcyKh0Mzx3rqCZKnHoJWV9xe1KXUJ5lx2JfcSHr/mhzS0PC/McAA==", "cpu": [ "s390x" ], "dev": true, "libc": [ "glibc" ], "license": "MIT", "optional": true, "os": [ "linux" ], "engines": { "node": "^20.19.0 || >=22.12.0" } }, "node_modules/@rolldown/binding-linux-x64-gnu": { "version": "1.1.5", "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.1.5.tgz", "integrity": "sha512-FjcpEKUyJygHgs1o50VYNvkt5+7Le/VEdYt0AkRpkL33MnyQfwr8l5mXwMmfmTbyMPr5vJLC+8/Gd9gXnwU1QQ==", "cpu": [ "x64" ], "dev": true, "libc": [ "glibc" ], "license": "MIT", "optional": true, "os": [ "linux" ], "engines": { "node": "^20.19.0 || >=22.12.0" } }, "node_modules/@rolldown/binding-linux-x64-musl": { "version": "1.1.5", "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.1.5.tgz", "integrity": "sha512-Me+PfPI2TMeOQk0gYWfLQZtTktrmzbr8cDboqX83XKc7UrgAi55gF+2dUkWdxd19n55Essp2yeca+O9N5rBxHg==", "cpu": [ "x64" ], "dev": true, "libc": [ "musl" ], "license": "MIT", "optional": true, "os": [ "linux" ], "engines": { "node": "^20.19.0 || >=22.12.0" } }, "node_modules/@rolldown/binding-openharmony-arm64": { "version": "1.1.5", "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.1.5.tgz", "integrity": "sha512-yc5WrLzXks6zCQfn9Oxr8pORKyl/pF+QjHmW/Qx3qu0oyrrNC+y2JLTU1E2rcWYAmzlnqngWXHQjy51VzW70Vw==", "cpu": [ "arm64" ], "dev": true, "license": "MIT", "optional": true, "os": [ "openharmony" ], "engines": { "node": "^20.19.0 || >=22.12.0" } }, "node_modules/@rolldown/binding-wasm32-wasi": { "version": "1.1.5", "resolved": "https://registry.npmjs.org/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-1.1.5.tgz", "integrity": "sha512-VbQGPX2b4r48TAMIM2cjgluIM1HYutm4pcTEJsle7iEP7sB1dFqtPLBVbdLAZCxy1txCcPxf4QFf4v8uvltPqA==", "cpu": [ "wasm32" ], "dev": true, "license": "MIT", "optional": true, "dependencies": { "@emnapi/core": "1.11.1", "@emnapi/runtime": "1.11.1", "@napi-rs/wasm-runtime": "^1.1.6" }, "engines": { "node": "^20.19.0 || >=22.12.0" } }, "node_modules/@rolldown/binding-win32-arm64-msvc": { "version": "1.1.5", "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.1.5.tgz", "integrity": "sha512-gHv82k63z4qpV5+Q1y/12KrK0ltWBukVDI8nZcbT7Tt/ZlOIVwppazneq0F93oDxTo3IgAMEDIoQh3E2n6mVsw==", "cpu": [ "arm64" ], "dev": true, "license": "MIT", "optional": true, "os": [ "win32" ], "engines": { "node": "^20.19.0 || >=22.12.0" } }, "node_modules/@rolldown/binding-win32-x64-msvc": { "version": "1.1.5", "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.1.5.tgz", "integrity": "sha512-tTZuDBPw85tEN5PQi1pnEBzDy0Z49HtScLAbD5t6hyeU92A95pRWaSMw1GZZi/RwgSgUIl0xrSlXIT/9QzvYSA==", "cpu": [ "x64" ], "dev": true, "license": "MIT", "optional": true, "os": [ "win32" ], "engines": { "node": "^20.19.0 || >=22.12.0" } }, "node_modules/@rolldown/pluginutils": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.1.tgz", "integrity": "sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw==", "dev": true, "license": "MIT" }, "node_modules/@standard-schema/spec": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.1.0.tgz", "integrity": "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==", "dev": true, "license": "MIT" }, "node_modules/@tweenjs/tween.js": { "version": "23.1.3", "resolved": "https://registry.npmjs.org/@tweenjs/tween.js/-/tween.js-23.1.3.tgz", "integrity": "sha512-vJmvvwFxYuGnF2axRtPYocag6Clbb5YS7kLL+SO/TeVFzHqDIWrNKYtcsPMibjDx9O+bu+psAy9NKfWklassUA==", "dev": true, "license": "MIT" }, "node_modules/@tybys/wasm-util": { "version": "0.10.3", "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.3.tgz", "integrity": "sha512-F3fo1MYrRJYL3zER0OUOmkutjr1Vp23m7OsSgp7nq4SP6OqX6C/56XFIPAl5bt3zaBRjmW7SGz3u/6LwFpYcOg==", "dev": true, "license": "MIT", "optional": true, "dependencies": { "tslib": "^2.4.0" } }, "node_modules/@types/chai": { "version": "5.2.3", "resolved": "https://registry.npmjs.org/@types/chai/-/chai-5.2.3.tgz", "integrity": "sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==", "dev": true, "license": "MIT", "dependencies": { "@types/deep-eql": "*", "assertion-error": "^2.0.1" } }, "node_modules/@types/deep-eql": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/@types/deep-eql/-/deep-eql-4.0.2.tgz", "integrity": "sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==", "dev": true, "license": "MIT" }, "node_modules/@types/estree": { "version": "1.0.9", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.9.tgz", "integrity": "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==", "dev": true, "license": "MIT" }, "node_modules/@types/node": { "version": "26.1.1", "resolved": "https://registry.npmjs.org/@types/node/-/node-26.1.1.tgz", "integrity": "sha512-nxAkRSVkN1Y0JC1W8ky/fTfkGsMmcrRsbx+3XoZE+rMOX71kLYTV7fLXpqud1GpbpP5TuffXFqfX7fH2GgZREw==", "dev": true, "license": "MIT", "dependencies": { "undici-types": "~8.3.0" } }, "node_modules/@types/stats.js": { "version": "0.17.4", "resolved": "https://registry.npmjs.org/@types/stats.js/-/stats.js-0.17.4.tgz", "integrity": "sha512-jIBvWWShCvlBqBNIZt0KAshWpvSjhkwkEu4ZUcASoAvhmrgAUI2t1dXrjSL4xXVLB4FznPrIsX3nKXFl/Dt4vA==", "dev": true, "license": "MIT" }, "node_modules/@types/three": { "version": "0.185.1", "resolved": "https://registry.npmjs.org/@types/three/-/three-0.185.1.tgz", "integrity": "sha512-db1xTb+EgYF2didW+eudSvVPtn75zo+fGsY8ShQrJY/B5ZBmC2Fiaykv3aImHAlCNEGuMPkPGXBJGLwzu5mC7A==", "dev": true, "license": "MIT", "dependencies": { "@dimforge/rapier3d-compat": "~0.12.0", "@tweenjs/tween.js": "~23.1.3", "@types/stats.js": "*", "@types/webxr": ">=0.5.17", "fflate": "~0.8.2", "meshoptimizer": "~1.1.1" } }, "node_modules/@types/webxr": { "version": "0.5.24", "resolved": "https://registry.npmjs.org/@types/webxr/-/webxr-0.5.24.tgz", "integrity": "sha512-h8fgEd/DpoS9CBrjEQXR+dIDraopAEfu4wYVNY2tEPwk60stPWhvZMf4Foo5FakuQ7HFZoa8WceaWFervK2Ovg==", "dev": true, "license": "MIT" }, "node_modules/@typescript/typescript-aix-ppc64": { "version": "7.0.2", "resolved": "https://registry.npmjs.org/@typescript/typescript-aix-ppc64/-/typescript-aix-ppc64-7.0.2.tgz", "integrity": "sha512-MTKKkWB7p/0E9xi1d1tHtZ5PiLkGEMIq88pK2CubZjOsLtYTLqhgIgi6zepFa+9GHZ6h05NMCkQxGKiPXMxXtQ==", "cpu": [ "ppc64" ], "dev": true, "license": "Apache-2.0", "optional": true, "os": [ "aix" ], "engines": { "node": ">=16.20.0" } }, "node_modules/@typescript/typescript-darwin-arm64": { "version": "7.0.2", "resolved": "https://registry.npmjs.org/@typescript/typescript-darwin-arm64/-/typescript-darwin-arm64-7.0.2.tgz", "integrity": "sha512-gowzar9MwS/aRWp6f3a4KUqzRjAZjOsmGNCM6LcTgXum+dBfgsBVMN+AgvOCCbguXyick6LJhpBszxMebJ8syA==", "cpu": [ "arm64" ], "dev": true, "license": "Apache-2.0", "optional": true, "os": [ "darwin" ], "engines": { "node": ">=16.20.0" } }, "node_modules/@typescript/typescript-darwin-x64": { "version": "7.0.2", "resolved": "https://registry.npmjs.org/@typescript/typescript-darwin-x64/-/typescript-darwin-x64-7.0.2.tgz", "integrity": "sha512-SZ9xZInqApNlNGc9s0W1VSsktYSOe9cFqNOIqmN1Gs8SmkjKZYFt017G4VwPxASInODuAdbTW7sXiFUf893RgA==", "cpu": [ "x64" ], "dev": true, "license": "Apache-2.0", "optional": true, "os": [ "darwin" ], "engines": { "node": ">=16.20.0" } }, "node_modules/@typescript/typescript-freebsd-arm64": { "version": "7.0.2", "resolved": "https://registry.npmjs.org/@typescript/typescript-freebsd-arm64/-/typescript-freebsd-arm64-7.0.2.tgz", "integrity": "sha512-W5NH4y/J0plIIS5b2xvTEkU7JFxyqdMAOgf+Ilhl0vHQXKO5dZoxd+C/jEtq56c4F3wk71RB4BMRQ2XdI+bwYQ==", "cpu": [ "arm64" ], "dev": true, "license": "Apache-2.0", "optional": true, "os": [ "freebsd" ], "engines": { "node": ">=16.20.0" } }, "node_modules/@typescript/typescript-freebsd-x64": { "version": "7.0.2", "resolved": "https://registry.npmjs.org/@typescript/typescript-freebsd-x64/-/typescript-freebsd-x64-7.0.2.tgz", "integrity": "sha512-UMGDx5sTpzNw3WiPebH7l90IWfJggEd+egHt/q6p7/Cm3zqoV7VxkGXt+3DxPIw8CcmvAB0j3sVVfbhX+M4Tpw==", "cpu": [ "x64" ], "dev": true, "license": "Apache-2.0", "optional": true, "os": [ "freebsd" ], "engines": { "node": ">=16.20.0" } }, "node_modules/@typescript/typescript-linux-arm": { "version": "7.0.2", "resolved": "https://registry.npmjs.org/@typescript/typescript-linux-arm/-/typescript-linux-arm-7.0.2.tgz", "integrity": "sha512-gffT3xPz9sR7j/YJExkyPntrI0P2EP9XbOyWzth2/Gs0RstK+90RBcO0ncXoXy/beYll1SXw846Nf2zdnEz0QQ==", "cpu": [ "arm" ], "dev": true, "license": "Apache-2.0", "optional": true, "os": [ "linux" ], "engines": { "node": ">=16.20.0" } }, "node_modules/@typescript/typescript-linux-arm64": { "version": "7.0.2", "resolved": "https://registry.npmjs.org/@typescript/typescript-linux-arm64/-/typescript-linux-arm64-7.0.2.tgz", "integrity": "sha512-Qh4eU4/y3yDjnfjjyPYihMj5/ODIlmt+Bzu17OI+fiSRDW57QmU5SiN63exPRNJPKUzcc1INa1NXdrJ+MqHjUQ==", "cpu": [ "arm64" ], "dev": true, "license": "Apache-2.0", "optional": true, "os": [ "linux" ], "engines": { "node": ">=16.20.0" } }, "node_modules/@typescript/typescript-linux-loong64": { "version": "7.0.2", "resolved": "https://registry.npmjs.org/@typescript/typescript-linux-loong64/-/typescript-linux-loong64-7.0.2.tgz", "integrity": "sha512-uEHck9i8hoAzXPiYRib1O7miOnz23SxIeVl6F4LXox+qov1K35jHcEW6VHKvZI+pyvl7fZEP4MCU5LYvIq1GuQ==", "cpu": [ "loong64" ], "dev": true, "license": "Apache-2.0", "optional": true, "os": [ "linux" ], "engines": { "node": ">=16.20.0" } }, "node_modules/@typescript/typescript-linux-mips64el": { "version": "7.0.2", "resolved": "https://registry.npmjs.org/@typescript/typescript-linux-mips64el/-/typescript-linux-mips64el-7.0.2.tgz", "integrity": "sha512-R4KvAMnE43W5Qeqb0Ly56O3mWMWIAgsMyz36DCaycd5nbg/9kzm0liw3JocfRqyJY0KPmzFjbswozXyW0DnIYA==", "cpu": [ "mips64el" ], "dev": true, "license": "Apache-2.0", "optional": true, "os": [ "linux" ], "engines": { "node": ">=16.20.0" } }, "node_modules/@typescript/typescript-linux-ppc64": { "version": "7.0.2", "resolved": "https://registry.npmjs.org/@typescript/typescript-linux-ppc64/-/typescript-linux-ppc64-7.0.2.tgz", "integrity": "sha512-DORx5b3sd/4S7eayxm4FQv+A7CrkUIGRaHiwI8oiHTAI1fAPWhF4J0vAlkC8biAlHSVVwxMQ3tjZ2/DVbnQiiA==", "cpu": [ "ppc64" ], "dev": true, "license": "Apache-2.0", "optional": true, "os": [ "linux" ], "engines": { "node": ">=16.20.0" } }, "node_modules/@typescript/typescript-linux-riscv64": { "version": "7.0.2", "resolved": "https://registry.npmjs.org/@typescript/typescript-linux-riscv64/-/typescript-linux-riscv64-7.0.2.tgz", "integrity": "sha512-wf0jqEDOjrPRnKwYRyyJDRo11KMbvMFrU+q4zqKyChODBzvlkbhNQfKvLxQCcwTpdDaXSHZTVuh0JoCrKCUMHQ==", "cpu": [ "riscv64" ], "dev": true, "license": "Apache-2.0", "optional": true, "os": [ "linux" ], "engines": { "node": ">=16.20.0" } }, "node_modules/@typescript/typescript-linux-s390x": { "version": "7.0.2", "resolved": "https://registry.npmjs.org/@typescript/typescript-linux-s390x/-/typescript-linux-s390x-7.0.2.tgz", "integrity": "sha512-IkwJc3L7yhytWd/ewjyxNDfOmswCm9GWMJT/ue/dU4aZNbwZeYAetq42VyLmsmSjvoX7z74X6ZaYCtzAr0EuGw==", "cpu": [ "s390x" ], "dev": true, "license": "Apache-2.0", "optional": true, "os": [ "linux" ], "engines": { "node": ">=16.20.0" } }, "node_modules/@typescript/typescript-linux-x64": { "version": "7.0.2", "resolved": "https://registry.npmjs.org/@typescript/typescript-linux-x64/-/typescript-linux-x64-7.0.2.tgz", "integrity": "sha512-EYdf2cNg7rgCWJnxCdJ+F3V39O8ihb37eHAu1LK8oAFizgTQbPOK7zHHXbPt8rX24COqODXeI3sIf0fCXG7H/A==", "cpu": [ "x64" ], "dev": true, "license": "Apache-2.0", "optional": true, "os": [ "linux" ], "engines": { "node": ">=16.20.0" } }, "node_modules/@typescript/typescript-netbsd-arm64": { "version": "7.0.2", "resolved": "https://registry.npmjs.org/@typescript/typescript-netbsd-arm64/-/typescript-netbsd-arm64-7.0.2.tgz", "integrity": "sha512-+polYF4MF04aPpO5FTkHran9yUQDSXqy5GiSDKpsll5jy3l3+g9QLhpf39T+ePtefhXLOGrLl0QIjkQP6VnelA==", "cpu": [ "arm64" ], "dev": true, "license": "Apache-2.0", "optional": true, "os": [ "netbsd" ], "engines": { "node": ">=16.20.0" } }, "node_modules/@typescript/typescript-netbsd-x64": { "version": "7.0.2", "resolved": "https://registry.npmjs.org/@typescript/typescript-netbsd-x64/-/typescript-netbsd-x64-7.0.2.tgz", "integrity": "sha512-8YIT0EHM/3dq10ZOVF/A7pc/YSMtbcecct4rWtexrnSCHOPcpC2KTLXfTCR6vDpnSiY12heNb1GiN/wu+T/FyA==", "cpu": [ "x64" ], "dev": true, "license": "Apache-2.0", "optional": true, "os": [ "netbsd" ], "engines": { "node": ">=16.20.0" } }, "node_modules/@typescript/typescript-openbsd-arm64": { "version": "7.0.2", "resolved": "https://registry.npmjs.org/@typescript/typescript-openbsd-arm64/-/typescript-openbsd-arm64-7.0.2.tgz", "integrity": "sha512-APT8+ClYnuYm1u9+kgGXoMj2VzWzcymwh2gNSQVySHfkRDGOTVkoWLjCmOQSaO+PoqQ57B0flRp9SA+7GnnkzQ==", "cpu": [ "arm64" ], "dev": true, "license": "Apache-2.0", "optional": true, "os": [ "openbsd" ], "engines": { "node": ">=16.20.0" } }, "node_modules/@typescript/typescript-openbsd-x64": { "version": "7.0.2", "resolved": "https://registry.npmjs.org/@typescript/typescript-openbsd-x64/-/typescript-openbsd-x64-7.0.2.tgz", "integrity": "sha512-yX7s+Q0Dln0Dt9tEzZsAjXXR/+ytBM7AlglaqyeMPxQszJ1JhlJdZ6jLA+IzldHtflX81em7lDao1xXu+aRRkg==", "cpu": [ "x64" ], "dev": true, "license": "Apache-2.0", "optional": true, "os": [ "openbsd" ], "engines": { "node": ">=16.20.0" } }, "node_modules/@typescript/typescript-sunos-x64": { "version": "7.0.2", "resolved": "https://registry.npmjs.org/@typescript/typescript-sunos-x64/-/typescript-sunos-x64-7.0.2.tgz", "integrity": "sha512-dLJDGaLZ1D4HPQn62u1n8mBDkJREwMsAkCdkwd4Ieqw+x3TUyTsqY0YiBCtE6H6OzzgGk3iuZ3vFWRS+E8/d1g==", "cpu": [ "x64" ], "dev": true, "license": "Apache-2.0", "optional": true, "os": [ "sunos" ], "engines": { "node": ">=16.20.0" } }, "node_modules/@typescript/typescript-win32-arm64": { "version": "7.0.2", "resolved": "https://registry.npmjs.org/@typescript/typescript-win32-arm64/-/typescript-win32-arm64-7.0.2.tgz", "integrity": "sha512-Gyl1Vy6OsWesLzmq+EP0Fb7b4Nid5232AvcA2SFcdYreldpNtYFFofPjnt62y9hQy7VTaZp65ICJjuAQRaVcIQ==", "cpu": [ "arm64" ], "dev": true, "license": "Apache-2.0", "optional": true, "os": [ "win32" ], "engines": { "node": ">=16.20.0" } }, "node_modules/@typescript/typescript-win32-x64": { "version": "7.0.2", "resolved": "https://registry.npmjs.org/@typescript/typescript-win32-x64/-/typescript-win32-x64-7.0.2.tgz", "integrity": "sha512-0BQ3HkAHHlKLSp1qRvf3SUhGpGsDuhB/jgFw75guyqbxJqEaS0Cw/VFO8i2nHglJUzQCRtMMR/IBAKE3ETMC4g==", "cpu": [ "x64" ], "dev": true, "license": "Apache-2.0", "optional": true, "os": [ "win32" ], "engines": { "node": ">=16.20.0" } }, "node_modules/@vitest/expect": { "version": "4.1.10", "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-4.1.10.tgz", "integrity": "sha512-YsCn+qAk1GWjQOWFEsEcL2gNQ0zmVmQu3T03qP6UyjhtmdtwtbuI+DASn/7iQB3HGTXkdBwGddzxPlmiql5vlA==", "dev": true, "license": "MIT", "dependencies": { "@standard-schema/spec": "^1.1.0", "@types/chai": "^5.2.2", "@vitest/spy": "4.1.10", "@vitest/utils": "4.1.10", "chai": "^6.2.2", "tinyrainbow": "^3.1.0" }, "funding": { "url": "https://opencollective.com/vitest" } }, "node_modules/@vitest/mocker": { "version": "4.1.10", "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-4.1.10.tgz", "integrity": "sha512-v0xaezt+DKEmKfaxg133ldzADrwLGd7Ze1MfQQTYfvs8OqZIwbxyxaYURivwV7sWy5fqn3rH5uOrSp07bp44Ow==", "dev": true, "license": "MIT", "dependencies": { "@vitest/spy": "4.1.10", "estree-walker": "^3.0.3", "magic-string": "^0.30.21" }, "funding": { "url": "https://opencollective.com/vitest" }, "peerDependencies": { "msw": "^2.4.9", "vite": "^6.0.0 || ^7.0.0 || ^8.0.0" }, "peerDependenciesMeta": { "msw": { "optional": true }, "vite": { "optional": true } } }, "node_modules/@vitest/pretty-format": { "version": "4.1.10", "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-4.1.10.tgz", "integrity": "sha512-W1HsjSH4MXQ9YfmmhLAoIYf1HRfekQCGngeIgcei6MP5QQGWUe0gkopdZQaVCFO+JDJMrAJGwa5pRpNpvy4P8Q==", "dev": true, "license": "MIT", "dependencies": { "tinyrainbow": "^3.1.0" }, "funding": { "url": "https://opencollective.com/vitest" } }, "node_modules/@vitest/runner": { "version": "4.1.10", "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-4.1.10.tgz", "integrity": "sha512-IKI6kpIH+LmpROplyLwBBaCfMgOZOMsygVa6BARD6ahA04VRuJSa6OaVG7kRvSEMD870Vd91rSSw0eegtWyLGg==", "dev": true, "license": "MIT", "dependencies": { "@vitest/utils": "4.1.10", "pathe": "^2.0.3" }, "funding": { "url": "https://opencollective.com/vitest" } }, "node_modules/@vitest/snapshot": { "version": "4.1.10", "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-4.1.10.tgz", "integrity": "sha512-xRkfOT1qpTAi/Ti4Y1LtfRc3kEuqxGw59eN2jN9pRWMtS/XDevekhcFSqvQqjUNGksfjMJu3Y+oJ+4Ypn2OaJw==", "dev": true, "license": "MIT", "dependencies": { "@vitest/pretty-format": "4.1.10", "@vitest/utils": "4.1.10", "magic-string": "^0.30.21", "pathe": "^2.0.3" }, "funding": { "url": "https://opencollective.com/vitest" } }, "node_modules/@vitest/spy": { "version": "4.1.10", "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-4.1.10.tgz", "integrity": "sha512-PLf/Ugvoq5wO/b4rwYCR1h2PSIdXz7wnkQFMiUpLdtM7l6pqVFcQIBEHyT1+l+cj7mNwAfZHzqXqDyjvOuwbDw==", "dev": true, "license": "MIT", "funding": { "url": "https://opencollective.com/vitest" } }, "node_modules/@vitest/utils": { "version": "4.1.10", "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-4.1.10.tgz", "integrity": "sha512-fy9am/HWxbaGt/Sawrp90vt6Y6jQwf1RX77cz3uwoJwJVMli/e1IEwRPnMNJ7vKfPTwo0diXifkpPvwH9v7nGA==", "dev": true, "license": "MIT", "dependencies": { "@vitest/pretty-format": "4.1.10", "convert-source-map": "^2.0.0", "tinyrainbow": "^3.1.0" }, "funding": { "url": "https://opencollective.com/vitest" } }, "node_modules/assertion-error": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz", "integrity": "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==", "dev": true, "license": "MIT", "engines": { "node": ">=12" } }, "node_modules/chai": { "version": "6.2.2", "resolved": "https://registry.npmjs.org/chai/-/chai-6.2.2.tgz", "integrity": "sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg==", "dev": true, "license": "MIT", "engines": { "node": ">=18" } }, "node_modules/convert-source-map": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", "dev": true, "license": "MIT" }, "node_modules/detect-libc": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", "dev": true, "license": "Apache-2.0", "engines": { "node": ">=8" } }, "node_modules/es-module-lexer": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-2.3.1.tgz", "integrity": "sha512-shc1dbU90Yl/xq1QrC7QRtfcwURZuVRfPhZbDoldJ1cn1gzDvBaBWlv0eFolj5+0znnPJz5TXLxsN77X/12KTA==", "dev": true, "license": "MIT" }, "node_modules/estree-walker": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", "dev": true, "license": "MIT", "dependencies": { "@types/estree": "^1.0.0" } }, "node_modules/expect-type": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.4.0.tgz", "integrity": "sha512-KfYbmpRm0VbLjEvVa9yGwCi9GI34xvi7A/HXYWQO65CSD2u3MczUJSuwXKFIxlGsgBQizV9q5J9NHj4VG0n+pA==", "dev": true, "license": "Apache-2.0", "engines": { "node": ">=12.0.0" } }, "node_modules/fdir": { "version": "6.5.0", "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", "dev": true, "license": "MIT", "engines": { "node": ">=12.0.0" }, "peerDependencies": { "picomatch": "^3 || ^4" }, "peerDependenciesMeta": { "picomatch": { "optional": true } } }, "node_modules/fflate": { "version": "0.8.3", "resolved": "https://registry.npmjs.org/fflate/-/fflate-0.8.3.tgz", "integrity": "sha512-tbZNuJrLwGUp3zshBtdy4W+ORxZuIh8a5ilyIEQDC5rY1f3U20JMry0Ll3WBzU58EZKsEuJFXhb5gwv8CsPvgA==", "dev": true, "license": "MIT" }, "node_modules/fsevents": { "version": "2.3.2", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", "dev": true, "hasInstallScript": true, "license": "MIT", "optional": true, "os": [ "darwin" ], "engines": { "node": "^8.16.0 || ^10.6.0 || >=11.0.0" } }, "node_modules/lightningcss": { "version": "1.33.0", "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.33.0.tgz", "integrity": "sha512-WkUDrojuJs0xkgGf2udWxa3yGBRxPtxUkB79i6aCZLRgc7PM8fZe9TosfPDcvEpQZbuFASnHYmRLBLUbmLOIIA==", "dev": true, "license": "MPL-2.0", "dependencies": { "detect-libc": "^2.0.3" }, "engines": { "node": ">= 12.0.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/parcel" }, "optionalDependencies": { "lightningcss-android-arm64": "1.33.0", "lightningcss-darwin-arm64": "1.33.0", "lightningcss-darwin-x64": "1.33.0", "lightningcss-freebsd-x64": "1.33.0", "lightningcss-linux-arm-gnueabihf": "1.33.0", "lightningcss-linux-arm64-gnu": "1.33.0", "lightningcss-linux-arm64-musl": "1.33.0", "lightningcss-linux-x64-gnu": "1.33.0", "lightningcss-linux-x64-musl": "1.33.0", "lightningcss-win32-arm64-msvc": "1.33.0", "lightningcss-win32-x64-msvc": "1.33.0" } }, "node_modules/lightningcss-android-arm64": { "version": "1.33.0", "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.33.0.tgz", "integrity": "sha512-gEpRTalKdosp4Bb8qWtc2iOgE5SeIHlpS1up9bFq2wAyYhl1UdTObYiHe98zEM9SQvSoqQZ1IQD0JNpg3Ml5pg==", "cpu": [ "arm64" ], "dev": true, "license": "MPL-2.0", "optional": true, "os": [ "android" ], "engines": { "node": ">= 12.0.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/parcel" } }, "node_modules/lightningcss-darwin-arm64": { "version": "1.33.0", "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.33.0.tgz", "integrity": "sha512-Sciaz8eenNTKn9b3t7+xr0ipTp9YxKQY4npwQ3mrRuL0BAVHBLyZxofhaKBAVtzmtRZ/zTyo0/to4B1uWG/Djg==", "cpu": [ "arm64" ], "dev": true, "license": "MPL-2.0", "optional": true, "os": [ "darwin" ], "engines": { "node": ">= 12.0.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/parcel" } }, "node_modules/lightningcss-darwin-x64": { "version": "1.33.0", "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.33.0.tgz", "integrity": "sha512-Z5UPAxzrjlWNNyGy6i65cJzzvgJ5D3T6wMvs+gWpY9d7qRhANrxqAp6LhxIgZhWEw18RfJTGcRxjuLIBr+m8XQ==", "cpu": [ "x64" ], "dev": true, "license": "MPL-2.0", "optional": true, "os": [ "darwin" ], "engines": { "node": ">= 12.0.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/parcel" } }, "node_modules/lightningcss-freebsd-x64": { "version": "1.33.0", "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.33.0.tgz", "integrity": "sha512-QQM/Ti/hQajJwCY+RiWuCZ9sdtI/XQk7nDK5vC8kkdwixezOlDgvDx7+RT+QjK6FcFT4MpsuoBnHIo/O3StRRg==", "cpu": [ "x64" ], "dev": true, "license": "MPL-2.0", "optional": true, "os": [ "freebsd" ], "engines": { "node": ">= 12.0.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/parcel" } }, "node_modules/lightningcss-linux-arm-gnueabihf": { "version": "1.33.0", "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.33.0.tgz", "integrity": "sha512-N7FVBe6iS24MlM6R/4RBTxGhQheZGs7tiQ9U32UtF75NzP5Q7xWPRqLBCKxlRQRk3rY1jCIPLzx7WzOhuUIRLQ==", "cpu": [ "arm" ], "dev": true, "license": "MPL-2.0", "optional": true, "os": [ "linux" ], "engines": { "node": ">= 12.0.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/parcel" } }, "node_modules/lightningcss-linux-arm64-gnu": { "version": "1.33.0", "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.33.0.tgz", "integrity": "sha512-j2v/itmy4HlNxlc6voKXYgBqNi0Ng2LShg4z7GufpEgs05P+2suBVyi9I6YHq5uoVFx9ETin3eCEhLVyXGQnKg==", "cpu": [ "arm64" ], "dev": true, "libc": [ "glibc" ], "license": "MPL-2.0", "optional": true, "os": [ "linux" ], "engines": { "node": ">= 12.0.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/parcel" } }, "node_modules/lightningcss-linux-arm64-musl": { "version": "1.33.0", "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.33.0.tgz", "integrity": "sha512-yiO5ROMuYQgXbC60yjZU5CYSFZGKXL0HFATXt9mHJn1+zW55oCtMI9NfcVhYLMFDL7gV7oBPon/EmMMGg2OvtQ==", "cpu": [ "arm64" ], "dev": true, "libc": [ "musl" ], "license": "MPL-2.0", "optional": true, "os": [ "linux" ], "engines": { "node": ">= 12.0.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/parcel" } }, "node_modules/lightningcss-linux-x64-gnu": { "version": "1.33.0", "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.33.0.tgz", "integrity": "sha512-ar+Ju7LmcN0Jo4FpL4hpFybwNG9/3A/Br5KW2n2jyODg3MEZXaDYADdemoNS+BDNfMgKvylJLj4S5tyRActuAg==", "cpu": [ "x64" ], "dev": true, "libc": [ "glibc" ], "license": "MPL-2.0", "optional": true, "os": [ "linux" ], "engines": { "node": ">= 12.0.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/parcel" } }, "node_modules/lightningcss-linux-x64-musl": { "version": "1.33.0", "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.33.0.tgz", "integrity": "sha512-RYiYbkokw0trfKqqzfF55lginwEPrD3OJDfTuJzFs1MK6iFnDenaz1fqLLtX4ITG3OktJQXOeTaw1awrBAlZPw==", "cpu": [ "x64" ], "dev": true, "libc": [ "musl" ], "license": "MPL-2.0", "optional": true, "os": [ "linux" ], "engines": { "node": ">= 12.0.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/parcel" } }, "node_modules/lightningcss-win32-arm64-msvc": { "version": "1.33.0", "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.33.0.tgz", "integrity": "sha512-1K+MPfLSFVpphzpdbfkhlWk6wBrTObBzS2T6db10PNOZgR9GoVsAWzwNyuhUYYbTp23j+4RrncfujZ4uAzXvwA==", "cpu": [ "arm64" ], "dev": true, "license": "MPL-2.0", "optional": true, "os": [ "win32" ], "engines": { "node": ">= 12.0.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/parcel" } }, "node_modules/lightningcss-win32-x64-msvc": { "version": "1.33.0", "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.33.0.tgz", "integrity": "sha512-OlEICDx/Xl0FqSp4bry8zFnCvGpig3Gl4gCquvYwHuqJKEC1+n9NgDniFvqHGmMv1ZkqDJrDqKKSykTDX+ehuA==", "cpu": [ "x64" ], "dev": true, "license": "MPL-2.0", "optional": true, "os": [ "win32" ], "engines": { "node": ">= 12.0.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/parcel" } }, "node_modules/magic-string": { "version": "0.30.21", "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", "dev": true, "license": "MIT", "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.5" } }, "node_modules/meshoptimizer": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/meshoptimizer/-/meshoptimizer-1.1.1.tgz", "integrity": "sha512-oRFNWJRDA/WTrVj7NWvqa5HqE1t9MYDj2VaWirQCzCCrAd2GHrqR/sQezCxiWATPNlKTcRaPRHPJwIRoPBAp5g==", "dev": true, "license": "MIT" }, "node_modules/nanoid": { "version": "3.3.18", "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.18.tgz", "integrity": "sha512-DTg4MJbGMWkfi6VZFdNt2/caMbQy4Ou+Op/hJQvGEWcnVfoA1QA+xzRKAzw9jD6+GVOOeYr/mIcuDSdug6F6+w==", "dev": true, "funding": [ { "type": "github", "url": "https://github.com/sponsors/ai" } ], "license": "MIT", "bin": { "nanoid": "bin/nanoid.cjs" }, "engines": { "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" } }, "node_modules/obug": { "version": "2.1.4", "resolved": "https://registry.npmjs.org/obug/-/obug-2.1.4.tgz", "integrity": "sha512-4a+OsYv9UktOJKE+l1A4OufDgdRF9PifWj+tJnHURo/P+WOxpG4GzUFL9qCalmWauao6ogiG+QvnCovwPoyAWA==", "dev": true, "funding": [ "https://github.com/sponsors/sxzz", "https://opencollective.com/debug" ], "license": "MIT", "engines": { "node": ">=12.20.0" } }, "node_modules/pathe": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", "dev": true, "license": "MIT" }, "node_modules/picocolors": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", "dev": true, "license": "ISC" }, "node_modules/picomatch": { "version": "4.0.5", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.5.tgz", "integrity": "sha512-RvwwcruNjI1ncT5xRakeyS9Lf8lcItv34KD+aif+VH9kduAyfYBipGh12274xtenIPZ119/R9BdTBa8gAwSh0A==", "dev": true, "license": "MIT", "engines": { "node": ">=12" }, "funding": { "url": "https://github.com/sponsors/jonschlinkert" } }, "node_modules/playwright": { "version": "1.61.1", "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.61.1.tgz", "integrity": "sha512-DWnY5o3YbLWK4GovuAVwpqL+1VwGNdUGrRr++8j8PtQQzvAVZUIMjKQ90fY689sEJZJBbZVw1rXaOKSTitkzPQ==", "dev": true, "license": "Apache-2.0", "dependencies": { "playwright-core": "1.61.1" }, "bin": { "playwright": "cli.js" }, "engines": { "node": ">=18" }, "optionalDependencies": { "fsevents": "2.3.2" } }, "node_modules/playwright-core": { "version": "1.61.1", "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.61.1.tgz", "integrity": "sha512-h7Qlt6m4REp25qvIdvbDtVmD4LqVXfpRxhORv9L0jzETM05p4fuPJ3dKyuSXQxDSbXnmS79HAgi9589lGSpLkg==", "dev": true, "license": "Apache-2.0", "bin": { "playwright-core": "cli.js" }, "engines": { "node": ">=18" } }, "node_modules/postcss": { "version": "8.5.26", "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.26.tgz", "integrity": "sha512-u82N74LFzG8ca+dD8puPnplTXoGH4fTPpVGuIbt36G3qvNlkvfD0lEAZSxaly3KX8TS/L1A1gsCEmvKmBcVbkQ==", "dev": true, "funding": [ { "type": "opencollective", "url": "https://opencollective.com/postcss/" }, { "type": "tidelift", "url": "https://tidelift.com/funding/github/npm/postcss" }, { "type": "github", "url": "https://github.com/sponsors/ai" } ], "license": "MIT", "dependencies": { "nanoid": "^3.3.17", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" }, "engines": { "node": "^10 || ^12 || >=14" } }, "node_modules/rolldown": { "version": "1.1.5", "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.1.5.tgz", "integrity": "sha512-t9z29cJjXf/vxQ8dyhCSpt6H6aSwHTk8cT5I3iy6SMXuFpk5mB6PL6XfC8PCwrPTx93udwKUm9HRteAlTGBLiA==", "dev": true, "license": "MIT", "dependencies": { "@oxc-project/types": "=0.139.0", "@rolldown/pluginutils": "^1.0.0" }, "bin": { "rolldown": "bin/cli.mjs" }, "engines": { "node": "^20.19.0 || >=22.12.0" }, "optionalDependencies": { "@rolldown/binding-android-arm64": "1.1.5", "@rolldown/binding-darwin-arm64": "1.1.5", "@rolldown/binding-darwin-x64": "1.1.5", "@rolldown/binding-freebsd-x64": "1.1.5", "@rolldown/binding-linux-arm-gnueabihf": "1.1.5", "@rolldown/binding-linux-arm64-gnu": "1.1.5", "@rolldown/binding-linux-arm64-musl": "1.1.5", "@rolldown/binding-linux-ppc64-gnu": "1.1.5", "@rolldown/binding-linux-s390x-gnu": "1.1.5", "@rolldown/binding-linux-x64-gnu": "1.1.5", "@rolldown/binding-linux-x64-musl": "1.1.5", "@rolldown/binding-openharmony-arm64": "1.1.5", "@rolldown/binding-wasm32-wasi": "1.1.5", "@rolldown/binding-win32-arm64-msvc": "1.1.5", "@rolldown/binding-win32-x64-msvc": "1.1.5" } }, "node_modules/siginfo": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz", "integrity": "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==", "dev": true, "license": "ISC" }, "node_modules/source-map-js": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", "dev": true, "license": "BSD-3-Clause", "engines": { "node": ">=0.10.0" } }, "node_modules/stackback": { "version": "0.0.2", "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz", "integrity": "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==", "dev": true, "license": "MIT" }, "node_modules/std-env": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/std-env/-/std-env-4.2.0.tgz", "integrity": "sha512-oCUKSupKTHX53EyjDtuZQ64pjLJ6yYCtpmEw0goYxtjG9KpbRe8KAsl2tBUGU9DyMcJ0RwJ8GqJAFzMXcXW1Rw==", "dev": true, "license": "MIT" }, "node_modules/three": { "version": "0.185.1", "resolved": "https://registry.npmjs.org/three/-/three-0.185.1.tgz", "integrity": "sha512-5aojFCXKwnjBRZvUnt3WFfEcvUJgkN5LlijRFN95hMy8WVkG4I0QNcJE+OuWvuJ0bOdStrbfXn0pkd6/QyiAlg==", "license": "MIT" }, "node_modules/tinybench": { "version": "2.9.0", "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz", "integrity": "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==", "dev": true, "license": "MIT" }, "node_modules/tinyexec": { "version": "1.2.4", "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.2.4.tgz", "integrity": "sha512-SHf/r48b7vOrjve9PxJo3MN5v5yuyjHvdUcrQffT3WXMUfnGmHDVbC4k3sHJaJTgZCwpUplIaAo5ANtMyp3YHg==", "dev": true, "license": "MIT", "engines": { "node": ">=18" } }, "node_modules/tinyglobby": { "version": "0.2.17", "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.17.tgz", "integrity": "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==", "dev": true, "license": "MIT", "dependencies": { "fdir": "^6.5.0", "picomatch": "^4.0.4" }, "engines": { "node": ">=12.0.0" }, "funding": { "url": "https://github.com/sponsors/SuperchupuDev" } }, "node_modules/tinyrainbow": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-3.1.0.tgz", "integrity": "sha512-Bf+ILmBgretUrdJxzXM0SgXLZ3XfiaUuOj/IKQHuTXip+05Xn+uyEYdVg0kYDipTBcLrCVyUzAPz7QmArb0mmw==", "dev": true, "license": "MIT", "engines": { "node": ">=14.0.0" } }, "node_modules/tslib": { "version": "2.8.1", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", "dev": true, "license": "0BSD", "optional": true }, "node_modules/typescript": { "version": "7.0.2", "resolved": "https://registry.npmjs.org/typescript/-/typescript-7.0.2.tgz", "integrity": "sha512-8FYau96o3NKOhbjKi/qNvG/W5jhzxkbdm5sj9AbZ/5T5sWqn3hJgLfGx27sRKZWTvyzCP8dLRBTf5tBTSRVUNA==", "dev": true, "license": "Apache-2.0", "bin": { "tsc": "bin/tsc" }, "engines": { "node": ">=16.20.0" }, "optionalDependencies": { "@typescript/typescript-aix-ppc64": "7.0.2", "@typescript/typescript-darwin-arm64": "7.0.2", "@typescript/typescript-darwin-x64": "7.0.2", "@typescript/typescript-freebsd-arm64": "7.0.2", "@typescript/typescript-freebsd-x64": "7.0.2", "@typescript/typescript-linux-arm": "7.0.2", "@typescript/typescript-linux-arm64": "7.0.2", "@typescript/typescript-linux-loong64": "7.0.2", "@typescript/typescript-linux-mips64el": "7.0.2", "@typescript/typescript-linux-ppc64": "7.0.2", "@typescript/typescript-linux-riscv64": "7.0.2", "@typescript/typescript-linux-s390x": "7.0.2", "@typescript/typescript-linux-x64": "7.0.2", "@typescript/typescript-netbsd-arm64": "7.0.2", "@typescript/typescript-netbsd-x64": "7.0.2", "@typescript/typescript-openbsd-arm64": "7.0.2", "@typescript/typescript-openbsd-x64": "7.0.2", "@typescript/typescript-sunos-x64": "7.0.2", "@typescript/typescript-win32-arm64": "7.0.2", "@typescript/typescript-win32-x64": "7.0.2" } }, "node_modules/undici-types": { "version": "8.3.0", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-8.3.0.tgz", "integrity": "sha512-j375ScV60dom+YkPFIfTLcOiPxkN/buHz5GobjLhixFuANaNs3C9l4GmrWqejgXWJ7BbJcFYpTEUkS1Ge8bpZQ==", "dev": true, "license": "MIT" }, "node_modules/vite": { "version": "8.1.5", "resolved": "https://registry.npmjs.org/vite/-/vite-8.1.5.tgz", "integrity": "sha512-7ULLwsCdYx/nRyrpiEwvqb5TFHrMVZyBt+rg/OAXT7rgj/z+DtTDyKFeLAdDkubDVDKD8jOsndmy7m55XcfUsw==", "dev": true, "license": "MIT", "dependencies": { "lightningcss": "^1.32.0", "picomatch": "^4.0.5", "postcss": "^8.5.17", "rolldown": "~1.1.5", "tinyglobby": "^0.2.17" }, "bin": { "vite": "bin/vite.js" }, "engines": { "node": "^20.19.0 || >=22.12.0" }, "funding": { "url": "https://github.com/vitejs/vite?sponsor=1" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^20.19.0 || >=22.12.0", "@vitejs/devtools": "^0.3.0", "esbuild": "^0.27.0 || ^0.28.0", "jiti": ">=1.21.0", "less": "^4.0.0", "sass": "^1.70.0", "sass-embedded": "^1.70.0", "stylus": ">=0.54.8", "sugarss": "^5.0.0", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "peerDependenciesMeta": { "@types/node": { "optional": true }, "@vitejs/devtools": { "optional": true }, "esbuild": { "optional": true }, "jiti": { "optional": true }, "less": { "optional": true }, "sass": { "optional": true }, "sass-embedded": { "optional": true }, "stylus": { "optional": true }, "sugarss": { "optional": true }, "terser": { "optional": true }, "tsx": { "optional": true }, "yaml": { "optional": true } } }, "node_modules/vite/node_modules/fsevents": { "version": "2.3.3", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", "dev": true, "hasInstallScript": true, "license": "MIT", "optional": true, "os": [ "darwin" ], "engines": { "node": "^8.16.0 || ^10.6.0 || >=11.0.0" } }, "node_modules/vitest": { "version": "4.1.10", "resolved": "https://registry.npmjs.org/vitest/-/vitest-4.1.10.tgz", "integrity": "sha512-R9jUTe5S4Qb0HCd4TNqpC7oGcrMssMRGXLW80ubjWsW9VH5GF8y1Y0SFLY9AbqSk6nt0PnOx4H4WNJYZ13GUPw==", "dev": true, "license": "MIT", "dependencies": { "@vitest/expect": "4.1.10", "@vitest/mocker": "4.1.10", "@vitest/pretty-format": "4.1.10", "@vitest/runner": "4.1.10", "@vitest/snapshot": "4.1.10", "@vitest/spy": "4.1.10", "@vitest/utils": "4.1.10", "es-module-lexer": "^2.0.0", "expect-type": "^1.3.0", "magic-string": "^0.30.21", "obug": "^2.1.1", "pathe": "^2.0.3", "picomatch": "^4.0.3", "std-env": "^4.0.0-rc.1", "tinybench": "^2.9.0", "tinyexec": "^1.0.2", "tinyglobby": "^0.2.15", "tinyrainbow": "^3.1.0", "vite": "^6.0.0 || ^7.0.0 || ^8.0.0", "why-is-node-running": "^2.3.0" }, "bin": { "vitest": "vitest.mjs" }, "engines": { "node": "^20.0.0 || ^22.0.0 || >=24.0.0" }, "funding": { "url": "https://opencollective.com/vitest" }, "peerDependencies": { "@edge-runtime/vm": "*", "@opentelemetry/api": "^1.9.0", "@types/node": "^20.0.0 || ^22.0.0 || >=24.0.0", "@vitest/browser-playwright": "4.1.10", "@vitest/browser-preview": "4.1.10", "@vitest/browser-webdriverio": "4.1.10", "@vitest/coverage-istanbul": "4.1.10", "@vitest/coverage-v8": "4.1.10", "@vitest/ui": "4.1.10", "happy-dom": "*", "jsdom": "*", "vite": "^6.0.0 || ^7.0.0 || ^8.0.0" }, "peerDependenciesMeta": { "@edge-runtime/vm": { "optional": true }, "@opentelemetry/api": { "optional": true }, "@types/node": { "optional": true }, "@vitest/browser-playwright": { "optional": true }, "@vitest/browser-preview": { "optional": true }, "@vitest/browser-webdriverio": { "optional": true }, "@vitest/coverage-istanbul": { "optional": true }, "@vitest/coverage-v8": { "optional": true }, "@vitest/ui": { "optional": true }, "happy-dom": { "optional": true }, "jsdom": { "optional": true }, "vite": { "optional": false } } }, "node_modules/why-is-node-running": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.3.0.tgz", "integrity": "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==", "dev": true, "license": "MIT", "dependencies": { "siginfo": "^2.0.0", "stackback": "0.0.2" }, "bin": { "why-is-node-running": "cli.js" }, "engines": { "node": ">=8" } } } } colmap-4.2.0/doc/package.json000066400000000000000000000007611524536416500160420ustar00rootroot00000000000000{ "name": "colmap-documentation-viewer", "private": true, "type": "module", "scripts": { "typecheck": "tsc --noEmit", "build": "vite build", "pretest": "vite build", "test": "vitest run", "test:browser": "playwright test" }, "dependencies": { "three": "0.185.1" }, "devDependencies": { "@playwright/test": "1.61.1", "@types/node": "26.1.1", "@types/three": "0.185.1", "typescript": "7.0.2", "vite": "8.1.5", "vitest": "4.1.10" } } colmap-4.2.0/doc/playwright.config.ts000066400000000000000000000004431524536416500175570ustar00rootroot00000000000000import {defineConfig} from "@playwright/test"; export default defineConfig({ testDir: "tests/browser", use: {baseURL: "http://127.0.0.1:4173", channel: "chromium"}, webServer: { command: "vite --host 127.0.0.1 --port 4173", port: 4173, reuseExistingServer: true, }, }); colmap-4.2.0/doc/pycolmap/000077500000000000000000000000001524536416500153745ustar00rootroot00000000000000colmap-4.2.0/doc/pycolmap/cost_functions.rst000066400000000000000000000002051524536416500211630ustar00rootroot00000000000000.. _pycolmap/cost_functions: Cost Functions ============== .. automodule:: pycolmap.cost_functions :members: :undoc-members: colmap-4.2.0/doc/pycolmap/index.rst000066400000000000000000000023051524536416500172350ustar00rootroot00000000000000.. _pycolmap/index: PyCOLMAP ======== PyCOLMAP exposes to Python most capabilities of COLMAP. Installation ------------ Pre-built wheels for Linux, macOS, and Windows can be installed using pip:: pip install pycolmap The wheels are automatically built and pushed to `PyPI `_ at each release. To benefit from GPU acceleration, wheels built for CUDA 12 (only for Linux - for now) are available under the `package pycolmap-cuda12 `_. To build PyCOLMAP from source, follow these steps: 1. Install COLMAP from source following :ref:`installation`. 2. Build PyCOLMAP: * On Linux and macOS:: python -m pip install . * On Windows, after installing COLMAP via VCPKG, run in powershell:: python -m pip install . ` --cmake.define.CMAKE_TOOLCHAIN_FILE="$VCPKG_ROOT/scripts/buildsystems/vcpkg.cmake" ` --cmake.define.VCPKG_TARGET_TRIPLET="x64-windows" Some features, such as cost functions, require that `PyCeres `_ is installed in the same manner as PyCOLMAP, so either from PyPI or from source. API ----- .. toctree:: :maxdepth: 2 pycolmap cost_functions colmap-4.2.0/doc/pycolmap/pycolmap.rst000066400000000000000000000001511524536416500177470ustar00rootroot00000000000000.. _pycolmap/pycolmap: pycolmap ============ .. automodule:: pycolmap :members: :undoc-members: colmap-4.2.0/doc/requirements.txt000066400000000000000000000002071524536416500170330ustar00rootroot00000000000000sphinx==9.1.0 sphinx-toolbox==4.1.2 pydata-sphinx-theme==0.20.0 sphinx-design==0.7.0 sphinx-sitemap==2.9.0 sphinxext-opengraph==0.13.0 colmap-4.2.0/doc/rigs.rst000066400000000000000000000224701524536416500152530ustar00rootroot00000000000000.. _rig-support: Rig Support =========== COLMAP has native support for modeling sensor rigs during the reconstruction process. The sensors in a rig are assumed to have fixed relative poses between each other with one reference sensor defining the origin of the rig. A frame defines a specific instance of the rig with all or a subset of sensors exposed at the same time. For example, in a stereo camera rig, one camera would be defined as the reference sensor and have an identity ``sensor_from_rig`` pose, whereas the second camera would be posed relative to the reference camera. Each frame would then usually be composed of two images as the measurements of both of the cameras at the same time. Workflow -------- By default, when running the standard reconstruction pipeline, each camera is modeled with a separate rig and thus each frame contains only a single image. To model rigs, the recommended workflow is to organize images by rigs and cameras in a folder structure as follows (ensure that images corresponding to the same frame have identical filenames across all folders):: rig1/ camera1/ image0001.jpg image0002.jpg ... camera2/ image0001.jpg # same frame as camera1/image0001.jpg image0002.jpg # same frame as camera1/image0002.jpg ... ... rig2/ camera1/ ... ... ... As a next step, we would first extract features using:: colmap feature_extractor \ --image_path $DATASET_PATH/images \ --database_path $DATASET_PATH/database.db \ --ImageReader.single_camera_per_folder 1 By default, the resulting database now contains a separate rig for each camera and a separate frame for each image. As such, we must adjust the relationships in the database with the desired rig configuration. This is done using:: colmap rig_configurator \ --database_path $DATASET_PATH/database.db \ --rig_config_path $DATASET_PATH/rig_config.json where the ``rig_config.json`` could look as follows, if the relative sensor poses in the rig are known a priori:: [ { "cameras": [ { "image_prefix": "rig1/camera1/", "ref_sensor": true }, { "image_prefix": "rig1/camera2/", "cam_from_rig_rotation": [ 0.7071067811865475, 0.0, 0.7071067811865476, 0.0 ], "cam_from_rig_translation": [ 0, 0, 0 ] } ] }, { "cameras": [ { "image_prefix": "rig2/camera1/", "ref_sensor": true }, ... ] }, ... ] Notice that this modifies the rig and frame configuration in the database, which contains the full specification of rigs that we later feed as an input to downstream processing steps. With known calibrated camera parameters, each camera can optionally also have specified ``camera_model_name`` and ``camera_params`` fields. For more fine-grain configuration of rigs and frames, the most convenient option is to manually configure the database using pycolmap by either using the ``apply_rig_config`` function or by individually adding the desired rig and frame objects to the reconstruction for the most flexibility. Next, we run standard feature matching. Note that it is important to configure the rigs before sequential feature matching, as images in consecutive frames will be automatically matched against each other. Finally, we can reconstruct the scene using the standard ``mapper`` command with the option of keeping the relative poses in the rig fixed using ``--Mapper.ba_refine_sensor_from_rig 0``. Unknown rig sensor poses ------------------------ If the relative poses of sensors in the rig are not known a priori and we only know that a specific set of sensors are rigidly mounted and exposed at the same time, one can attempt the following two-step reconstruction approach. Before starting, ensure to organize your images as detailed above and perform feature extraction with the ``--ImageReader.single_camera_per_folder 1`` option. Next, reconstruct the scene without rig constraints by modeling each camera as its own rig (the default behavior of COLMAP without further configuration). Note that this can be a partial reconstruction from a subset of the full set of input images. The only requirement is that each camera must have at least one registered image in the same frame with a registered image of the reference camera. If the reconstruction was successful and the relative poses between registered images look roughly correct, we can proceed with the next step. The ``rig_configurator`` can also work without ``cam_from_rig_*`` transformations. By providing an existing (partial) reconstruction of the scene, it can compute the average relative rig sensor poses from all registered images:: colmap rig_configurator \ --database_path $DATASET_PATH/database.db \ --input_path $DATASET_PATH/sparse-model-without-rigs-and-frames \ --rig_config_path $DATASET_PATH/rig_config.json \ [ --output_path $DATASET_PATH/sparse-model-with-rigs-and-frames ] The provided ``rig_config.json`` must simply omit the respective ``cam_from_rig_rotation`` and ``cam_from_rig_translation`` fields. Now, we can either run rig bundle adjustment on the (optional) output reconstruction with configured rigs and frames:: colmap bundle_adjuster \ --input_path $DATASET_PATH/sparse-model-with-rigs-and-frames \ --output_path $DATASET_PATH/bundled-sparse-model-with-rigs-and-frames or alternatively start the reconstruction process from scratch with rig constraints, which may lead to more accurate reconstruction results:: colmap mapper --image_path $DATASET_PATH/images \ --database_path $DATASET_PATH/database.db \ --output_path $DATASET_PATH/sparse-model-with-rigs-and-frames Example ------- The following example shows an end-to-end example for how to reconstruct one of the ETH3D rig datasets using COLMAP's rig support:: wget https://www.eth3d.net/data/terrains_rig_undistorted.7z 7zz x terrains_rig_undistorted.7z colmap feature_extractor \ --database_path terrains/database.db \ --image_path terrains/images \ --ImageReader.single_camera_per_folder 1 The ETH3D dataset conveniently comes with a groundtruth COLMAP reconstruction that we use to configure the sensor rig poses as well as camera models using:: colmap rig_configurator \ --database_path terrains/database.db \ --rig_config_path terrains/rig_config.json \ --input_path terrains/rig_calibration_undistorted with the ``rig_config.json``:: [ { "cameras": [ { "image_prefix": "images_rig_cam4_undistorted/", "ref_sensor": true }, { "image_prefix": "images_rig_cam5_undistorted/" }, { "image_prefix": "images_rig_cam6_undistorted/" }, { "image_prefix": "images_rig_cam7_undistorted/" } ] } ] Notice that we do not specify the sensor poses, because we used an existing reconstruction (in this case, the groundtruth but it can also be a reconstruction without rig constraints, as explained in the previous section) to automatically infer the average rig extrinsics and camera parameters. Next, we sequentially match the frames, since they were captured as a video:: colmap sequential_matcher --database_path terrains/database.db Depending on the accuracy of the provided sensor_from_rig poses, you can optionally enable the option `--FeatureMatching.rig_verification 1` or, if you know that the sensors within the same frame do not have visual overlap, you can enable the option `--FeatureMatching.skip_image_pairs_in_same_frame 1`. Finally, we reconstruct the scene using the mapper while keeping the groundtruth sensor rig poses and camera parameters fixed:: mkdir -p terrains/sparse colmap mapper \ --database_path terrains/database.db \ --Mapper.ba_refine_sensor_from_rig 0 \ --Mapper.ba_refine_focal_length 0 \ --Mapper.ba_refine_extra_params 0 \ --image_path terrains/images \ --output_path terrains/sparse Reconstruction from 360° spherical images ----------------------------------------- COLMAP can handle collections of 360° panoramas by rendering virtual pinhole images (similar to a cubemap) and treating them as a camera rig. Since the rig extrinsics and camera intrinsics are known, the reconstruction process is more robust. We provide an example Python script to reconstruct a 360° collection:: python python/examples/panorama_sfm.py \ --input_image_path image_directory \ --output_path output_directory Make sure to use the version of the script that corresponds to the version of COLMAP that you are using, as the script at HEAD is not guaranteed to be compatible. The example is a command-line wrapper around ``pycolmap.panorama.reconstruct``. Perspective rendering requires the optional ``panorama`` dependencies, which can be installed with ``pip install 'pycolmap[panorama]'``. colmap-4.2.0/doc/robots.txt000066400000000000000000000001061524536416500156160ustar00rootroot00000000000000User-agent: * Allow: / Sitemap: https://colmap.github.io/sitemap.xml colmap-4.2.0/doc/sample-project/000077500000000000000000000000001524536416500164755ustar00rootroot00000000000000colmap-4.2.0/doc/sample-project/CMakeLists.txt000066400000000000000000000004001524536416500212270ustar00rootroot00000000000000cmake_minimum_required(VERSION 3.10) project(SampleProject) find_package(colmap REQUIRED) # or to require a specific version: find_package(colmap 3.4 REQUIRED) add_executable(hello_world hello_world.cc) target_link_libraries(hello_world colmap::colmap) colmap-4.2.0/doc/sample-project/hello_world.cc000066400000000000000000000007061524536416500213210ustar00rootroot00000000000000#include #include #include #include int main(int argc, char** argv) { colmap::InitializeGlog(argv); std::string message; colmap::OptionManager options; options.AddRequiredOption("message", &message); if (!options.Parse(argc, argv)) { return EXIT_FAILURE; } std::cout << colmap::StringPrintf("Hello %s!\n", message.c_str()); return EXIT_SUCCESS; } colmap-4.2.0/doc/tests/000077500000000000000000000000001524536416500147125ustar00rootroot00000000000000colmap-4.2.0/doc/tests/binary_fixture.ts000066400000000000000000000044471524536416500203250ustar00rootroot00000000000000type Numeric = number | bigint; export class BinaryWriter { private bytes: number[] = []; private add(size: number, write: (view: DataView) => void): this { const buffer = new ArrayBuffer(size); write(new DataView(buffer)); this.bytes.push(...new Uint8Array(buffer)); return this; } u8(value: number): this { this.bytes.push(value); return this; } u32(value: number): this { return this.add(4, (view) => view.setUint32(0, value, true)); } i32(value: number): this { return this.add(4, (view) => view.setInt32(0, value, true)); } u64(value: Numeric): this { return this.add(8, (view) => view.setBigUint64(0, BigInt(value), true)); } f64(value: number): this { return this.add(8, (view) => view.setFloat64(0, value, true)); } string(value: string): this { this.bytes.push(...new TextEncoder().encode(value), 0); return this; } file(name: string): File { return new File([new Uint8Array(this.bytes)], name); } } function rigid(writer: BinaryWriter, translation: [number, number, number]): void { writer.f64(1).f64(0).f64(0).f64(0).f64(translation[0]).f64(translation[1]).f64(translation[2]); } export function cameraFile(cameraId = 1): File { return new BinaryWriter() .u64(1).u32(cameraId).i32(0).u64(640).u64(480) .f64(500).f64(320).f64(240) .file("cameras.bin"); } export function imageFile(imageId = 2, cameraId = 1, pointId = 42n): File { const writer = new BinaryWriter().u64(1).u32(imageId); rigid(writer, [4, 5, 6]); return writer.u32(cameraId).string("images/frame.jpg").u64(1) .f64(330).f64(220).u64(pointId) .file("images.bin"); } export function pointFile(imageId = 2, pointId = 42n, trackLength = 1): File { const writer = new BinaryWriter().u64(1).u64(pointId) .f64(1).f64(2).f64(3).u8(10).u8(20).u8(30).f64(0.25) .u64(trackLength); for (let i = 0; i < trackLength; ++i) writer.u32(imageId).u32(0); return writer.file("points3D.bin"); } export function modernRigFiles(cameraId = 2, imageId = 2): [File, File] { const rigs = new BinaryWriter().u64(1).u32(7).u32(2) .i32(0).u32(1) .i32(0).u32(cameraId).u8(1); rigid(rigs, [1, 0, 0]); const frames = new BinaryWriter().u64(1).u32(9).u32(7); rigid(frames, [0, 2, 0]); frames.u32(1).i32(0).u32(cameraId).u64(imageId); return [rigs.file("rigs.bin"), frames.file("frames.bin")]; } colmap-4.2.0/doc/tests/browser/000077500000000000000000000000001524536416500163755ustar00rootroot00000000000000colmap-4.2.0/doc/tests/browser/viewer.spec.ts000066400000000000000000000067431524536416500212110ustar00rootroot00000000000000import {expect, test} from "@playwright/test"; import {cameraFile, imageFile, pointFile} from "../binary_fixture"; test("initializes the local-only viewer shell", async ({page}) => { await page.goto("/tests/viewer.html"); await expect(page.getByRole("heading", {name: "Open a COLMAP reconstruction"})).toBeVisible(); await expect(page.getByRole("button", {name: "Choose folder"})).toBeVisible(); await expect(page.locator('[data-viewer="canvas"]')).toBeAttached(); await expect(page.locator('[data-viewer="title"]')).toHaveText("COLMAP - 3D Web Viewer"); await expect(page.locator('[data-viewer="stats"]')).toBeHidden(); // The point needs a track of at least 3 observations to pass the viewer's default track length filter. const files = await Promise.all([cameraFile(), imageFile(), pointFile(2, 42n, 3)].map(async (file) => ({ name: file.name, mimeType: "application/octet-stream", buffer: Buffer.from(await file.arrayBuffer()), }))); const input = page.locator('[data-viewer="folder-input"]'); await input.evaluate((element) => element.removeAttribute("webkitdirectory")); await input.setInputFiles(files); await expect(page.locator('[data-viewer="stats"]')).toHaveText("1 images / 1 visible points"); await expect(page.getByRole("heading", {name: "Model loaded"})).toBeVisible(); const drop = page.locator('[data-viewer="drop"]'); await drop.evaluate((element) => element.closest(".colmap-viewer-host")!.dispatchEvent(new DragEvent("dragenter", {bubbles: true}))); await expect(drop).toBeVisible(); await drop.evaluate((element) => element.closest(".colmap-viewer-host")!.dispatchEvent(new DragEvent("dragleave", {bubbles: true, relatedTarget: null}))); await expect(drop).toBeHidden(); await page.locator('[data-viewer="projection"]').selectOption("orthographic"); await expect(page.locator('[data-viewer="projection"]')).toHaveValue("orthographic"); const canvas = page.locator('[data-viewer="canvas"]'); await canvas.evaluate((element) => element.dispatchEvent(new Event("webglcontextlost", {cancelable: true}))); await expect(page.locator('[data-viewer="status"]')).toContainText("WebGL context lost"); await canvas.evaluate((element) => element.dispatchEvent(new Event("webglcontextrestored"))); await expect(page.locator('[data-viewer="status"]')).toBeHidden(); const malformedFiles = files.map((file) => file.name === "cameras.bin" ? {...file, buffer: Buffer.from([1])} : file); await input.setInputFiles(malformedFiles); await expect(page.locator('[data-viewer="stats"]')).toBeHidden(); await expect(page.getByRole("heading", {name: "Open a COLMAP reconstruction"})).toBeVisible(); await expect(page.locator('[data-viewer="status"]')).toContainText("Failed to parse model"); await expect(page.locator('[data-viewer="reset"]')).toBeDisabled(); const lifecycle = await page.evaluate(async () => { const modulePath = "/viewer_src/main.ts"; const {mountColmapViewer} = await import(/* @vite-ignore */ modulePath) as typeof import("../../viewer_src/main"); const host = document.createElement("div"); document.body.append(host); const embedded = mountColmapViewer(host, {title: "Embedded viewer"}); const title = host.querySelector('[data-viewer="title"]')?.textContent; embedded.clear(); embedded.dispose(); return {title, childCount: host.childElementCount, hasHostClass: host.classList.contains("colmap-viewer-host")}; }); expect(lifecycle).toEqual({title: "Embedded viewer", childCount: 0, hasHostClass: false}); }); colmap-4.2.0/doc/tests/bundle.test.ts000066400000000000000000000020051524536416500175060ustar00rootroot00000000000000import {readdir, readFile} from "node:fs/promises"; import {expect, test} from "vitest"; test("uses a viewer-relative parser worker URL", async () => { const names = await readdir("_static/viewer", {recursive: true}); const scripts = names.filter((name) => name.endsWith(".js")); const bundle = (await Promise.all(scripts.map((name) => readFile(`_static/viewer/${name}`, "utf8")))).join("\n"); expect(bundle).toMatch(/new URL\("assets\/parser\.worker-[A-Za-z0-9_-]+\.js"/); expect(bundle).not.toMatch(/new URL\("\/assets\/parser\.worker-/); }); test("emits an independently reusable component and scoped stylesheet", async () => { const component = await readFile("_static/viewer/component.js", "utf8"); const styles = await readFile("_static/viewer/viewer.css", "utf8"); expect(component).toContain("mountColmapViewer"); expect(component).not.toContain("#colmap-viewer-root"); expect(styles).toContain(".colmap-viewer-host .viewer-workspace"); expect(styles).not.toMatch(/(^|})\.viewer-workspace/); }); colmap-4.2.0/doc/tests/camera_models.test.ts000066400000000000000000000042471524536416500210420ustar00rootroot00000000000000import {describe, expect, test} from "vitest"; import {CAMERA_MODEL_PARAM_COUNTS, project} from "../viewer_src/camera_models"; import type {Camera} from "../viewer_src/types"; function camera(modelId: number, params: number[]): Camera { return {id: 1, modelId, width: 640, height: 480, params}; } describe("camera projections", () => { test("matches hand-computed pinhole and radial projections", () => { expect(project(camera(0, [100, 320, 240]), [0.1, -0.2, 1])).toEqual([330, 220]); expect(project(camera(1, [100, 120, 320, 240]), [0.1, -0.2, 1])).toEqual([330, 216]); const radial = project(camera(2, [100, 320, 240, 0.1]), [0.1, -0.2, 1]); expect(radial?.[0]).toBeCloseTo(330.05, 10); expect(radial?.[1]).toBeCloseTo(219.9, 10); }); test("projects division, EUCM, and full-sphere equirectangular cameras", () => { expect(project(camera(12, [100, 320, 240, 0]), [0.1, -0.2, 1])).toEqual([330, 220]); expect(project(camera(16, [100, 100, 320, 240, 0, 1]), [0.1, -0.2, 1])).toEqual([330, 220]); expect(project(camera(17, [640, 320]), [0, 0, -1])).toEqual([640, 160]); }); test("has a valid projection implementation for every current model", () => { const params = [ [100, 320, 240], [100, 100, 320, 240], [100, 320, 240, 0], [100, 320, 240, 0, 0], [100, 100, 320, 240, 0, 0, 0, 0], [100, 100, 320, 240, 0, 0, 0, 0], [100, 100, 320, 240, 0, 0, 0, 0, 0, 0, 0, 0], [100, 100, 320, 240, 0.5], [100, 320, 240, 0], [100, 320, 240, 0, 0], [100, 100, 320, 240, 0, 0, 0, 0, 0, 0, 0, 0], [100, 100, 320, 240, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [100, 320, 240, 0], [100, 100, 320, 240, 0], [100, 320, 240], [100, 100, 320, 240], [100, 100, 320, 240, 0, 1], [640, 320], ]; expect(params.map((value) => value.length)).toEqual([...CAMERA_MODEL_PARAM_COUNTS]); for (let modelId = 0; modelId < params.length; ++modelId) { const result = project(camera(modelId, params[modelId]!), [0.1, -0.2, 1]); expect(result, `model ${modelId}`).not.toBeNull(); expect(result?.every(Number.isFinite), `model ${modelId}`).toBe(true); } }); }); colmap-4.2.0/doc/tests/camera_models_registry_test.py000066400000000000000000000030161524536416500230460ustar00rootroot00000000000000import re from pathlib import Path import pycolmap CAMERA_MODELS = Path(__file__).parents[1] / "viewer_src" / "camera_models.ts" def parse_array(source: str, name: str) -> list[str]: match = re.search( rf"export const {name} = \[(.*?)\] as const;", source, re.DOTALL ) if match is None: raise AssertionError(f"Could not find {name} in {CAMERA_MODELS}") return [ value.strip().strip('"') for value in match.group(1).split(",") if value.strip() ] def main() -> None: source = CAMERA_MODELS.read_text(encoding="utf-8") names = parse_array(source, "CAMERA_MODEL_NAMES") param_counts = [ int(value) for value in parse_array(source, "CAMERA_MODEL_PARAM_COUNTS") ] expected = list(zip(names, param_counts, strict=True)) actual: list[tuple[str, int] | None] = [None] * len(expected) for name, model in pycolmap.CameraModelId.__members__.items(): if name == "INVALID": continue camera = pycolmap.Camera.create_from_model_id(1, model, 1.0, 100, 100) model_id = int(model.value) if model_id >= len(actual): raise AssertionError( f"Viewer is missing pycolmap camera model {name} ({model_id})" ) actual[model_id] = (name, len(camera.params)) message = ( "Viewer camera model registry differs from pycolmap:\n" f"viewer={expected}\n" f"pycolmap={actual}" ) assert actual == expected, message if __name__ == "__main__": main() colmap-4.2.0/doc/tests/math.test.ts000066400000000000000000000004711524536416500171730ustar00rootroot00000000000000import {expect, test} from "vitest"; import {median, percentile} from "../viewer_src/math"; test("matches COLMAP percentile interpolation and median", () => { expect(percentile([0, 100], 0.01)).toBe(1); expect(percentile([0, 1, 2, 3], 0.34)).toBeCloseTo(1.02); expect(median([4, 1, 3, 2])).toBe(2.5); }); colmap-4.2.0/doc/tests/parser.test.ts000066400000000000000000000077411524536416500175450ustar00rootroot00000000000000import {describe, expect, test} from "vitest"; import {discoverSparseModels, parseReconstruction, reconstructionTransferables} from "../viewer_src/parser"; import {findPoint3DIndex, point2DAt, point3DAt} from "../viewer_src/types"; import {BinaryWriter, cameraFile, imageFile, modernRigFiles, pointFile} from "./binary_fixture"; function modelFiles(cameraId = 1): Map { return new Map([ ["cameras.bin", cameraFile(cameraId)], ["images.bin", imageFile(2, cameraId)], ["points3D.bin", pointFile()], ]); } describe("binary reconstruction parser", () => { test("parses a legacy model without losing uint64 point ids", async () => { const pointId = 9007199254740993n; const files = modelFiles(); files.set("images.bin", imageFile(2, 1, pointId)); files.set("points3D.bin", pointFile(2, pointId)); const reconstruction = await parseReconstruction(files); expect(reconstruction.modernRigFormat).toBe(false); expect(reconstruction.cameras.get(1)?.params).toEqual([500, 320, 240]); expect(reconstruction.images.get(2)?.camFromWorld.translation).toEqual([4, 5, 6]); expect(point2DAt(reconstruction.images.get(2)!, 0)?.point3DId).toBe(pointId); const pointIndex = findPoint3DIndex(reconstruction.points3D, pointId); expect(point3DAt(reconstruction.points3D, pointIndex).track).toEqual([{imageId: 2, point2DIdx: 0}]); expect(reconstruction.points3D.xyz).toBeInstanceOf(Float64Array); expect(reconstruction.points3D.colors).toBeInstanceOf(Uint8Array); expect(reconstruction.points3D.trackImageIds).toBeInstanceOf(Uint32Array); const transfer = reconstructionTransferables(reconstruction); expect(transfer).toHaveLength(9); const transferred = structuredClone(reconstruction, {transfer}); expect(reconstruction.points3D.ids.byteLength).toBe(0); expect(transferred.points3D.ids[0]).toBe(pointId); }); test("sorts point ids for compact binary-search lookup", async () => { const points = new BinaryWriter().u64(2); for (const [id, x] of [[9, 90], [3, 30]] as const) { points.u64(id).f64(x).f64(0).f64(0).u8(1).u8(2).u8(3).f64(0.5).u64(0); } const files = modelFiles(); files.set("points3D.bin", points.file("points3D.bin")); const reconstruction = await parseReconstruction(files); expect([...reconstruction.points3D.ids]).toEqual([3n, 9n]); expect(point3DAt(reconstruction.points3D, findPoint3DIndex(reconstruction.points3D, 3n)).xyz).toEqual([30, 0, 0]); expect(point3DAt(reconstruction.points3D, findPoint3DIndex(reconstruction.points3D, 9n)).xyz).toEqual([90, 0, 0]); }); test("composes modern sensor and rig poses", async () => { const files = modelFiles(2); const [rigs, frames] = modernRigFiles(); files.set("rigs.bin", rigs); files.set("frames.bin", frames); const reconstruction = await parseReconstruction(files); expect(reconstruction.modernRigFormat).toBe(true); expect(reconstruction.images.get(2)?.camFromWorld.translation).toEqual([1, 2, 0]); expect(reconstruction.images.get(2)?.frameId).toBe(9); expect(reconstruction.images.get(2)?.rigId).toBe(7); }); test("rejects incomplete and truncated models", async () => { const files = modelFiles(); files.set("rigs.bin", new File([new Uint8Array(8)], "rigs.bin")); await expect(parseReconstruction(files)).rejects.toThrow("both rigs.bin and frames.bin"); files.delete("rigs.bin"); files.set("cameras.bin", new File([new Uint8Array([1])], "cameras.bin")); await expect(parseReconstruction(files)).rejects.toThrow("truncated"); }); }); test("discovers nested models and ignores unrelated files", () => { const files = [cameraFile(), imageFile(), pointFile()]; const entries = files.map((file) => ({path: `workspace/sparse/0/${file.name}`, file})); entries.push({path: "workspace/images/frame.jpg", file: new File([], "frame.jpg")}); const candidates = discoverSparseModels(entries); expect(candidates).toHaveLength(1); expect(candidates[0]?.path).toBe("workspace/sparse/0"); }); colmap-4.2.0/doc/tests/viewer.html000066400000000000000000000003331524536416500171000ustar00rootroot00000000000000 Viewer test
colmap-4.2.0/doc/tests/viewer.test.ts000066400000000000000000000024671524536416500175520ustar00rootroot00000000000000import * as THREE from "three"; import {expect, test} from "vitest"; import {CAMERA_FRUSTUM_COLORS, COORDINATE_COLORS, photometricPointColor, scaleFromNativeWheel} from "../viewer_src/viewer"; test("matches native COLMAP camera frustum colors", () => { expect(CAMERA_FRUSTUM_COLORS).toEqual({ frame: [0.8, 0.1, 0, 1], plane: [1, 0.1, 0, 0.6], selectedFrame: [0.8, 0, 0.8, 1], selectedPlane: [1, 0, 1, 0.6], sameFrame: [0.6, 0, 0.6, 179 / 255], sameFramePlane: [0.8, 0, 0.8, 77 / 255], }); }); test("matches native COLMAP coordinate overlay colors", () => { expect(COORDINATE_COLORS).toEqual({ grid: [51 / 255, 51 / 255, 51 / 255, 153 / 255], x: [230 / 255, 0, 0, 128 / 255], y: [0, 230 / 255, 0, 128 / 255], z: [0, 0, 230 / 255, 128 / 255], }); }); test("matches native modifier-wheel scaling and limits", () => { expect(scaleFromNativeWheel(1, -120, 0, 0.5, 100)).toBeCloseTo(1.12); expect(scaleFromNativeWheel(1, 120, 0, 0.5, 100)).toBeCloseTo(0.88); expect(scaleFromNativeWheel(0.5, 1000, 0, 0.5, 100)).toBe(0.5); expect(scaleFromNativeWheel(100, -1000, 0, 0.5, 100)).toBe(100); }); test("preserves COLMAP photometric RGB values through Three.js color management", () => { expect(photometricPointColor([115, 121, 122]).getHex(THREE.SRGBColorSpace)).toBe(0x73797a); }); colmap-4.2.0/doc/tsconfig.json000066400000000000000000000007261524536416500162640ustar00rootroot00000000000000{ "compilerOptions": { "target": "ES2022", "useDefineForClassFields": true, "module": "ESNext", "lib": ["ES2022", "DOM", "DOM.Iterable", "WebWorker"], "skipLibCheck": true, "moduleResolution": "Bundler", "allowImportingTsExtensions": true, "isolatedModules": true, "moduleDetection": "force", "noEmit": true, "strict": true, "noUncheckedIndexedAccess": true }, "include": ["viewer_src", "vite.config.ts", "tests"] } colmap-4.2.0/doc/tutorial.rst000077500000000000000000000727061524536416500161640ustar00rootroot00000000000000.. _tutorial: Tutorial ======== This tutorial covers the topic of image-based 3D reconstruction by demonstrating the individual processing steps in COLMAP. If you are interested in a more general and mathematical introduction to the topic of image-based 3D reconstruction, please also refer to the `CVPR 2017 Tutorial on Large-scale 3D Modeling from Crowdsourced Data `_ and [schoenberger_thesis]_. Image-based 3D reconstruction from images traditionally first recovers a sparse representation of the scene and the camera poses of the input images using Structure-from-Motion. This output then serves as the input to Multi-View Stereo to recover a dense representation of the scene. .. contents:: Contents :local: :depth: 1 .. _quick-start: Quickstart ---------- First, start the graphical user interface of COLMAP, as described :ref:`here `. COLMAP provides an automatic reconstruction tool that simply takes a folder of input images and produces a sparse and dense reconstruction in a workspace folder. Click ``Reconstruction > Automatic Reconstruction`` in the GUI and specify the relevant options. The output is written to the workspace folder. For example, if your images are located in ``path/to/project/images``, you could select ``path/to/project`` as a workspace folder and after running the automatic reconstruction tool, the folder would look similar to this:: +── images │ +── image1.jpg │ +── image2.jpg │ +── ... +── sparse │ +── 0 │ │ +── rigs.bin │ │ +── cameras.bin │ │ +── frames.bin │ │ +── images.bin │ │ +── points3D.bin │ +── ... +── dense │ +── 0 │ │ +── images │ │ +── sparse │ │ +── stereo │ │ +── fused.ply │ │ +── meshed-poisson.ply │ │ +── meshed-delaunay.ply │ │ +── meshed-advancing-front.ply │ +── ... +── database.db Here, the ``path/to/project/sparse`` contains the sparse models for all reconstructed components, while ``path/to/project/dense`` contains their corresponding dense models. The dense point cloud ``fused.ply`` can be imported in COLMAP using ``File > Import from ...``, while the dense mesh must be visualized with an external viewer such as Meshlab. The same automatic reconstruction can be run from the command-line, without the GUI, using the ``automatic_reconstructor`` command, which produces the identical workspace layout shown above:: colmap automatic_reconstructor \\ --workspace_path path/to/project \\ --image_path path/to/project/images The following sections give general recommendations and describe the reconstruction process in more detail, if you need more control over the reconstruction process/parameters or if you are interested in the underlying technology in COLMAP. Preface ------- COLMAP requires only a few steps to perform a standard reconstruction for a general user. For more experienced users, the program exposes many different parameters, only some of which are intuitive to a beginner. The program should usually work without the need to modify any parameters. The defaults are chosen as a trade- off between reconstruction robustness/quality and speed. You can set "optimal" options for different reconstruction scenarios by choosing ``Extras > Set options for ... data``. If in doubt what settings to choose, stick to the defaults. The source code contains more documentation about all parameters. COLMAP is research software, and in rare cases it may exit ungracefully if some constraints are not fulfilled. In this case, the program prints a traceback to stderr. To see this traceback or more debug information, it is recommended to run the executables (including the GUI) from the command-line, where you can also define various levels of logging verbosity. Structure-from-Motion --------------------- .. figure:: images/incremental-sfm.webp :alt: Incremental Structure-from-Motion pipeline :figclass: align-center COLMAP's incremental Structure-from-Motion pipeline. Structure-from-Motion (SfM) is the process of reconstructing 3D structure from its projections into a series of images. The input is a set of overlapping images of the same object, taken from different viewpoints. The output is a 3-D reconstruction of the object, and the reconstructed intrinsic and extrinsic camera parameters of all images. Typically, Structure-from-Motion systems divide this process into three stages: 1) Feature detection and extraction 2) Feature matching and geometric verification 3) Structure and motion reconstruction COLMAP reflects these stages in different modules that can be combined depending on the application. More information on Structure-from-Motion in general and the algorithms in COLMAP can be found in [schoenberger16sfm]_ and [schoenberger16mvs]_. If you have control over the picture capture process, please follow these guidelines for optimal reconstruction results: - Capture images with **good texture**. Avoid completely texture-less images (e.g., a white wall or empty desk). If the scene does not contain enough texture itself, you could place additional background objects, such as posters, etc. - Capture images at **similar illumination** conditions. Avoid high dynamic range scenes (e.g., pictures against the sun with shadows or pictures through doors/windows). Avoid specularities on shiny surfaces. - Capture images with **high visual overlap**. Make sure that each object is seen in at least 3 images -- the more images the better. - Capture images from **different viewpoints**. Do not take images from the same location by only rotating the camera, e.g., make a few steps after each shot. At the same time, try to have enough images from a relatively similar viewpoint. Note that more images are not necessarily better and might lead to a slow reconstruction process. If you use a video as input, consider down-sampling the frame rate. Multi-View Stereo ----------------- Multi-View Stereo (MVS) takes the output of SfM to compute depth and/or normal information for every pixel in an image. Fusion of the depth and normal maps of multiple images in 3D then produces a dense point cloud of the scene. Using the depth and normal information of the fused point cloud, algorithms such as the (screened) Poisson surface reconstruction [kazhdan2013]_ or the advancing front surface reconstruction [cohen-steiner2004]_ can then recover the 3D surface geometry of the scene. The resulting meshes can optionally be simplified using Quadric Error Metric (QEM) decimation [garland1997]_ to reduce their complexity while preserving shape and appearance. Additionally, the meshes can be textured using multi-view texture mapping [waechter2014]_, which assigns each face to the best-view camera image and produces a texture atlas with UV coordinates. More information on Multi-View Stereo in general and the algorithms in COLMAP can be found in [schoenberger16mvs]_. Terminology ----------- The term **camera** refers to a physical camera using the same zoom factor and lens. A camera defines the intrinsic projection model in COLMAP. A single camera can take multiple images with the same resolution, intrinsic parameters, and distortion characteristics. The term **image** is associated with a bitmap file, e.g., a JPEG or PNG file on disk. COLMAP detects **keypoints** in each image whose appearance is described by numerical **descriptors**. Pure appearance-based correspondences between keypoints/descriptors are defined by **matches**, while **inlier matches** are geometrically verified and used for the reconstruction procedure. The term **rig** describes a fixed assembly of one or more cameras whose relative poses are constant over time, e.g., a stereo or multi-camera setup, or a single moving camera (a trivial rig with one camera). The term **frame** denotes a single snapshot in time, i.e., the set of images captured simultaneously by all cameras of a rig. A frame therefore groups images that share the same rig pose, and COLMAP optimizes one pose per frame rather than one pose per image. Rigs and frames are stored as ``rigs.bin`` and ``frames.bin`` in the reconstruction output (see :ref:`Rig Support ` and :ref:`Output Format `). Data Structure -------------- COLMAP assumes that all input images are in one input directory with potentially nested sub-directories. It recursively considers all images stored in this directory, and it supports various image formats through OpenImageIO. Other files are automatically ignored. If high performance is a requirement, then you should separate any files that are not images. Images are identified uniquely by their relative file path. For later processing, such as image undistortion or dense reconstruction, the relative folder structure should be preserved. COLMAP does not modify the input images or directory and all extracted data is stored in a single, self-contained SQLite database file (see :doc:`database`). The first step is to start the graphical user interface of COLMAP by running the pre-built binaries (Windows: ``COLMAP.bat``, Mac: ``COLMAP.app``) or by executing ``./src/colmap/exe/colmap gui`` from the CMake build folder. Next, create a new project by choosing ``File > New project``. In this dialog, you must select where to store the database and the folder that contains the input images. For convenience, you can save the entire project settings to a configuration file by choosing ``File > Save project``. The project configuration stores the absolute path information of the database and image folder in addition to any other parameter settings. If you decide to move the database or image folder, you must change the paths accordingly by creating a new project. Alternatively, the resulting ``.ini`` configuration file can be directly modified in a text editor of your choice. To reopen an existing project, you can simply open the configuration file by choosing ``File > Open project`` and all parameter settings should be recovered. Note that all COLMAP executables can be started from the command-line by either specifying individual settings as command-line arguments or by providing the path to the project configuration file (see :ref:`Interface `). An example folder structure could look like this:: /path/to/project/... +── images │   +── image1.jpg │   +── image2.jpg │   +── ... │   +── imageN.jpg +── database.db +── project.ini In this example, you would select ``/path/to/project/images`` as the image folder path, ``/path/to/project/database.db`` as the database file path, and save the project configuration to ``/path/to/project/project.ini``. Feature Detection and Extraction -------------------------------- In the first step, feature detection/extraction finds sparse feature points in the image and describes their appearance using a numerical descriptor. COLMAP imports images and performs feature detection/extraction in a single step, so that each image is loaded from disk only once. Next, choose ``Processing > Extract features``. In this dialog, you must first decide on the intrinsic camera model to use. You can either automatically extract focal length information from the embedded EXIF information or manually specify intrinsic parameters, e.g., as obtained in a lab calibration. If an image has partial EXIF information, COLMAP tries to find the missing camera specifications in a large database of camera models automatically. If all your images were captured by the same physical camera with identical zoom factor, it is recommended to share intrinsics between all images. Note that the program will exit ungracefully if the same camera model is shared among all images but not all images have the same size or EXIF focal length. If you have several groups of images that share the same intrinsic camera parameters, you can easily modify the camera models at a later point as well (see :ref:`Database Management `). If in doubt what to choose in this step, simply stick to the default parameters. You can either detect and extract new features from the images or import existing features from text files. By default, COLMAP extracts SIFT [lowe04]_ features either on the GPU or the CPU. When COLMAP is built with CUDA support (recommended), GPU feature extraction runs without an attached display and is suitable for headless servers. The OpenGL-based fallback (used when CUDA is not available) instead requires an attached display, so on such systems the CPU version is recommended for use on a server. In general, the GPU version is favorable, as it has a customized feature detection mode that often produces higher-quality features for high-contrast images. COLMAP also supports ALIKED and LoMa feature extraction, learned feature extractors using ONNX models, which can be selected via the ``--FeatureExtraction.type`` option (see :ref:`Feature Extraction and Matching ` for details). If you import existing features, every image must have a text file next to it (e.g., ``/path/to/image1.jpg`` and ``/path/to/image1.jpg.txt``) in the following format:: NUM_FEATURES 128 X Y SCALE ORIENTATION D_1 D_2 D_3 ... D_128 ... X Y SCALE ORIENTATION D_1 D_2 D_3 ... D_128 where ``X, Y, SCALE, ORIENTATION`` are floating point numbers and ``D_1...D_128`` values in the range ``0...255``. The file should have ``NUM_FEATURES`` lines with one line per feature. For example, if an image has 4 features, then the text file should look something like this:: 4 128 1.2 2.3 0.1 0.3 1 2 3 4 ... 21 2.2 3.3 1.1 0.3 3 2 3 2 ... 32 0.2 1.3 1.1 0.3 3 2 3 2 ... 2 1.2 2.3 1.1 0.3 3 2 3 2 ... 3 Note that by convention the upper left corner of an image has coordinate ``(0, 0)`` and the center of the upper left most pixel has coordinate ``(0.5, 0.5)``. If you must import features for large image collections, it is much more efficient to directly access the database with your favorite scripting language (see :ref:`Database Format `). If you are done setting all options, choose ``Extract`` and wait for the extraction to finish or cancel. If you cancel during the extraction process, the next time you start extracting images for the same project, COLMAP automatically continues where it left off. This also allows you to add images to an existing project/reconstruction. In this case, be sure to verify the camera parameters when using shared intrinsics. All extracted data will be stored in the database file and can be reviewed/managed in the database management tool (see :ref:`Database Management `) or, for experts, directly modified using SQLite (see :ref:`Database Format `). Feature Matching and Geometric Verification ------------------------------------------- In the second step, feature matching and geometric verification finds correspondences between the feature points in different images. Please choose ``Processing > Feature matching`` and select one of the provided matching modes, which are intended for different input scenarios: - **Exhaustive Matching**: If the number of images in your dataset is relatively low (up to several hundreds), this matching mode should be fast enough and lead to the best reconstruction results. Here, every image is matched against every other image, while the block size determines how many images are loaded from disk into memory at the same time. - **Sequential Matching**: This mode is useful if the images are acquired in sequential order, e.g., by a video camera. In this case, consecutive frames have visual overlap and there is no need to match all image pairs exhaustively. Instead, consecutively captured images are matched against each other. This matching mode has built-in loop detection based on a vocabulary tree, where every N-th image (``--SequentialMatching.loop_detection_period``) is matched against its visually most similar images (``--SequentialMatching.loop_detection_num_images``). Retrieved images can be restricted to those that are sufficiently far from the query in the sequence (``--SequentialMatching.loop_detection_min_index_distance``), so that nearby images do not consume the loop detection budget. A value of zero disables this restriction. Note that image file names must be ordered sequentially (e.g., ``image0001.jpg``, ``image0002.jpg``, etc.). The order in the database is not relevant, since the images are explicitly ordered according to their file names. Note that loop detection requires a pre-trained vocabulary tree. A default tree will be automatically downloaded and cached. More trees are available and can be downloaded from https://demuc.de/colmap/. In case rigs and frames are configured appropriately in the database, sequential matching will automatically match all images in consecutive frames against each other. - **Vocabulary Tree Matching**: In this matching mode [schoenberger16vote]_, every image is matched against its visual nearest neighbors using a vocabulary tree with spatial re-ranking. This is the recommended matching mode for large image collections (several thousands). This requires a pre-trained vocabulary tree, that can be downloaded from https://demuc.de/colmap/. - **Spatial Matching**: This matching mode matches every image against its spatial nearest neighbors. Spatial locations can be manually set in the database management. By default, COLMAP also extracts GPS information from EXIF and uses it for spatial nearest neighbor search. If accurate prior location information is available, this is the recommended matching mode. - **Transitive Matching**: This matching mode uses the transitive relations of already existing feature matches to produce a more complete matching graph. If an image A matches to an image B and B matches to C, then this matcher attempts to match A to C directly. - **Custom Matching**: This mode allows you to specify individual image pairs for matching or to import individual feature matches. To specify image pairs, you have to provide a text file with one image pair per line:: image1.jpg image2.jpg image1.jpg image3.jpg ... where ``image1.jpg`` is the relative path in the image folder. You have two options for importing individual feature matches: either raw feature matches, which are not geometrically verified, or already geometrically verified feature matches. In both cases, the expected format is:: image1.jpg image2.jpg 0 1 1 2 3 4 image1.jpg image3.jpg 0 1 1 2 3 4 4 5 ... where ``image1.jpg`` is the relative path in the image folder and the pairs of numbers are zero-based feature indices in the respective images. If you must import many matches for large image collections, it is more efficient to directly access the database with a scripting language of your choice. If you are done setting all options, choose ``Match`` and wait for the matching to finish or cancel in between. Note that this step can take a significant amount of time depending on the number of images, the number of features per image, and the chosen matching mode. Expected times for exhaustive matching are from a few minutes for tens of images to a few hours for hundreds of images to days or weeks for thousands of images. Exhaustive matching scales quadratically with the number of images and quickly becomes impractical for large collections; for thousands of images or more, use vocabulary tree or sequential matching instead, which are dramatically faster. If you cancel the matching process or import new images after matching, COLMAP only matches image pairs that have not been matched previously. The overhead of skipping already matched image pairs is low. This also makes it possible to match additional images imported after an initial matching, and to combine different matching modes for the same dataset. All extracted data will be stored in the database file and can be reviewed/managed in the database management tool (see :ref:`Database Management `) or, for experts, directly modified using SQLite (see :ref:`Database Format `). Note that SIFT feature matching can use a GPU for acceleration, and the display performance of your computer might degrade significantly during the matching process. If your system has multiple CUDA-enabled GPUs, you can select specific GPUs with the ``--FeatureMatching.gpu_index`` option. Feature matching can also be performed on the CPU by setting ``--FeatureMatching.use_gpu 0``, although this will be significantly slower for large datasets. Sparse Reconstruction --------------------- After producing the scene graph in the previous two steps, you can start the incremental reconstruction process by choosing ``Reconstruction > Start``. COLMAP first loads all extracted data from the database into memory and seeds the reconstruction from an initial image pair. Then, the scene is incrementally extended by registering new images and triangulating new points. The results are visualized in "real-time" during this reconstruction process. Refer to the :ref:`Graphical User Interface ` section for more details about the available controls. COLMAP attempts to reconstruct multiple models if not all images are registered into the same model. The different models can be selected from the drop-down menu in the toolbar. If the different models have common registered images, you can use the ``model_merger`` executable to merge them into a single reconstruction (see :ref:`FAQ ` for details). Ideally, the reconstruction works fine and all images are registered. If this is not the case, it is recommended to: - Perform additional matching. For best results, use exhaustive matching, enable guided matching, increase the number of nearest neighbors in vocabulary tree matching, or increase the overlap in sequential matching, etc. - Manually choose an initial image pair, if COLMAP fails to initialize. Choose ``Reconstruction > Reconstruction options > Init`` and set images from the database management tool that have enough matches from different viewpoints. Importing and Exporting ----------------------- COLMAP provides several export options for further processing. For full flexibility, it is recommended to export the reconstruction in COLMAP's data format by choosing ``File > Export model`` to export the currently viewed model or ``File > Export all models`` to export all reconstructed models. The model is exported in the selected folder using separate text files for the reconstructed cameras, images, and points. When exporting in COLMAP's data format, you can re-import the reconstruction for later visualization, image undistortion, or to continue an existing reconstruction from where it left off (e.g., after importing and matching new images). To import a model, choose ``File > Import model`` and select the export folder path. Alternatively, you can export the model in various other formats, such as Bundler, VisualSfM [#f1]_, PLY, or VRML by choosing ``File > Export model as...``. COLMAP can visualize plain PLY point cloud files with RGB information by choosing ``File > Import from ...``. Further information about the format of the exported models can be found :ref:`here `. .. _dense-reconstruction: Dense Reconstruction -------------------- After reconstructing a sparse representation of the scene and the camera poses of the input images, MVS can now recover denser scene geometry. COLMAP has an integrated dense reconstruction pipeline to produce depth and normal maps for all registered images, to fuse the depth and normal maps into a dense point cloud with normal information, and to finally estimate a dense surface from the fused point cloud using Poisson [kazhdan2013]_ or Delaunay reconstruction. Optionally, the resulting meshes can be simplified using the ``mesh_simplifier`` command to reduce the number of faces while preserving the overall shape. The meshes can also be textured using the ``mesh_texturer`` command, which produces a texture atlas and per-face UV coordinates from the undistorted images. To get started, import your sparse 3D model into COLMAP (or select the reconstructed model after finishing the previous sparse reconstruction steps). Then, choose ``Reconstruction > Multi-view stereo`` and select an empty or existing workspace folder, which is used for the output of all dense reconstruction results. The first step is to ``undistort`` the images, second to compute the depth and normal maps using ``stereo``, third to ``fuse`` the depth and normal maps into a point cloud, followed by a final, optional point cloud ``meshing`` step. These steps are also available from the command-line as the ``image_undistorter``, ``patch_match_stereo``, ``stereo_fusion``, and ``poisson_mesher`` / ``delaunay_mesher`` commands, respectively. During the stereo reconstruction process, the display might freeze due to heavy compute load and, if your GPU does not have enough memory, the reconstruction process might crash ungracefully. Please refer to the FAQ (:ref:`freeze ` and :ref:`memory `) for information on how to avoid these problems. Note that the reconstructed normals of the point cloud cannot be visualized directly in COLMAP, but can be viewed in external tools such as Meshlab by enabling ``Render > Show Normal/Curvature``. Similarly, the reconstructed dense surface mesh model must be visualized with external software. In addition to the internal dense reconstruction functionality, COLMAP can export to several other dense reconstruction libraries, such as CMVS/PMVS [furukawa10]_ or CMP-MVS [jancosek11]_. Please choose ``Extras > Undistort images`` and select the appropriate format. The output folders contain the reconstruction and the undistorted images. In addition, the folders contain sample shell scripts to perform the dense reconstruction. To run PMVS2, execute the following command:: ./path/to/pmvs2 /path/to/undistortion/folder/pmvs/ option-all where ``/path/to/undistortion/folder`` is the folder selected in the undistortion dialog. Make sure not to forget the trailing slash in ``/path/to/undistortion/folder/pmvs/`` in the above command-line arguments. For large datasets, you probably want to first run CMVS to cluster the scene into more manageable parts and then run COLMAP or PMVS2. Please refer to the sample shell scripts in the undistortion output folder on how to run CMVS in combination with COLMAP or PMVS2. Moreover, there are a number of external libraries that support COLMAP's output: - `CMVS/PMVS `_ [furukawa10]_ - `CMP-MVS `_ [jancosek11]_ - `Line3D++ `_ [hofer16]_. .. _database-management: Database Management ------------------- You can review and manage the imported cameras, images, and feature matches in the database management tool. Choose ``Processing > Manage database``. In the opening dialog, you can see the list of imported images and cameras. You can view the features and matches for each image by clicking ``Show image`` and ``Overlapping images``. Individual entries in the database tables can be modified by double-clicking specific cells. Note that any changes to the database are only effective after clicking ``Save``. To share intrinsic camera parameters between arbitrary groups of images, select one or more images, choose ``Set camera`` and set the ``camera_id``, which corresponds to the unique ``camera_id`` column in the cameras table. You can also add new cameras with specific parameters. By setting the ``prior_focal_length`` flag to 0 or 1, you can give a hint whether the reconstruction algorithm should trust the focal length value. In case of a prior lab calibration, you should set this value to 1. Without prior knowledge about the focal length, it is recommended to set this value to ``1.25 * max(width_in_px, height_in_px)``. The database management tool has only limited functionality and, for full control over the data, you must directly modify the SQLite database (see :ref:`Database Format `). By accessing the database directly, you can use COLMAP only for feature extraction and matching, or you can import your own features and matches and use COLMAP solely for its incremental reconstruction algorithm. .. _interface: Graphical and Command-line Interface ------------------------------------ Most of COLMAP's features are accessible from both the graphical and the command-line interface, which are both embedded in the same executable. You can provide the options directly as command-line arguments or you can provide a ``.ini`` project configuration file containing the options using the ``--project_path path/to/project.ini`` argument. To start the GUI application, please execute ``colmap gui`` or directly specify a project configuration as ``colmap gui --project_path path/to/project.ini`` to avoid tedious selection in the GUI. To list the different commands available from the command-line, execute ``colmap help``. For example, to run feature extraction from the command-line, you must execute ``colmap feature_extractor``. The :ref:`graphical user interface ` and :ref:`command-line interface ` sections provide more details about the available commands. .. rubric:: Footnotes .. [#f1] VisualSfM's [wu13]_ projection model applies the distortion to the measurements and COLMAP to the projection, hence the exported NVM file is not fully compatible with VisualSfM. colmap-4.2.0/doc/viewer.rst000066400000000000000000000077011524536416500156100ustar00rootroot00000000000000:html_theme.sidebar_secondary.remove: true :og:description: Inspect COLMAP sparse reconstructions directly in your browser with an interactive, private, local-only 3D viewer. .. meta:: :description: Inspect binary COLMAP sparse reconstructions directly in your browser with an interactive, private, local-only Three.js viewer. 3D Viewer ========= Open a binary COLMAP sparse reconstruction directly in your browser. Processing happens locally: reconstruction files and images are never uploaded. The 3D Viewer follows the visual conventions and model-view controls of the native :ref:`Graphical User Interface `, including navigation, point and image selection, observation inspection, and point/camera scaling. It is a read-only inspection tool with intentionally less functionality than the native GUI: it does not run reconstruction pipelines, edit models, or visualize dense point clouds and meshes. .. raw:: html

The interactive viewer is unavailable in this documentation build.

Supported Inputs ---------------- Drop a sparse model folder containing ``cameras.bin``, ``images.bin``, and ``points3D.bin``, or drop a workspace containing one or more sparse models. Current reconstructions with ``rigs.bin`` and ``frames.bin`` and legacy binary reconstructions are supported. If several models are found, choose one from the toolbar. To inspect image observations and reprojections, drop a workspace that also contains the source image tree. You can also drop the images folder after the model has loaded. Image paths are matched against the relative names recorded in ``images.bin``. Controls -------- - **Rotate:** left-click and drag. - **Pan:** right-click and drag. - **Zoom:** scroll. - **Point size:** -scroll (-scroll) or use the toolbar. - **Camera size:** -scroll or use the toolbar. - **Select:** double-click a point or camera. - **Clear selection:** double-click the background or use the toolbar. The viewer requires a current desktop browser with WebGL2. Folder selection is available as a fallback when directory drag-and-drop is not supported by the browser. Reusing the Viewer ------------------ The viewer is also an ES module component. Build it with ``npm run build`` in the ``doc`` directory, copy the complete ``_static/viewer`` directory so that the parser worker remains beside the module, and include both generated assets: .. code-block:: html
Each ``LocalFile`` entry has the shape ``{path, file}``, where ``path`` is the file's relative path and ``file`` is a browser ``File`` object. Multiple component instances can coexist on one page. The TypeScript source also exports the mount settings and lifecycle types. When embedding the component on another website, please include visible attribution to the `COLMAP project `_ and reproduce the full :doc:`COLMAP new BSD license notice ` in the website's legal or third-party notices. Also retain the bundled `Three.js license notice <_static/viewer-licenses.txt>`_ for that dependency. colmap-4.2.0/doc/viewer_src/000077500000000000000000000000001524536416500157205ustar00rootroot00000000000000colmap-4.2.0/doc/viewer_src/auto_mount.ts000066400000000000000000000004331524536416500204620ustar00rootroot00000000000000import {mountColmapViewer} from "./main"; export * from "./main"; const root = document.querySelector("#colmap-viewer-root"); if (root) { try { mountColmapViewer(root); } catch (error) { console.error("[COLMAP viewer] Initialization failed", error); } } colmap-4.2.0/doc/viewer_src/camera_models.ts000066400000000000000000000141031524536416500210620ustar00rootroot00000000000000import type {Camera, Vec2, Vec3} from "./types"; export const CAMERA_MODEL_NAMES = [ "SIMPLE_PINHOLE", "PINHOLE", "SIMPLE_RADIAL", "RADIAL", "OPENCV", "OPENCV_FISHEYE", "FULL_OPENCV", "FOV", "SIMPLE_RADIAL_FISHEYE", "RADIAL_FISHEYE", "THIN_PRISM_FISHEYE", "RAD_TAN_THIN_PRISM_FISHEYE", "SIMPLE_DIVISION", "DIVISION", "SIMPLE_FISHEYE", "FISHEYE", "EUCM", "EQUIRECTANGULAR", ] as const; export const CAMERA_MODEL_PARAM_COUNTS = [ 3, 4, 4, 5, 8, 8, 12, 5, 4, 5, 12, 16, 4, 5, 3, 4, 6, 2, ] as const; function fisheyeFromNormal(u: number, v: number): Vec2 { const radius = Math.hypot(u, v); if (radius <= Number.EPSILON) return [u, v]; const scale = Math.atan(radius) / radius; return [u * scale, v * scale]; } function radial(u: number, v: number, coeffs: number[]): Vec2 { const r2 = u * u + v * v; let power = r2; let factor = 1; for (const coefficient of coeffs) { factor += coefficient * power; power *= r2; } return [u * factor, v * factor]; } function pinhole(params: number[], uv: Vec2, sharedFocal: boolean): Vec2 { if (sharedFocal) return [params[0]! * uv[0] + params[1]!, params[0]! * uv[1] + params[2]!]; return [params[0]! * uv[0] + params[2]!, params[1]! * uv[1] + params[3]!]; } function opencvDistortion(u: number, v: number, p: number[]): Vec2 { const [k1 = 0, k2 = 0, p1 = 0, p2 = 0] = p; const u2 = u * u; const v2 = v * v; const uv = u * v; const r2 = u2 + v2; const factor = 1 + k1 * r2 + k2 * r2 * r2; return [ u * factor + 2 * p1 * uv + p2 * (r2 + 2 * u2), v * factor + 2 * p2 * uv + p1 * (r2 + 2 * v2), ]; } function fullOpenCVDistortion(u: number, v: number, p: number[]): Vec2 { const [k1 = 0, k2 = 0, p1 = 0, p2 = 0, k3 = 0, k4 = 0, k5 = 0, k6 = 0] = p; const u2 = u * u; const v2 = v * v; const uv = u * v; const r2 = u2 + v2; const r4 = r2 * r2; const r6 = r4 * r2; const factor = (1 + k1 * r2 + k2 * r4 + k3 * r6) / (1 + k4 * r2 + k5 * r4 + k6 * r6); return [ u * factor + 2 * p1 * uv + p2 * (r2 + 2 * u2), v * factor + 2 * p2 * uv + p1 * (r2 + 2 * v2), ]; } function thinPrismDistortion(u: number, v: number, p: number[]): Vec2 { const [k1 = 0, k2 = 0, p1 = 0, p2 = 0, k3 = 0, k4 = 0, sx = 0, sy = 0] = p; const u2 = u * u; const v2 = v * v; const uv = u * v; const r2 = u2 + v2; const r4 = r2 * r2; const factor = 1 + k1 * r2 + k2 * r4 + k3 * r4 * r2 + k4 * r4 * r4; return [ u * factor + 2 * p1 * uv + p2 * (r2 + 2 * u2) + sx * r2, v * factor + 2 * p2 * uv + p1 * (r2 + 2 * v2) + sy * r2, ]; } function radTanThinPrism(u: number, v: number, p: number[]): Vec2 { const r2Theta = u * u + v * v; let thetaPower = r2Theta; let thetaFactor = 1; for (let i = 0; i < 6; ++i) { thetaFactor += (p[i] ?? 0) * thetaPower; thetaPower *= r2Theta; } const x = u * thetaFactor; const y = v * thetaFactor; const x2 = x * x; const y2 = y * y; const xy = x * y; const r2 = x2 + y2; const r4 = r2 * r2; const p0 = p[6] ?? 0; const p1 = p[7] ?? 0; return [ x + 2 * p1 * xy + p0 * (r2 + 2 * x2) + (p[8] ?? 0) * r2 + (p[9] ?? 0) * r4, y + 2 * p0 * xy + p1 * (r2 + 2 * y2) + (p[10] ?? 0) * r2 + (p[11] ?? 0) * r4, ]; } export function project(camera: Camera, pointInCamera: Vec3): Vec2 | null { const [u, v, w] = pointInCamera; const p = camera.params; const model = camera.modelId; if (model === 17) { const horizontal = Math.hypot(u, w); if (horizontal + Math.abs(v) < Number.EPSILON) return null; const theta = Math.atan2(u, w); const phi = Math.atan2(-v, horizontal); return [(theta / (2 * Math.PI) + 0.5) * p[0]!, (0.5 - phi / Math.PI) * p[1]!]; } if (w < Number.EPSILON) return null; if (model === 12 || model === 13) { const k = p[model === 12 ? 3 : 4]!; const discriminant = w * w - 4 * (u * u + v * v) * k; if (discriminant < 0) return null; const scale = 2 / (w + Math.sqrt(discriminant)); return model === 12 ? [p[0]! * scale * u + p[1]!, p[0]! * scale * v + p[2]!] : [p[0]! * scale * u + p[2]!, p[1]! * scale * v + p[3]!]; } if (model === 16) { const rho2 = p[5]! * (u * u + v * v) + w * w; if (rho2 < 0) return null; const denominator = p[4]! * Math.sqrt(rho2) + (1 - p[4]!) * w; if (denominator < Number.EPSILON) return null; return [p[0]! * u / denominator + p[2]!, p[1]! * v / denominator + p[3]!]; } if (model === 0) return pinhole(p, [u / w, v / w], true); if (model === 1) return pinhole(p, [u / w, v / w], false); if (model === 7) { const uu = u / w; const vv = v / w; const radius2 = uu * uu + vv * vv; const omega = p[4]!; const omega2 = omega * omega; let factor: number; if (omega2 < 1e-4) factor = omega2 * radius2 / 3 - omega2 / 12 + 1; else if (radius2 < 1e-4) { const tangent = Math.tan(omega / 2); factor = -2 * tangent * (4 * radius2 * tangent * tangent - 3) / (3 * omega); } else factor = Math.atan(Math.sqrt(radius2) * 2 * Math.tan(omega / 2)) / (Math.sqrt(radius2) * omega); return pinhole(p, [factor * uu, factor * vv], false); } const fisheye = [5, 8, 9, 10, 11, 14, 15].includes(model); let normalized: Vec2 = fisheye ? fisheyeFromNormal(u / w, v / w) : [u / w, v / w]; switch (model) { case 2: normalized = radial(normalized[0], normalized[1], [p[3]!]); break; case 3: normalized = radial(normalized[0], normalized[1], p.slice(3, 5)); break; case 4: normalized = opencvDistortion(normalized[0], normalized[1], p.slice(4)); break; case 5: normalized = radial(normalized[0], normalized[1], p.slice(4, 8)); break; case 6: normalized = fullOpenCVDistortion(normalized[0], normalized[1], p.slice(4)); break; case 8: normalized = radial(normalized[0], normalized[1], [p[3]!]); break; case 9: normalized = radial(normalized[0], normalized[1], p.slice(3, 5)); break; case 10: normalized = thinPrismDistortion(normalized[0], normalized[1], p.slice(4)); break; case 11: normalized = radTanThinPrism(normalized[0], normalized[1], p.slice(4)); break; } return [0, 2, 3, 8, 9, 14].includes(model) ? pinhole(p, normalized, true) : pinhole(p, normalized, false); } colmap-4.2.0/doc/viewer_src/main.ts000066400000000000000000000664611524536416500172310ustar00rootroot00000000000000import "./viewer.css"; import {CAMERA_MODEL_NAMES, project} from "./camera_models"; import {projectionCenter, transformPoint} from "./math"; import {discoverSparseModels, normalizePath, parseReconstruction} from "./parser"; import {INVALID_POINT3D_ID, point2DAt, point2DCount, point3DCount} from "./types"; import type {ImageRecord, LocalFile, Point3D, Reconstruction, SparseModelCandidate} from "./types"; import {ReconstructionViewer} from "./viewer"; export {discoverSparseModels, parseReconstruction, ReconstructionViewer}; export type {LocalFile, Reconstruction}; export interface ColmapViewerOptions { title?: string; onError?: (error: Error) => void; } export interface ColmapViewerHandle { readonly viewer: ReconstructionViewer; load(source: Reconstruction | readonly LocalFile[], imageFiles?: readonly LocalFile[]): Promise; clear(): void; dispose(): void; } interface InspectorResources { observer: IntersectionObserver | null; } interface ImageFileIndex { exact: Map; suffix: Map; } const mountedViewers = new WeakMap(); export function mountColmapViewer(container: HTMLElement, options: ColmapViewerOptions = {}): ColmapViewerHandle { mountedViewers.get(container)?.dispose(); container.classList.add("colmap-viewer-host"); container.innerHTML = `
COLMAP - 3D Web Viewer

Open a COLMAP reconstruction

Drop a workspace or sparse model folder here.

Requires cameras.bin, images.bin, and points3D.bin. Source images are optional and never leave this browser.

`; viewerElement(container, "title").textContent = options.title ?? "COLMAP - 3D Web Viewer"; const canvas = viewerElement(container, "canvas"); let viewer: ReconstructionViewer; try { viewer = new ReconstructionViewer(canvas); } catch (error) { const message = `WebGL2 is unavailable: ${error instanceof Error ? error.message : String(error)}`; showFatal(container, message); throw new Error(message, {cause: error}); } const drop = viewerElement(container, "drop"); const status = viewerElement(container, "status"); const inspector = viewerElement(container, "inspector"); const stats = viewerElement(container, "stats"); const modelSelect = viewerElement(container, "model"); const input = viewerElement(container, "folder-input"); input.setAttribute("webkitdirectory", ""); const lifecycle = new AbortController(); const inspectorResources: InspectorResources = {observer: null}; let allEntries: LocalFile[] = []; let candidates: SparseModelCandidate[] = []; let reconstruction: Reconstruction | null = null; let imageFiles = createImageFileIndex([]); let currentSelection: {type: "point"; value: Point3D} | {type: "image"; value: ImageRecord} | null = null; let activeLoad: AbortController | null = null; let statusTimeout: number | null = null; let disposed = false; const setStatus = (message: string | null, error = false): void => { if (disposed) return; if (statusTimeout !== null) { window.clearTimeout(statusTimeout); statusTimeout = null; } status.hidden = message === null; status.textContent = message ?? ""; status.title = message ?? ""; status.classList.toggle("is-error", error); }; const refreshImages = (): void => { imageFiles = createImageFileIndex(allEntries); if (currentSelection && reconstruction) renderInspector(inspector, currentSelection, reconstruction, imageFiles, inspectorResources); }; const clearLoadedModel = (): void => { inspectorResources.observer?.disconnect(); inspectorResources.observer = null; reconstruction = null; currentSelection = null; viewer.clearReconstruction(); stats.textContent = ""; stats.hidden = true; for (const button of container.querySelectorAll('[data-viewer="reset"], [data-viewer="clear"]')) button.disabled = true; inspector.innerHTML = `

Inspector

Double-click a point or camera to inspect it.

`; }; const displayReconstruction = (parsed: Reconstruction): void => { if (disposed) throw new Error("Cannot load a disposed COLMAP viewer"); viewer.setReconstruction(parsed); reconstruction = parsed; drop.hidden = true; setStatus(null); stats.textContent = `${parsed.images.size.toLocaleString()} images / ${viewer.visiblePointCount.toLocaleString()} visible points`; stats.hidden = false; for (const button of container.querySelectorAll('[data-viewer="reset"], [data-viewer="clear"]')) button.disabled = false; currentSelection = null; inspector.innerHTML = `

Model loaded

Double-click a point or camera to inspect it.

Format
${parsed.modernRigFormat ? "Binary with rigs" : "Legacy binary"}
Images
${parsed.images.size.toLocaleString()}
Points
${point3DCount(parsed.points3D).toLocaleString()}
`; refreshImages(); }; const showLoadError = (phase: string, error: unknown): Error => { const parsedError = error instanceof Error ? error : new Error(String(error)); clearLoadedModel(); drop.hidden = false; console.error(`[COLMAP viewer] Failed to ${phase}`, parsedError); setStatus(`Failed to ${phase}: ${parsedError.name}: ${parsedError.message || "Unknown error"}`, true); options.onError?.(parsedError); return parsedError; }; const loadCandidate = async (candidate: SparseModelCandidate): Promise => { activeLoad?.abort(); const load = new AbortController(); activeLoad = load; clearLoadedModel(); setStatus("Parsing reconstruction..."); let phase = "parse model"; try { const parsed = await parseInWorker(candidate.files, load.signal); if (load.signal.aborted || activeLoad !== load) return; phase = "build Three.js scene"; displayReconstruction(parsed); } catch (error) { if (load.signal.aborted || activeLoad !== load || (error instanceof DOMException && error.name === "AbortError")) return; throw showLoadError(phase, error); } finally { if (activeLoad === load) activeLoad = null; } }; const acceptEntries = async (entries: LocalFile[]): Promise => { if (entries.length === 0) return; const found = discoverSparseModels(entries); if (found.length === 0 && reconstruction) { allEntries.push(...entries); refreshImages(); setStatus(`Added ${entries.length.toLocaleString()} image files`); statusTimeout = window.setTimeout(() => setStatus(null), 1800); return; } if (found.length === 0) { const error = new Error("No binary sparse model was found in that folder"); setStatus(error.message, true); options.onError?.(error); throw error; } allEntries = entries; candidates = found; refreshImages(); modelSelect.replaceChildren(...candidates.map((candidate, index) => { const option = document.createElement("option"); option.value = String(index); option.textContent = candidate.path === "." ? "Sparse model" : candidate.path; return option; })); modelSelect.hidden = candidates.length < 2; await loadCandidate(candidates[0]!); }; const openPicker = (): void => input.click(); const listenerOptions = {signal: lifecycle.signal}; viewerElement(container, "open").addEventListener("click", openPicker, listenerOptions); viewerElement(container, "drop-open").addEventListener("click", openPicker, listenerOptions); input.addEventListener("change", () => { const entries = [...(input.files ?? [])].map((file) => ({path: file.webkitRelativePath || file.name, file})); input.value = ""; void acceptEntries(entries).catch(() => undefined); }, listenerOptions); modelSelect.addEventListener("change", () => { const candidate = candidates[Number(modelSelect.value)]; if (candidate) void loadCandidate(candidate).catch(() => undefined); }, listenerOptions); for (const eventName of ["dragenter", "dragover"] as const) container.addEventListener(eventName, (event) => { event.preventDefault(); drop.hidden = false; drop.classList.add("is-dragging"); }, listenerOptions); container.addEventListener("dragleave", (event) => { if (!container.contains(event.relatedTarget as Node | null)) { drop.classList.remove("is-dragging"); if (reconstruction) drop.hidden = true; } }, listenerOptions); container.addEventListener("drop", (event) => { event.preventDefault(); drop.classList.remove("is-dragging"); void filesFromDrop(event.dataTransfer) .then(acceptEntries) .then(() => { if (reconstruction) drop.hidden = true; }) .catch(() => undefined); }, listenerOptions); viewer.onSelection = (selection) => { currentSelection = selection; if (selection && reconstruction) renderInspector(inspector, selection, reconstruction, imageFiles, inspectorResources); else inspector.innerHTML = `

Inspector

Double-click a point or camera to inspect it.

`; }; viewer.onError = (error) => { console.error("[COLMAP viewer] WebGL render failed", error); setStatus(`WebGL render failed: ${error.name}: ${error.message || "Unknown error"}`, true); options.onError?.(error); }; viewer.onContextChange = (contextLost) => { setStatus(contextLost ? "WebGL context lost. Waiting for the browser to restore it..." : null, contextLost); }; viewer.onSettingsChange = (settings) => { viewerElement(container, "point-size").value = String(settings.pointSize); viewerElement(container, "camera-size").value = String(settings.cameraSize); }; viewerElement(container, "reset").addEventListener("click", () => viewer.resetView(), listenerOptions); viewerElement(container, "clear").addEventListener("click", () => viewer.clearSelection(), listenerOptions); bindControls(container, viewer, lifecycle.signal); const handle: ColmapViewerHandle = { viewer, async load(source, sourceImages = []): Promise { if (disposed) throw new Error("Cannot load a disposed COLMAP viewer"); if (Array.isArray(source)) { await acceptEntries([...source]); return; } activeLoad?.abort(); const load = new AbortController(); activeLoad = load; clearLoadedModel(); allEntries = [...sourceImages]; candidates = []; modelSelect.replaceChildren(); modelSelect.hidden = true; refreshImages(); setStatus("Building Three.js scene..."); try { displayReconstruction(source as Reconstruction); } catch (error) { throw showLoadError("build Three.js scene", error); } finally { if (activeLoad === load) activeLoad = null; } }, clear(): void { if (disposed) return; activeLoad?.abort(); activeLoad = null; allEntries = []; candidates = []; imageFiles = createImageFileIndex([]); modelSelect.replaceChildren(); modelSelect.hidden = true; clearLoadedModel(); drop.hidden = false; setStatus(null); }, dispose(): void { if (disposed) return; disposed = true; activeLoad?.abort(); if (statusTimeout !== null) window.clearTimeout(statusTimeout); inspectorResources.observer?.disconnect(); lifecycle.abort(); viewer.dispose(); container.replaceChildren(); container.classList.remove("colmap-viewer-host"); if (mountedViewers.get(container) === handle) mountedViewers.delete(container); }, }; mountedViewers.set(container, handle); return handle; } function viewerElement(root: ParentNode, name: string): T { const selector = `[data-viewer="${name}"]`; const element = root.querySelector(selector); if (!element) throw new Error(`Missing viewer element ${selector}`); return element; } function bindControls(root: ParentNode, viewer: ReconstructionViewer, signal: AbortSignal): void { const options = {signal}; viewerElement(root, "projection").addEventListener("change", (event) => viewer.updateSettings({projection: (event.currentTarget as HTMLSelectElement).value as "perspective" | "orthographic"}), options); viewerElement(root, "point-size").addEventListener("input", (event) => viewer.updateSettings({pointSize: Number((event.currentTarget as HTMLInputElement).value)}), options); viewerElement(root, "camera-size").addEventListener("input", (event) => viewer.updateSettings({cameraSize: Number((event.currentTarget as HTMLInputElement).value)}), options); viewerElement(root, "track").addEventListener("change", (event) => viewer.updateSettings({minTrackLength: Math.max(0, Number((event.currentTarget as HTMLInputElement).value))}), options); viewerElement(root, "error").addEventListener("change", (event) => viewer.updateSettings({maxError: Math.max(0, Number((event.currentTarget as HTMLInputElement).value))}), options); viewerElement(root, "connections").addEventListener("change", (event) => viewer.updateSettings({showConnections: (event.currentTarget as HTMLInputElement).checked}), options); } async function parseInWorker(files: Map, signal: AbortSignal): Promise { const worker = new Worker(new URL("./parser.worker.ts", import.meta.url), {type: "module"}); return await new Promise((resolve, reject) => { let settled = false; const finish = (callback: () => void): void => { if (settled) return; settled = true; signal.removeEventListener("abort", abort); worker.terminate(); callback(); }; const abort = (): void => finish(() => reject(new DOMException("Model load superseded", "AbortError"))); if (signal.aborted) { abort(); return; } signal.addEventListener("abort", abort, {once: true}); worker.onmessage = (event: MessageEvent<{ok: boolean; reconstruction?: Reconstruction; error?: string}>) => { if (event.data.ok && event.data.reconstruction) finish(() => resolve(event.data.reconstruction!)); else finish(() => reject(new Error(event.data.error ?? "Could not parse reconstruction"))); }; worker.onerror = (event) => finish(() => reject(new Error(event.message || "Parser worker failed"))); worker.onmessageerror = () => finish(() => reject(new Error("Parser worker returned an unreadable result"))); try { worker.postMessage(files); } catch (error) { finish(() => reject(error instanceof Error ? error : new Error(String(error)))); } }); } async function filesFromDrop(transfer: DataTransfer | null): Promise { if (!transfer) return []; const roots = [...transfer.items].map((item) => item.webkitGetAsEntry()).filter((entry): entry is FileSystemEntry => entry !== null); if (roots.length > 0) { const files = await Promise.all(roots.map((entry) => readEntry(entry, entry.name))); return files.flat(); } return [...transfer.files].map((file) => ({path: file.name, file})); } async function readEntry(entry: FileSystemEntry, path: string): Promise { if (entry.isFile) return [{path, file: await new Promise((resolve, reject) => (entry as FileSystemFileEntry).file(resolve, reject))}]; if (!entry.isDirectory) return []; const reader = (entry as FileSystemDirectoryEntry).createReader(); const children: FileSystemEntry[] = []; while (true) { const batch = await new Promise((resolve, reject) => reader.readEntries(resolve, reject)); if (batch.length === 0) break; children.push(...batch); } const nested = await Promise.all(children.map((child) => readEntry(child, `${path}/${child.name}`))); return nested.flat(); } function createImageFileIndex(entries: readonly LocalFile[]): ImageFileIndex { const exact = new Map(entries.map((entry) => [normalizePath(entry.path), entry.file])); const suffix = new Map(); const suffixPathLength = new Map(); for (const [path, file] of exact) { for (let start = 0; start < path.length;) { const candidate = path.slice(start); if (!suffixPathLength.has(candidate) || path.length < suffixPathLength.get(candidate)!) { suffix.set(candidate, file); suffixPathLength.set(candidate, path.length); } const slash = path.indexOf("/", start); if (slash < 0) break; start = slash + 1; } } return {exact, suffix}; } function findImageFile(files: ImageFileIndex, name: string): File | null { const normalized = normalizePath(name); return files.exact.get(normalized) ?? files.suffix.get(normalized) ?? null; } function metadata(title: string, rows: Array<[string, string]>): HTMLElement { const section = document.createElement("section"); const heading = document.createElement("h2"); heading.textContent = title; const list = document.createElement("dl"); for (const [key, value] of rows) { const term = document.createElement("dt"); term.textContent = key; const description = document.createElement("dd"); description.textContent = value; list.append(term, description); } section.append(heading, list); return section; } function renderInspector( inspector: HTMLElement, selection: {type: "point"; value: Point3D} | {type: "image"; value: ImageRecord}, reconstruction: Reconstruction, files: ImageFileIndex, resources: InspectorResources, ): void { resources.observer?.disconnect(); resources.observer = null; inspector.replaceChildren(); if (selection.type === "image") { const image = selection.value; const camera = reconstruction.cameras.get(image.cameraId)!; let triangulated = 0; for (const point3DId of image.points2D.point3DIds) if (point3DId !== INVALID_POINT3D_ID) ++triangulated; const center = projectionCenter(image.camFromWorld); inspector.append(metadata(`Image ${image.id}`, [ ["Image", image.name], ["Camera model", CAMERA_MODEL_NAMES[camera.modelId] ?? `Unknown (${camera.modelId})`], ["Dimensions", `${camera.width} x ${camera.height}`], ["Frame / rig", `${image.frameId} / ${image.rigId}`], ["Observations", `${triangulated.toLocaleString()} / ${point2DCount(image).toLocaleString()} triangulated`], ["Center", center.map((value) => value.toFixed(6)).join(", ")], ["Pose (qw,qx,qy,qz | tx,ty,tz)", `${image.camFromWorld.rotation.map((value) => value.toFixed(6)).join(", ")} | ${image.camFromWorld.translation.map((value) => value.toFixed(6)).join(", ")}`], ])); const file = findImageFile(files, image.name); const figure = document.createElement("figure"); figure.className = "viewer-image"; if (file) { const canvas = document.createElement("canvas"); figure.append(canvas); void drawCameraImage(canvas, file, image, camera.width, camera.height).catch(() => showMissingImage(figure, "The browser could not decode this image.")); } else showMissingImage(figure, "Source image unavailable. Drop the images folder to display keypoints."); inspector.append(figure); } else { const point = selection.value; inspector.append(metadata(`Point ${point.id}`, [ ["Position", point.xyz.map((value) => value.toFixed(8)).join(", ")], ["Color", point.color.join(", ")], ["Error", `${point.error.toFixed(4)} px`], ["Track length", point.track.length.toLocaleString()], ])); const heading = document.createElement("h3"); heading.textContent = "Observations"; const gallery = document.createElement("div"); gallery.className = "viewer-observations"; const tracks = [...point.track].sort((a, b) => (reconstruction.images.get(a.imageId)?.name ?? "").localeCompare(reconstruction.images.get(b.imageId)?.name ?? "")); for (const track of tracks) { const image = reconstruction.images.get(track.imageId); if (!image) continue; const observed = point2DAt(image, track.point2DIdx); const camera = reconstruction.cameras.get(image.cameraId); if (!observed || !camera) continue; const projected = project(camera, transformPoint(image.camFromWorld, point.xyz)); const card = document.createElement("article"); card.className = "viewer-observation"; const label = document.createElement("div"); label.className = "viewer-observation-label"; label.textContent = image.name; const detail = document.createElement("small"); detail.textContent = `Image ${image.id}`; label.append(detail); card.append(label); const file = findImageFile(files, image.name); if (file) { const canvas = document.createElement("canvas"); canvas.width = 280; canvas.height = 180; card.prepend(canvas); if (projected) { const error = Math.hypot(observed.xy[0] - projected[0], observed.xy[1] - projected[1]); detail.textContent = `Image ${image.id} / ${error.toFixed(3)} px`; } const load = (): void => { void drawObservation(canvas, file, observed.xy, projected).catch(() => { canvas.replaceWith(document.createTextNode("Image unavailable")); }); }; if ("IntersectionObserver" in window) { resources.observer ??= new IntersectionObserver((entries, observer) => { for (const entry of entries) { if (!entry.isIntersecting) continue; observer.unobserve(entry.target); (entry.target as HTMLElement).dispatchEvent(new Event("viewer-load-image")); } }, {root: inspector, rootMargin: "180px"}); canvas.addEventListener("viewer-load-image", load, {once: true}); resources.observer.observe(canvas); } else load(); } else { const missing = document.createElement("div"); missing.className = "viewer-observation-missing"; missing.textContent = "Image unavailable"; card.prepend(missing); } gallery.append(card); } inspector.append(heading, gallery); } } async function drawCameraImage(canvas: HTMLCanvasElement, file: File, image: ImageRecord, width: number, height: number): Promise { const bitmap = await createImageBitmap(file); const scale = Math.min(1, 1000 / Math.max(bitmap.width, bitmap.height)); canvas.width = Math.max(1, Math.round(bitmap.width * scale)); canvas.height = Math.max(1, Math.round(bitmap.height * scale)); const context = canvas.getContext("2d")!; context.drawImage(bitmap, 0, 0, canvas.width, canvas.height); bitmap.close(); const sx = canvas.width / width; const sy = canvas.height / height; for (let pointIndex = 0; pointIndex < point2DCount(image); ++pointIndex) { const point = point2DAt(image, pointIndex)!; context.fillStyle = point.point3DId === null ? "#ef3028" : "#ff00ff"; context.beginPath(); context.arc(point.xy[0] * sx, point.xy[1] * sy, 1.6, 0, Math.PI * 2); context.fill(); } } async function drawObservation(canvas: HTMLCanvasElement, file: File, observed: [number, number], projected: [number, number] | null): Promise { const bitmap = await createImageBitmap(file); const centerX = projected ? (observed[0] + projected[0]) / 2 : observed[0]; const centerY = projected ? (observed[1] + projected[1]) / 2 : observed[1]; const distance = projected ? Math.hypot(observed[0] - projected[0], observed[1] - projected[1]) : 0; const crop = Math.min(Math.max(120, distance * 2 + 50), Math.min(bitmap.width, bitmap.height)); const sourceX = Math.max(0, Math.min(bitmap.width - crop, centerX - crop / 2)); const sourceY = Math.max(0, Math.min(bitmap.height - crop, centerY - crop / 2)); const context = canvas.getContext("2d")!; context.drawImage(bitmap, sourceX, sourceY, crop, crop, 0, 0, canvas.width, canvas.height); bitmap.close(); const mapPoint = (xy: [number, number]): [number, number] => [(xy[0] - sourceX) / crop * canvas.width, (xy[1] - sourceY) / crop * canvas.height]; const [ox, oy] = mapPoint(observed); context.strokeStyle = "#00e13a"; context.lineWidth = 3; context.beginPath(); context.moveTo(ox - 9, oy - 9); context.lineTo(ox + 9, oy + 9); context.moveTo(ox - 9, oy + 9); context.lineTo(ox + 9, oy - 9); context.stroke(); if (projected) { const [px, py] = mapPoint(projected); context.strokeStyle = "#ef3028"; context.lineWidth = 2; for (const radius of [4, 12, 30]) { context.beginPath(); context.arc(px, py, radius, 0, Math.PI * 2); context.stroke(); } } } function showMissingImage(figure: HTMLElement, message: string): void { figure.replaceChildren(); const note = document.createElement("p"); note.className = "viewer-image-missing"; note.textContent = message; figure.append(note); } function showFatal(container: HTMLElement, message: string): void { container.replaceChildren(); const alert = document.createElement("div"); alert.className = "viewer-fatal"; alert.setAttribute("role", "alert"); alert.textContent = message; container.append(alert); } colmap-4.2.0/doc/viewer_src/math.ts000066400000000000000000000043421524536416500172240ustar00rootroot00000000000000import type {Quat, Rigid3d, Vec3} from "./types"; export function quatMultiply(a: Quat, b: Quat): Quat { return [ a[0] * b[0] - a[1] * b[1] - a[2] * b[2] - a[3] * b[3], a[0] * b[1] + a[1] * b[0] + a[2] * b[3] - a[3] * b[2], a[0] * b[2] - a[1] * b[3] + a[2] * b[0] + a[3] * b[1], a[0] * b[3] + a[1] * b[2] - a[2] * b[1] + a[3] * b[0], ]; } export function quatRotate(q: Quat, v: Vec3): Vec3 { const [w, x, y, z] = q; const tx = 2 * (y * v[2] - z * v[1]); const ty = 2 * (z * v[0] - x * v[2]); const tz = 2 * (x * v[1] - y * v[0]); return [ v[0] + w * tx + y * tz - z * ty, v[1] + w * ty + z * tx - x * tz, v[2] + w * tz + x * ty - y * tx, ]; } export function composeRigid(a: Rigid3d, b: Rigid3d): Rigid3d { const t = quatRotate(a.rotation, b.translation); return { rotation: quatMultiply(a.rotation, b.rotation), translation: [ t[0] + a.translation[0], t[1] + a.translation[1], t[2] + a.translation[2], ], }; } export function transformPoint(transform: Rigid3d, point: Vec3): Vec3 { const rotated = quatRotate(transform.rotation, point); return [ rotated[0] + transform.translation[0], rotated[1] + transform.translation[1], rotated[2] + transform.translation[2], ]; } export function projectionCenter(camFromWorld: Rigid3d): Vec3 { const [w, x, y, z] = camFromWorld.rotation; const rotated = quatRotate( [w, -x, -y, -z], camFromWorld.translation, ); return [-rotated[0], -rotated[1], -rotated[2]]; } export function percentile(values: number[], q: number): number { if (values.length === 0) return 0; const sorted = [...values].sort((a, b) => a - b); const index = Math.min(sorted.length - 1, Math.max(0, q * (sorted.length - 1))); const left = Math.floor(index); const right = Math.ceil(index); if (left === right) return sorted[left] ?? 0; return (sorted[left] ?? 0) * (right - index) + (sorted[right] ?? 0) * (index - left); } export function median(values: number[]): number { if (values.length === 0) return 0; const sorted = [...values].sort((a, b) => a - b); const middle = Math.floor(sorted.length / 2); return sorted.length % 2 ? (sorted[middle] ?? 0) : ((sorted[middle - 1] ?? 0) + (sorted[middle] ?? 0)) / 2; } colmap-4.2.0/doc/viewer_src/parser.ts000066400000000000000000000334101524536416500175650ustar00rootroot00000000000000import {CAMERA_MODEL_PARAM_COUNTS} from "./camera_models"; import {composeRigid} from "./math"; import type { Camera, ImageRecord, LocalFile, Point3DData, Quat, Reconstruction, Rigid3d, SparseModelCandidate, Vec3, } from "./types"; class BinaryReader { private readonly view: DataView; private offset = 0; constructor(buffer: ArrayBuffer, private readonly label: string) { this.view = new DataView(buffer); } private require(bytes: number): void { if (bytes < 0 || this.offset + bytes > this.view.byteLength) { throw new Error(`${this.label} is truncated at byte ${this.offset}`); } } u8(): number { this.require(1); return this.view.getUint8(this.offset++); } i32(): number { this.require(4); const value = this.view.getInt32(this.offset, true); this.offset += 4; return value; } u32(): number { this.require(4); const value = this.view.getUint32(this.offset, true); this.offset += 4; return value; } u64(): bigint { this.require(8); const value = this.view.getBigUint64(this.offset, true); this.offset += 8; return value; } f64(): number { this.require(8); const value = this.view.getFloat64(this.offset, true); this.offset += 8; return value; } skip(bytes: number): void { this.require(bytes); this.offset += bytes; } count(name: string, minimumBytesPerItem = 1): number { const value = this.u64(); if (value > BigInt(Number.MAX_SAFE_INTEGER)) throw new Error(`${this.label} has an invalid ${name}`); const count = Number(value); if (count > Math.floor(this.remaining / minimumBytesPerItem)) throw new Error(`${this.label} has an invalid ${name}`); return count; } string(): string { const bytes: number[] = []; while (true) { const byte = this.u8(); if (byte === 0) break; bytes.push(byte); } return new TextDecoder("utf-8", {fatal: true}).decode(new Uint8Array(bytes)); } get remaining(): number { return this.view.byteLength - this.offset; } } interface Rig { id: number; refSensor: string; sensors: Map; } interface Frame { id: number; rigId: number; rigFromWorld: Rigid3d; data: Array<{sensor: string; dataId: bigint}>; } function sensorKey(type: number, id: number): string { return `${type}:${id}`; } function readQuat(reader: BinaryReader): Quat { return [reader.f64(), reader.f64(), reader.f64(), reader.f64()]; } function readVec3(reader: BinaryReader): Vec3 { return [reader.f64(), reader.f64(), reader.f64()]; } function readRigid(reader: BinaryReader): Rigid3d { const transform = {rotation: readQuat(reader), translation: readVec3(reader)}; if (![...transform.rotation, ...transform.translation].every(Number.isFinite)) throw new Error("Reconstruction contains a non-finite pose"); return transform; } function parseCameras(buffer: ArrayBuffer): Map { const reader = new BinaryReader(buffer, "cameras.bin"); const count = reader.count("camera count", 24); const cameras = new Map(); for (let i = 0; i < count; ++i) { const id = reader.u32(); const modelId = reader.i32(); const paramCount = CAMERA_MODEL_PARAM_COUNTS[modelId]; if (paramCount === undefined) throw new Error(`Unsupported camera model id ${modelId}`); const widthBig = reader.u64(); const heightBig = reader.u64(); if (widthBig > BigInt(Number.MAX_SAFE_INTEGER) || heightBig > BigInt(Number.MAX_SAFE_INTEGER)) throw new Error(`Camera ${id} has invalid dimensions`); const params = Array.from({length: paramCount}, () => reader.f64()); if (widthBig === 0n || heightBig === 0n || !params.every(Number.isFinite)) throw new Error(`Camera ${id} has invalid parameters`); cameras.set(id, {id, modelId, width: Number(widthBig), height: Number(heightBig), params}); } if (reader.remaining !== 0) throw new Error("cameras.bin contains trailing data"); return cameras; } function parseRigs(buffer: ArrayBuffer): Map { const reader = new BinaryReader(buffer, "rigs.bin"); const count = reader.count("rig count", 8); const rigs = new Map(); for (let i = 0; i < count; ++i) { const id = reader.u32(); const sensorCount = reader.u32(); if (sensorCount > reader.remaining / 8) throw new Error("rigs.bin has an invalid sensor count"); let refSensor = ""; const sensors = new Map(); if (sensorCount > 0) { refSensor = sensorKey(reader.i32(), reader.u32()); sensors.set(refSensor, {rotation: [1, 0, 0, 0], translation: [0, 0, 0]}); } for (let j = 1; j < sensorCount; ++j) { const sensor = sensorKey(reader.i32(), reader.u32()); sensors.set(sensor, reader.u8() ? readRigid(reader) : null); } rigs.set(id, {id, refSensor, sensors}); } if (reader.remaining !== 0) throw new Error("rigs.bin contains trailing data"); return rigs; } function parseFrames(buffer: ArrayBuffer): Map { const reader = new BinaryReader(buffer, "frames.bin"); const count = reader.count("frame count", 68); const frames = new Map(); for (let i = 0; i < count; ++i) { const id = reader.u32(); const rigId = reader.u32(); const rigFromWorld = readRigid(reader); const dataCount = reader.u32(); if (dataCount > reader.remaining / 16) throw new Error("frames.bin has an invalid data count"); const data = Array.from({length: dataCount}, () => ({ sensor: sensorKey(reader.i32(), reader.u32()), dataId: reader.u64(), })); frames.set(id, {id, rigId, rigFromWorld, data}); } if (reader.remaining !== 0) throw new Error("frames.bin contains trailing data"); return frames; } function parseImages( buffer: ArrayBuffer, rigs: Map | null, frames: Map | null, ): Map { const reader = new BinaryReader(buffer, "images.bin"); const count = reader.count("image count", 72); const images = new Map(); const imageToFrame = new Map(); if (frames) for (const frame of frames.values()) for (const data of frame.data) if (data.sensor.startsWith("0:")) imageToFrame.set(data.dataId, frame); for (let i = 0; i < count; ++i) { const id = reader.u32(); const serializedPose = readRigid(reader); const cameraId = reader.u32(); const name = reader.string(); const pointCount = reader.count("point2D count", 24); const points2D = {xy: new Float64Array(pointCount * 2), point3DIds: new BigUint64Array(pointCount)}; for (let pointIdx = 0; pointIdx < pointCount; ++pointIdx) { const x = reader.f64(); const y = reader.f64(); if (!Number.isFinite(x) || !Number.isFinite(y)) throw new Error(`Image ${id} has a non-finite observation`); points2D.xy[pointIdx * 2] = x; points2D.xy[pointIdx * 2 + 1] = y; points2D.point3DIds[pointIdx] = reader.u64(); } let camFromWorld = serializedPose; let frameId = id; let rigId = cameraId; if (rigs && frames) { const frame = imageToFrame.get(BigInt(id)); if (!frame) throw new Error(`No frame contains image ${id}`); const rig = rigs.get(frame.rigId); if (!rig) throw new Error(`Frame ${frame.id} references missing rig ${frame.rigId}`); const sensorFromRig = rig.sensors.get(sensorKey(0, cameraId)); if (sensorFromRig === undefined) throw new Error(`Rig ${rig.id} does not contain camera ${cameraId}`); if (sensorFromRig === null) throw new Error(`Rig ${rig.id} has no pose for camera ${cameraId}`); camFromWorld = composeRigid(sensorFromRig, frame.rigFromWorld); frameId = frame.id; rigId = rig.id; } images.set(id, {id, cameraId, frameId, rigId, name, camFromWorld, points2D}); } if (reader.remaining !== 0) throw new Error("images.bin contains trailing data"); return images; } function parsePoints3D(buffer: ArrayBuffer): Point3DData { const scan = new BinaryReader(buffer, "points3D.bin"); const count = scan.count("point3D count", 51); if (count > 0xffffffff) throw new Error("points3D.bin has too many points for this browser"); const sourceIds = new BigUint64Array(count); const trackLengths = new Uint32Array(count); let totalTracks = 0; let sorted = true; for (let i = 0; i < count; ++i) { const id = scan.u64(); sourceIds[i] = id; sorted &&= i === 0 || sourceIds[i - 1]! < id; scan.skip(35); const trackLength = scan.count("track length", 8); if (trackLength > 0xffffffff || totalTracks + trackLength > 0xffffffff) { throw new Error("points3D.bin has too many track elements for this browser"); } trackLengths[i] = trackLength; totalTracks += trackLength; scan.skip(trackLength * 8); } if (scan.remaining !== 0) throw new Error("points3D.bin contains trailing data"); let ids = sourceIds; let destinationForSource: Uint32Array | null = null; let sourceForDestination: Uint32Array | null = null; if (!sorted) { sourceForDestination = new Uint32Array(count); for (let index = 0; index < count; ++index) sourceForDestination[index] = index; sourceForDestination.sort((a, b) => sourceIds[a]! < sourceIds[b]! ? -1 : sourceIds[a]! > sourceIds[b]! ? 1 : 0); ids = new BigUint64Array(count); destinationForSource = new Uint32Array(count); for (let destination = 0; destination < count; ++destination) { const source = sourceForDestination[destination]!; ids[destination] = sourceIds[source]!; destinationForSource[source] = destination; } } for (let i = 1; i < count; ++i) if (ids[i - 1] === ids[i]) throw new Error(`points3D.bin contains duplicate point id ${ids[i]}`); const points: Point3DData = { ids, xyz: new Float64Array(count * 3), colors: new Uint8Array(count * 3), errors: new Float32Array(count), trackOffsets: new Uint32Array(count + 1), trackImageIds: new Uint32Array(totalTracks), trackPoint2DIdxs: new Uint32Array(totalTracks), }; for (let destination = 0; destination < count; ++destination) { const source = sourceForDestination?.[destination] ?? destination; points.trackOffsets[destination + 1] = points.trackOffsets[destination]! + trackLengths[source]!; } const reader = new BinaryReader(buffer, "points3D.bin"); reader.count("point3D count", 51); for (let source = 0; source < count; ++source) { const destination = destinationForSource?.[source] ?? source; const id = reader.u64(); const xyz = readVec3(reader); const color: [number, number, number] = [reader.u8(), reader.u8(), reader.u8()]; const error = reader.f64(); if (![...xyz, error].every(Number.isFinite)) throw new Error(`Point ${id} contains non-finite values`); points.xyz.set(xyz, destination * 3); points.colors.set(color, destination * 3); points.errors[destination] = error; const trackLength = reader.count("track length", 8); let trackIndex = points.trackOffsets[destination]!; for (let i = 0; i < trackLength; ++i, ++trackIndex) { points.trackImageIds[trackIndex] = reader.u32(); points.trackPoint2DIdxs[trackIndex] = reader.u32(); } } return points; } export function normalizePath(path: string): string { return path.replaceAll("\\", "/").replace(/^\.\//, "").replace(/\/+/g, "/"); } export function discoverSparseModels(entries: LocalFile[]): SparseModelCandidate[] { const groups = new Map>(); for (const entry of entries) { const path = normalizePath(entry.path); const slash = path.lastIndexOf("/"); const directory = slash < 0 ? "." : path.slice(0, slash); const basename = path.slice(slash + 1); const files = groups.get(directory) ?? new Map(); files.set(basename, entry.file); groups.set(directory, files); } return [...groups] .filter(([, files]) => ["cameras.bin", "images.bin", "points3D.bin"].every((name) => files.has(name))) .map(([path, files]) => ({path, files})) .sort((a, b) => a.path.localeCompare(b.path)); } export async function parseReconstruction(files: Map): Promise { const required = ["cameras.bin", "images.bin", "points3D.bin"]; for (const name of required) if (!files.has(name)) throw new Error(`Missing ${name}`); const hasRigs = files.has("rigs.bin"); const hasFrames = files.has("frames.bin"); if (hasRigs !== hasFrames) throw new Error("Modern reconstructions require both rigs.bin and frames.bin"); const buffers = await Promise.all([ files.get("cameras.bin")!.arrayBuffer(), files.get("images.bin")!.arrayBuffer(), files.get("points3D.bin")!.arrayBuffer(), hasRigs ? files.get("rigs.bin")!.arrayBuffer() : Promise.resolve(null), hasFrames ? files.get("frames.bin")!.arrayBuffer() : Promise.resolve(null), ]); const parsedRigs = buffers[3] ? parseRigs(buffers[3]) : null; const parsedFrames = buffers[4] ? parseFrames(buffers[4]) : null; const modernRigFormat = Boolean(parsedRigs && parsedFrames && (parsedRigs.size > 0 || parsedFrames.size > 0)); const rigs = modernRigFormat ? parsedRigs : null; const frames = modernRigFormat ? parsedFrames : null; const cameras = parseCameras(buffers[0]); const images = parseImages(buffers[1], rigs, frames); for (const image of images.values()) if (!cameras.has(image.cameraId)) throw new Error(`Image ${image.id} references missing camera ${image.cameraId}`); return {cameras, images, points3D: parsePoints3D(buffers[2]), modernRigFormat}; } export function reconstructionTransferables(reconstruction: Reconstruction): ArrayBuffer[] { const points = reconstruction.points3D; const buffers = [ points.ids.buffer, points.xyz.buffer, points.colors.buffer, points.errors.buffer, points.trackOffsets.buffer, points.trackImageIds.buffer, points.trackPoint2DIdxs.buffer, ] as ArrayBuffer[]; for (const image of reconstruction.images.values()) { buffers.push(image.points2D.xy.buffer as ArrayBuffer, image.points2D.point3DIds.buffer as ArrayBuffer); } return buffers; } colmap-4.2.0/doc/viewer_src/parser.worker.ts000066400000000000000000000007031524536416500210740ustar00rootroot00000000000000import {parseReconstruction, reconstructionTransferables} from "./parser"; self.onmessage = async (event: MessageEvent>) => { try { const reconstruction = await parseReconstruction(event.data); self.postMessage({ok: true, reconstruction}, {transfer: reconstructionTransferables(reconstruction)}); } catch (error) { self.postMessage({ok: false, error: error instanceof Error ? error.message : String(error)}); } }; colmap-4.2.0/doc/viewer_src/styles.d.ts000066400000000000000000000000301524536416500200260ustar00rootroot00000000000000declare module "*.css"; colmap-4.2.0/doc/viewer_src/types.ts000066400000000000000000000067221524536416500174430ustar00rootroot00000000000000export type Vec2 = [number, number]; export type Vec3 = [number, number, number]; export type Quat = [number, number, number, number]; // w, x, y, z export interface Rigid3d { rotation: Quat; translation: Vec3; } export interface Camera { id: number; modelId: number; width: number; height: number; params: number[]; } export const INVALID_POINT3D_ID = 0xffffffffffffffffn; // Observations are flattened so their buffers can be transferred from the parser worker. export interface Point2DData { // [x0, y0, x1, y1, ...] xy: Float64Array; point3DIds: BigUint64Array; } export interface Point2D { xy: Vec2; point3DId: bigint | null; } export interface ImageRecord { id: number; cameraId: number; frameId: number; rigId: number; name: string; camFromWorld: Rigid3d; points2D: Point2DData; } export interface TrackElement { imageId: number; point2DIdx: number; } export interface Point3D { id: bigint; xyz: Vec3; color: [number, number, number]; error: number; track: TrackElement[]; } export interface Point3DData { ids: BigUint64Array; xyz: Float64Array; colors: Uint8Array; errors: Float32Array; // Offsets delimit each point's range in the two parallel track arrays. trackOffsets: Uint32Array; trackImageIds: Uint32Array; trackPoint2DIdxs: Uint32Array; } export interface Reconstruction { cameras: Map; images: Map; points3D: Point3DData; modernRigFormat: boolean; } export function point2DCount(image: ImageRecord): number { return image.points2D.point3DIds.length; } export function point2DAt(image: ImageRecord, index: number): Point2D | undefined { if (index < 0 || index >= point2DCount(image)) return undefined; const point3DId = image.points2D.point3DIds[index]!; return { xy: [image.points2D.xy[index * 2]!, image.points2D.xy[index * 2 + 1]!], point3DId: point3DId === INVALID_POINT3D_ID ? null : point3DId, }; } export function point3DCount(points: Point3DData): number { return points.ids.length; } export function point3DTrackLength(points: Point3DData, index: number): number { return points.trackOffsets[index + 1]! - points.trackOffsets[index]!; } export function point3DAt(points: Point3DData, index: number): Point3D { if (index < 0 || index >= point3DCount(points)) throw new RangeError(`Invalid 3D point index ${index}`); const track = Array.from( {length: point3DTrackLength(points, index)}, (_, offset) => { const trackIndex = points.trackOffsets[index]! + offset; return { imageId: points.trackImageIds[trackIndex]!, point2DIdx: points.trackPoint2DIdxs[trackIndex]!, }; }, ); return { id: points.ids[index]!, xyz: [points.xyz[index * 3]!, points.xyz[index * 3 + 1]!, points.xyz[index * 3 + 2]!], color: [points.colors[index * 3]!, points.colors[index * 3 + 1]!, points.colors[index * 3 + 2]!], error: points.errors[index]!, track, }; } export function findPoint3DIndex(points: Point3DData, id: bigint): number { let lower = 0; let upper = points.ids.length; while (lower < upper) { const middle = lower + Math.floor((upper - lower) / 2); const middleId = points.ids[middle]!; if (middleId < id) lower = middle + 1; else upper = middle; } return lower < points.ids.length && points.ids[lower] === id ? lower : -1; } export interface LocalFile { path: string; file: File; } export interface SparseModelCandidate { path: string; files: Map; } colmap-4.2.0/doc/viewer_src/viewer.css000066400000000000000000000173041524536416500177400ustar00rootroot00000000000000.colmap-viewer-host { --viewer-ink: #202226; --viewer-muted: #686c74; --viewer-line: #d9d9d7; --viewer-panel: #f5f4f1; --viewer-red: #b21f1a; --viewer-font-family: var(--pst-font-family-base, "Helvetica Neue", Helvetica, Arial, sans-serif); color: var(--viewer-ink); margin: 0; } .colmap-viewer-host .colmap-viewer { background: #fff; border: 1px solid var(--viewer-line); border-radius: 0.45rem; box-shadow: 0 12px 42px rgb(24 24 23 / 12%); font-family: var(--viewer-font-family); min-height: min(760px, calc(100vh - 8rem)); overflow: hidden; } .colmap-viewer-host .viewer-toolbar, .colmap-viewer-host .viewer-controls { align-items: center; border-bottom: 1px solid var(--viewer-line); display: flex; gap: 0.75rem; justify-content: space-between; padding: 0.55rem 0.75rem; } .colmap-viewer-host .viewer-toolbar { background: var(--viewer-ink); color: #fff; } .colmap-viewer-host .viewer-brand, .colmap-viewer-host .viewer-actions { align-items: center; display: flex; gap: 0.8rem; } .colmap-viewer-host .viewer-brand strong { font-family: var(--viewer-font-family); font-size: 1.08rem; letter-spacing: 0.02em; } .colmap-viewer-host .viewer-stats { color: #c9c9c6; font-size: 0.76rem; } .colmap-viewer-host .colmap-viewer button, .colmap-viewer-host .colmap-viewer select, .colmap-viewer-host .colmap-viewer input { border: 1px solid #aaa9a5; border-radius: 0.25rem; font: inherit; } .colmap-viewer-host .colmap-viewer button, .colmap-viewer-host .colmap-viewer select { background: #fff; color: var(--viewer-ink); cursor: pointer; padding: 0.32rem 0.58rem; } .colmap-viewer-host .colmap-viewer button:hover:not(:disabled), .colmap-viewer-host .colmap-viewer button:focus-visible { border-color: var(--viewer-red); color: var(--viewer-red); } .colmap-viewer-host .colmap-viewer button:disabled { cursor: default; opacity: 0.45; } .colmap-viewer-host .viewer-controls { background: var(--viewer-panel); flex-wrap: wrap; justify-content: flex-start; font-size: 0.76rem; } .colmap-viewer-host .viewer-controls label { align-items: center; display: flex; gap: 0.35rem; margin: 0; white-space: nowrap; } .colmap-viewer-host .viewer-controls input[type="number"] { padding: 0.2rem 0.3rem; width: 4.2rem; } .colmap-viewer-host .viewer-controls input[type="range"] { accent-color: var(--viewer-red); max-width: 7rem; } .colmap-viewer-host .viewer-workspace { display: grid; grid-template-columns: minmax(0, 1fr) minmax(260px, 23rem); height: min(680px, calc(100vh - 13rem)); min-height: 520px; } .colmap-viewer-host .viewer-stage { background: #fff; min-width: 0; position: relative; } .colmap-viewer-host .viewer-canvas { display: block; height: 100%; outline: none; touch-action: none; width: 100%; } .colmap-viewer-host .viewer-drop { align-items: center; background: linear-gradient(135deg, rgb(255 255 255 / 94%), rgb(242 240 235 / 96%)), repeating-linear-gradient(45deg, transparent 0 18px, rgb(178 31 26 / 5%) 18px 19px); display: flex; flex-direction: column; inset: 0; justify-content: center; padding: 2rem; position: absolute; text-align: center; z-index: 3; } .colmap-viewer-host .viewer-drop[hidden] { display: none; } .colmap-viewer-host .viewer-drop.is-dragging { box-shadow: inset 0 0 0 4px var(--viewer-red); } .colmap-viewer-host .viewer-drop-mark { align-items: center; background: var(--viewer-red); color: #fff; display: flex; font-family: var(--viewer-font-family); font-size: 1.15rem; font-weight: 700; height: 3.6rem; justify-content: center; margin-bottom: 1rem; transform: rotate(-4deg); width: 3.6rem; } .colmap-viewer-host .viewer-drop h2 { font-family: var(--viewer-font-family); margin: 0 0 0.35rem; } .colmap-viewer-host .viewer-drop p { color: var(--viewer-muted); margin: 0.15rem 0; } .colmap-viewer-host .viewer-drop .viewer-drop-detail { font-size: 0.78rem; margin: 0.75rem auto 1rem; max-width: 34rem; } .colmap-viewer-host .viewer-status { background: rgb(32 34 38 / 88%); border-radius: 0.25rem; bottom: 0.75rem; color: #fff; left: 0.75rem; max-width: calc(100% - 1.5rem); min-width: 12rem; overflow-wrap: anywhere; padding: 0.42rem 0.65rem; position: absolute; z-index: 5; } .colmap-viewer-host .viewer-status.is-error { background: #8f1b18; } .colmap-viewer-host .viewer-inspector { background: var(--viewer-panel); border-left: 1px solid var(--viewer-line); overflow: auto; padding: 0.85rem; } .colmap-viewer-host .viewer-inspector h2, .colmap-viewer-host .viewer-inspector h3 { font-family: var(--viewer-font-family); margin: 0 0 0.6rem; } .colmap-viewer-host .viewer-inspector h3 { font-size: 1rem; margin-top: 1rem; } .colmap-viewer-host .viewer-inspector dl { display: grid; font-size: 0.76rem; grid-template-columns: max-content minmax(0, 1fr); margin: 0; } .colmap-viewer-host .viewer-inspector dt, .colmap-viewer-host .viewer-inspector dd { border-top: 1px solid var(--viewer-line); margin: 0; padding: 0.35rem 0.3rem; } .colmap-viewer-host .viewer-inspector dt { color: var(--viewer-muted); font-weight: 600; } .colmap-viewer-host .viewer-inspector dd { overflow-wrap: anywhere; } .colmap-viewer-host .viewer-inspector-empty p, .colmap-viewer-host .viewer-image-missing { color: var(--viewer-muted); font-size: 0.82rem; } .colmap-viewer-host .viewer-image { background: #deddd9; margin: 0.85rem 0 0; min-height: 8rem; } .colmap-viewer-host .viewer-image canvas { display: block; height: auto; max-width: 100%; } .colmap-viewer-host .viewer-image-missing { padding: 2rem 1rem; text-align: center; } .colmap-viewer-host .viewer-observations { display: grid; gap: 0.65rem; } .colmap-viewer-host .viewer-observation { background: #fff; border: 1px solid var(--viewer-line); } .colmap-viewer-host .viewer-observation canvas, .colmap-viewer-host .viewer-observation-missing { display: block; height: auto; width: 100%; } .colmap-viewer-host .viewer-observation-missing { color: var(--viewer-muted); padding: 2rem 1rem; text-align: center; } .colmap-viewer-host .viewer-observation-label { font-size: 0.72rem; overflow-wrap: anywhere; padding: 0.45rem; } .colmap-viewer-host .viewer-observation-label small { color: var(--viewer-muted); display: block; margin-top: 0.15rem; } @media (max-width: 900px) { .colmap-viewer-host .viewer-workspace { grid-template-columns: 1fr; grid-template-rows: minmax(420px, 62vh) auto; height: auto; } .colmap-viewer-host .viewer-inspector { border-left: 0; border-top: 1px solid var(--viewer-line); max-height: 28rem; } } @media (max-width: 620px) { .colmap-viewer-host .viewer-toolbar, .colmap-viewer-host .viewer-brand, .colmap-viewer-host .viewer-actions { align-items: stretch; flex-direction: column; } .colmap-viewer-host .viewer-toolbar { gap: 0.45rem; } .colmap-viewer-host .viewer-actions { display: grid; grid-template-columns: repeat(2, 1fr); } .colmap-viewer-host .viewer-workspace { grid-template-rows: minmax(360px, 58vh) auto; min-height: 0; } } .colmap-viewer-host .viewer-fatal { background: #fff1f0; border: 1px solid #b21f1a; border-radius: 0.35rem; color: #7f1713; font-family: var(--viewer-font-family); padding: 1rem; } colmap-4.2.0/doc/viewer_src/viewer.ts000066400000000000000000001110461524536416500175740ustar00rootroot00000000000000import * as THREE from "three"; import {OrbitControls} from "three/addons/controls/OrbitControls.js"; import {LineMaterial} from "three/addons/lines/LineMaterial.js"; import {LineSegments2} from "three/addons/lines/LineSegments2.js"; import {LineSegmentsGeometry} from "three/addons/lines/LineSegmentsGeometry.js"; import {median, percentile, projectionCenter, quatRotate} from "./math"; import { findPoint3DIndex, INVALID_POINT3D_ID, point2DCount, point3DAt, point3DCount, point3DTrackLength, } from "./types"; import type {Camera, ImageRecord, Point3D, Reconstruction, Vec3} from "./types"; export const CAMERA_FRUSTUM_COLORS = { frame: [0.8, 0.1, 0, 1], plane: [1, 0.1, 0, 0.6], selectedFrame: [0.8, 0, 0.8, 1], selectedPlane: [1, 0, 1, 0.6], sameFrame: [0.6, 0, 0.6, 179 / 255], sameFramePlane: [0.8, 0, 0.8, 77 / 255], } as const; export const COORDINATE_COLORS = { grid: [51 / 255, 51 / 255, 51 / 255, 153 / 255], x: [230 / 255, 0, 0, 128 / 255], y: [0, 230 / 255, 0, 128 / 255], z: [0, 0, 230 / 255, 128 / 255], } as const; const COLORS = { selectedPoint: new THREE.Color(0, 1, 0), selectedCameraPlane: new THREE.Color(1, 0, 1), pointConnection: new THREE.Color(0, 1, 0), imageConnection: new THREE.Color(0.8, 0, 0.8), }; type Selection = {type: "point"; value: Point3D} | {type: "image"; value: ImageRecord} | null; type InternalSelection = {type: "point"; index: number} | {type: "image"; value: ImageRecord} | null; export interface ViewerSettings { pointSize: number; cameraSize: number; minTrackLength: number; maxError: number; showConnections: boolean; projection: "perspective" | "orthographic"; } function toThreeQuaternion(rotation: [number, number, number, number]): THREE.Quaternion { return new THREE.Quaternion(rotation[1], rotation[2], rotation[3], rotation[0]).invert(); } function pushRgba(target: number[], color: readonly [number, number, number, number], count = 1): void { for (let i = 0; i < count; ++i) target.push(...color); } function vertexRgbaMaterial(side: THREE.Side = THREE.FrontSide, depthWrite = true): THREE.ShaderMaterial { return new THREE.ShaderMaterial({ vertexShader: "attribute vec4 color; varying vec4 vColor; void main(){vColor=color; gl_Position=projectionMatrix*modelViewMatrix*vec4(position,1.0);}", fragmentShader: "varying vec4 vColor; void main(){gl_FragColor=vColor;}", transparent: true, depthWrite, side, toneMapped: false, }); } function cameraRgbaMaterial(cameraSize: number, side: THREE.Side = THREE.FrontSide, depthWrite = true): THREE.ShaderMaterial { return new THREE.ShaderMaterial({ uniforms: {cameraSize: {value: cameraSize}}, vertexShader: "attribute vec3 cameraCenter; attribute vec4 color; varying vec4 vColor; uniform float cameraSize; void main(){vColor=color; vec3 p=cameraCenter+(position-cameraCenter)*cameraSize; gl_Position=projectionMatrix*modelViewMatrix*vec4(p,1.0);}", fragmentShader: "varying vec4 vColor; void main(){gl_FragColor=vColor;}", transparent: true, depthWrite, side, toneMapped: false, }); } function cameraPickingMaterial(cameraSize: number): THREE.ShaderMaterial { return new THREE.ShaderMaterial({ uniforms: {cameraSize: {value: cameraSize}}, vertexShader: "attribute vec3 cameraCenter; attribute vec3 color; varying vec3 vColor; uniform float cameraSize; void main(){vColor=color; vec3 p=cameraCenter+(position-cameraCenter)*cameraSize; gl_Position=projectionMatrix*modelViewMatrix*vec4(p,1.0);}", fragmentShader: "varying vec3 vColor; void main(){gl_FragColor=vec4(vColor,1.0);}", side: THREE.DoubleSide, toneMapped: false, }); } function pushVector(target: number[], vector: THREE.Vector3, count: number): void { for (let i = 0; i < count; ++i) target.push(vector.x, vector.y, vector.z); } function coordinateLines(colors: ReadonlyArray): THREE.LineSegments { const geometry = new THREE.BufferGeometry(); geometry.setAttribute("position", new THREE.Float32BufferAttribute(new Float32Array(colors.length * 2 * 3), 3)); const vertexColors: number[] = []; for (const color of colors) pushRgba(vertexColors, color, 2); geometry.setAttribute("color", new THREE.Float32BufferAttribute(vertexColors, 4)); return new THREE.LineSegments(geometry, vertexRgbaMaterial()); } function thickCoordinateAxes(): LineSegments2 { const geometry = new LineSegmentsGeometry(); geometry.setPositions(new Float32Array(18)); geometry.setColors([ ...COORDINATE_COLORS.x.slice(0, 3), ...COORDINATE_COLORS.x.slice(0, 3), ...COORDINATE_COLORS.y.slice(0, 3), ...COORDINATE_COLORS.y.slice(0, 3), ...COORDINATE_COLORS.z.slice(0, 3), ...COORDINATE_COLORS.z.slice(0, 3), ]); const material = new LineMaterial({transparent: true, opacity: 128 / 255}); material.linewidth = 2; material.vertexColors = true; const axes = new LineSegments2(geometry, material); axes.frustumCulled = false; return axes; } export function scaleFromNativeWheel(value: number, deltaY: number, deltaMode: number, minimum: number, maximum: number): number { const unitScale = deltaMode === 1 ? 40 : deltaMode === 2 ? 100 : 1; const nativeDelta = -deltaY * unitScale; const factor = Math.max(0.01, 1 + nativeDelta / 100 * 0.1); return THREE.MathUtils.clamp(value * factor, minimum, maximum); } export function photometricPointColor(rgb: readonly [number, number, number]): THREE.Color { return new THREE.Color().setRGB(rgb[0] / 255, rgb[1] / 255, rgb[2] / 255, THREE.SRGBColorSpace); } function disposeObject(object: THREE.Object3D): void { object.traverse((child) => { const renderable = child as THREE.Mesh; renderable.geometry?.dispose(); const material = renderable.material; if (Array.isArray(material)) material.forEach((item) => item.dispose()); else material?.dispose(); }); } export class ReconstructionViewer { readonly settings: ViewerSettings = { pointSize: 2, cameraSize: 0.025, minTrackLength: 3, maxError: 2, showConnections: false, projection: "perspective", }; onSelection: (selection: Selection) => void = () => undefined; onError: (error: Error) => void = () => undefined; onContextChange: (contextLost: boolean) => void = () => undefined; onSettingsChange: (settings: Readonly) => void = () => undefined; private readonly renderer: THREE.WebGLRenderer; private readonly scene = new THREE.Scene(); private readonly pickingScene = new THREE.Scene(); private readonly perspectiveCamera = new THREE.PerspectiveCamera(25, 1, 0.0001, 1e6); private readonly orthographicCamera = new THREE.OrthographicCamera(-1, 1, 1, -1, 0.0001, 1e6); private activeCamera: THREE.PerspectiveCamera | THREE.OrthographicCamera = this.perspectiveCamera; private controls: OrbitControls; private readonly renderTarget = new THREE.WebGLRenderTarget(1, 1, {depthBuffer: true, stencilBuffer: false}); private readonly content = new THREE.Group(); private readonly pickingContent = new THREE.Group(); private readonly pointContent = new THREE.Group(); private readonly cameraContent = new THREE.Group(); private readonly connectionContent = new THREE.Group(); private readonly pointPickingContent = new THREE.Group(); private readonly cameraPickingContent = new THREE.Group(); private readonly coordinateGrid = coordinateLines([COORDINATE_COLORS.grid, COORDINATE_COLORS.grid, COORDINATE_COLORS.grid]); private readonly coordinateAxes = thickCoordinateAxes(); private readonly resizeObserver: ResizeObserver; private reconstruction: Reconstruction | null = null; private pointsObject: THREE.Points | null = null; private pickPointsObject: THREE.Points | null = null; private pointDrawList = new Uint32Array(); private imageDrawList: ImageRecord[] = []; private center: Vec3 = [0, 0, 0]; private scale = 1; private viewCenter = new THREE.Vector3(); private viewRadius = 1; private coordinateOrigin = new THREE.Vector3(); private selection: InternalSelection = null; private animationFrame = 0; private contextLost = false; private disposed = false; constructor(private readonly canvas: HTMLCanvasElement) { this.renderer = new THREE.WebGLRenderer({canvas, antialias: true, preserveDrawingBuffer: false}); this.renderer.setClearColor(0xffffff, 1); this.renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2)); this.coordinateGrid.visible = false; this.coordinateAxes.visible = false; this.coordinateGrid.renderOrder = -2; this.coordinateAxes.renderOrder = -1; this.content.add(this.pointContent, this.cameraContent, this.connectionContent); this.pickingContent.add(this.pointPickingContent, this.cameraPickingContent); this.scene.add(this.coordinateGrid, this.coordinateAxes, this.content); this.pickingScene.add(this.pickingContent); this.perspectiveCamera.position.set(1.8, -1.8, 1.8); this.perspectiveCamera.up.set(0, -1, 0); this.orthographicCamera.up.copy(this.perspectiveCamera.up); this.controls = this.createControls(this.activeCamera); this.resizeObserver = new ResizeObserver(() => this.resize()); this.resizeObserver.observe(canvas.parentElement ?? canvas); canvas.addEventListener("dblclick", (event) => this.pick(event)); canvas.addEventListener("contextmenu", (event) => event.preventDefault()); canvas.addEventListener("wheel", (event) => this.handleModifiedWheel(event), {passive: false, capture: true}); canvas.addEventListener("webglcontextlost", this.handleContextLost); canvas.addEventListener("webglcontextrestored", this.handleContextRestored); this.resize(); this.startAnimation(); } private createControls(camera: THREE.Camera): OrbitControls { this.controls?.dispose(); const controls = new OrbitControls(camera, this.canvas); controls.enableDamping = true; controls.dampingFactor = 0.08; controls.screenSpacePanning = true; controls.mouseButtons.LEFT = THREE.MOUSE.ROTATE; controls.mouseButtons.RIGHT = THREE.MOUSE.PAN; return controls; } private startAnimation(): void { if (this.animationFrame === 0 && !this.contextLost && !this.disposed) this.animationFrame = requestAnimationFrame(this.animate); } private animate = (): void => { this.animationFrame = 0; if (this.contextLost || this.disposed) return; this.controls.update(); this.updateCoordinateOverlays(); try { this.renderer.render(this.scene, this.activeCamera); this.startAnimation(); } catch (error) { this.onError(error instanceof Error ? error : new Error(String(error))); } }; private handleContextLost = (event: Event): void => { event.preventDefault(); this.contextLost = true; cancelAnimationFrame(this.animationFrame); this.animationFrame = 0; if (!this.disposed) this.onContextChange(true); }; private handleContextRestored = (): void => { if (this.disposed) return; this.contextLost = false; this.renderer.resetState(); this.resize(); this.onContextChange(false); this.startAnimation(); }; dispose(): void { if (this.disposed) return; this.disposed = true; cancelAnimationFrame(this.animationFrame); this.animationFrame = 0; this.canvas.removeEventListener("webglcontextlost", this.handleContextLost); this.canvas.removeEventListener("webglcontextrestored", this.handleContextRestored); this.resizeObserver.disconnect(); this.controls.dispose(); this.clearGroups(); disposeObject(this.coordinateGrid); disposeObject(this.coordinateAxes); this.renderTarget.dispose(); this.renderer.dispose(); } setReconstruction(reconstruction: Reconstruction): void { this.reconstruction = reconstruction; this.selection = null; const centers = [...reconstruction.images.values()].map((image) => projectionCenter(image.camFromWorld)); const xs = centers.map((center) => center[0]); const ys = centers.map((center) => center[1]); const zs = centers.map((center) => center[2]); this.center = [median(xs), median(ys), median(zs)]; const extent = Math.max( percentile(xs, 0.95) - percentile(xs, 0.05), percentile(ys, 0.95) - percentile(ys, 0.05), percentile(zs, 0.95) - percentile(zs, 0.05), 1e-6, ); this.scale = 1 / extent; this.coordinateOrigin.copy(this.normalized([0, 0, 0])); this.coordinateGrid.visible = true; this.coordinateAxes.visible = true; this.rebuild(); this.computeViewBounds(); this.resize(); this.resetView(); } clearReconstruction(): void { this.reconstruction = null; this.selection = null; this.clearGroups(); this.pointDrawList = new Uint32Array(); this.imageDrawList = []; this.coordinateGrid.visible = false; this.coordinateAxes.visible = false; this.onSelection(null); } get visiblePointCount(): number { return this.pointDrawList.length; } updateSettings(settings: Partial): void { const projectionChanged = settings.projection !== undefined && settings.projection !== this.settings.projection; const pointSizeChanged = settings.pointSize !== undefined && settings.pointSize !== this.settings.pointSize; const cameraSizeChanged = settings.cameraSize !== undefined && settings.cameraSize !== this.settings.cameraSize; const filtersChanged = (settings.minTrackLength !== undefined && settings.minTrackLength !== this.settings.minTrackLength) || (settings.maxError !== undefined && settings.maxError !== this.settings.maxError); const connectionsChanged = settings.showConnections !== undefined && settings.showConnections !== this.settings.showConnections; Object.assign(this.settings, settings); this.onSettingsChange(this.settings); if (projectionChanged) this.switchProjection(); if (filtersChanged) { this.rebuild(); return; } if (pointSizeChanged) { const material = this.pointsObject?.material as THREE.PointsMaterial | undefined; if (material) material.size = this.settings.pointSize; const pickMaterial = this.pickPointsObject?.material as THREE.ShaderMaterial | undefined; if (pickMaterial) pickMaterial.uniforms.pointSize!.value = Math.max(8, this.settings.pointSize * 2); } if (cameraSizeChanged) this.updateCameraSize(); if (connectionsChanged) this.rebuildConnections(); } resetView(): void { const distance = Math.max( 0.5, this.viewRadius / Math.tan(THREE.MathUtils.degToRad(this.perspectiveCamera.fov) / 2) * 1.25, ); const direction = new THREE.Vector3(1, -1, 1).normalize(); this.activeCamera.position.copy(this.viewCenter).addScaledVector(direction, distance); this.controls.target.copy(this.viewCenter); this.activeCamera.lookAt(this.viewCenter); this.controls.update(); this.resize(); } clearSelection(): void { this.selection = null; this.rebuild(); this.onSelection(null); } private normalized(point: Vec3): THREE.Vector3 { return new THREE.Vector3( (point[0] - this.center[0]) * this.scale, (point[1] - this.center[1]) * this.scale, (point[2] - this.center[2]) * this.scale, ); } private normalizedPoint(index: number): THREE.Vector3 { const xyz = this.reconstruction!.points3D.xyz; const offset = index * 3; return this.normalized([xyz[offset]!, xyz[offset + 1]!, xyz[offset + 2]!]); } private worldUnitsPerPixel(): number { const height = Math.max(1, this.canvas.clientHeight); if (this.activeCamera instanceof THREE.OrthographicCamera) { return (this.activeCamera.top - this.activeCamera.bottom) / (this.activeCamera.zoom * height); } const distance = this.activeCamera.position.distanceTo(this.controls.target); return 2 * Math.tan(THREE.MathUtils.degToRad(this.activeCamera.fov) / 2) * distance / height; } private updateCoordinateOverlays(): void { if (!this.reconstruction) return; const unitsPerPixel = this.worldUnitsPerPixel(); const gridExtent = 20 * unitsPerPixel; const axesExtent = 50 * unitsPerPixel; const target = this.controls.target; const gridPositions = this.coordinateGrid.geometry.getAttribute("position") as THREE.BufferAttribute; const axesStarts = this.coordinateAxes.geometry.getAttribute("instanceStart") as THREE.InterleavedBufferAttribute; const axesEnds = this.coordinateAxes.geometry.getAttribute("instanceEnd") as THREE.InterleavedBufferAttribute; for (let axis = 0; axis < 3; ++axis) { const negative = target.clone(); const positive = target.clone(); negative.setComponent(axis, negative.getComponent(axis) - gridExtent); positive.setComponent(axis, positive.getComponent(axis) + gridExtent); gridPositions.setXYZ(axis * 2, negative.x, negative.y, negative.z); gridPositions.setXYZ(axis * 2 + 1, positive.x, positive.y, positive.z); const endpoint = this.coordinateOrigin.clone(); endpoint.setComponent(axis, endpoint.getComponent(axis) + axesExtent); axesStarts.setXYZ(axis, this.coordinateOrigin.x, this.coordinateOrigin.y, this.coordinateOrigin.z); axesEnds.setXYZ(axis, endpoint.x, endpoint.y, endpoint.z); } gridPositions.needsUpdate = true; axesStarts.data.needsUpdate = true; } private clearGroup(group: THREE.Group): void { for (const child of [...group.children]) { group.remove(child); disposeObject(child); } } private clearGroups(): void { for (const group of [this.pointContent, this.cameraContent, this.connectionContent, this.pointPickingContent, this.cameraPickingContent]) { this.clearGroup(group); } this.pointsObject = null; this.pickPointsObject = null; } private rebuild(): void { if (!this.reconstruction) return; this.clearGroups(); this.buildPoints(); this.buildCameras(); this.buildConnections(); } private rebuildConnections(): void { if (!this.reconstruction) return; this.clearGroup(this.connectionContent); this.buildConnections(); } private updateCameraSize(): void { for (const group of [this.cameraContent, this.cameraPickingContent]) { group.traverse((object) => { const material = (object as THREE.Mesh).material; const materials = Array.isArray(material) ? material : [material]; for (const item of materials) { if (item instanceof THREE.ShaderMaterial && item.uniforms.cameraSize) { item.uniforms.cameraSize.value = this.settings.cameraSize; } } }); } } private computeViewBounds(): void { if (!this.reconstruction) return; const axes: [number[], number[], number[]] = [[], [], []]; const append = (point: THREE.Vector3): void => { axes[0].push(point.x); axes[1].push(point.y); axes[2].push(point.z); }; const stride = Math.max(1, Math.floor(this.pointDrawList.length / 100000)); for (let i = 0; i < this.pointDrawList.length; i += stride) { const pointIndex = this.pointDrawList[i]!; const offset = pointIndex * 3; append(this.normalized([ this.reconstruction.points3D.xyz[offset]!, this.reconstruction.points3D.xyz[offset + 1]!, this.reconstruction.points3D.xyz[offset + 2]!, ])); } for (const image of this.reconstruction.images.values()) append(this.normalized(projectionCenter(image.camFromWorld))); if (axes[0].length === 0) { this.viewCenter.set(0, 0, 0); this.viewRadius = 1; return; } const minimum = new THREE.Vector3(...axes.map((values) => percentile(values, 0.01)) as Vec3); const maximum = new THREE.Vector3(...axes.map((values) => percentile(values, 0.99)) as Vec3); this.viewCenter.copy(minimum).add(maximum).multiplyScalar(0.5); this.viewRadius = Math.max(0.05, minimum.distanceTo(maximum) * 0.5); } private buildPoints(): void { const reconstruction = this.reconstruction!; const selectedImage = this.selection?.type === "image" ? this.selection.value : null; const observed = new Set(); if (selectedImage) { for (const point3DId of selectedImage.points2D.point3DIds) { if (point3DId !== INVALID_POINT3D_ID) observed.add(point3DId); } } const points = reconstruction.points3D; let visibleCount = 0; for (let pointIndex = 0; pointIndex < point3DCount(points); ++pointIndex) { if (points.errors[pointIndex]! <= this.settings.maxError && point3DTrackLength(points, pointIndex) >= this.settings.minTrackLength) { ++visibleCount; } } this.pointDrawList = new Uint32Array(visibleCount); const positions = new Float32Array(visibleCount * 3); const colors = new Float32Array(visibleCount * 3); const pickColors = new Float32Array(visibleCount * 3); const color = new THREE.Color(); let drawIndex = 0; for (let pointIndex = 0; pointIndex < point3DCount(points); ++pointIndex) { if (points.errors[pointIndex]! > this.settings.maxError || point3DTrackLength(points, pointIndex) < this.settings.minTrackLength) continue; this.pointDrawList[drawIndex] = pointIndex; const sourceOffset = pointIndex * 3; const targetOffset = drawIndex * 3; positions[targetOffset] = (points.xyz[sourceOffset]! - this.center[0]) * this.scale; positions[targetOffset + 1] = (points.xyz[sourceOffset + 1]! - this.center[1]) * this.scale; positions[targetOffset + 2] = (points.xyz[sourceOffset + 2]! - this.center[2]) * this.scale; color.setRGB( points.colors[sourceOffset]! / 255, points.colors[sourceOffset + 1]! / 255, points.colors[sourceOffset + 2]! / 255, THREE.SRGBColorSpace, ); if (this.selection?.type === "point" && this.selection.index === pointIndex) color.copy(COLORS.selectedPoint); else if (observed.has(points.ids[pointIndex]!)) color.copy(COLORS.selectedCameraPlane); colors[targetOffset] = color.r; colors[targetOffset + 1] = color.g; colors[targetOffset + 2] = color.b; const pickIndex = drawIndex + 1; pickColors[targetOffset] = (pickIndex & 255) / 255; pickColors[targetOffset + 1] = ((pickIndex >> 8) & 255) / 255; pickColors[targetOffset + 2] = ((pickIndex >> 16) & 255) / 255; ++drawIndex; } const geometry = new THREE.BufferGeometry(); geometry.setAttribute("position", new THREE.Float32BufferAttribute(positions, 3)); geometry.setAttribute("color", new THREE.Float32BufferAttribute(colors, 3)); this.pointsObject = new THREE.Points(geometry, new THREE.PointsMaterial({size: this.settings.pointSize, sizeAttenuation: false, vertexColors: true})); this.pointContent.add(this.pointsObject); const pickGeometry = new THREE.BufferGeometry(); pickGeometry.setAttribute("position", new THREE.Float32BufferAttribute(positions, 3)); pickGeometry.setAttribute("pickColor", new THREE.Float32BufferAttribute(pickColors, 3)); const pickMaterial = new THREE.ShaderMaterial({ uniforms: {pointSize: {value: Math.max(8, this.settings.pointSize * 2)}}, vertexShader: "attribute vec3 pickColor; varying vec3 vColor; uniform float pointSize; void main(){vColor=pickColor; gl_Position=projectionMatrix*modelViewMatrix*vec4(position,1.0); gl_PointSize=pointSize;}", fragmentShader: "varying vec3 vColor; void main(){if(length(gl_PointCoord-vec2(0.5))>0.5) discard; gl_FragColor=vec4(vColor,1.0);}", toneMapped: false, }); this.pickPointsObject = new THREE.Points(pickGeometry, pickMaterial); this.pointPickingContent.add(this.pickPointsObject); } private cameraGeometry(image: ImageRecord, camera: Camera): {center: THREE.Vector3; corners: THREE.Vector3[]} { const center = this.normalized(projectionCenter(image.camFromWorld)); const quaternion = toThreeQuaternion(image.camFromWorld.rotation); const aspectWidth = camera.width / Math.max(camera.width, camera.height); const aspectHeight = camera.height / Math.max(camera.width, camera.height); const focal = (camera.params[0] ?? Math.max(camera.width, camera.height)) / Math.max(camera.width, camera.height); const halfWidth = aspectWidth * 0.5; const halfHeight = aspectHeight * 0.5; const depth = Math.max(focal, 0.25); const corners = [ new THREE.Vector3(-halfWidth, -halfHeight, depth), new THREE.Vector3(halfWidth, -halfHeight, depth), new THREE.Vector3(halfWidth, halfHeight, depth), new THREE.Vector3(-halfWidth, halfHeight, depth), ].map((corner) => corner.applyQuaternion(quaternion).add(center)); return {center, corners}; } private sphericalCameraGeometry(image: ImageRecord): {center: THREE.Vector3; lines: THREE.Vector3[]; triangles: THREE.Vector3[]} { const center = this.normalized(projectionCenter(image.camFromWorld)); const quaternion = toThreeQuaternion(image.camFromWorld.rotation); const radius = 0.55; const worldPoint = (point: THREE.Vector3): THREE.Vector3 => point.applyQuaternion(quaternion).add(center); const lines: THREE.Vector3[] = []; const segments = 24; for (let plane = 0; plane < 3; ++plane) { for (let i = 0; i < segments; ++i) { const angles = [i / segments * Math.PI * 2, (i + 1) / segments * Math.PI * 2]; for (const angle of angles) { const a = Math.cos(angle) * radius; const b = Math.sin(angle) * radius; const local = plane === 0 ? new THREE.Vector3(a, b, 0) : plane === 1 ? new THREE.Vector3(a, 0, b) : new THREE.Vector3(0, a, b); lines.push(worldPoint(local)); } } } const vertices = [ new THREE.Vector3(radius, 0, 0), new THREE.Vector3(-radius, 0, 0), new THREE.Vector3(0, radius, 0), new THREE.Vector3(0, -radius, 0), new THREE.Vector3(0, 0, radius), new THREE.Vector3(0, 0, -radius), ]; const faces = [ 0, 2, 4, 2, 1, 4, 1, 3, 4, 3, 0, 4, 2, 0, 5, 1, 2, 5, 3, 1, 5, 0, 3, 5, ]; return {center, lines, triangles: faces.map((index) => worldPoint(vertices[index]!.clone()))}; } private buildCameras(): void { const reconstruction = this.reconstruction!; this.imageDrawList = [...reconstruction.images.values()]; const linePositions: number[] = []; const lineColors: number[] = []; const lineCenters: number[] = []; const planePositions: number[] = []; const planeColors: number[] = []; const planeCenters: number[] = []; const pickPositions: number[] = []; const pickColors: number[] = []; const pickCenters: number[] = []; const selectedFrame = this.selection?.type === "image" ? this.selection.value.frameId : -1; for (let imageIndex = 0; imageIndex < this.imageDrawList.length; ++imageIndex) { const image = this.imageDrawList[imageIndex]!; const camera = reconstruction.cameras.get(image.cameraId)!; const selected = this.selection?.type === "image" && this.selection.value.id === image.id; const sameFrame = !selected && image.frameId === selectedFrame; const lineColor = selected ? CAMERA_FRUSTUM_COLORS.selectedFrame : sameFrame ? CAMERA_FRUSTUM_COLORS.sameFrame : CAMERA_FRUSTUM_COLORS.frame; const planeColor = selected ? CAMERA_FRUSTUM_COLORS.selectedPlane : sameFrame ? CAMERA_FRUSTUM_COLORS.sameFramePlane : CAMERA_FRUSTUM_COLORS.plane; const index = this.pointDrawList.length + imageIndex + 1; if (camera.modelId === 17) { const sphere = this.sphericalCameraGeometry(image); for (const vertex of sphere.lines) linePositions.push(...vertex.toArray()); pushRgba(lineColors, lineColor, sphere.lines.length); pushVector(lineCenters, sphere.center, sphere.lines.length); for (const vertex of sphere.triangles) { planePositions.push(...vertex.toArray()); pickPositions.push(...vertex.toArray()); } pushRgba(planeColors, planeColor, sphere.triangles.length); pushVector(planeCenters, sphere.center, sphere.triangles.length); pushVector(pickCenters, sphere.center, sphere.triangles.length); for (let i = 0; i < sphere.triangles.length; ++i) pickColors.push((index & 255) / 255, ((index >> 8) & 255) / 255, ((index >> 16) & 255) / 255); continue; } const {center, corners} = this.cameraGeometry(image, camera); for (let i = 0; i < 4; ++i) { linePositions.push(...center.toArray(), ...corners[i]!.toArray()); linePositions.push(...corners[i]!.toArray(), ...corners[(i + 1) % 4]!.toArray()); pushRgba(lineColors, lineColor, 4); pushVector(lineCenters, center, 4); } const triangles = [corners[0]!, corners[1]!, corners[2]!, corners[0]!, corners[2]!, corners[3]!]; for (const vertex of triangles) planePositions.push(...vertex.toArray()); pushRgba(planeColors, planeColor, 6); pushVector(planeCenters, center, 6); for (const vertex of triangles) pickPositions.push(...vertex.toArray()); pushVector(pickCenters, center, 6); for (let i = 0; i < 6; ++i) pickColors.push((index & 255) / 255, ((index >> 8) & 255) / 255, ((index >> 16) & 255) / 255); } const lineGeometry = new THREE.BufferGeometry(); lineGeometry.setAttribute("position", new THREE.Float32BufferAttribute(linePositions, 3)); lineGeometry.setAttribute("color", new THREE.Float32BufferAttribute(lineColors, 4)); lineGeometry.setAttribute("cameraCenter", new THREE.Float32BufferAttribute(lineCenters, 3)); this.cameraContent.add(new THREE.LineSegments(lineGeometry, cameraRgbaMaterial(this.settings.cameraSize))); const planeGeometry = new THREE.BufferGeometry(); planeGeometry.setAttribute("position", new THREE.Float32BufferAttribute(planePositions, 3)); planeGeometry.setAttribute("color", new THREE.Float32BufferAttribute(planeColors, 4)); planeGeometry.setAttribute("cameraCenter", new THREE.Float32BufferAttribute(planeCenters, 3)); this.cameraContent.add(new THREE.Mesh(planeGeometry, cameraRgbaMaterial(this.settings.cameraSize, THREE.DoubleSide, false))); const pickGeometry = new THREE.BufferGeometry(); pickGeometry.setAttribute("position", new THREE.Float32BufferAttribute(pickPositions, 3)); pickGeometry.setAttribute("color", new THREE.Float32BufferAttribute(pickColors, 3)); pickGeometry.setAttribute("cameraCenter", new THREE.Float32BufferAttribute(pickCenters, 3)); this.cameraPickingContent.add(new THREE.Mesh(pickGeometry, cameraPickingMaterial(this.settings.cameraSize))); } private buildConnections(): void { if (!this.reconstruction || (!this.selection && !this.settings.showConnections)) return; const points = this.reconstruction.points3D; const positions: number[] = []; let color = COLORS.pointConnection; if (!this.selection) { color = COLORS.imageConnection; const pairs = new Set(); for (const image of this.reconstruction.images.values()) { const from = this.normalized(projectionCenter(image.camFromWorld)); for (let observationIndex = 0; observationIndex < point2DCount(image); ++observationIndex) { const point3DId = image.points2D.point3DIds[observationIndex]!; if (point3DId === INVALID_POINT3D_ID) continue; const pointIndex = findPoint3DIndex(points, point3DId); if (pointIndex < 0) continue; for (let trackIndex = points.trackOffsets[pointIndex]!; trackIndex < points.trackOffsets[pointIndex + 1]!; ++trackIndex) { const trackImageId = points.trackImageIds[trackIndex]!; if (trackImageId === image.id) continue; const a = Math.min(image.id, trackImageId); const b = Math.max(image.id, trackImageId); const key = `${a}:${b}`; if (pairs.has(key)) continue; const connected = this.reconstruction.images.get(trackImageId); if (!connected) continue; pairs.add(key); positions.push(...from.toArray(), ...this.normalized(projectionCenter(connected.camFromWorld)).toArray()); } } } } else if (this.selection.type === "point") { const pointPosition = this.normalizedPoint(this.selection.index); for (let trackIndex = points.trackOffsets[this.selection.index]!; trackIndex < points.trackOffsets[this.selection.index + 1]!; ++trackIndex) { const image = this.reconstruction.images.get(points.trackImageIds[trackIndex]!); if (image) positions.push(...pointPosition.toArray(), ...this.normalized(projectionCenter(image.camFromWorld)).toArray()); } } else { color = COLORS.imageConnection; const selectedCenter = this.normalized(projectionCenter(this.selection.value.camFromWorld)); const connected = new Set(); for (const point3DId of this.selection.value.points2D.point3DIds) { if (point3DId === INVALID_POINT3D_ID) continue; const pointIndex = findPoint3DIndex(points, point3DId); if (pointIndex < 0) continue; for (let trackIndex = points.trackOffsets[pointIndex]!; trackIndex < points.trackOffsets[pointIndex + 1]!; ++trackIndex) { connected.add(points.trackImageIds[trackIndex]!); } } connected.delete(this.selection.value.id); for (const imageId of connected) { const image = this.reconstruction.images.get(imageId); if (image) positions.push(...selectedCenter.toArray(), ...this.normalized(projectionCenter(image.camFromWorld)).toArray()); } } if (positions.length === 0) return; const geometry = new THREE.BufferGeometry(); geometry.setAttribute("position", new THREE.Float32BufferAttribute(positions, 3)); this.connectionContent.add(new THREE.LineSegments(geometry, new THREE.LineBasicMaterial({color, transparent: true, opacity: 0.8}))); } private pick(event: MouseEvent): void { if (!this.reconstruction || this.pointDrawList.length + this.imageDrawList.length === 0) return; const bounds = this.canvas.getBoundingClientRect(); const x = Math.floor(event.clientX - bounds.left); const y = Math.floor(event.clientY - bounds.top); const width = Math.max(1, Math.floor(bounds.width)); const height = Math.max(1, Math.floor(bounds.height)); this.activeCamera.setViewOffset(width, height, x, y, 1, 1); this.renderer.setRenderTarget(this.renderTarget); this.renderer.setClearColor(0x000000, 1); this.renderer.clear(); this.renderer.render(this.pickingScene, this.activeCamera); const pixel = new Uint8Array(4); this.renderer.readRenderTargetPixels(this.renderTarget, 0, 0, 1, 1, pixel); this.renderer.setRenderTarget(null); this.renderer.setClearColor(0xffffff, 1); this.activeCamera.clearViewOffset(); const index = pixel[0]! + (pixel[1]! << 8) + (pixel[2]! << 16); if (index > 0 && index <= this.pointDrawList.length) { this.selection = {type: "point", index: this.pointDrawList[index - 1]!}; } else { const image = this.imageDrawList[index - this.pointDrawList.length - 1]; this.selection = image ? {type: "image", value: image} : null; } this.rebuild(); if (this.selection?.type === "point") { this.onSelection({type: "point", value: point3DAt(this.reconstruction.points3D, this.selection.index)}); } else { this.onSelection(this.selection); } } private switchProjection(): void { const previous = this.activeCamera; const target = this.controls.target.clone(); this.activeCamera = this.settings.projection === "perspective" ? this.perspectiveCamera : this.orthographicCamera; this.activeCamera.position.copy(previous.position); this.activeCamera.quaternion.copy(previous.quaternion); this.activeCamera.up.copy(previous.up); this.controls = this.createControls(this.activeCamera); this.controls.target.copy(target); this.controls.update(); this.resize(); } private resize(): void { const parent = this.canvas.parentElement; if (!parent) return; const width = Math.max(1, parent.clientWidth); const height = Math.max(1, parent.clientHeight); this.renderer.setSize(width, height, false); this.perspectiveCamera.aspect = width / height; this.perspectiveCamera.updateProjectionMatrix(); const distance = Math.max(0.2, this.activeCamera.position.distanceTo(this.controls.target)); const extent = Math.tan(THREE.MathUtils.degToRad(this.perspectiveCamera.fov) / 2) * distance; this.orthographicCamera.left = -extent * width / height; this.orthographicCamera.right = extent * width / height; this.orthographicCamera.top = extent; this.orthographicCamera.bottom = -extent; this.orthographicCamera.updateProjectionMatrix(); } private handleModifiedWheel(event: WheelEvent): void { if (!event.ctrlKey && !event.metaKey && !event.altKey) return; event.preventDefault(); event.stopImmediatePropagation(); if (event.ctrlKey || event.metaKey) this.updateSettings({pointSize: scaleFromNativeWheel(this.settings.pointSize, event.deltaY, event.deltaMode, 0.5, 100)}); else this.updateSettings({cameraSize: scaleFromNativeWheel(this.settings.cameraSize, event.deltaY, event.deltaMode, 1e-6, 1e3)}); } } colmap-4.2.0/doc/vite.config.ts000066400000000000000000000011201524536416500163250ustar00rootroot00000000000000import {defineConfig} from "vitest/config"; export default defineConfig({ base: "./", build: { outDir: "_static/viewer", emptyOutDir: true, lib: { entry: { component: "viewer_src/main.ts", viewer: "viewer_src/auto_mount.ts", }, cssFileName: "viewer", formats: ["es"], fileName: (_format, entryName) => `${entryName}.js`, }, rollupOptions: { output: { assetFileNames: "[name][extname]", chunkFileNames: "[name]-[hash].js", }, }, }, test: { include: ["tests/**/*.test.ts"], }, }); colmap-4.2.0/docker/000077500000000000000000000000001524536416500142525ustar00rootroot00000000000000colmap-4.2.0/docker/Dockerfile000066400000000000000000000066151524536416500162540ustar00rootroot00000000000000# syntax=docker/dockerfile:1 ARG UBUNTU_VERSION=24.04 ARG NVIDIA_CUDA_VERSION=12.9.1 # # Docker builder stage. # FROM nvidia/cuda:${NVIDIA_CUDA_VERSION}-devel-ubuntu${UBUNTU_VERSION} AS builder ARG CUDA_ARCHITECTURES=all-major ARG CCACHE_MAXSIZE=500M ENV QT_XCB_GL_INTEGRATION=xcb_egl ENV CCACHE_DIR=/colmap/build/.ccache ENV CCACHE_BASEDIR=/colmap ENV CCACHE_COMPILERCHECK=content ENV CCACHE_MAXSIZE=${CCACHE_MAXSIZE} ENV CCACHE_NOHASHDIR=true # Prevent stop building ubuntu at time zone selection. ENV DEBIAN_FRONTEND=noninteractive # Prepare and empty machine for building. RUN apt-get update && \ apt-get install -y \ ccache \ cmake \ ninja-build \ build-essential \ libboost-program-options-dev \ libboost-graph-dev \ libboost-system-dev \ libeigen3-dev \ libopenimageio-dev \ openimageio-tools \ libmetis-dev \ libgoogle-glog-dev \ libgtest-dev \ libgmock-dev \ libsqlite3-dev \ libglew-dev \ qt6-base-dev \ libqt6opengl6-dev \ libqt6openglwidgets6 \ qt6-svg-dev \ libcgal-dev \ libceres-dev \ libcurl4-openssl-dev \ libssl-dev \ libmkl-full-dev # Fix issue in Ubuntu's openimageio CMake config. # We don't depend on any of openimageio's OpenCV functionality, # but it still requires the OpenCV include directory to exist. RUN mkdir -p /usr/include/opencv4 # Copy source into the image. COPY . /colmap # Build and install COLMAP. RUN cd /colmap && \ mkdir -p build/.ccache && \ ccache --zero-stats && \ cd build && \ cmake .. \ -GNinja \ -DCMAKE_CUDA_ARCHITECTURES=${CUDA_ARCHITECTURES} \ -DCMAKE_INSTALL_PREFIX=/colmap-install \ -DBLA_VENDOR=Intel10_64lp && \ ninja install # # Stage to export build caches for CI round-tripping via actions/cache. # FROM scratch AS cache-export COPY --from=builder /colmap/build/.ccache/ /.ccache/ # # Docker runtime stage. # FROM nvidia/cuda:${NVIDIA_CUDA_VERSION}-runtime-ubuntu${UBUNTU_VERSION} AS runtime # Minimal dependencies to run COLMAP binary compiled in the builder stage. # Note: this reduces the size of the final image considerably, since all the # build dependencies are not needed. RUN apt-get update && \ apt-get install -y --no-install-recommends --no-install-suggests \ libboost-program-options1.83.0 \ libc6 \ libomp5 \ libopengl0 \ libmetis5 \ libceres4t64 \ libopenimageio2.4t64 \ libgcc-s1 \ libgl1 \ libglew2.2 \ libgoogle-glog0v6t64 \ libqt6core6 \ libqt6gui6 \ libqt6widgets6 \ libqt6openglwidgets6 \ libqt6svg6 \ libcurl4 \ libssl3t64 \ libmkl-locale \ libmkl-intel-lp64 \ libmkl-intel-thread \ libmkl-core && \ apt-get clean && \ rm -rf /var/lib/apt/lists/* # Copy all files from /colmap-install/ in the builder stage to /usr/local/ in # the runtime stage. This simulates installing COLMAP in the default location # (/usr/local/), which simplifies environment variables. It also allows the user # of this Docker image to use it as a base image for compiling against COLMAP as # a library. For instance, CMake will be able to find COLMAP easily with the # command: find_package(COLMAP REQUIRED). COPY --from=builder /colmap-install/ /usr/local/ colmap-4.2.0/docker/README.md000066400000000000000000000026571524536416500155430ustar00rootroot00000000000000# How to build COLMAP using Docker ## Requirements - Host machine with at least one NVIDIA GPU/CUDA support and installed drivers (to support dense reconstruction). - Docker (for CUDA support 19.03+). ## Quick Start 1. Check that Docker >=19.03 installed on your host machine: ```bash docker --version ``` 2. Setup the NVIDIA driver and nvidia-toolkit on your host machine: For Ubuntu host machines: `./setup-ubuntu.sh` For CentOS host machines: `./setup-centos.sh` 3. Run the *run* script, using the *full local path* to your preferred local working directory (a folder with your input files/images, etc.): ```bash ./run.sh /path/where/your/working/folder/is ``` This will put you in a directory (inside the Docker container) mounted to the local path you specified. Now you can run COLMAP binaries on your own inputs like this: ```bash colmap automatic_reconstructor --image_path ./images --workspace_path . ``` Alternatively, you can run the *run-gui* script, which will start the graphical user interface of COLMAP: ```bash ./run-gui.sh /path/where/your/working/folder/is ``` ## Build from Scratch After completing steps 1-2, you can build the Docker image from scratch using the **Dockerfile**. First, update the CUDA and Ubuntu versions in Dockerfile lines 1-2 to match your system, then: ```bash ./build.sh ./run.sh /path/where/your/working/folder/is ``` colmap-4.2.0/docker/build.sh000077500000000000000000000006331524536416500157120ustar00rootroot00000000000000#!/bin/bash # Build COLMAP Docker image from the repository root. SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" REPO_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)" docker build "$REPO_ROOT" \ -f "$REPO_ROOT/docker/Dockerfile" \ -t colmap:latest # In some cases, you may have to explicitly specify the compute architecture: # docker build . -f docker/Dockerfile -t colmap:latest --build-arg CUDA_ARCHITECTURES=75 colmap-4.2.0/docker/run-gui.sh000077500000000000000000000055011524536416500162000ustar00rootroot00000000000000#!/bin/bash # # A robust script to run the COLMAP GUI inside a Docker container, # with automatic GPU detection and flexible argument passing. # --- Initial Checks --- # Exit immediately if a command exits with a non-zero status. set -e # --- Cleanup Function and Trap --- # This function will be called automatically when the script exits # to ensure that X server permissions are always restored. function cleanup { echo "Revoking X-server access..." # The '|| true' prevents the script from failing if xhost has issues. xhost -local:root > /dev/null || true } trap cleanup EXIT # Check if any argument is provided. if [ $# -eq 0 ]; then echo "Usage: $0 " echo "Example: $0 ../dataset/" exit 1 fi # --- Docker Image Selection --- # Check if local colmap:latest image exists (in case you ran build.sh), otherwise use official image if docker image inspect colmap:latest >/dev/null 2>&1; then echo "Using local COLMAP Docker image..." COLMAP_IMAGE="colmap:latest" else echo "Local COLMAP image not found, pulling official image..." docker pull colmap/colmap:latest COLMAP_IMAGE="colmap/colmap:latest" fi # Get absolute path HOST_DIR=$(realpath "$1") if [ ! -d "$HOST_DIR" ]; then echo "Error: Directory '$HOST_DIR' does not exist." exit 1 fi echo "Running COLMAP container with directory: $HOST_DIR" # --- Build Docker Arguments --- # Start with the base arguments for GUI forwarding. DOCKER_ARGS=( -it --rm --net=host -e DISPLAY -v "${HOST_DIR}:/working" -w /working ) # --- GPU Detection and Configuration --- echo "Testing for GPU acceleration..." # A successful `nvidia-smi` call is the most reliable test. if docker run --rm --runtime=nvidia "${COLMAP_IMAGE}" nvidia-smi >/dev/null 2>&1; then echo "✅ GPU detected. Using --runtime=nvidia and mapping all graphics devices." # Use the nvidia runtime AND also pass through the host's render devices. # This can solve driver conflicts on hybrid graphics systems. DOCKER_ARGS+=( --runtime=nvidia -e NVIDIA_DRIVER_CAPABILITIES=all ) if [ -d /dev/dri ]; then DOCKER_ARGS+=( --device=/dev/dri ) fi else echo "âš ï¸ GPU not detected. Falling back to CPU rendering." # For CPU mode, we give the container access to the host's render devices. if [ -d /dev/dri ]; then DOCKER_ARGS+=( --device=/dev/dri ) fi fi # --- X11 Forwarding Security --- # Grant permissions just before running the container. xhost +local:root > /dev/null # --- Execute the Container --- # Pass all arguments after the directory path ("${@:2}") to the colmap gui command. echo "Launching GUI..." # "${@:2}" accepts extra arguments for the COLMAP GUI. docker run "${DOCKER_ARGS[@]}" "${COLMAP_IMAGE}" colmap gui "${@:2}" # The `trap` will automatically call the `cleanup` function here, colmap-4.2.0/docker/run.sh000077500000000000000000000026521524536416500154220ustar00rootroot00000000000000#!/bin/bash # Check if any argument is provided. if [ $# -eq 0 ]; then echo "Usage: $0 " echo "Example: $0 ../dataset/" exit 1 fi # Check if local colmap:latest image exists (in case you ran build.sh), otherwise use official image if docker image inspect colmap:latest >/dev/null 2>&1; then echo "Using local COLMAP Docker image..." COLMAP_IMAGE="colmap:latest" else echo "Local COLMAP image not found, pulling official image..." docker pull colmap/colmap:latest COLMAP_IMAGE="colmap/colmap:latest" fi # Get absolute path HOST_DIR=$(realpath "$1") if [ ! -d "$HOST_DIR" ]; then echo "Error: Directory '$HOST_DIR' does not exist." exit 1 fi echo "Running COLMAP container with directory: $HOST_DIR" # --- Build Docker Arguments --- # Start with the base arguments. DOCKER_ARGS=( -it --rm -v "${HOST_DIR}:/working" -w /working ) # --- GPU Detection and Configuration --- echo "Testing for GPU acceleration..." # A successful `nvidia-smi` call is the most reliable test. if docker run --rm --runtime=nvidia "${COLMAP_IMAGE}" nvidia-smi >/dev/null 2>&1; then echo "✅ GPU detected. Using --runtime=nvidia." DOCKER_ARGS+=( --runtime=nvidia ) else echo "âš ï¸ GPU not detected. Using CPU mode." fi # --- Execute the Container --- # Always start an interactive bash shell. echo "Starting interactive bash shell..." docker run "${DOCKER_ARGS[@]}" "${COLMAP_IMAGE}" bashcolmap-4.2.0/docker/setup-centos.sh000077500000000000000000000006261524536416500172460ustar00rootroot00000000000000# Add the package repositories distribution=$(. /etc/os-release;echo $ID$VERSION_ID) curl -s -L https://nvidia.github.io/nvidia-docker/$distribution/nvidia-docker.repo | sudo tee /etc/yum.repos.d/nvidia-docker.repo # Install nvidia-container-toolkit sudo yum install -y nvidia-container-toolkit sudo systemctl restart docker # Check that it worked! docker run --gpus all nvidia/cuda:10.2-base nvidia-smi colmap-4.2.0/docker/setup-ubuntu.sh000077500000000000000000000102701524536416500172710ustar00rootroot00000000000000#!/bin/bash echo "🚀 Starting intelligent NVIDIA Docker setup..." echo "📦 Updating NVIDIA driver to latest..." sudo apt update sudo ubuntu-drivers autoinstall echo "🔄 Rebooting required after driver update. Run this script again after reboot." # Check if reboot is needed if [ -f /var/run/reboot-required ]; then echo "âš ï¸ System reboot required. Please reboot and run this script again." echo "After reboot, run: sudo reboot && ./setup-ubuntu.sh" exit 0 fi echo "🔠Detecting NVIDIA driver version..." DRIVER_VERSION=$(nvidia-smi --query-gpu=driver_version --format=csv,noheader,nounits | head -1) echo "Found NVIDIA driver: $DRIVER_VERSION" echo "🔠Determine compatible CUDA version based on driver..." get_compatible_cuda_version() { local driver_ver=$1 # Extract major version (e.g., 560 from 560.35.03) local major_ver=$(echo $driver_ver | cut -d'.' -f1) # NVIDIA Driver-CUDA Compatibility Matrix if [ $major_ver -ge 565 ]; then echo "12.9" # Driver 565+ supports CUDA 12.9 elif [ $major_ver -ge 560 ]; then echo "12.6" # Driver 560+ supports CUDA 12.6 (your current case) elif [ $major_ver -ge 555 ]; then echo "12.5" # Driver 555+ supports CUDA 12.5 elif [ $major_ver -ge 550 ]; then echo "12.4" # Driver 550+ supports CUDA 12.4 elif [ $major_ver -ge 535 ]; then echo "12.2" # Driver 535+ supports CUDA 12.2 elif [ $major_ver -ge 525 ]; then echo "12.0" # Driver 525+ supports CUDA 12.0 else echo "11.8" # Fallback to CUDA 11.8 fi } COMPATIBLE_CUDA=$(get_compatible_cuda_version $DRIVER_VERSION) echo "✅ Compatible CUDA version: $COMPATIBLE_CUDA" echo "📦 Installing latest nvidia-container-toolkit..." curl -fsSL https://nvidia.github.io/libnvidia-container/gpgkey | sudo gpg --dearmor --yes -o /usr/share/keyrings/nvidia-container-toolkit-keyring.gpg \ && curl -s -L https://nvidia.github.io/libnvidia-container/stable/deb/nvidia-container-toolkit.list | \ sed 's#deb https://#deb [signed-by=/usr/share/keyrings/nvidia-container-toolkit-keyring.gpg] https://#g' | \ sudo tee /etc/apt/sources.list.d/nvidia-container-toolkit.list sudo apt-get update sudo apt-get install -y nvidia-container-toolkit echo "📦 Configure Docker" sudo nvidia-ctk runtime configure --runtime=docker sudo systemctl restart docker echo "🔠Finding latest patch version for CUDA $COMPATIBLE_CUDA..." AVAILABLE_VERSIONS=$(curl -s "https://registry.hub.docker.com/v2/repositories/nvidia/cuda/tags/?page_size=100" | jq -r '.results[].name' | grep -E "^${COMPATIBLE_CUDA}\.[0-9]+-base-ubuntu24\.04$" | head -1) if [ -n "$AVAILABLE_VERSIONS" ]; then # Extract full version (e.g., "12.9.1" from "12.9.1-base-ubuntu24.04") FULL_CUDA_VERSION=$(echo "$AVAILABLE_VERSIONS" | cut -d'-' -f1) echo "✅ Found CUDA version: $FULL_CUDA_VERSION" else echo "⌠No CUDA $COMPATIBLE_CUDA images found for Ubuntu 24.04, trying Ubuntu 22.04..." AVAILABLE_VERSIONS=$(curl -s "https://registry.hub.docker.com/v2/repositories/nvidia/cuda/tags/?page_size=100" | jq -r '.results[].name' | grep -E "^${COMPATIBLE_CUDA}\.[0-9]+-base-ubuntu22\.04$" | head -1) if [ -n "$AVAILABLE_VERSIONS" ]; then FULL_CUDA_VERSION=$(echo "$AVAILABLE_VERSIONS" | cut -d'-' -f1) UBUNTU_VERSION="22.04" echo "✅ Found CUDA version: $FULL_CUDA_VERSION for Ubuntu 22.04" else echo "⌠No compatible CUDA images found" exit 1 fi fi echo "🧪 Testing with automatically detected compatible CUDA version: $COMPATIBLE_CUDA..." if docker run --rm --runtime=nvidia nvidia/cuda:${FULL_CUDA_VERSION}-base-ubuntu${UBUNTU_VERSION:-24.04} nvidia-smi; then echo "✅ GPU support working with CUDA $FULL_CUDA_VERSION!" # Update Dockerfile with compatible version if [ -f "Dockerfile" ]; then sed -i "s/ARG NVIDIA_CUDA_VERSION=.*/ARG NVIDIA_CUDA_VERSION=${FULL_CUDA_VERSION}/" Dockerfile if [ "${UBUNTU_VERSION:-24.04}" = "22.04" ]; then sed -i "s/ARG UBUNTU_VERSION=.*/ARG UBUNTU_VERSION=22.04/" Dockerfile fi echo "✅ Updated Dockerfile to use CUDA $FULL_CUDA_VERSION" fi else echo "⌠GPU test failed with CUDA $FULL_CUDA_VERSION" ficolmap-4.2.0/pyproject.toml000066400000000000000000000070161524536416500157230ustar00rootroot00000000000000[build-system] requires = [ "scikit-build-core>=0.3.3", "pybind11==3.0.4", "pybind11_stubgen @ git+https://github.com/sarlinpe/pybind11-stubgen@sarlinpe/fix-2025-08-20", "numpy", "ruff==0.15.20", "clang-format==22.1.5", ] build-backend = "scikit_build_core.build" [project] name = "pycolmap" # WARNING: This version must follow the MAJOR.MINOR.PATCH format. If only # MAJOR.MINOR is used, cibuildwheel will add a .dev0 patch version, which # results in releasing a pre-release version on PyPI. version = "4.2.0" description = "COLMAP bindings" readme = "python/README.md" authors = [ { name = "Johannes Schönberger", email = "jsch@demuc.de" }, { name = "Mihai Dusmanu", email = "mihai.dusmanu@gmail.com" }, { name = "Paul-Edouard Sarlin", email = "paul.edouard.sarlin@gmail.com" }, { name = "Shaohui Liu", email = "b1ueber2y@gmail.com" }, { name = "Philipp Lindenberger", email = "plindenbe@ethz.ch" }, ] license = {text = "BSD-3-Clause"} urls = {Repository = "https://github.com/colmap/colmap"} requires-python = ">=3.10" dependencies = ["numpy"] classifiers = [ "License :: OSI Approved :: BSD License", "Programming Language :: Python :: 3 :: Only", ] [project.optional-dependencies] panorama = ["opencv-python-headless", "pillow", "tqdm"] [tool.scikit-build] wheel.expand-macos-universal-tags = true cmake.source-dir = "python/" wheel.packages = ["python/pycolmap"] [tool.cibuildwheel] build = "cp3{10,11,12,13,14}-{macosx,manylinux,win}*" archs = ["auto64"] # A fresh PEP 517 isolation directory changes compiler include paths on every # run, preventing ccache reuse. Install the same requirements into the stable # cibuildwheel Python environment instead. build-frontend = {name = "build", args = ["--no-isolation"]} before-build = "python {project}/python/ci/install-build-requirements.py" # pillow and requests are imported by the TartanAir packaging script, whose # tests are otherwise silently skipped. test-requires = "pytest>=8.0 mypy==2.1.0 enlighten==1.14.1 pillow requests" test-command = """\ python -c "import pycolmap; print(pycolmap.__version__)" && \ cd {project} && \ mypy --package pycolmap && \ mypy --install-types --non-interactive python benchmark && \ mypy src/pycolmap && \ pytest\ """ [tool.cibuildwheel.linux] before-all = "{project}/python/ci/install-colmap-almalinux.sh" [tool.cibuildwheel.macos] before-all = "{project}/python/ci/install-colmap-macos.sh" [tool.cibuildwheel.windows] before-all = "pwsh -File {project}/python/ci/install-colmap-windows.ps1" before-build = "python {project}/python/ci/install-build-requirements.py && pip install delvewheel" test-command = "pwsh -File {project}/python/ci/test-colmap-windows.ps1" # Skip mypy on source files for Python 3.10. There's a bug in numpy's type # annotations that was fixed in version 2.3.0, but numpy stopped shipping # wheels for Python 3.10 in 2.3.0. This causes mypy to fail. # TODO: remove once support for Python 3.10 is dropped after its EOL in # October 2026. [[tool.cibuildwheel.overrides]] select = "cp310-{macosx,manylinux}*" test-command = """\ python -c "import pycolmap; print(pycolmap.__version__)" && \ cd {project} && \ mypy --package pycolmap && \ mypy src/pycolmap && \ pytest\ """ [tool.pytest.ini_options] minversion = "8.0" addopts = ["-ra", "-q", "--import-mode=importlib"] testpaths = [ "python", "benchmark", "src/pycolmap", ] norecursedirs = [ "build", "python/build", ] [tool.mypy] implicit_optional = true ignore_missing_imports = true explicit_package_bases = true colmap-4.2.0/python/000077500000000000000000000000001524536416500143245ustar00rootroot00000000000000colmap-4.2.0/python/.gitignore000066400000000000000000000000341524536416500163110ustar00rootroot00000000000000*.egg-info/ build/ example/ colmap-4.2.0/python/CMakeLists.txt000066400000000000000000000123421524536416500170660ustar00rootroot00000000000000cmake_minimum_required(VERSION 3.10) project(${SKBUILD_PROJECT_NAME} VERSION ${SKBUILD_PROJECT_VERSION}) option(GENERATE_STUBS "Whether to generate stubs" ON) option(CCACHE_ENABLED "Whether to enable compiler caching, if available" ON) option(WERROR_ENABLED "Whether to treat compiler warnings as errors" OFF) set(CMAKE_POSITION_INDEPENDENT_CODE ON) set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD_REQUIRED ON) set(CMAKE_CUDA_STANDARD 14) set(CMAKE_CUDA_STANDARD_REQUIRED ON) if(CMAKE_CXX_COMPILER_ID STREQUAL "MSVC") # Some fixes for the Glog library. add_compile_definitions(GLOG_NO_ABBREVIATED_SEVERITIES) add_compile_definitions(GL_GLEXT_PROTOTYPES) add_compile_definitions(NOMINMAX) set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /EHsc") # Enable object level parallel builds in Visual Studio. set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} /MP") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /MP") add_compile_options($<$:/W3>) if(WERROR_ENABLED) add_compile_options($<$:/WX>) endif() elseif(CMAKE_CXX_COMPILER_ID STREQUAL "GNU" OR CMAKE_CXX_COMPILER_ID MATCHES ".*Clang") add_compile_options($<$:-Wall>) if(WERROR_ENABLED) add_compile_options($<$:-Werror>) endif() endif() find_package(colmap REQUIRED) if(CCACHE_ENABLED) find_program(CCACHE ccache) if(CCACHE) message(STATUS "Enabling ccache support") set(CMAKE_C_COMPILER_LAUNCHER ${CCACHE}) set(CMAKE_CXX_COMPILER_LAUNCHER ${CCACHE}) if(CUDA_ENABLED) set(CMAKE_CUDA_COMPILER_LAUNCHER ${CCACHE}) endif() else() message(STATUS "Disabling ccache support") endif() else() message(STATUS "Disabling ccache support") endif() # If colmap was built with ONNX support, we need to set the rpath so the # Python module can find the ONNX Runtime library at runtime. set(ONNX_RPATH "") if(DEFINED onnxruntime_LIBRARY_DIR_HINTS AND EXISTS "${onnxruntime_LIBRARY_DIR_HINTS}") message(STATUS "ONNX Runtime library directory: ${onnxruntime_LIBRARY_DIR_HINTS}") set(ONNX_RPATH "${onnxruntime_LIBRARY_DIR_HINTS}") elseif(TARGET onnxruntime::onnxruntime) # Derive the library directory from the imported target. get_target_property(_ort_type onnxruntime::onnxruntime TYPE) if(_ort_type STREQUAL "INTERFACE_LIBRARY") # Find module creates an INTERFACE target with link libraries. get_target_property(_ort_libs onnxruntime::onnxruntime INTERFACE_LINK_LIBRARIES) list(GET _ort_libs 0 _ort_location) else() get_target_property(_ort_location onnxruntime::onnxruntime IMPORTED_LOCATION) if(NOT _ort_location) get_target_property(_ort_location onnxruntime::onnxruntime IMPORTED_LOCATION_RELEASE) endif() endif() if(_ort_location) get_filename_component(ONNX_RPATH "${_ort_location}" DIRECTORY) message(STATUS "ONNX Runtime library directory (from target): ${ONNX_RPATH}") endif() endif() if (CMAKE_VERSION VERSION_LESS 3.18) set(DEV_MODULE Development) else() set(DEV_MODULE Development.Module) endif() find_package(Python REQUIRED COMPONENTS Interpreter ${DEV_MODULE} REQUIRED) find_package(pybind11 3.0.2 REQUIRED) file(GLOB_RECURSE SOURCE_FILES "${PROJECT_SOURCE_DIR}/../src/pycolmap/*.cc") if(NOT MVS_ENABLED) list(FILTER SOURCE_FILES EXCLUDE REGEX ".*/mvs/.*") list(FILTER SOURCE_FILES EXCLUDE REGEX ".*/pipeline/mvs\\.cc$") list(FILTER SOURCE_FILES EXCLUDE REGEX ".*/pipeline/meshing\\.cc$") endif() pybind11_add_module(_core ${SOURCE_FILES}) target_include_directories(_core PRIVATE ${PROJECT_SOURCE_DIR}/../src/) target_link_libraries(_core PRIVATE colmap::colmap glog::glog Ceres::ceres) target_compile_definitions(_core PRIVATE VERSION_INFO="${PROJECT_VERSION}") # Set rpath to find ONNX Runtime library at runtime. if(ONNX_RPATH) if(APPLE) set_target_properties(_core PROPERTIES BUILD_RPATH "${ONNX_RPATH}" INSTALL_RPATH "${ONNX_RPATH}" ) elseif(UNIX) set_target_properties(_core PROPERTIES BUILD_RPATH "${ONNX_RPATH}" INSTALL_RPATH "$ORIGIN/../lib:${ONNX_RPATH}" ) endif() endif() install(TARGETS _core LIBRARY DESTINATION pycolmap) if(GENERATE_STUBS AND UNIX) message(STATUS "Enabling stubs generation") set(STUBGEN_OUTPUT_DIR "${CMAKE_CURRENT_BINARY_DIR}/_core") # Set library path for stub generation to find ONNX Runtime. if(APPLE AND ONNX_RPATH) set(STUBGEN_LIB_PATH_VAR "DYLD_LIBRARY_PATH=${ONNX_RPATH}:$ENV{DYLD_LIBRARY_PATH}") elseif(ONNX_RPATH) set(STUBGEN_LIB_PATH_VAR "LD_LIBRARY_PATH=${ONNX_RPATH}:$ENV{LD_LIBRARY_PATH}") else() set(STUBGEN_LIB_PATH_VAR "") endif() add_custom_command( TARGET _core POST_BUILD COMMAND "${CMAKE_COMMAND}" -E env "PYTHONPATH=$:$ENV{PYTHONPATH}" ${STUBGEN_LIB_PATH_VAR} bash ${PROJECT_SOURCE_DIR}/generate_stubs.sh "${Python_EXECUTABLE}" "${CMAKE_CURRENT_BINARY_DIR}" WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} COMMENT "Generating pybind11 stubs" VERBATIM ) install(DIRECTORY ${STUBGEN_OUTPUT_DIR} DESTINATION pycolmap) endif() colmap-4.2.0/python/README.md000066400000000000000000000256361524536416500156170ustar00rootroot00000000000000# Python Bindings for COLMAP PyCOLMAP exposes to Python most capabilities of the [COLMAP](https://colmap.github.io/) Structure-from-Motion (SfM) and Multi-View Stereo (MVS) pipeline. ## Installation Pre-built wheels for Linux, macOS, and Windows can be installed using pip: ```bash pip install pycolmap ``` The wheels are automatically built and pushed to [PyPI](https://pypi.org/project/pycolmap/) at each release. To benefit from GPU acceleration, wheels built for CUDA 12 (only for Linux - for now) are available under the [package `pycolmap-cuda12`](https://pypi.org/project/pycolmap-cuda12/).
[Building PyCOLMAP from source - click to expand] 1. Install COLMAP from source following [the official guide](https://colmap.github.io/install.html). 2. Build PyCOLMAP: - On Linux and macOS: ```bash python -m pip install . ``` - On Windows, after installing COLMAP [via VCPKG](https://colmap.github.io/install.html#id3), run in powershell: ```powershell python -m pip install . ` --cmake.define.CMAKE_TOOLCHAIN_FILE="$VCPKG_ROOT/scripts/buildsystems/vcpkg.cmake" ` --cmake.define.VCPKG_TARGET_TRIPLET="x64-windows" ```
## Reconstruction Pipeline PyCOLMAP provides bindings for multiple steps of the standard reconstruction pipeline: - Extracting and matching SIFT features - Importing an image folder into a COLMAP database - Inferring the camera parameters from the EXIF metadata of an image file - Running two-view geometric verification of matches on a COLMAP database - Triangulating points into an existing COLMAP model - Running incremental reconstruction from a COLMAP database - Dense reconstruction with multi-view stereo ### Sparse & Dense Reconstruction Sparse & Dense reconstruction from a folder of images can be performed with: ```python output_path: pathlib.Path image_dir: pathlib.Path output_path.mkdir() mvs_path = output_path / "mvs" database_path = output_path / "database.db" pycolmap.extract_features(database_path, image_dir) pycolmap.match_exhaustive(database_path) maps = pycolmap.incremental_mapping(database_path, image_dir, output_path) maps[0].write(output_path) # Dense reconstruction pycolmap.undistort_images(mvs_path, output_path, image_dir) pycolmap.patch_match_stereo(mvs_path) # requires compilation with CUDA pycolmap.stereo_fusion(mvs_path / "dense.ply", mvs_path) ``` PyCOLMAP can leverage the GPU for feature extraction, matching, and multi-view stereo if COLMAP was compiled with CUDA support. Similarly, PyCOLMAP can run Delaunay Triangulation and Advancing Front Surface Reconstruction if COLMAP was compiled with CGAL support. This requires to build the package from source and is not available with the PyPI wheels. ### Configuration Options All of the above steps are easily configurable with python dicts which are recursively merged into their respective defaults, for example: ```python pycolmap.extract_features( database_path, image_dir, extraction_options={"sift": {"max_num_features": 512}} ) # Equivalent to: ops = pycolmap.FeatureExtractionOptions() ops.sift.max_num_features = 512 pycolmap.extract_features(database_path, image_dir, extraction_options=ops) ``` To list available options and their default parameters: ```python help(pycolmap.SiftExtractionOptions) ``` For another example of usage, see [`example.py`](./examples/example.py) or [`hloc/reconstruction.py`](https://github.com/cvg/Hierarchical-Localization/blob/master/hloc/reconstruction.py). ## Reconstruction Object We can load and manipulate an existing COLMAP 3D reconstruction: ```python import pycolmap reconstruction = pycolmap.Reconstruction("path/to/reconstruction/dir") print(reconstruction.summary()) for image_id, image in reconstruction.images.items(): print(image_id, image) for point3D_id, point3D in reconstruction.points3D.items(): print(point3D_id, point3D) for camera_id, camera in reconstruction.cameras.items(): print(camera_id, camera) reconstruction.write("path/to/reconstruction/dir/") ``` ### Common Operations The object API mirrors the COLMAP C++ library. The bindings support many operations, for example: **Projecting a 3D point into an image** with arbitrary camera model: ```python uv = camera.img_from_cam(image.cam_from_world * point3D.xyz) ``` **Aligning two 3D reconstructions** by their camera poses: ```python rec2_from_rec1 = pycolmap.align_reconstructions_via_reprojections( reconstruction1, reconstruction2 ) reconstruction1.transform(rec2_from_rec1) print(rec2_from_rec1.scale, rec2_from_rec1.rotation, rec2_from_rec1.translation) ``` **Exporting reconstructions** to text, PLY, or other formats: ```python reconstruction.write_text("path/to/new/reconstruction/dir/") # text format reconstruction.export_PLY("rec.ply") # PLY format ``` ## Estimators We provide robust RANSAC-based estimators for: - Absolute camera pose (single-camera and multi-camera-rig) - Essential matrix - Fundamental matrix - Homography - Two-view relative pose for calibrated cameras All RANSAC and estimation parameters are exposed as objects that behave similarly as Python dataclasses. The RANSAC options are described in [`colmap/optim/ransac.h`](https://github.com/colmap/colmap/blob/main/src/colmap/optim/ransac.h#L43-L72) and their default values are: ```python ransac_options = pycolmap.RANSACOptions( max_error=4.0, # For example the reprojection error in pixels min_inlier_ratio=0.01, confidence=0.9999, min_num_trials=1000, max_num_trials=100000, ) ``` ### Absolute Pose Estimation To estimate the absolute pose of a query camera given 2D-3D correspondences: ```python # Parameters: # - points2D: Nx2 array; pixel coordinates # - points3D: Nx3 array; world coordinates # - camera: pycolmap.Camera # Optional parameters: # - estimation_options: dict or pycolmap.AbsolutePoseEstimationOptions # - refinement_options: dict or pycolmap.AbsolutePoseRefinementOptions answer = pycolmap.estimate_and_refine_absolute_pose(points2D, points3D, camera) # Returns: dictionary of estimation outputs or None if failure ``` 2D and 3D points are passed as Numpy arrays or lists. The options are defined in [`estimators/absolute_pose.cc`](./pycolmap/estimators/absolute_pose.h#L100-L122) and can be passed as regular (nested) Python dictionaries: ```python pycolmap.estimate_and_refine_absolute_pose( points2D, points3D, camera, estimation_options=dict(ransac=dict(max_error=12.0)), refinement_options=dict(refine_focal_length=True), ) ``` ### Absolute Pose Refinement ```python # Parameters: # - cam_from_world: pycolmap.Rigid3d, initial pose # - points2D: Nx2 array; pixel coordinates # - points3D: Nx3 array; world coordinates # - inlier_mask: array of N bool; inlier_mask[i] is true if correspondence i is an inlier # - camera: pycolmap.Camera # Optional parameters: # - refinement_options: dict or pycolmap.AbsolutePoseRefinementOptions answer = pycolmap.refine_absolute_pose( cam_from_world, points2D, points3D, inlier_mask, camera ) # Returns: dictionary of refinement outputs or None if failure ``` ### Essential Matrix Estimation ```python # Parameters: # - points1: Nx2 array; 2D pixel coordinates in image 1 # - points2: Nx2 array; 2D pixel coordinates in image 2 # - camera1: pycolmap.Camera of image 1 # - camera2: pycolmap.Camera of image 2 # Optional parameters: # - options: dict or pycolmap.RANSACOptions (default inlier threshold is 4px) answer = pycolmap.estimate_essential_matrix(points1, points2, camera1, camera2) # Returns: dictionary of estimation outputs or None if failure ``` ### Fundamental Matrix Estimation ```python answer = pycolmap.estimate_fundamental_matrix( points1, points2, [options], # optional dict or pycolmap.RANSACOptions ) ``` ### Homography Estimation ```python answer = pycolmap.estimate_homography_matrix( points1, points2, [options], # optional dict or pycolmap.RANSACOptions ) ``` ### Two-View Geometry Estimation COLMAP can also estimate a relative pose between two calibrated cameras by estimating both E and H and accounting for the degeneracies of each model. ```python # Parameters: # - camera1: pycolmap.Camera of image 1 # - points1: Nx2 array; 2D pixel coordinates in image 1 # - camera2: pycolmap.Camera of image 2 # - points2: Nx2 array; 2D pixel coordinates in image 2 # Optional parameters: # - matches: Nx2 integer array; correspondences across images # - options: dict or pycolmap.TwoViewGeometryOptions answer = pycolmap.estimate_calibrated_two_view_geometry( camera1, points1, camera2, points2 ) # Returns: pycolmap.TwoViewGeometry ``` The `TwoViewGeometryOptions` control how each model is selected. The output structure contains the geometric model, inlier matches, the relative pose (if `options.compute_relative_pose=True`), and the type of camera configuration, which is an instance of the enum `pycolmap.TwoViewGeometryConfiguration`. ### Camera Argument Some estimators expect a COLMAP camera object, which can be created as follows: ```python camera = pycolmap.Camera( model=camera_model_name_or_id, width=width, height=height, params=params, ) ``` The different camera models and their extra parameters are defined in [`colmap/src/colmap/sensor/models.h`](https://github.com/colmap/colmap/blob/main/src/colmap/sensor/models.h). For example for a pinhole camera: ```python camera = pycolmap.Camera( model='SIMPLE_PINHOLE', width=width, height=height, params=[focal_length, cx, cy], ) ``` Alternatively, we can also pass a camera dictionary: ```python camera_dict = { 'model': COLMAP_CAMERA_MODEL_NAME_OR_ID, 'width': IMAGE_WIDTH, 'height': IMAGE_HEIGHT, 'params': EXTRA_CAMERA_PARAMETERS_LIST } ``` ## SIFT Feature Extraction ```python import numpy as np import pycolmap from PIL import Image, ImageOps # Input should be grayscale image with range [0, 1]. img = Image.open('image.jpg').convert('RGB') img = ImageOps.grayscale(img) img = np.array(img).astype(np.float) / 255. # Optional parameters: # - options: dict or pycolmap.SiftExtractionOptions # - device: default pycolmap.Device.auto uses the GPU if available sift = pycolmap.Sift() # Parameters: # - image: HxW float array keypoints, descriptors = sift.extract(img) # Returns: # - keypoints: Nx4 array; format: x (j), y (i), scale, orientation # - descriptors: Nx128 array; L2-normalized descriptors ``` ## Bitmap PyCOLMAP provides bindings for the `Bitmap` class to work with images and convert them to/from NumPy arrays: ```python import numpy as np import pycolmap # Read a bitmap from file bitmap = pycolmap.Bitmap.read("image.jpg", as_rgb=True) print(f"Size: {bitmap.width}x{bitmap.height}, Channels: {bitmap.channels}") # Convert to NumPy array array = bitmap.to_array() # Shape: (H, W, 3) for RGB or (H, W) for grayscale # Create bitmap from NumPy array array = np.random.randint(0, 255, (480, 640, 3), dtype=np.uint8) bitmap = pycolmap.Bitmap.from_array(array) # Write bitmap to file bitmap.write("output.jpg") # Rescale bitmap bitmap.rescale(new_width=320, new_height=240) ``` colmap-4.2.0/python/build.sh000077500000000000000000000015231524536416500157630ustar00rootroot00000000000000#!/bin/bash # Invoke from anywhere to perform an incremental build of pycolmap bindings. # Make sure to install the requirements from pyproject.toml. If colmap is not # installed globally but in a custom directory, you should set the colmap_DIR # environment variable, e.g.: # # colmap_DIR=/path/to/cmake/install/prefix pycolmap/incremental_build.sh set -e script_dir=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" &> /dev/null && pwd) pip install \ -ve \ "$script_dir/.." # Symlink the compiled _core extension into the source tree so that the # editable install (which adds python/ to sys.path) can find it. site_pkg=$(python -c "import sysconfig; print(sysconfig.get_path('purelib'))") core_so=$(ls -t "$site_pkg/pycolmap"/_core*.so 2>/dev/null | head -1) if [ -n "$core_so" ]; then ln -sf "$core_so" "$script_dir/pycolmap/" fi colmap-4.2.0/python/ci/000077500000000000000000000000001524536416500147175ustar00rootroot00000000000000colmap-4.2.0/python/ci/install-build-requirements.py000066400000000000000000000025561524536416500225650ustar00rootroot00000000000000"""Install PEP 517 build requirements into the active Python environment.""" import importlib import os import shutil import subprocess import sys from pathlib import Path if sys.version_info < (3, 11): subprocess.run( [sys.executable, "-m", "pip", "install", "tomli"], check=True ) tomllib = importlib.import_module( "tomllib" if sys.version_info >= (3, 11) else "tomli" ) project_dir = Path(__file__).resolve().parents[2] with (project_dir / "pyproject.toml").open("rb") as fid: requirements = tomllib.load(fid)["build-system"]["requires"] subprocess.run( [sys.executable, "-m", "pip", "install", *requirements], check=True ) # cibuildwheel creates its virtual environment below a randomized temporary # directory on macOS and Windows. Keep pybind11's headers at a stable path so # that the absolute include directory does not invalidate compiler cache keys. if "CCACHE_DIR" in os.environ: pybind11 = importlib.import_module("pybind11") if pybind11.__file__ is None: raise RuntimeError("Cannot locate the installed pybind11 package") pybind11_source = Path(pybind11.__file__).parent pybind11_destination = ( Path(os.environ["CCACHE_DIR"]).parent / "build-requirements" / "pybind11" ) shutil.rmtree(pybind11_destination, ignore_errors=True) shutil.copytree(pybind11_source, pybind11_destination) colmap-4.2.0/python/ci/install-colmap-almalinux.sh000077500000000000000000000046521524536416500221740ustar00rootroot00000000000000#!/bin/bash set -e -x uname -a CURRDIR=$(pwd) export PATH="/usr/bin" # Install config manager and EPEL release yum install -y dnf-plugins-core epel-release # Enable the PowerTools repository (required for ninja-build) yum config-manager --set-enabled powertools # Install toolchain under AlmaLinux 8, # see https://almalinux.pkgs.org/8/almalinux-appstream-x86_64/ yum install -y \ gcc-toolset-12-gcc \ gcc-toolset-12-gcc-c++ \ gcc-toolset-12-gcc-gfortran \ kernel-headers \ perl-IPC-Cmd \ scl-utils \ git \ cmake3 \ ninja-build \ curl \ zip \ unzip \ tar \ perl \ libXmu-devel \ libXi-devel \ mesa-libGL-devel \ mesa-libGLU-devel source scl_source enable gcc-toolset-12 CUDA_HOME="/usr/local/cuda" if [ ! -d "${CUDA_HOME}" ] && [ -d "${CUDA_HOME}-12.9" ]; then ln -s "${CUDA_HOME}-12.9" "${CUDA_HOME}" fi if [ -d "${CUDA_HOME}" ]; then export PATH="${CUDA_HOME}/bin:${PATH}" if [ ! -f "/usr/local/bin/nvcc" ] && [ -f "${CUDA_HOME}/bin/nvcc" ]; then ln -s "${CUDA_HOME}/bin/nvcc" /usr/local/bin/nvcc fi echo "${CUDA_HOME}/lib64" > /etc/ld.so.conf.d/cuda.conf fi # ccache shipped by CentOS is too old so we download and cache it. COMPILER_TOOLS_DIR="${CONTAINER_COMPILER_CACHE_DIR}/bin" mkdir -p ${COMPILER_TOOLS_DIR} if [ ! -f "${COMPILER_TOOLS_DIR}/ccache" ]; then FILE="ccache-4.10.1-linux-x86_64" curl -sSLO https://github.com/ccache/ccache/releases/download/v4.10.1/${FILE}.tar.xz tar -xf ${FILE}.tar.xz cp ${FILE}/ccache ${COMPILER_TOOLS_DIR} fi export PATH="${COMPILER_TOOLS_DIR}:${PATH}" ln -sf ${COMPILER_TOOLS_DIR}/ccache /usr/local/bin/ccache ccache --zero-stats # Setup vcpkg git clone https://github.com/microsoft/vcpkg ${VCPKG_INSTALLATION_ROOT} cd ${VCPKG_INSTALLATION_ROOT} ./bootstrap-vcpkg.sh ./vcpkg integrate install # Build COLMAP cd ${CURRDIR} mkdir build && cd build cmake3 .. -GNinja \ -DCUDA_ENABLED="${BUILD_CUDA_ENABLED}" \ -DCMAKE_CUDA_ARCHITECTURES="all-major" \ -DONNX_ENABLED=OFF \ -DGUI_ENABLED=OFF \ -DCGAL_ENABLED=OFF \ -DLSD_ENABLED=OFF \ -DCCACHE_ENABLED=ON \ -DCMAKE_BUILD_TYPE=Release \ -DCMAKE_MAKE_PROGRAM=/usr/bin/ninja \ -DCMAKE_TOOLCHAIN_FILE="${CMAKE_TOOLCHAIN_FILE}" \ -DVCPKG_TARGET_TRIPLET="${VCPKG_TARGET_TRIPLET}" \ -DCMAKE_EXE_LINKER_FLAGS_INIT="-ldl" \ -DFETCHCONTENT_BASE_DIR="${FETCHCONTENT_BASE_DIR}" ninja install ccache --show-stats --verbose colmap-4.2.0/python/ci/install-colmap-macos.sh000077500000000000000000000025131524536416500212760ustar00rootroot00000000000000#!/bin/bash set -x -e CURRDIR=$(pwd) # Fix `brew link` error. find /usr/local/bin -lname '*/Library/Frameworks/Python.framework/*' -delete brew uninstall cmake # Workaround for CI failures. brew install git cmake ninja gfortran ccache libomp brew link --force libomp ccache --zero-stats sudo xcode-select --reset # When building lapack-reference, vcpkg/cmake looks for gfortran. ln -sf $(which gfortran-14) "$(dirname $(which gfortran-14))/gfortran" # Setup vcpkg git clone https://github.com/microsoft/vcpkg ${VCPKG_INSTALLATION_ROOT} cd ${VCPKG_INSTALLATION_ROOT} ./bootstrap-vcpkg.sh ./vcpkg integrate install # Build COLMAP cd ${CURRDIR} "$(brew --prefix cmake)/bin/cmake" \ -S . -B build/ \ -GNinja \ -DCUDA_ENABLED=OFF \ -DONNX_ENABLED=OFF \ -DGUI_ENABLED=OFF \ -DCGAL_ENABLED=OFF \ -DLSD_ENABLED=OFF \ -DCCACHE_ENABLED=ON \ -DCMAKE_BUILD_TYPE=Release \ -DCMAKE_MAKE_PROGRAM="$(brew --prefix ninja)/bin/ninja" \ -DCMAKE_TOOLCHAIN_FILE="${CMAKE_TOOLCHAIN_FILE}" \ -DVCPKG_TARGET_TRIPLET="${VCPKG_TARGET_TRIPLET}" \ -DCMAKE_OSX_ARCHITECTURES="${CMAKE_OSX_ARCHITECTURES}" \ -DFETCHCONTENT_BASE_DIR="${FETCHCONTENT_BASE_DIR}" \ `if [[ ${CIBW_ARCHS_MACOS} == "arm64" ]]; then echo "-DSIMD_ENABLED=OFF"; fi` cmake --build build/ sudo cmake --install build/ ccache --show-stats --verbose colmap-4.2.0/python/ci/install-colmap-windows.ps1000077500000000000000000000023141524536416500217560ustar00rootroot00000000000000$ErrorActionPreference = "Stop" Set-StrictMode -Version Latest $PSNativeCommandUseErrorActionPreference = $true $CURRDIR = $PWD $COMPILER_TOOLS_DIR = "${env:COMPILER_CACHE_DIR}/bin" New-Item -ItemType Directory -Force -Path ${COMPILER_TOOLS_DIR} $env:Path = "${COMPILER_TOOLS_DIR};" + $env:Path If (!(Test-Path -path "${COMPILER_TOOLS_DIR}/ccache.exe" -PathType Leaf)) { .github/workflows/install-ccache.ps1 -Destination "${COMPILER_TOOLS_DIR}" } ccache --zero-stats # Setup vcpkg cd ${CURRDIR} git clone https://github.com/microsoft/vcpkg ${env:VCPKG_INSTALLATION_ROOT} cd ${env:VCPKG_INSTALLATION_ROOT} ./bootstrap-vcpkg.bat cd ${CURRDIR} & "./scripts/shell/enter_vs_dev_shell.ps1" & "${env:VCPKG_ROOT}/vcpkg.exe" integrate install # Build COLMAP mkdir build cd build cmake .. ` -GNinja ` -DCMAKE_MAKE_PROGRAM=ninja ` -DCUDA_ENABLED="OFF" ` -DONNX_ENABLED="OFF" ` -DGUI_ENABLED="OFF" ` -DCGAL_ENABLED="OFF" ` -DLSD_ENABLED="OFF" ` -DCMAKE_BUILD_TYPE="Release" ` -DCMAKE_TOOLCHAIN_FILE="${env:CMAKE_TOOLCHAIN_FILE}" ` -DVCPKG_TARGET_TRIPLET="${env:VCPKG_TARGET_TRIPLET}" ` -DFETCHCONTENT_BASE_DIR="${env:FETCHCONTENT_BASE_DIR}" ninja install ccache --show-stats --verbose colmap-4.2.0/python/ci/test-colmap-windows.ps1000066400000000000000000000004641524536416500212700ustar00rootroot00000000000000$ErrorActionPreference = "Stop" $PSNativeCommandUseErrorActionPreference = $true & "$PSScriptRoot/../../scripts/shell/enter_vs_dev_shell.ps1" & "${env:VCPKG_ROOT}/vcpkg.exe" integrate install & python -c "import pycolmap; print(pycolmap.__version__)" Set-Location "$PSScriptRoot/../.." & python -m pytest colmap-4.2.0/python/ci/test_regression_eth3d.py000066400000000000000000000140721524536416500216030ustar00rootroot00000000000000""" Runs the COLMAP automatic reconstruction pipeline on the ETH3D dataset and asserts that the reconstructed model is close to the ground truth. This script is intended to be run as a CI test. It is not intended to be run manually. Instead use benchmark/reconstruction/evaluate.py. """ import argparse import logging import os import subprocess import sys import urllib.request def download_file(url: str, file_path: str, max_retries: int = 3) -> None: if os.path.exists(file_path): return logging.info(f"Downloading {url} to {file_path}") for retry in range(max_retries): try: urllib.request.urlretrieve(url, file_path) return except Exception as exc: logging.error( f"Failed to download {url} (trial={retry + 1}) " f"to {file_path} due to {exc}" ) def check_small_errors_or_exit( dataset_name: str, max_rotation_error: float, max_proj_center_error: float, expected_num_images: float, errors_csv_path: str, ) -> None: logging.info(f"Evaluating errors for {dataset_name}") error = False with open(errors_csv_path) as fid: num_images = 0 for line in fid: line = line.strip() if len(line) == 0 or line.startswith("#"): continue rotation_error, proj_center_error = map(float, line.split(",")) num_images += 1 if rotation_error > max_rotation_error: logging.info( "Exceeded rotation error threshold:", rotation_error ) error = True if proj_center_error > max_proj_center_error: logging.info( "Exceeded projection center error threshold:", proj_center_error, ) error = True if num_images != expected_num_images: logging.error("Unexpected number of images:", num_images) error = True if error: sys.exit(1) def process_dataset(args: argparse.Namespace, dataset_name: str) -> None: logging.info("Processing dataset:", dataset_name) workspace_path = os.path.join( os.path.realpath(args.workspace_path), dataset_name ) os.makedirs(workspace_path, exist_ok=True) dataset_archive_path = os.path.join(workspace_path, f"{dataset_name}.7z") download_file( f"https://www.eth3d.net/data/{dataset_name}_dslr_undistorted.7z", dataset_archive_path, ) subprocess.check_call( ["7zz", "x", "-y", f"{dataset_name}.7z"], cwd=workspace_path ) # Find undistorted parameters of first camera and # initialize all images with it. This is an approximation # because not all datasets have only a single camera. # However, it is a good enough initialization. with open( os.path.join( workspace_path, f"{dataset_name}/dslr_calibration_undistorted/cameras.txt", ), ) as fid: for line in fid: if not line.startswith("#"): first_camera_data = line.split() camera_model = first_camera_data[1] assert camera_model == "PINHOLE" camera_params = first_camera_data[4:] assert len(camera_params) == 4 break # Count the number of expected images in the GT. expected_num_images = 0 with open( os.path.join( workspace_path, f"{dataset_name}/dslr_calibration_undistorted/images.txt", ), ) as fid: for line in fid: if not line.startswith("#") and line.strip(): expected_num_images += 1 # Each image uses two consecutive lines. assert expected_num_images % 2 == 0 expected_num_images //= 2 # Run automatic reconstruction pipeline. subprocess.check_call( [ os.path.realpath(args.colmap_path), "automatic_reconstructor", "--image_path", f"{dataset_name}/images/", "--workspace_path", workspace_path, "--use_gpu", "1" if args.use_gpu else "0", "--num_threads", str(args.num_threads), "--quality", args.quality, "--camera_model", "PINHOLE", "--camera_params", ",".join(camera_params), ], cwd=workspace_path, ) # Compare reconstructed model to GT model. subprocess.check_call( [ os.path.realpath(args.colmap_path), "model_comparer", "--input_path1", "sparse/0", "--input_path2", f"{dataset_name}/dslr_calibration_undistorted/", "--output_path", ".", "--alignment_error", "proj_center", "--max_proj_center_error", str(args.max_proj_center_error), ], cwd=workspace_path, ) # Ensure discrepancy between reconstructed model and GT is small. check_small_errors_or_exit( dataset_name, args.max_rotation_error, args.max_proj_center_error, expected_num_images, os.path.join(workspace_path, "errors.csv"), ) def parse_args() -> argparse.Namespace: parser = argparse.ArgumentParser() parser.add_argument("--dataset_names", nargs="+", required=True) parser.add_argument("--workspace_path", required=True) parser.add_argument("--colmap_path", required=True) parser.add_argument("--use_gpu", default=True, action="store_true") parser.add_argument("--use_cpu", dest="use_gpu", action="store_false") parser.add_argument("--num_threads", type=int, default=-1) parser.add_argument("--quality", default="medium") parser.add_argument("--max_rotation_error", type=float, default=1.0) parser.add_argument("--max_proj_center_error", type=float, default=0.1) return parser.parse_args() def main() -> None: args = parse_args() for dataset_name in args.dataset_names: process_dataset(args, dataset_name) if __name__ == "__main__": main() colmap-4.2.0/python/ci/update_pyproject_toml.py000066400000000000000000000020651524536416500217100ustar00rootroot00000000000000import argparse from pathlib import Path import tomlkit # Set up command-line argument parser parser = argparse.ArgumentParser( description="Modify pyproject.toml for a custom build." ) parser.add_argument( "--name", required=True, help="The new package name for the wheel." ) parser.add_argument( "--add-deps", nargs="+", default=[], help="Space-separated list of Python dependencies to add.", ) args = parser.parse_args() # Modify the pyproject.toml file pyproject_path = Path("pyproject.toml") if not pyproject_path.exists(): raise FileNotFoundError(pyproject_path) with open(pyproject_path, encoding="utf-8") as f: config = tomlkit.load(f) config["project"]["name"] = args.name if args.add_deps: if "dependencies" not in config["project"]: config["project"]["dependencies"] = [] existing_deps = config["project"]["dependencies"] for req in args.add_deps: if req not in existing_deps: existing_deps.append(req) with open(pyproject_path, "w", encoding="utf-8") as f: tomlkit.dump(config, f) colmap-4.2.0/python/examples/000077500000000000000000000000001524536416500161425ustar00rootroot00000000000000colmap-4.2.0/python/examples/conftest.py000066400000000000000000000001241524536416500203360ustar00rootroot00000000000000import sys from pathlib import Path sys.path.insert(0, str(Path(__file__).parent)) colmap-4.2.0/python/examples/convert_legacy_rotation_averaging_format.py000066400000000000000000000211271524536416500270350ustar00rootroot00000000000000""" Convert legacy rotation averaging file formats to a COLMAP database. This script provides a migration path from deprecated file-based input to database-based input for the rotation averaging CLI. Legacy file formats: - Relative poses: IMAGE_NAME_1 IMAGE_NAME_2 QW QX QY QZ TX TY TZ - Gravity priors: IMAGE_NAME GX GY GZ (optional) After conversion, use the database with: colmap rotation_averager --database_path database.db --output_path output/ Usage: python legacy_rotation_averaging_io.py \ --relpose_path relative_poses.txt \ --database_path database.db \ [--gravity_path gravity_priors.txt] """ import argparse from dataclasses import dataclass from pathlib import Path import numpy as np from numpy.typing import NDArray import pycolmap from pycolmap import logging @dataclass class RelativePose: """Relative pose between two images.""" image_name1: str image_name2: str cam2_from_cam1: pycolmap.Rigid3d @dataclass class GravityPrior: """Gravity prior for an image.""" image_name: str gravity: NDArray[np.float64] def read_relative_poses(file_path: Path | str) -> list[RelativePose]: """Read relative poses from a file. Format: IMAGE_NAME_1 IMAGE_NAME_2 QW QX QY QZ TX TY TZ Args: file_path: Path to the relative poses file. Returns: List of RelativePose objects. """ file_path = Path(file_path) relative_poses = [] with open(file_path) as f: for line in f: line = line.strip() if not line or line.startswith("#"): continue parts = line.split() if len(parts) < 9: continue name1, name2 = parts[0], parts[1] qw, qx, qy, qz = map(float, parts[2:6]) tx, ty, tz = map(float, parts[6:9]) # pycolmap.Rotation3d takes quaternion in xyzw format rotation = pycolmap.Rotation3d(np.array([qx, qy, qz, qw])) translation = np.array([tx, ty, tz]) cam2_from_cam1 = pycolmap.Rigid3d(rotation, translation) relative_poses.append( RelativePose( image_name1=name1, image_name2=name2, cam2_from_cam1=cam2_from_cam1, ) ) return relative_poses def read_gravity_priors(file_path: Path | str) -> list[GravityPrior]: """Read gravity priors from a file. Format: IMAGE_NAME GX GY GZ Args: file_path: Path to the gravity priors file. Returns: List of GravityPrior objects. """ file_path = Path(file_path) gravity_priors = [] with open(file_path) as f: for line in f: line = line.strip() if not line or line.startswith("#"): continue parts = line.split() if len(parts) < 4: continue name = parts[0] gx, gy, gz = map(float, parts[1:4]) gravity_priors.append( GravityPrior( image_name=name, gravity=np.array([gx, gy, gz], dtype=np.float64), ) ) return gravity_priors def get_image_names_from_relative_poses( relative_poses: list[RelativePose], ) -> dict[str, int]: """Extract unique image names from relative poses and assign IDs. Args: relative_poses: List of RelativePose objects. Returns: Dictionary mapping image names to IDs. """ image_names: dict[str, int] = {} next_id = 1 # COLMAP uses 1-based IDs for pose in relative_poses: for name in [pose.image_name1, pose.image_name2]: if name not in image_names: image_names[name] = next_id next_id += 1 return image_names def create_database_from_relative_poses( database_path: Path, relative_poses: list[RelativePose], gravity_priors: list[GravityPrior] | None = None, ) -> dict[str, int]: """Create a COLMAP database from relative poses and gravity priors. Args: database_path: Path to the output database. relative_poses: List of relative poses between image pairs. gravity_priors: Optional list of gravity priors for images. Returns: Dictionary mapping image names to their database image IDs. """ image_name_to_id = get_image_names_from_relative_poses(relative_poses) if database_path.exists(): database_path.unlink() # Use Reconstruction to create cameras, rigs, frames, and images # with the trivial rig/frame helper methods reconstruction = pycolmap.Reconstruction() for image_name, image_id in image_name_to_id.items(): camera_id = image_id # Create camera with trivial rig (rig_id = camera_id) camera = pycolmap.Camera.create( camera_id=camera_id, model=pycolmap.CameraModelId.SIMPLE_PINHOLE, focal_length=1.0, width=1, height=1, ) reconstruction.add_camera_with_trivial_rig(camera) # Create image with trivial frame (frame_id = image_id) image = pycolmap.Image() image.image_id = image_id image.name = image_name image.camera_id = camera_id reconstruction.add_image_with_trivial_frame(image) # Write to database with pycolmap.Database.open(database_path) as db: for camera in reconstruction.cameras.values(): db.write_camera(camera, use_camera_id=True) for rig in reconstruction.rigs.values(): db.write_rig(rig, use_rig_id=True) for frame in reconstruction.frames.values(): db.write_frame(frame, use_frame_id=True) for image in reconstruction.images.values(): db.write_image(image, use_image_id=True) # Write two-view geometries with relative poses for rel_pose in relative_poses: id1 = image_name_to_id[rel_pose.image_name1] id2 = image_name_to_id[rel_pose.image_name2] two_view_geom = pycolmap.TwoViewGeometry() two_view_geom.config = ( pycolmap.TwoViewGeometryConfiguration.CALIBRATED ) two_view_geom.cam2_from_cam1 = rel_pose.cam2_from_cam1 db.write_two_view_geometry(id1, id2, two_view_geom) # Write gravity priors if provided if gravity_priors: gravity_by_name = { gp.image_name: gp.gravity for gp in gravity_priors } for image in reconstruction.images.values(): if image.name in gravity_by_name: pose_prior = pycolmap.PosePrior() pose_prior.pose_prior_id = image.image_id pose_prior.corr_data_id = image.data_id pose_prior.gravity = gravity_by_name[image.name] db.write_pose_prior(pose_prior, use_pose_prior_id=True) return image_name_to_id def main(): parser = argparse.ArgumentParser( description="Convert rotation averaging files to database format" ) parser.add_argument( "--relpose_path", type=Path, required=True, help="Path to relative poses file", ) parser.add_argument( "--database_path", type=Path, required=True, help="Path for output database", ) parser.add_argument( "--gravity_path", type=Path, default=None, help="Optional path to gravity priors file", ) args = parser.parse_args() if not args.relpose_path.exists(): logging.error(f"Relative poses file not found: {args.relpose_path}") return 1 logging.info(f"Reading relative poses from {args.relpose_path}") relative_poses = read_relative_poses(args.relpose_path) logging.info(f"Loaded {len(relative_poses)} relative poses") gravity_priors = None if args.gravity_path is not None and args.gravity_path.exists(): logging.info(f"Reading gravity priors from {args.gravity_path}") gravity_priors = read_gravity_priors(args.gravity_path) logging.info(f"Loaded {len(gravity_priors)} gravity priors") logging.info(f"Creating database at {args.database_path}") image_name_to_id = create_database_from_relative_poses( args.database_path, relative_poses, gravity_priors ) logging.info(f"Database created with {len(image_name_to_id)} images") logging.info( f"Conversion complete. Use the database with:\n" f" colmap rotation_averager --database_path {args.database_path} " f"--output_path " ) return 0 if __name__ == "__main__": exit(main()) colmap-4.2.0/python/examples/custom_bundle_adjustment.py000066400000000000000000000303731524536416500236230ustar00rootroot00000000000000""" Python reimplementation of the bundle adjustment for the incremental mapper of C++ with equivalent logic. As a result, one can add customized residuals on top of the exposed ceres problem from conventional bundle adjustment. """ import collections import copy import pycolmap from pycolmap import logging def solve_bundle_adjustment( reconstruction: pycolmap.Reconstruction, ba_options: pycolmap.BundleAdjustmentOptions, ba_config: pycolmap.BundleAdjustmentConfig, ) -> pycolmap.BundleAdjustmentSummary: bundle_adjuster = pycolmap.create_default_bundle_adjuster( ba_options, ba_config, reconstruction ) summary = bundle_adjuster.solve() # Alternatively, you can customize the existing Ceres problem or options as: # import pyceres # The minimal bindings in pycolmap aren't sufficient. # bundle_adjuster = pycolmap.create_default_ceres_bundle_adjuster( # ba_options, ba_config, reconstruction # ) # solver_options = ba_options.ceres.create_solver_options( # ba_config, bundle_adjuster.problem # ) # summary = pyceres.SolverSummary() # pyceres.solve(solver_options, bundle_adjuster.problem, summary) return summary def adjust_global_bundle( mapper: pycolmap.IncrementalMapper, mapper_options: pycolmap.IncrementalMapperOptions, ba_options: pycolmap.BundleAdjustmentOptions, ) -> bool: """Equivalent to mapper.adjust_global_bundle(...)""" reconstruction = mapper.reconstruction assert reconstruction is not None reg_frame_ids = reconstruction.reg_frame_ids() if len(reg_frame_ids) < 2: logging.fatal("At least two images must be registered for global BA") custom_ba_options = copy.deepcopy(ba_options) # Use stricter convergence criteria for first registered images if len(reg_frame_ids) < 10: # kMinNumRegImagesForFastBA = 10 custom_ba_options.ceres.solver_options.function_tolerance /= 10 custom_ba_options.ceres.solver_options.gradient_tolerance /= 10 custom_ba_options.ceres.solver_options.parameter_tolerance /= 10 custom_ba_options.ceres.solver_options.max_num_iterations *= 2 custom_ba_options.ceres.solver_options.max_linear_solver_iterations = ( 200 ) # Avoid degeneracies in bundle adjustment mapper.observation_manager.filter_observations_with_negative_depth() # Configure bundle adjustment ba_config = pycolmap.BundleAdjustmentConfig() for frame_id in reg_frame_ids: frame = reconstruction.frame(frame_id) for data_id in frame.data_ids: if data_id.sensor_id.type != pycolmap.SensorType.CAMERA: continue ba_config.add_image(data_id.id) # Fix the existing images, if option specified if mapper_options.fix_existing_frames: for frame_id in reg_frame_ids: if frame_id in mapper.existing_frame_ids: ba_config.set_constant_rig_from_world_pose(frame_id) for rig_id in mapper_options.constant_rigs: for sensor_id in reconstruction.rig(rig_id).non_ref_sensors: ba_config.set_constant_sensor_from_rig_pose(sensor_id) for camera_id in mapper_options.constant_cameras: ba_config.set_constant_cam_intrinsics(camera_id) # TODO: Add python support for prior positions # Fixing the gauge with two cameras leads to a more stable optimization # with fewer steps as compared to fixing three points. ba_config.fix_gauge(pycolmap.BundleAdjustmentGauge.TWO_CAMS_FROM_WORLD) # Run bundle adjustment summary = solve_bundle_adjustment( reconstruction, custom_ba_options, ba_config ) logging.info("Global Bundle Adjustment") logging.info(summary.brief_report()) return summary.is_solution_usable() def iterative_global_refinement( mapper: pycolmap.IncrementalMapper, max_num_refinements: int, max_refinement_change: float, mapper_options: pycolmap.IncrementalMapperOptions, ba_options: pycolmap.BundleAdjustmentOptions, tri_options: pycolmap.IncrementalTriangulatorOptions, normalize_reconstruction: bool = True, ) -> bool: """Equivalent to mapper.iterative_global_refinement(...)""" reconstruction = mapper.reconstruction mapper.complete_and_merge_tracks(tri_options) num_retriangulated_observations = mapper.retriangulate(tri_options) logging.verbose( 1, f"=> Retriangulated observations: {num_retriangulated_observations}" ) for _ in range(max_num_refinements): num_observations = reconstruction.compute_num_observations() # mapper.adjust_global_bundle(mapper_options, ba_options) if not adjust_global_bundle(mapper, mapper_options, ba_options): return False if normalize_reconstruction: reconstruction.normalize() num_changed_observations = mapper.complete_and_merge_tracks(tri_options) num_changed_observations += mapper.filter_points(mapper_options) changed = ( num_changed_observations / num_observations if num_observations > 0 else 0 ) logging.verbose(1, f"=> Changed observations: {changed:.6f}") if changed < max_refinement_change: break return True def adjust_local_bundle( mapper: pycolmap.IncrementalMapper, mapper_options: pycolmap.IncrementalMapperOptions, ba_options: pycolmap.BundleAdjustmentOptions, tri_options: pycolmap.IncrementalTriangulatorOptions, image_id: int, point3D_ids: set[int], ) -> pycolmap.LocalBundleAdjustmentReport: """Equivalent to mapper.adjust_local_bundle(...)""" reconstruction = mapper.reconstruction assert reconstruction is not None report = pycolmap.LocalBundleAdjustmentReport() # Find images that have most 3D points with given image in common local_bundle = mapper.find_local_bundle(mapper_options, image_id) image_ids = set() # Do the bundle adjustment only if there is any connected images if local_bundle: ba_config = pycolmap.BundleAdjustmentConfig() ba_config.fix_gauge(pycolmap.BundleAdjustmentGauge.THREE_POINTS) # Insert the images of all local frames. image = reconstruction.image(image_id) frame_ids = {image.frame_id} assert image.frame is not None for data_id in image.frame.image_ids: ba_config.add_image(data_id.id) for local_image_id in local_bundle: local_image = reconstruction.image(local_image_id) frame_ids.add(local_image.frame_id) assert local_image.frame is not None for data_id in local_image.frame.image_ids: ba_config.add_image(data_id.id) # Fix the existing images, if options specified if mapper_options.fix_existing_frames: for frame_id in frame_ids: if frame_id in mapper.existing_frame_ids: ba_config.set_constant_rig_from_world_pose(frame_id) # Fix rig poses, if not all frames within the local bundle. num_frames_per_rig: dict[int, int] = collections.defaultdict(int) for frame_id in frame_ids: frame = reconstruction.frame(frame_id) num_frames_per_rig[frame.rig_id] += 1 for rig_id, num_frames_local in num_frames_per_rig.items(): if ( rig_id in mapper_options.constant_rigs or num_frames_local < mapper.num_reg_frames_per_rig[rig_id] ): for sensor_id in reconstruction.rig(rig_id).non_ref_sensors: ba_config.set_constant_sensor_from_rig_pose(sensor_id) # Fix camera intrinsics, if not all images within local bundle. num_images_per_camera: dict[int, int] = collections.defaultdict(int) for image_id in ba_config.images: image = reconstruction.images[image_id] num_images_per_camera[image.camera_id] += 1 for camera_id, num_images_local in num_images_per_camera.items(): if ( camera_id in mapper_options.constant_cameras or num_images_local < mapper.num_reg_images_per_camera[camera_id] ): ba_config.set_constant_cam_intrinsics(camera_id) # Make sure, we refine all new and short-track 3D points, no matter if # they are fully contained in the local image set or not. Do not include # long track 3D points as they are usually already very stable and # adding to them to bundle adjustment and track merging/completion would # slow down the local bundle adjustment significantly. variable_point3D_ids = set() for point3D_id in list(point3D_ids): point3D = reconstruction.point3D(point3D_id) kMaxTrackLength = 15 if ( point3D.error == -1.0 ) or point3D.track.length() <= kMaxTrackLength: ba_config.add_variable_point(point3D_id) variable_point3D_ids.add(point3D_id) # Adjust the local bundle summary = solve_bundle_adjustment( mapper.reconstruction, ba_options, ba_config ) logging.info("Local Bundle Adjustment") logging.info(summary.brief_report()) image_ids = ba_config.images report.num_adjusted_observations = int(summary.num_residuals / 2) # Merge refined tracks with other existing points report.num_merged_observations = mapper.triangulator.merge_tracks( tri_options, variable_point3D_ids ) # Complete tracks that may have failed to triangulate before refinement # of camera pose and calibration in bundle adjustment. This may avoid # that some points are filtered and helps for subsequent image # registrations. report.num_completed_observations = mapper.triangulator.complete_tracks( tri_options, variable_point3D_ids ) report.num_completed_observations += mapper.triangulator.complete_image( tri_options, image_id ) report.num_filtered_observations = ( mapper.observation_manager.filter_points3D_in_images( mapper_options.filter_max_reproj_error, mapper_options.filter_min_tri_angle, image_ids, ) ) report.num_filtered_observations += ( mapper.observation_manager.filter_points3D( mapper_options.filter_max_reproj_error, mapper_options.filter_min_tri_angle, point3D_ids, ) ) return report def iterative_local_refinement( mapper: pycolmap.IncrementalMapper, max_num_refinements: int, max_refinement_change: float, mapper_options: pycolmap.IncrementalMapperOptions, ba_options: pycolmap.BundleAdjustmentOptions, tri_options: pycolmap.IncrementalTriangulatorOptions, image_id: int, ) -> None: """Equivalent to mapper.iterative_local_refinement(...)""" custom_ba_options = copy.deepcopy(ba_options) for _ in range(max_num_refinements): # report = mapper.adjust_local_bundle( # mapper_options, # custom_ba_options, # tri_options, # image_id, # mapper.get_modified_points3D(), # ) report = adjust_local_bundle( mapper, mapper_options, custom_ba_options, tri_options, image_id, mapper.get_modified_points3D(), ) logging.verbose( 1, f"=> Merged observations: {report.num_merged_observations}" ) logging.verbose( 1, f"=> Completed observations: {report.num_completed_observations}" ) logging.verbose( 1, f"=> Filtered observations: {report.num_filtered_observations}" ) changed = 0.0 if report.num_adjusted_observations > 0: changed = ( report.num_merged_observations + report.num_completed_observations + report.num_filtered_observations ) / report.num_adjusted_observations logging.verbose(1, f"=> Changed observations: {changed:.6f}") if changed < max_refinement_change: break # Only use robust cost function for first iteration custom_ba_options.ceres.loss_function_type = ( pycolmap.LossFunctionType.TRIVIAL ) mapper.clear_modified_points3D() colmap-4.2.0/python/examples/custom_incremental_pipeline.py000066400000000000000000000472531524536416500243070ustar00rootroot00000000000000""" Python reimplementation of the C++ incremental mapper with equivalent logic. """ import argparse import time from pathlib import Path import custom_bundle_adjustment import enlighten import pycolmap from pycolmap import ( IncrementalMapper, IncrementalMapperOptions, IncrementalPipeline, IncrementalPipelineCallback, IncrementalPipelineOptions, IncrementalPipelineStatus, Reconstruction, ReconstructionManager, logging, ) def write_snapshot(reconstruction: Reconstruction, snapshot_path: Path) -> None: logging.info("Creating snapshot") timestamp = time.time() * 1000 path = snapshot_path / f"{timestamp:010d}" path.mkdir(exist_ok=True, parents=True) logging.verbose(1, f"=> Writing to {path}") reconstruction.write(path) def has_unknown_sensor_from_rig( reconstruction: Reconstruction, ) -> bool: parameterized_rig_ids = set() for image in reconstruction.images.values(): parameterized_rig_ids.add(image.frame.rig_id) for rig_id in parameterized_rig_ids: rig = reconstruction.rig(rig_id) for sensor_id, sensor_from_rig in rig.non_ref_sensors.items(): if ( sensor_id.type == pycolmap.SensorType.CAMERA and sensor_from_rig is None ): return True return False def iterative_global_refinement( options: IncrementalPipelineOptions, mapper_options: IncrementalMapperOptions, mapper: IncrementalMapper, ) -> None: logging.info("Retriangulation and Global bundle adjustment") # The following is equivalent to mapper.iterative_global_refinement(...) custom_bundle_adjustment.iterative_global_refinement( mapper, options.ba_global_max_refinements, options.ba_global_max_refinement_change, mapper_options, options.get_global_bundle_adjustment(), options.get_triangulation(), ) mapper.filter_frames(mapper_options) def initialize_reconstruction( controller: IncrementalPipeline, mapper: IncrementalMapper, mapper_options: IncrementalMapperOptions, reconstruction: Reconstruction, ) -> IncrementalPipelineStatus: """Equivalent to IncrementalPipeline.initialize_reconstruction(...)""" options = controller.options init_pair = (options.init_image_id1, options.init_image_id2) # Try to find good initial pair if not options.is_initial_pair_provided(): logging.info("Finding good initial image pair") ret = mapper.find_initial_image_pair(mapper_options, *init_pair) if ret is None: logging.info("No good initial image pair found.") return IncrementalPipelineStatus.NO_INITIAL_PAIR init_pair, init_cam2_from_cam1 = ret else: if not all(reconstruction.exists_image(i) for i in init_pair): logging.info(f"=> Initial image pair {init_pair} does not exist.") return IncrementalPipelineStatus.NO_INITIAL_PAIR maybe_init_cam2_from_cam1 = mapper.estimate_initial_two_view_geometry( mapper_options, *init_pair ) if maybe_init_cam2_from_cam1 is None: logging.info("Provided pair is unsuitable for initialization") return IncrementalPipelineStatus.BAD_INITIAL_PAIR init_cam2_from_cam1 = maybe_init_cam2_from_cam1 logging.info( f"Registering initial image pair #{init_pair[0]} and #{init_pair[1]}" ) mapper.register_initial_image_pair( mapper_options, *init_pair, init_cam2_from_cam1 ) tri_options = options.get_triangulation() tri_options.min_angle = mapper_options.init_min_tri_angle for image_id in init_pair: image = reconstruction.images[image_id] assert image.frame is not None for data_id in image.frame.image_ids: mapper.triangulate_image(tri_options, data_id.id) logging.info("Global bundle adjustment") # The following is equivalent to: mapper.adjust_global_bundle(...) custom_bundle_adjustment.adjust_global_bundle( mapper, mapper_options, options.get_global_bundle_adjustment() ) reconstruction.normalize() mapper.filter_points(mapper_options) mapper.filter_frames(mapper_options) # Initial image pair failed to register if ( reconstruction.num_reg_frames() == 0 or reconstruction.num_points3D() == 0 ): return IncrementalPipelineStatus.BAD_INITIAL_PAIR if options.extract_colors: reconstruction.extract_colors_for_all_images(options.image_path) return IncrementalPipelineStatus.SUCCESS def reconstruct_sub_model( controller: IncrementalPipeline, mapper: IncrementalMapper, mapper_options: IncrementalMapperOptions, reconstruction: Reconstruction, ) -> IncrementalPipelineStatus: """Equivalent to IncrementalPipeline.reconstruct_sub_model(...)""" mapper.begin_reconstruction(reconstruction) if has_unknown_sensor_from_rig(reconstruction): return IncrementalPipelineStatus.UNKNOWN_SENSOR_FROM_RIG if reconstruction.num_reg_frames() == 0: init_status = initialize_reconstruction( controller, mapper, mapper_options, reconstruction ) if init_status != IncrementalPipelineStatus.SUCCESS: return init_status controller.callback( IncrementalPipelineCallback.INITIAL_IMAGE_PAIR_REG_CALLBACK ) options = controller.options structure_less_flags = [] if options.structure_less_registration_only: structure_less_flags = [True] else: if options.structure_less_registration_fallback: structure_less_flags = [False, True] else: structure_less_flags = [False] snapshot_prev_num_reg_frames = reconstruction.num_reg_frames() ba_prev_num_reg_frames = reconstruction.num_reg_frames() ba_prev_num_points = reconstruction.num_points3D() reg_next_success, prev_reg_next_success = True, True while True: if not (reg_next_success or prev_reg_next_success): break if controller.check_reached_max_runtime(): break prev_reg_next_success = reg_next_success reg_next_success = False next_image_id = None for structure_less in structure_less_flags: next_images = mapper.find_next_images( mapper_options, structure_less=structure_less ) for reg_trial, next_image_id in enumerate(next_images): logging.info( f"Registering image #{next_image_id} " f"(num_reg_frames={reconstruction.num_reg_frames()})" ) if structure_less: logging.info( "Registering image with structure-less fallback" ) num_vis = ( mapper.observation_manager.num_visible_correspondences( next_image_id ) ) num_corrs = mapper.observation_manager.num_correspondences( next_image_id ) logging.info( f"=> Image sees {num_vis} / {num_corrs} correspondences" ) reg_next_success = ( mapper.register_next_structure_less_image( mapper_options, next_image_id ) ) else: num_vis = mapper.observation_manager.num_visible_points3D( next_image_id ) num_obs = mapper.observation_manager.num_observations( next_image_id ) logging.info(f"=> Image sees {num_vis} / {num_obs} points") reg_next_success = mapper.register_next_image( mapper_options, next_image_id ) if reg_next_success: break else: logging.info("=> Could not register, trying another image.") # If initial pair fails to continue for some time, # abort and try different initial pair. kMinNumInitialRegTrials = 30 if ( reg_trial >= kMinNumInitialRegTrials and reconstruction.num_reg_images() < options.min_model_size ): break if reg_next_success: break if reg_next_success and next_image_id is not None: image = reconstruction.images[next_image_id] assert image.frame is not None for data_id in image.frame.image_ids: mapper.triangulate_image( options.get_triangulation(), data_id.id ) # This is equivalent to mapper.iterative_local_refinement(...) custom_bundle_adjustment.iterative_local_refinement( mapper, options.ba_local_max_refinements, options.ba_local_max_refinement_change, mapper_options, options.get_local_bundle_adjustment(), options.get_triangulation(), next_image_id, ) if controller.check_run_global_refinement( reconstruction, ba_prev_num_reg_frames, ba_prev_num_points ): iterative_global_refinement(options, mapper_options, mapper) ba_prev_num_points = reconstruction.num_points3D() ba_prev_num_reg_frames = reconstruction.num_reg_frames() if options.extract_colors: for data_id in image.frame.image_ids: if not reconstruction.extract_colors_for_image( data_id.id, options.image_path ): logging.warning( f"Could not read image " f"{reconstruction.images[data_id.id].name} " f"at path {options.image_path}" ) if ( options.snapshot_frames_freq > 0 and reconstruction.num_reg_frames() >= options.snapshot_frames_freq + snapshot_prev_num_reg_frames ): snapshot_prev_num_reg_frames = reconstruction.num_reg_frames() write_snapshot(reconstruction, Path(options.snapshot_path)) controller.callback( IncrementalPipelineCallback.NEXT_IMAGE_REG_CALLBACK ) if mapper.num_shared_reg_images() >= int(options.max_model_overlap): break if (not reg_next_success) and prev_reg_next_success: iterative_global_refinement(options, mapper_options, mapper) if controller.check_reached_max_runtime(): return pycolmap.IncrementalPipelineStatus.INTERRUPTED # Only run final global BA, if last incremental BA was not global if ( reconstruction.num_reg_frames() > 0 and reconstruction.num_reg_frames() != ba_prev_num_reg_frames and reconstruction.num_points3D() != ba_prev_num_points ): iterative_global_refinement(options, mapper_options, mapper) return IncrementalPipelineStatus.SUCCESS def reconstruct( controller: IncrementalPipeline, mapper: IncrementalMapper, mapper_options: IncrementalMapperOptions, continue_reconstruction: bool, ) -> IncrementalPipelineStatus: """Equivalent to IncrementalPipeline.reconstruct(...)""" options = controller.options database_cache = controller.database_cache reconstruction_manager = controller.reconstruction_manager for num_trials in range(options.init_num_trials): if controller.check_reached_max_runtime(): break if not continue_reconstruction or num_trials > 0: reconstruction_idx = reconstruction_manager.add() else: reconstruction_idx = 0 reconstruction = reconstruction_manager.get(reconstruction_idx) status = reconstruct_sub_model( controller, mapper, mapper_options, reconstruction ) if status == IncrementalPipelineStatus.INTERRUPTED: reconstruction.update_point_3d_errors() logging.info("Keeping reconstruction due to interrupt") mapper.end_reconstruction(False) pycolmap.align_reconstruction_to_orig_rig_scales( database_cache.rigs, reconstruction ) elif status == IncrementalPipelineStatus.UNKNOWN_SENSOR_FROM_RIG: logging.error( "Discarding reconstruction due to unknown sensor_from_rig " "poses. Either explicitly define the poses by configuring the " "rigs or first run reconstruction without configured rigs and " "then derive the poses from the initial reconstruction for a " "subsequent reconstruction with rig constraints. See " "documentation for detailed instructions." ) mapper.end_reconstruction(True) reconstruction_manager.delete(reconstruction_idx) return IncrementalPipelineStatus.STOP elif status == IncrementalPipelineStatus.BAD_INITIAL_PAIR: logging.info("Disacarding reconstruction due to bad initial pair") mapper.end_reconstruction(True) reconstruction_manager.delete(reconstruction_idx) elif status == IncrementalPipelineStatus.NO_INITIAL_PAIR: logging.info("Disacarding reconstruction due to no initial pair") mapper.end_reconstruction(True) reconstruction_manager.delete(reconstruction_idx) return IncrementalPipelineStatus.CONTINUE elif status == IncrementalPipelineStatus.SUCCESS: num_reg_images = reconstruction.num_reg_images() total_num_reg_images = mapper.num_total_reg_images() if ( options.multiple_models and reconstruction_manager.size() > 1 and num_reg_images < options.min_model_size ) or num_reg_images == 0: logging.info( "Discarding reconstruction due to insufficient size" ) mapper.end_reconstruction(True) reconstruction_manager.delete(reconstruction_idx) else: reconstruction.update_point_3d_errors() logging.info("Keeping successful reconstruction") mapper.end_reconstruction(False) pycolmap.align_reconstruction_to_orig_rig_scales( database_cache.rigs, reconstruction ) controller.callback( IncrementalPipelineCallback.LAST_IMAGE_REG_CALLBACK ) if ( not options.multiple_models or reconstruction_manager.size() >= options.max_num_models or total_num_reg_images >= database_cache.num_images() - 1 ): return IncrementalPipelineStatus.STOP else: logging.fatal(f"Unknown reconstruction status: {status}") return IncrementalPipelineStatus.CONTINUE def main_incremental_mapper(controller: IncrementalPipeline) -> None: """Equivalent to IncrementalPipeline.run()""" timer = pycolmap.Timer() timer.start() database_cache = controller.database_cache if database_cache.num_images() == 0: logging.warning("No images with matches found in the database") return if ( controller.options.use_prior_position and database_cache.num_pose_priors() == 0 ): logging.warning("No pose priors") return reconstruction_manager = controller.reconstruction_manager continue_reconstruction = reconstruction_manager.size() > 0 if reconstruction_manager.size() > 1: logging.fatal( "Can only resume from a single reconstruction, " "but multiple are given" ) num_images = database_cache.num_images() mapper = IncrementalMapper(database_cache) mapper_options = controller.options.get_mapper() if ( reconstruct(controller, mapper, mapper_options, continue_reconstruction) == IncrementalPipelineStatus.STOP ): return def should_stop(): return ( mapper.num_total_reg_images() == num_images or controller.check_reached_max_runtime() ) for _ in range(2): # number of relaxations if should_stop(): break logging.info("=> Relaxing the initialization constraints") mapper_options.init_min_num_inliers = int( mapper_options.init_min_num_inliers / 2 ) mapper.reset_initialization_stats() if ( reconstruct( controller, mapper, mapper_options, continue_reconstruction=False, ) == IncrementalPipelineStatus.STOP ): return if should_stop(): break logging.info("=> Relaxing the initialization constraints") mapper_options.init_min_tri_angle /= 2 mapper.reset_initialization_stats() if ( reconstruct( controller, mapper, mapper_options, continue_reconstruction=False, ) == IncrementalPipelineStatus.STOP ): return timer.print_minutes() def main( database_path: Path, image_path: Path, output_path: Path, options: IncrementalPipelineOptions | None = None, input_path: Path | None = None, ) -> dict[int, Reconstruction]: if options is None: options = IncrementalPipelineOptions() options.image_path = image_path if not database_path.exists(): logging.fatal(f"Database path does not exist: {database_path}") if not image_path.exists(): logging.fatal(f"Image path does not exist: {image_path}") output_path.mkdir(exist_ok=True, parents=True) reconstruction_manager = ReconstructionManager() if input_path: reconstruction_manager.read(input_path) with pycolmap.Database.open(database_path) as database: mapper = IncrementalPipeline(options, database, reconstruction_manager) num_images = database.num_images() with enlighten.Manager() as manager: with manager.counter( total=num_images, desc="Images registered:" ) as pbar: pbar.update(0, force=True) mapper.add_callback( IncrementalPipelineCallback.INITIAL_IMAGE_PAIR_REG_CALLBACK, lambda: pbar.update(2), ) mapper.add_callback( IncrementalPipelineCallback.NEXT_IMAGE_REG_CALLBACK, lambda: pbar.update(1), ) main_incremental_mapper(mapper) # write and output reconstruction_manager.write(output_path) reconstructions = {} for i in range(reconstruction_manager.size()): reconstructions[i] = reconstruction_manager.get(i) return reconstructions def parse_args() -> argparse.Namespace: parser = argparse.ArgumentParser() parser.add_argument("--database_path", required=True) parser.add_argument("--image_path", required=True) parser.add_argument("--input_path", default=None) parser.add_argument("--output_path", required=True) return parser.parse_args() if __name__ == "__main__": args = parse_args() main( database_path=Path(args.database_path), image_path=Path(args.image_path), input_path=Path(args.input_path) if args.input_path else None, output_path=Path(args.output_path), ) colmap-4.2.0/python/examples/custom_incremental_pipeline_test.py000066400000000000000000000146651524536416500253470ustar00rootroot00000000000000# Equivalent tests to src/colmap/controllers/incremental_pipeline_test.cc from pathlib import Path import custom_incremental_pipeline import pycolmap def expect_equal_reconstructions( gt: pycolmap.Reconstruction, computed: pycolmap.Reconstruction, max_rotation_error_deg: float, max_proj_center_error: float, num_obs_tolerance: float, ) -> None: assert computed.num_cameras() == gt.num_cameras() assert computed.num_images() == gt.num_images() assert computed.num_reg_images() == gt.num_reg_images() assert ( computed.compute_num_observations() >= (1 - num_obs_tolerance) * gt.compute_num_observations() ) result = pycolmap.compare_reconstructions( computed, gt, alignment_error="proj_center", max_proj_center_error=max_proj_center_error, ) assert result is not None for error in result["errors"]: assert error.rotation_error_deg < max_rotation_error_deg assert error.proj_center_error < max_proj_center_error def create_test_options() -> pycolmap.IncrementalPipelineOptions: options = pycolmap.IncrementalPipelineOptions() # Use single thread for deterministic behavior. options.num_threads = 1 return options def test_without_noise(tmp_path: Path) -> None: pycolmap.set_random_seed(0) database_path = tmp_path / "database.db" image_path = tmp_path / "images" image_path.mkdir() output_path = tmp_path / "sparse" output_path.mkdir() with pycolmap.Database.open(database_path) as database: synthetic_dataset_options = pycolmap.SyntheticDatasetOptions() synthetic_dataset_options.num_cameras_per_rig = 2 synthetic_dataset_options.num_frames_per_rig = 7 synthetic_dataset_options.num_points3D = 50 gt_reconstruction = pycolmap.synthesize_dataset( synthetic_dataset_options, database ) custom_incremental_pipeline.main( database_path=database_path, image_path=image_path, output_path=output_path, options=create_test_options(), ) expect_equal_reconstructions( gt_reconstruction, pycolmap.Reconstruction(output_path / "0"), max_rotation_error_deg=1e-2, max_proj_center_error=1e-4, num_obs_tolerance=0, ) def test_with_noise(tmp_path: Path) -> None: pycolmap.set_random_seed(0) database_path = tmp_path / "database.db" image_path = tmp_path / "images" image_path.mkdir() output_path = tmp_path / "sparse" output_path.mkdir() with pycolmap.Database.open(database_path) as database: synthetic_dataset_options = pycolmap.SyntheticDatasetOptions() synthetic_dataset_options.num_cameras_per_rig = 2 synthetic_dataset_options.num_frames_per_rig = 7 synthetic_dataset_options.num_points3D = 100 gt_reconstruction = pycolmap.synthesize_dataset( synthetic_dataset_options, database ) synthetic_noise_options = pycolmap.SyntheticNoiseOptions() synthetic_noise_options.point2D_stddev = 0.5 pycolmap.synthesize_noise(synthetic_noise_options, gt_reconstruction) custom_incremental_pipeline.main( database_path=database_path, image_path=image_path, output_path=output_path, options=create_test_options(), ) expect_equal_reconstructions( gt_reconstruction, pycolmap.Reconstruction(output_path / "0"), max_rotation_error_deg=1e-1, max_proj_center_error=1e-1, num_obs_tolerance=0.02, ) def test_multi_reconstruction(tmp_path: Path) -> None: pycolmap.set_random_seed(0) database_path = tmp_path / "database.db" image_path = tmp_path / "images" image_path.mkdir() output_path = tmp_path / "sparse" output_path.mkdir() with pycolmap.Database.open(database_path) as database: synthetic_dataset_options = pycolmap.SyntheticDatasetOptions() synthetic_dataset_options.num_cameras_per_rig = 1 synthetic_dataset_options.num_frames_per_rig = 5 synthetic_dataset_options.num_points3D = 50 gt_reconstruction1 = pycolmap.synthesize_dataset( synthetic_dataset_options, database ) synthetic_dataset_options.num_frames_per_rig = 4 gt_reconstruction2 = pycolmap.synthesize_dataset( synthetic_dataset_options, database ) options = create_test_options() options.min_model_size = 4 custom_incremental_pipeline.main( database_path=database_path, image_path=image_path, output_path=output_path, options=options, ) assert len(list(output_path.iterdir())) == 2 reconstruction1 = pycolmap.Reconstruction(output_path / "0") reconstruction2 = pycolmap.Reconstruction(output_path / "1") if reconstruction1 == gt_reconstruction2.num_reg_images(): reconstruction1, reconstruction2 = reconstruction2, reconstruction1 expect_equal_reconstructions( gt_reconstruction1, reconstruction1, max_rotation_error_deg=1e-2, max_proj_center_error=1e-4, num_obs_tolerance=0, ) expect_equal_reconstructions( gt_reconstruction2, reconstruction2, max_rotation_error_deg=1e-2, max_proj_center_error=1e-4, num_obs_tolerance=0, ) def test_chained_matches(tmp_path: Path) -> None: pycolmap.set_random_seed(0) database_path = tmp_path / "database.db" image_path = tmp_path / "images" image_path.mkdir() output_path = tmp_path / "sparse" output_path.mkdir() with pycolmap.Database.open(database_path) as database: synthetic_dataset_options = pycolmap.SyntheticDatasetOptions() synthetic_dataset_options.num_cameras_per_rig = 1 synthetic_dataset_options.num_frames_per_rig = 4 synthetic_dataset_options.num_points3D = 100 synthetic_dataset_options.match_config = ( pycolmap.SyntheticDatasetMatchConfig.CHAINED ) gt_reconstruction = pycolmap.synthesize_dataset( synthetic_dataset_options, database ) custom_incremental_pipeline.main( database_path=database_path, image_path=image_path, output_path=output_path, options=create_test_options(), ) expect_equal_reconstructions( gt_reconstruction, pycolmap.Reconstruction(output_path / "0"), max_rotation_error_deg=1e-2, max_proj_center_error=1e-4, num_obs_tolerance=0, ) colmap-4.2.0/python/examples/example.py000066400000000000000000000045711524536416500201560ustar00rootroot00000000000000""" An example for running incremental SfM on images with the pycolmap interface. """ import shutil import urllib.request import zipfile from pathlib import Path import enlighten import pycolmap from pycolmap import logging def incremental_mapping_with_pbar( database_path: Path, image_path: Path, sfm_path: Path ) -> dict[int, pycolmap.Reconstruction]: with pycolmap.Database.open(database_path) as database: num_images = database.num_images() with enlighten.Manager() as manager: with manager.counter( total=num_images, desc="Images registered:" ) as pbar: pbar.update(0, force=True) reconstructions = pycolmap.incremental_mapping( database_path, image_path, sfm_path, initial_image_pair_callback=lambda: pbar.update(2), next_image_callback=lambda: pbar.update(1), ) return reconstructions def run() -> None: output_path = Path("example/") image_path = output_path / "Fountain/images" database_path = output_path / "database.db" sfm_path = output_path / "sfm" output_path.mkdir(exist_ok=True) # The log filename is postfixed with the execution timestamp. logging.set_log_destination(logging.INFO, output_path / "INFO.log.") data_url = "https://cvg-data.inf.ethz.ch/local-feature-evaluation-schoenberger2017/Strecha-Fountain.zip" if not image_path.exists(): logging.info("Downloading the data.") zip_path = output_path / "data.zip" urllib.request.urlretrieve(data_url, zip_path) with zipfile.ZipFile(zip_path, "r") as fid: fid.extractall(output_path) logging.info(f"Data extracted to {output_path}.") if database_path.exists(): database_path.unlink() pycolmap.set_random_seed(0) pycolmap.extract_features(database_path, image_path) pycolmap.match_exhaustive(database_path) if sfm_path.exists(): shutil.rmtree(sfm_path) sfm_path.mkdir(exist_ok=True) recs = incremental_mapping_with_pbar(database_path, image_path, sfm_path) # alternatively, use: # import custom_incremental_pipeline # recs = custom_incremental_pipeline.main( # database_path, image_path, sfm_path # ) for idx, rec in recs.items(): logging.info(f"#{idx} {rec.summary()}") if __name__ == "__main__": run() colmap-4.2.0/python/examples/panorama_sfm.py000066400000000000000000000032631524536416500211630ustar00rootroot00000000000000"""Run Structure-from-Motion on 360-degree panorama images.""" import argparse from pathlib import Path from pycolmap.panorama import ( Mapper, Matcher, PanoramaReconstructionOptions, PanoRenderType, reconstruct, ) def main() -> None: parser = argparse.ArgumentParser(description=__doc__) parser.add_argument("--input_image_path", type=Path, required=True) parser.add_argument("--output_path", type=Path, required=True) parser.add_argument( "--matcher", type=Matcher, default=Matcher.SEQUENTIAL, choices=list(Matcher), ) parser.add_argument( "--mapper", type=Mapper, default=Mapper.INCREMENTAL, choices=list(Mapper), ) parser.add_argument( "--pano_render_type", type=PanoRenderType, default=PanoRenderType.PERSPECTIVE_OVERLAPPING, choices=list(PanoRenderType), ) parser.add_argument("--random_seed", type=int, default=0) parser.add_argument("--num_threads", type=int, default=-1) parser.add_argument("--gpu_index", default="-1") parser.add_argument("--use_gpu", default=True, action="store_true") parser.add_argument("--use_cpu", dest="use_gpu", action="store_false") args = parser.parse_args() reconstruct( args.input_image_path, args.output_path, PanoramaReconstructionOptions( matcher=args.matcher, mapper=args.mapper, render_type=args.pano_render_type, random_seed=args.random_seed, num_threads=args.num_threads, gpu_index=args.gpu_index, use_gpu=args.use_gpu, ), ) if __name__ == "__main__": main() colmap-4.2.0/python/examples/panorama_sfm_test.py000066400000000000000000000004261524536416500222200ustar00rootroot00000000000000import subprocess import sys from pathlib import Path def test_help(): script_path = Path(__file__).with_name("panorama_sfm.py") subprocess.run( [sys.executable, script_path, "--help"], check=True, capture_output=True, text=True, ) colmap-4.2.0/python/examples/visualize_model.py000077500000000000000000000166521524536416500217240ustar00rootroot00000000000000# Copyright (c), ETH Zurich and UNC Chapel Hill. # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are met: # # * Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer. # # * Redistributions in binary form must reproduce the above copyright # notice, this list of conditions and the following disclaimer in the # documentation and/or other materials provided with the distribution. # # * Neither the name of ETH Zurich and UNC Chapel Hill nor the names of # its contributors may be used to endorse or promote products derived # from this software without specific prior written permission. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. import argparse import numpy as np import numpy.typing as npt import open3d import pycolmap class Model: def __init__(self) -> None: self.reconstruction: pycolmap.Reconstruction self.visualizer: open3d.visualization.Visualizer def read_model(self, path: str) -> None: self.reconstruction = pycolmap.Reconstruction(path) def add_points( self, min_track_len: int = 3, remove_statistical_outlier: bool = True ) -> None: pcd = open3d.geometry.PointCloud() xyz = [] rgb = [] for point in self.reconstruction.points3D.values(): if point.track.length() < min_track_len: continue xyz.append(point.xyz) rgb.append(point.color / 255) pcd.points = open3d.utility.Vector3dVector(xyz) pcd.colors = open3d.utility.Vector3dVector(rgb) # remove obvious outliers if remove_statistical_outlier: [pcd, _] = pcd.remove_statistical_outlier( nb_neighbors=20, std_ratio=2.0 ) # open3d.visualization.draw_geometries([pcd]) self.visualizer.add_geometry(pcd) self.visualizer.poll_events() self.visualizer.update_renderer() def add_cameras(self, scale: float = 1) -> None: frustums = [] for img in self.reconstruction.images.values(): # extrinsics world_from_cam = img.cam_from_world().inverse() R = world_from_cam.rotation.matrix() t = world_from_cam.translation # intrinsics cam = img.camera if cam.model in ( pycolmap.CameraModelId.SIMPLE_PINHOLE, pycolmap.CameraModelId.SIMPLE_RADIAL, pycolmap.CameraModelId.RADIAL, ): fx = fy = cam.params[0] cx = cam.params[1] cy = cam.params[2] elif cam.model in ( pycolmap.CameraModelId.PINHOLE, pycolmap.CameraModelId.OPENCV, pycolmap.CameraModelId.OPENCV_FISHEYE, pycolmap.CameraModelId.FULL_OPENCV, ): fx = cam.params[0] fy = cam.params[1] cx = cam.params[2] cy = cam.params[3] else: raise Exception("Camera model not supported") # intrinsics K = np.identity(3) K[0, 0] = fx K[1, 1] = fy K[0, 2] = cx K[1, 2] = cy # create axis, plane and pyramid geometries that will be drawn cam_model = draw_camera(K, R, t, cam.width, cam.height, scale) frustums.extend(cam_model) # add geometries to visualizer for i in frustums: self.visualizer.add_geometry(i) def create_window(self) -> None: self.visualizer = open3d.visualization.Visualizer() self.visualizer.create_window() def show(self) -> None: self.visualizer.poll_events() self.visualizer.update_renderer() self.visualizer.run() self.visualizer.destroy_window() def draw_camera( K: npt.NDArray[np.float64], R: npt.NDArray[np.float64], t: npt.NDArray[np.float64], w: int, h: int, scale: float = 1, color: list[float] | None = None, ) -> list[open3d.geometry.Geometry]: """Create axis, plane and pyramed geometries in Open3D format. :param K: calibration matrix (camera intrinsics) :param R: rotation matrix :param t: translation :param w: image width :param h: image height :param scale: camera model scale :param color: color of the image plane and pyramid lines :return: camera model geometries (axis, plane and pyramid) """ if color is None: color = [0.8, 0.2, 0.8] # intrinsics K = K.copy() / scale Kinv = np.linalg.inv(K) # 4x4 transformation T = np.column_stack((R, t)) T = np.vstack((T, (0, 0, 0, 1))) # axis axis = open3d.geometry.TriangleMesh.create_coordinate_frame( size=0.5 * scale ) axis.transform(T) # points in pixel points_pixel = [ [0, 0, 0], [0, 0, 1], [w, 0, 1], [0, h, 1], [w, h, 1], ] # pixel to camera coordinate system points = [Kinv @ p for p in points_pixel] # image plane width = abs(points[1][0]) + abs(points[3][0]) height = abs(points[1][1]) + abs(points[3][1]) plane = open3d.geometry.TriangleMesh.create_box(width, height, depth=1e-6) plane.paint_uniform_color(color) plane.translate([points[1][0], points[1][1], scale]) plane.transform(T) # pyramid points_in_world = [(R @ p + t) for p in points] lines = [ [0, 1], [0, 2], [0, 3], [0, 4], ] colors = [color for i in range(len(lines))] line_set = open3d.geometry.LineSet( points=open3d.utility.Vector3dVector(points_in_world), lines=open3d.utility.Vector2iVector(lines), ) line_set.colors = open3d.utility.Vector3dVector(colors) # return as list in Open3D format return [axis, plane, line_set] def parse_args() -> argparse.Namespace: parser = argparse.ArgumentParser( description="Visualize COLMAP binary and text models" ) parser.add_argument( "--input_model", required=True, help="path to input model folder" ) args = parser.parse_args() return args def main() -> None: args = parse_args() # read COLMAP model model = Model() model.read_model(args.input_model) print("num_cameras:", model.reconstruction.num_cameras()) print("num_images:", model.reconstruction.num_images()) print("num_points3D:", model.reconstruction.num_points3D()) # display using Open3D visualization tools model.create_window() model.add_points() model.add_cameras(scale=0.25) model.show() if __name__ == "__main__": main() colmap-4.2.0/python/generate_stubs.sh000077500000000000000000000022711524536416500176770ustar00rootroot00000000000000#!/bin/bash set -e PYTHON_EXEC=$1 OUTPUT=$2 PACKAGE_NAME="_core" echo "Building stubs with $PYTHON_EXEC to $OUTPUT" $PYTHON_EXEC -m pybind11_stubgen $PACKAGE_NAME -o $OUTPUT \ --numpy-array-use-type-var \ --enum-class-locations=.+:$PACKAGE_NAME \ --ignore-invalid-expressions "ceres::*" \ --print-invalid-expressions-as-is \ --print-safe-value-reprs "[a-zA-Z]+Options\(\)" FILES=$(find $OUTPUT/$PACKAGE_NAME/ -name '*.pyi' -type f) perl -i -pe's/\b_core\b/pycolmap/g' $FILES perl -i -pe's/: ceres::([a-zA-Z]|::)+//g' $FILES perl -i -pe's/ -> ceres::([a-zA-Z]|::)+:$/:/g' $FILES # pybind issue, will not be fixed: https://github.com/pybind/pybind11/pull/2277 perl -i -pe's/(?<=\b__(eq|ne)__\(self, )arg0: [a-zA-Z0-9_]+\)/other: object)/g' $FILES # mypy bug: https://github.com/python/mypy/issues/4266 perl -i -pe's/(__hash__:? .*= None)$/\1 # type: ignore/g' $FILES # pybind issue: dictionary keys should not be cast to the more generic types. perl -i -pe's/Mapping\[typing.Supports(Int|Float)/Mapping\[\L\1/g' $FILES COLMAP_DIR=$(dirname $( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )) ruff format --config ${COLMAP_DIR}/ruff.toml ${FILES} colmap-4.2.0/python/incremental_build.sh000077500000000000000000000016241524536416500203460ustar00rootroot00000000000000#!/bin/bash # Invoke from anywhere to perform an incremental build of pycolmap bindings. # Make sure to install the requirements from pyproject.toml. If colmap is not # installed globally but in a custom directory, you should set the colmap_DIR # environment variable, e.g.: # # colmap_DIR=/path/to/cmake/install/prefix pycolmap/incremental_build.sh set -e script_dir=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" &> /dev/null && pwd) pip install \ --no-build-isolation \ -Cbuild-dir="$script_dir/build" \ -ve \ "$script_dir/.." # Symlink the compiled _core extension into the source tree so that the # editable install (which adds python/ to sys.path) can find it. site_pkg=$(python -c "import sysconfig; print(sysconfig.get_path('purelib'))") core_so=$(ls -t "$site_pkg/pycolmap"/_core*.so 2>/dev/null | head -1) if [ -n "$core_so" ]; then ln -sf "$core_so" "$script_dir/pycolmap/" fi colmap-4.2.0/python/pycolmap/000077500000000000000000000000001524536416500161505ustar00rootroot00000000000000colmap-4.2.0/python/pycolmap/__init__.py000066400000000000000000000042151524536416500202630ustar00rootroot00000000000000import contextlib import ctypes import importlib import platform import textwrap from pathlib import Path from typing import TYPE_CHECKING from .utils import import_module_symbols def _preload_cuda_lib(module_name: str, lib_name: str): """Preload a single library.""" try: module = importlib.import_module(module_name) except ImportError: return else: # TODO: update the logic to handle CUDA 13, # using as reference https://github.com/pytorch/pytorch/pull/163661. # Resolve the library directory robustly if module_file_path := getattr(module, "__file__", None): lib_dir = Path(module_file_path).parent elif paths := getattr(module, "__path__", None): # Implicit namespace packages have __path__ but no __file__ lib_dir = Path(list(paths)[0]) else: return # Find the first file matching the pattern if lib_path := next((lib_dir / "lib").glob(lib_name), None): with contextlib.suppress(OSError): ctypes.CDLL(str(lib_path)) def _preload_cuda_deps(): """Preloads CUDA dependencies from pip packages on Linux.""" if platform.system() != "Linux": return cuda_libs = { "nvidia.cuda_runtime": "libcudart.so.*[0-9]", "nvidia.curand": "libcurand.so.*[0-9]", } for module_name, lib_glob in cuda_libs.items(): _preload_cuda_lib(module_name, lib_glob) _preload_cuda_deps() try: from . import _core except ImportError as e: raise RuntimeError( textwrap.dedent(""" Cannot import the C++ backend pycolmap._core. Make sure that you successfully install the package with $ python -m pip install pycolmap/ """) ) from e # Type checkers cannot deal with dynamic manipulation of globals. # Instead, we use the same workaround as PyTorch. if TYPE_CHECKING: from ._core import * # noqa F403 __all__ = import_module_symbols( globals(), _core, exclude={"cost_functions", "pyceres"} ) __all__.extend(["__version__", "__ceres_version__"]) __version__ = _core.__version__ __ceres_version__ = _core.__ceres_version__ colmap-4.2.0/python/pycolmap/cost_functions/000077500000000000000000000000001524536416500212105ustar00rootroot00000000000000colmap-4.2.0/python/pycolmap/cost_functions/__init__.py000066400000000000000000000003611524536416500233210ustar00rootroot00000000000000from typing import TYPE_CHECKING from .. import _core from ..utils import import_module_symbols if TYPE_CHECKING: from .._core.cost_functions import * # noqa __all__ = import_module_symbols(globals(), _core.cost_functions) del _core colmap-4.2.0/python/pycolmap/panorama.py000066400000000000000000000711271524536416500203300ustar00rootroot00000000000000"""Structure-from-Motion pipelines for 360-degree panorama images.""" import collections import enum import os import sys from collections.abc import Callable, Sequence from concurrent.futures import ThreadPoolExecutor, as_completed from dataclasses import dataclass from pathlib import Path from threading import Lock from typing import Literal, TypeVar, cast import numpy as np import numpy.typing as npt from . import _core as pycolmap logging = pycolmap.logging if sys.version_info >= (3, 11): StrEnum = enum.StrEnum else: # Backport of enum.StrEnum, added in Python 3.11. Members compare equal to # their string value and auto() yields the lower-cased member name. # TODO: remove once support for Python 3.10 is dropped after its EOL in # October 2026. class StrEnum(str, enum.Enum): @staticmethod def _generate_next_value_( name: str, start: int, count: int, last_values: list ) -> str: return name.lower() def __str__(self) -> str: return str(self.value) class Matcher(StrEnum): SEQUENTIAL = enum.auto() EXHAUSTIVE = enum.auto() VOCABTREE = enum.auto() SPATIAL = enum.auto() class Mapper(StrEnum): INCREMENTAL = enum.auto() GLOBAL = enum.auto() class PanoRenderType(StrEnum): PERSPECTIVE_OVERLAPPING = enum.auto() PERSPECTIVE_NON_OVERLAPPING = enum.auto() # Reconstruct directly on the panoramas with the native EQUIRECTANGULAR # camera model instead of rendering perspective images. SPHERICAL = enum.auto() N = TypeVar("N", bound=int) NDArrayNx2 = np.ndarray[tuple[N, Literal[2]], np.dtype[np.float64]] NDArray3x1 = np.ndarray[tuple[Literal[3], Literal[1]], np.dtype[np.float64]] NDArray3x3 = np.ndarray[tuple[Literal[3], Literal[3]], np.dtype[np.float64]] @dataclass(kw_only=True) class PanoRenderOptions: num_steps_yaw: int pitches_deg: Sequence[float] hfov_deg: float vfov_deg: float PANO_RENDER_OPTIONS: dict[PanoRenderType, PanoRenderOptions] = { PanoRenderType.PERSPECTIVE_OVERLAPPING: PanoRenderOptions( num_steps_yaw=4, pitches_deg=(-35.0, 0.0, 35.0), hfov_deg=90.0, vfov_deg=90.0, ), # Cubemap without top and bottom images. PanoRenderType.PERSPECTIVE_NON_OVERLAPPING: PanoRenderOptions( num_steps_yaw=4, pitches_deg=(0.0,), hfov_deg=90.0, vfov_deg=90.0, ), } @dataclass(kw_only=True) class PanoramaReconstructionOptions: matcher: Matcher = Matcher.SEQUENTIAL mapper: Mapper = Mapper.INCREMENTAL render_type: PanoRenderType = PanoRenderType.PERSPECTIVE_OVERLAPPING random_seed: int = 0 num_threads: int = -1 gpu_index: str = "-1" use_gpu: bool = True covisibility_path: Path | None = None covisibility_min_shared_points: int = 1 show_progress: bool = True def create_virtual_camera( *, pano_width: int, pano_height: int, hfov_deg: float, vfov_deg: float, ) -> pycolmap.Camera: """Create a virtual perspective camera.""" image_width = int(pano_width * hfov_deg / 360) image_height = int(pano_height * vfov_deg / 180) focal = image_width / (2 * np.tan(np.deg2rad(hfov_deg) / 2)) camera = pycolmap.Camera.create_from_model_id( camera_id=0, model=pycolmap.CameraModelId.SIMPLE_PINHOLE, focal_length=focal, width=image_width, height=image_height, ) # Not set by create_from_model_id. camera.has_prior_focal_length = True return camera def get_virtual_camera_rays( camera: pycolmap.Camera, ) -> npt.NDArray[np.floating]: size = (camera.width, camera.height) x, y = np.indices(size).astype(np.float32) xy: NDArrayNx2 = np.column_stack([x.ravel(), y.ravel()]) # The center of the upper left most pixel has coordinate (0.5, 0.5) xy += 0.5 xy_norm: NDArrayNx2 = camera.cam_from_img(image_points=xy) rays = np.concatenate([xy_norm, np.ones_like(xy_norm[:, :1])], -1) rays /= np.linalg.norm(rays, axis=-1, keepdims=True) return rays def spherical_img_from_cam( image_size: tuple[int, int], rays_in_cam: npt.NDArray[np.floating] ) -> npt.NDArray[np.floating]: """Project rays into a 360 panorama (spherical) image.""" if image_size[0] != image_size[1] * 2: raise ValueError("Only 360° panoramas are supported.") if rays_in_cam.ndim != 2 or rays_in_cam.shape[1] != 3: raise ValueError(f"{rays_in_cam.shape=} but expected (N,3).") r = rays_in_cam.T yaw = np.arctan2(r[0], r[2]) pitch = -np.arctan2(r[1], np.linalg.norm(r[[0, 2]], axis=0)) u = (1 + yaw / np.pi) / 2 v = (1 - pitch * 2 / np.pi) / 2 return np.stack([u, v], -1) * image_size def get_virtual_rotations( num_steps_yaw: int, pitches_deg: Sequence[float] ) -> Sequence[npt.NDArray[np.floating]]: """Get the relative rotations of the virtual cameras w.r.t. the panorama.""" # Assuming that the panos are approximately upright. cams_from_pano_r = [] yaws = np.linspace(0, 360, num_steps_yaw, endpoint=False) for pitch_deg in pitches_deg: yaw_offset = (360 / num_steps_yaw / 2) if pitch_deg > 0 else 0 for yaw_deg in yaws + yaw_offset: pitch, yaw = np.deg2rad([-pitch_deg, -yaw_deg]) cos_pitch, sin_pitch = np.cos(pitch), np.sin(pitch) cos_yaw, sin_yaw = np.cos(yaw), np.sin(yaw) rotation_x = np.array( [ [1.0, 0.0, 0.0], [0.0, cos_pitch, -sin_pitch], [0.0, sin_pitch, cos_pitch], ] ) rotation_y = np.array( [ [cos_yaw, 0.0, sin_yaw], [0.0, 1.0, 0.0], [-sin_yaw, 0.0, cos_yaw], ] ) cam_from_pano_r = rotation_x @ rotation_y cams_from_pano_r.append(cam_from_pano_r) return cams_from_pano_r def create_pano_rig_config( cams_from_pano_rotation: Sequence[npt.NDArray[np.floating]], ref_idx: int = 0, ) -> pycolmap.RigConfig: """Create a RigConfig for the given virtual rotations.""" rig_cameras = [] zero_translation = cast(NDArray3x1, np.zeros((3, 1), dtype=np.float64)) for idx, cam_from_pano_rotation in enumerate(cams_from_pano_rotation): if idx == ref_idx: cam_from_rig = None else: cam_from_ref_rotation = ( cam_from_pano_rotation @ cams_from_pano_rotation[ref_idx].T ) cam_from_rig = pycolmap.Rigid3d( pycolmap.Rotation3d(cam_from_ref_rotation), zero_translation, ) rig_cameras.append( pycolmap.RigConfigCamera( ref_sensor=idx == ref_idx, image_prefix=f"pano_camera{idx}/", cam_from_rig=cam_from_rig, ) ) return pycolmap.RigConfig(cameras=rig_cameras) class PanoProcessor: def __init__( self, pano_image_dir: Path, output_image_dir: Path, mask_dir: Path, render_options: PanoRenderOptions, ): self.render_options = render_options self.pano_image_dir = pano_image_dir self.output_image_dir = output_image_dir self.mask_dir = mask_dir self.cams_from_pano_rotation = get_virtual_rotations( num_steps_yaw=render_options.num_steps_yaw, pitches_deg=render_options.pitches_deg, ) self.rig_config = create_pano_rig_config(self.cams_from_pano_rotation) # We assign each pano pixel to the virtual camera # with the closest camera center. self.cam_centers_in_pano = np.einsum( "nij,i->nj", self.cams_from_pano_rotation, [0, 0, 1] ) self._lock = Lock() # These are initialized on the first pano image # to avoid recomputing the rays for each pano image. self._camera: pycolmap.Camera | None = None self._pano_size: tuple[int, int] | None = None self._rays_in_cam: npt.NDArray[np.floating] | None = None def process(self, pano_name: str) -> None: import cv2 import PIL.ExifTags import PIL.Image pano_path = self.pano_image_dir / pano_name try: pano_pil_image = PIL.Image.open(pano_path) except PIL.Image.UnidentifiedImageError: logging.info(f"Skipping file {pano_path} as it cannot be read.") return pano_exif = pano_pil_image.getexif() gpsonly_exif = PIL.Image.Exif() gpsonly_exif[PIL.ExifTags.IFD.GPSInfo] = pano_exif.get_ifd( PIL.ExifTags.IFD.GPSInfo ) pano_image = np.asarray(pano_pil_image) pano_height, pano_width, *_ = pano_image.shape if pano_width != pano_height * 2: raise ValueError("Only 360° panoramas are supported.") with self._lock: if self._camera is None: # First image, precompute rays once. self._camera = create_virtual_camera( pano_width=pano_width, pano_height=pano_height, hfov_deg=self.render_options.hfov_deg, vfov_deg=self.render_options.vfov_deg, ) for rig_camera in self.rig_config.cameras: rig_camera.camera = self._camera self._pano_size = (pano_width, pano_height) self._rays_in_cam = get_virtual_camera_rays(self._camera) else: # Later images, verify consistent panoramas. if (pano_width, pano_height) != self._pano_size: raise ValueError( "Panoramas of different sizes are not supported." ) for cam_idx, cam_from_pano_r in enumerate(self.cams_from_pano_rotation): assert self._rays_in_cam is not None rays_in_pano = self._rays_in_cam @ cam_from_pano_r xy_in_pano = spherical_img_from_cam(self._pano_size, rays_in_pano) xy_in_pano = xy_in_pano.reshape( self._camera.width, self._camera.height, 2 ).astype(np.float32) xy_in_pano -= 0.5 # COLMAP to OpenCV pixel origin. x_coords, y_coords = np.moveaxis(xy_in_pano, [0, 1, 2], [2, 1, 0]) image = cv2.remap( pano_image, x_coords, y_coords, cv2.INTER_LINEAR, borderMode=cv2.BORDER_WRAP, ) # We define a mask such that each pixel of the panorama has its # features extracted only in a single virtual camera. closest_camera = np.argmax( rays_in_pano @ self.cam_centers_in_pano.T, -1 ) mask = ( ((closest_camera == cam_idx) * 255) .astype(np.uint8) .reshape(self._camera.width, self._camera.height) .transpose() ) image_name = ( self.rig_config.cameras[cam_idx].image_prefix + pano_name ) mask_name = f"{image_name}.png" image_path = self.output_image_dir / image_name image_path.parent.mkdir(exist_ok=True, parents=True) PIL.Image.fromarray(image).save(image_path, exif=gpsonly_exif) mask_path = self.mask_dir / mask_name mask_path.parent.mkdir(exist_ok=True, parents=True) if not pycolmap.Bitmap.from_array(mask).write(mask_path): raise RuntimeError(f"Cannot write {mask_path}") def split_image_name(self, image_name: str) -> tuple[int, str]: """Split a rendered image name into (virtual camera idx, pano name).""" for cam_idx, rig_camera in enumerate(self.rig_config.cameras): prefix = rig_camera.image_prefix if image_name.startswith(prefix): return cam_idx, image_name[len(prefix) :] raise ValueError(f"Unknown virtual camera for image {image_name!r}.") def convert_to_equirectangular( self, reconstruction: pycolmap.Reconstruction ) -> pycolmap.Reconstruction: """Convert a reconstruction built from the rig of perspective virtual cameras back to one equirectangular camera/image per input panorama. The output reconstruction references the original panorama images with the native EQUIRECTANGULAR camera model. Frame poses, 3D points, and all keypoints (including those without a 3D observation) are carried over by re-projecting the perspective keypoints onto the panorama through the same spherical mapping used for rendering, so the result is a valid, bundle-adjustable reconstruction. """ if self._camera is None or self._pano_size is None: raise RuntimeError("No panorama was rendered yet.") pano_width, pano_height = self._pano_size equirect = pycolmap.Reconstruction() equirect_camera = pycolmap.Camera.create_from_model_id( camera_id=1, model=pycolmap.CameraModelId.EQUIRECTANGULAR, focal_length=0.0, width=pano_width, height=pano_height, ) equirect.add_camera_with_trivial_rig(equirect_camera) # The rig reference sensor is virtual camera 0 (see # create_pano_rig_config), so rig_from_world == cam0_from_world and # pano_from_world = pano_from_cam0 @ cam0_from_world. The virtual # cameras share the panorama center, hence the zero translation. pano_from_ref = pycolmap.Rigid3d( pycolmap.Rotation3d( cast(NDArray3x3, self.cams_from_pano_rotation[0]) ), cast(NDArray3x1, np.zeros((3, 1), dtype=np.float64)), ).inverse() # Group the registered virtual cameras by frame. All virtual cameras of # a frame observe the same panorama and share its pose, so we can # accumulate their keypoints into a single equirectangular image. images_by_frame: dict[int, list[pycolmap.Image]] = ( collections.defaultdict(list) ) for image in reconstruction.images.values(): if image.has_pose: images_by_frame[image.frame_id].append(image) # Maps an image_id of a virtual camera to a dict from its old point2D # index to the (new point2D index, pano_name) in the equirectangular # image, so we can later rebuild the 3D point tracks. old_to_new_point2D: dict[int, dict[int, tuple[int, str]]] = {} pano_to_image_id: dict[str, int] = {} frame_images = sorted( images_by_frame.values(), key=lambda images: self.split_image_name(images[0].name)[1], ) for image_id, images in enumerate(frame_images, start=1): pano_name = self.split_image_name(images[0].name)[1] pano_to_image_id[pano_name] = image_id frame = images[0].frame assert frame is not None rig_from_world = frame.rig_from_world assert rig_from_world is not None pano_from_world = pano_from_ref * rig_from_world # Concatenate the keypoints of all virtual cameras of this panorama. keypoints: list[npt.NDArray[np.floating]] = [] for image in images: cam_idx = self.split_image_name(image.name)[0] num_points2D = len(image.points2D) if num_points2D == 0: old_to_new_point2D[image.image_id] = {} continue # The cast is needed because numpy<2.3, the latest version # supporting Python 3.10, does not infer the array shape. # TODO: remove once support for Python 3.10 is dropped after # its EOL in October 2026. xy = cast( NDArrayNx2, np.array([point2D.xy for point2D in image.points2D]), ) rays_in_cam: npt.NDArray[np.floating] = np.asarray( self._camera.cam_ray_from_img(image_points=xy) ) rays_in_cam /= np.linalg.norm( rays_in_cam, axis=-1, keepdims=True ) rays_in_pano = ( rays_in_cam @ self.cams_from_pano_rotation[cam_idx] ) xy_in_pano = spherical_img_from_cam( self._pano_size, rays_in_pano ) base_idx = len(keypoints) keypoints.extend(xy_in_pano) old_to_new_point2D[image.image_id] = { point2D_idx: (base_idx + point2D_idx, pano_name) for point2D_idx in range(num_points2D) } equirect.add_image_with_trivial_frame( pycolmap.Image( name=pano_name, keypoints=keypoints, camera_id=equirect_camera.camera_id, image_id=image_id, ), pano_from_world, ) for point3D_id, point3D in reconstruction.points3D.items(): track = pycolmap.Track() for element in point3D.track.elements: new_point2D_idx, pano_name = old_to_new_point2D[ element.image_id ][element.point2D_idx] track.add_element(pano_to_image_id[pano_name], new_point2D_idx) equirect.add_point3D_with_id( point3D_id, pycolmap.Point3D( xyz=point3D.xyz, color=point3D.color, track=track ), ) return equirect def render_perspective_images( pano_image_names: Sequence[str], pano_image_dir: Path, output_image_dir: Path, mask_dir: Path, render_options: PanoRenderOptions, show_progress: bool, ) -> PanoProcessor: processor = PanoProcessor( pano_image_dir, output_image_dir, mask_dir, render_options ) num_panos = len(pano_image_names) max_workers = min(32, (os.cpu_count() or 2) - 1) pbar = None if show_progress: from tqdm import tqdm pbar = tqdm(total=num_panos) try: with ThreadPoolExecutor(max_workers=max_workers) as thread_pool: futures = [ thread_pool.submit(processor.process, pano_name) for pano_name in pano_image_names ] for future in as_completed(futures): future.result() if pbar is not None: pbar.update(1) finally: if pbar is not None: pbar.close() return processor def run_matcher( options: PanoramaReconstructionOptions, database_path: Path, matching_options: pycolmap.FeatureMatchingOptions, ) -> None: matching_options.use_gpu = options.use_gpu matching_options.gpu_index = options.gpu_index matching_options.num_threads = options.num_threads if options.matcher == Matcher.SEQUENTIAL: pycolmap.match_sequential( database_path, pairing_options=pycolmap.SequentialPairingOptions(), matching_options=matching_options, ) elif options.matcher == Matcher.EXHAUSTIVE: pycolmap.match_exhaustive( database_path, matching_options=matching_options ) elif options.matcher == Matcher.VOCABTREE: pycolmap.match_vocabtree( database_path, matching_options=matching_options ) elif options.matcher == Matcher.SPATIAL: pycolmap.match_spatial(database_path, matching_options=matching_options) else: raise ValueError(f"Unknown matcher: {options.matcher}") def filter_database_by_covisibility( database_path: Path, covisibility_path: Path, min_shared_points: int, split_image_name: Callable[[str], tuple[int, str]] | None = None, ) -> None: with np.load(covisibility_path) as covisibility: image_names = covisibility["image_names"].tolist() overlap_counts = covisibility["directed_overlap_counts"] image_name_to_idx = {name: idx for idx, name in enumerate(image_names)} def pano_name(image_name: str) -> str: if split_image_name is None: return image_name return split_image_name(image_name)[1] with pycolmap.Database.open(database_path) as database: database_image_names = { image.image_id: pano_name(image.name) for image in database.read_all_images() } pair_ids, _ = database.read_two_view_geometry_num_inliers() num_filtered = 0 for pair_id in pair_ids: image_id1, image_id2 = pycolmap.pair_id_to_image_pair(pair_id) idx1 = image_name_to_idx.get(database_image_names[image_id1]) idx2 = image_name_to_idx.get(database_image_names[image_id2]) if idx1 is None or idx2 is None: continue shared_points = max( overlap_counts[idx1, idx2], overlap_counts[idx2, idx1] ) if shared_points < min_shared_points: database.delete_two_view_geometry(image_id1, image_id2) num_filtered += 1 logging.info( f"Depth covisibility filtering removed {num_filtered}/{len(pair_ids)} " "verified image pairs" ) def run_spherical( input_image_path: Path, options: PanoramaReconstructionOptions, database_path: Path, rec_path: Path, ) -> dict[int, pycolmap.Reconstruction]: """Reconstruct directly on the equirectangular panoramas with the native EQUIRECTANGULAR camera model, without rendering perspective images.""" logging.info("Reconstructing with spherical camera") reader_options = pycolmap.ImageReaderOptions(camera_model="EQUIRECTANGULAR") extraction_options = pycolmap.FeatureExtractionOptions( use_gpu=options.use_gpu, gpu_index=options.gpu_index, num_threads=options.num_threads, ) pycolmap.extract_features( database_path, input_image_path, reader_options=reader_options, camera_mode=pycolmap.CameraMode.SINGLE, extraction_options=extraction_options, ) # A single EQUIRECTANGULAR camera observes the whole sphere from one # center, so there is no rig and no per-frame image-pair skipping. run_matcher(options, database_path, pycolmap.FeatureMatchingOptions()) if options.covisibility_path is not None: filter_database_by_covisibility( database_path, options.covisibility_path, options.covisibility_min_shared_points, ) # The EQUIRECTANGULAR model has no focal length, principal point, or # distortion to refine; its (w, h) params are held constant in bundle # adjustment. if options.mapper == Mapper.INCREMENTAL: incremental_options = pycolmap.IncrementalPipelineOptions( num_threads=options.num_threads, random_seed=options.random_seed ) recs = pycolmap.incremental_mapping( database_path, input_image_path, rec_path, incremental_options, ) elif options.mapper == Mapper.GLOBAL: global_options = pycolmap.GlobalPipelineOptions( num_threads=options.num_threads, random_seed=options.random_seed ) recs = pycolmap.global_mapping( database_path, input_image_path, rec_path, global_options ) else: raise ValueError(f"Unknown mapper: {options.mapper}") for idx, rec in recs.items(): logging.info(f"#{idx} {rec.summary()}") return recs def run_perspective( input_image_path: Path, output_path: Path, options: PanoramaReconstructionOptions, database_path: Path, rec_path: Path, ) -> dict[int, pycolmap.Reconstruction]: """Render the panoramas into a rig of perspective virtual cameras and reconstruct from those.""" logging.info("Reconstructing with rig of perspective virtual cameras") image_dir = output_path / "images" mask_dir = output_path / "masks" image_dir.mkdir(exist_ok=True, parents=True) mask_dir.mkdir(exist_ok=True, parents=True) # Search for input images. pano_image_dir = input_image_path pano_image_names = sorted( p.relative_to(pano_image_dir).as_posix() for p in pano_image_dir.rglob("*") if not p.is_dir() ) logging.info(f"Found {len(pano_image_names)} images in {pano_image_dir}.") processor = render_perspective_images( pano_image_names, pano_image_dir, image_dir, mask_dir, PANO_RENDER_OPTIONS[options.render_type], options.show_progress, ) rig_config = processor.rig_config rendered_camera = rig_config.cameras[0].camera assert rendered_camera is not None # Make mypy happy. extraction_options = pycolmap.FeatureExtractionOptions( use_gpu=options.use_gpu, gpu_index=options.gpu_index, num_threads=options.num_threads, ) pycolmap.extract_features( database_path, image_dir, reader_options=pycolmap.ImageReaderOptions( mask_path=mask_dir, camera_model=rendered_camera.model_name, camera_params=rendered_camera.params_to_string(), ), camera_mode=pycolmap.CameraMode.PER_FOLDER, extraction_options=extraction_options, ) with pycolmap.Database.open(database_path) as db: pycolmap.apply_rig_config([rig_config], db) matching_options = pycolmap.FeatureMatchingOptions() # We have perfect sensor_from_rig poses (except for potential stitching # artifacts by the spherical image provider), so we can perform geometric # verification using rig constraints. matching_options.rig_verification = True # The images within a frame do not have overlap due to the provided masks. matching_options.skip_image_pairs_in_same_frame = True run_matcher(options, database_path, matching_options) if options.covisibility_path is not None: filter_database_by_covisibility( database_path, options.covisibility_path, options.covisibility_min_shared_points, processor.split_image_name, ) if options.mapper == Mapper.INCREMENTAL: opts = pycolmap.IncrementalPipelineOptions( num_threads=options.num_threads, random_seed=options.random_seed, ba_refine_sensor_from_rig=False, ba_refine_focal_length=False, ba_refine_principal_point=False, ba_refine_extra_params=False, ) recs = pycolmap.incremental_mapping( database_path, image_dir, rec_path, opts ) elif options.mapper == Mapper.GLOBAL: global_opts = pycolmap.GlobalPipelineOptions( num_threads=options.num_threads, random_seed=options.random_seed, mapper=pycolmap.GlobalMapperOptions( num_threads=options.num_threads, random_seed=options.random_seed, refine_sensor_from_rig=False, ), ) # Don't set these in the init to not overwrite custom default options. global_opts.mapper.bundle_adjustment.refine_focal_length = False global_opts.mapper.bundle_adjustment.refine_principal_point = False global_opts.mapper.bundle_adjustment.refine_extra_params = False recs = pycolmap.global_mapping( database_path, image_dir, rec_path, global_opts ) else: raise ValueError(f"Unknown mapper: {options.mapper}") for idx, rec in recs.items(): logging.info(f"#{idx} {rec.summary()}") logging.info("Converting virtual cameras back to equirectangular") equirect_rec_path = output_path / "sparse_equirectangular" for idx, rec in recs.items(): equirect_rec = processor.convert_to_equirectangular(rec) model_path = equirect_rec_path / str(idx) model_path.mkdir(exist_ok=True, parents=True) equirect_rec.write(model_path) logging.info(f"#{idx} {equirect_rec.summary()}") return recs def reconstruct( input_image_path: Path, output_path: Path, options: PanoramaReconstructionOptions | None = None, ) -> dict[int, pycolmap.Reconstruction]: """Reconstruct 360-degree panoramas with spherical or virtual cameras.""" options = options or PanoramaReconstructionOptions() pycolmap.set_random_seed(options.random_seed) database_path = output_path / "database.db" if database_path.exists(): database_path.unlink() rec_path = output_path / "sparse" rec_path.mkdir(exist_ok=True, parents=True) if options.render_type == PanoRenderType.SPHERICAL: return run_spherical(input_image_path, options, database_path, rec_path) return run_perspective( input_image_path, output_path, options, database_path, rec_path, ) colmap-4.2.0/python/pycolmap/panorama_test.py000066400000000000000000000042111524536416500213550ustar00rootroot00000000000000import numpy as np import pycolmap from .panorama import ( filter_database_by_covisibility, get_virtual_rotations, ) def test_get_virtual_rotations(): rotations = get_virtual_rotations(4, [-35.0, 0.0, 35.0]) assert len(rotations) == 12 np.testing.assert_allclose( rotations[4], np.eye(3), atol=1e-15, ) np.testing.assert_allclose( rotations[1], [ [0.0, 0.0, -1.0], [-0.573576436351046, 0.8191520442889917, 0.0], [0.8191520442889917, 0.573576436351046, 0.0], ], atol=1e-15, ) for rotation in rotations: np.testing.assert_allclose(rotation @ rotation.T, np.eye(3), atol=1e-15) np.testing.assert_allclose(np.linalg.det(rotation), 1.0, atol=1e-15) def test_filter_database_by_covisibility(tmp_path): database_path = tmp_path / "database.db" with pycolmap.Database.open(database_path) as database: camera = pycolmap.Camera.create_from_model_name( 1, "SIMPLE_PINHOLE", 100.0, 100, 100 ) database.write_camera(camera) for image_id, name in enumerate(["a.png", "b.png", "c.png"], start=1): database.write_image( pycolmap.Image( image_id=image_id, camera_id=camera.camera_id, name=name ), use_image_id=True, ) geometry = pycolmap.TwoViewGeometry() geometry.inlier_matches = np.array([[0, 0]], dtype=np.uint32) database.write_two_view_geometry(1, 2, geometry) database.write_two_view_geometry(1, 3, geometry) covisibility_path = tmp_path / "covisibility.npz" np.savez( covisibility_path, image_names=np.array(["a.png", "b.png", "c.png"]), directed_overlap_counts=np.array( [[1, 10, 0], [10, 1, 0], [0, 0, 1]], dtype=np.uint32 ), ) filter_database_by_covisibility( database_path, covisibility_path, min_shared_points=1 ) with pycolmap.Database.open(database_path) as database: assert database.exists_two_view_geometry(1, 2) assert not database.exists_two_view_geometry(1, 3) colmap-4.2.0/python/pycolmap/py.typed000066400000000000000000000000001524536416500176350ustar00rootroot00000000000000colmap-4.2.0/python/pycolmap/pyceres/000077500000000000000000000000001524536416500176225ustar00rootroot00000000000000colmap-4.2.0/python/pycolmap/pyceres/__init__.py000066400000000000000000000003431524536416500217330ustar00rootroot00000000000000from typing import TYPE_CHECKING from .. import _core from ..utils import import_module_symbols if TYPE_CHECKING: from .._core.pyceres import * # noqa __all__ = import_module_symbols(globals(), _core.pyceres) del _core colmap-4.2.0/python/pycolmap/utils.py000066400000000000000000000007701524536416500176660ustar00rootroot00000000000000from collections.abc import MutableSequence from types import ModuleType from typing import Any def import_module_symbols( dst_vars: dict[str, Any], src_module: ModuleType, exclude: set[str] | None = None, ) -> MutableSequence[str]: symbols = {} for n, s in vars(src_module).items(): if n.startswith("_"): continue if exclude is not None and n in exclude: continue symbols[n] = s dst_vars.update(symbols) return list(symbols) colmap-4.2.0/python/util/000077500000000000000000000000001524536416500153015ustar00rootroot00000000000000colmap-4.2.0/python/util/flickr_downloader.py000077500000000000000000000152151524536416500213520ustar00rootroot00000000000000# Copyright (c), ETH Zurich and UNC Chapel Hill. # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are met: # # * Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer. # # * Redistributions in binary form must reproduce the above copyright # notice, this list of conditions and the following disclaimer in the # documentation and/or other materials provided with the distribution. # # * Neither the name of ETH Zurich and UNC Chapel Hill nor the names of # its contributors may be used to endorse or promote products derived # from this software without specific prior written permission. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. import argparse import datetime import multiprocessing import os import time import urllib.error import urllib.request import xml.etree.ElementTree as ElementTree from typing import Final import urllib2 import urlparse PER_PAGE: Final[int] = 500 SORT: Final[str] = "date-posted-desc" URL: Final[str] = ( "https://api.flickr.com/services/rest/?method=flickr.photos.search&" "api_key=%s&text=%s&sort=%s&per_page=%d&page=%d&min_upload_date=%s&" "max_upload_date=%s&format=rest&extras=url_o,url_l,url_c,url_z,url_n" ) MAX_PAGE_REQUESTS: Final[int] = 5 MAX_PAGE_TIMEOUT: Final[int] = 20 MAX_IMAGE_REQUESTS: Final[int] = 3 TIME_SKIP: Final[int] = 24 * 60 * 60 MAX_DATE: Final[float] = time.time() MIN_DATE: Final[float] = MAX_DATE - TIME_SKIP def parse_args() -> argparse.Namespace: parser = argparse.ArgumentParser() parser.add_argument("--search_text", required=True) parser.add_argument("--api_key", required=True) parser.add_argument("--image_path", required=True) parser.add_argument("--num_procs", type=int, default=10) parser.add_argument("--max_days_without_image", type=int, default=365) args = parser.parse_args() return args def compose_url( page: int, api_key: str, text: str, min_date: float, max_date: float ) -> str: return URL % ( api_key, text, SORT, PER_PAGE, page, str(min_date), str(max_date), ) def parse_page( page: int, api_key: str, text: str, min_date: float, max_date: float ) -> tuple[dict[str, str], tuple[dict[str, str], ...]]: f: urllib2.urlopen | None = None for _ in range(MAX_PAGE_REQUESTS): try: f = urllib2.urlopen( compose_url(page, api_key, text, min_date, max_date), timeout=MAX_PAGE_TIMEOUT, ) except TimeoutError: continue else: break if f is None: return { "pages": "0", "total": "0", "page": "0", "perpage": "0", }, tuple() response = f.read() root = ElementTree.fromstring(response) if root.attrib["stat"] != "ok": raise OSError metadata = root.find("photos") assert metadata is not None photos = tuple(photo.attrib for photo in root.iter("photo")) return metadata.attrib, photos class PhotoDownloader: def __init__(self, image_path: str) -> None: self.image_path: str = image_path def __call__(self, photo: dict[str, str]) -> None: # Find the URL corresponding to the highest image resolution. We will # need this URL here to determine the image extension (typically .jpg, # but could be .png, .gif, etc). url: str | None = None for url_suffix in ("o", "l", "k", "h", "b", "c", "z"): url_attr = f"url_{url_suffix}" if photo.get(url_attr) is not None: url = photo.get(url_attr) break if url is not None: # Note that the following statement may fail in Python 3. urlparse # may need to be replaced with urllib.parse. url_filename = urlparse.urlparse(url).path image_ext = os.path.splitext(url_filename)[1] image_name = f"{photo['id']}_{photo['secret']}{image_ext}" path = os.path.join(self.image_path, image_name) if not os.path.exists(path): print(url) for _ in range(MAX_IMAGE_REQUESTS): try: urllib.request.urlretrieve(url, path) except urllib.error.ContentTooShortError: continue else: break def main() -> None: args = parse_args() downloader = PhotoDownloader(args.image_path) pool = multiprocessing.Pool(processes=args.num_procs) num_pages = float("inf") page = 0 min_date = MIN_DATE max_date = MAX_DATE days_in_row = 0 search_text = args.search_text.replace(" ", "-") while num_pages > page: page += 1 metadata, photos = parse_page( page, args.api_key, search_text, min_date, max_date ) num_pages = int(metadata["pages"]) print(78 * "=") print("Page:\t\t", page, "of", num_pages) print("Min-Date:\t", datetime.datetime.fromtimestamp(min_date)) print("Max-Date:\t", datetime.datetime.fromtimestamp(max_date)) print("Num-Photos:\t", len(photos)) print(78 * "=") try: pool.map_async(downloader, photos).get(1e10) except KeyboardInterrupt: pool.close() pool.join() break if page >= num_pages: max_date -= TIME_SKIP min_date -= TIME_SKIP page = 0 if num_pages == 0: days_in_row = days_in_row + 1 num_pages = float("inf") print(" No images in", days_in_row, "days in a row") if days_in_row == args.max_days_without_image: break else: days_in_row = 0 if __name__ == "__main__": main() colmap-4.2.0/ruff.toml000066400000000000000000000004441524536416500146440ustar00rootroot00000000000000line-length = 80 [lint] select = [ # pycodestyle "E", # Pyflakes "F", # pyupgrade "UP", # flake8-bugbear "B", # flake8-simplify "SIM", # isort "I", ] ignore = ["SIM117"] [lint.per-file-ignores] "scripts/python/*.py" = ["E", "SIM", "UP", "B"] colmap-4.2.0/scripts/000077500000000000000000000000001524536416500144725ustar00rootroot00000000000000colmap-4.2.0/scripts/format/000077500000000000000000000000001524536416500157625ustar00rootroot00000000000000colmap-4.2.0/scripts/format/c++.sh000077500000000000000000000033711524536416500166750ustar00rootroot00000000000000#!/usr/bin/env bash # This script applies clang-format to C++ files in the repository. # By default, on non-main branches it only formats files changed relative to # the main branch. On the main branch or with --all, it formats all files. # Check version version_string=$(clang-format --version | sed -E 's/^.*(\d+\.\d+\.\d+-.*).*$/\1/') expected_version_string='22.1.5' if [[ "$version_string" =~ "$expected_version_string" ]]; then echo "clang-format version '$version_string' matches '$expected_version_string'" else echo "clang-format version '$version_string' doesn't match '$expected_version_string'" exit 1 fi root_folder=$(git rev-parse --show-toplevel) extensions_regex="\(\.cc\|\.h\|\.hpp\|\.cpp\|\.cu\)" path_regex="\(^src/\(colmap\|glomap\|pycolmap\).*$extensions_regex$\)\|\(benchmark/.*$extensions_regex$\)" format_all=false if [[ "$1" == "--all" ]]; then format_all=true fi current_branch=$(git rev-parse --abbrev-ref HEAD) staged_files=$( \ git diff --cached --name-only --diff-filter=d \ | grep "$path_regex" || true) if [[ "$format_all" == true ]] || [[ "$current_branch" == "main" ]]; then committed_files=$( \ git ls-tree --full-tree -r --name-only HEAD . \ | grep "$path_regex" || true) else merge_base=$(git merge-base main HEAD) committed_files=$( \ git diff --name-only --diff-filter=d "$merge_base" \ | grep "$path_regex" || true) fi all_files=$( \ printf '%s\n' "$committed_files" "$staged_files" \ | grep -v '^$' | sort -u \ | sed "s~^~$root_folder/~") if [[ -z "$all_files" ]]; then echo "No C++ files to format" exit 0 fi num_files=$(echo "$all_files" | wc -l) echo "Formatting ${num_files} files" echo "$all_files" | tr '\n' '\0' | xargs -0 clang-format -i colmap-4.2.0/scripts/format/python.sh000077500000000000000000000033301524536416500176410ustar00rootroot00000000000000#!/usr/bin/env bash # This script runs the ruff Python formatter on the repository. # By default, on non-main branches it only formats files changed relative to # the main branch. On the main branch or with --all, it formats all files. # Check version version_string=$(ruff --version | sed -E 's/^.*(\d+\.\d+-.*).*$/\1/') expected_version_string='0.15.20' if [[ "$version_string" =~ "$expected_version_string" ]]; then echo "ruff version '$version_string' matches '$expected_version_string'" else echo "ruff version '$version_string' doesn't match '$expected_version_string'" exit 1 fi root_folder=$(git rev-parse --show-toplevel) path_regex="^.*\(\.py\)$" format_all=false if [[ "$1" == "--all" ]]; then format_all=true fi current_branch=$(git rev-parse --abbrev-ref HEAD) staged_files=$( \ git diff --cached --name-only --diff-filter=d \ | grep "$path_regex" || true) if [[ "$format_all" == true ]] || [[ "$current_branch" == "main" ]]; then committed_files=$( \ git ls-tree --full-tree -r --name-only HEAD . \ | grep "$path_regex" || true) else merge_base=$(git merge-base main HEAD) committed_files=$( \ git diff --name-only --diff-filter=d "$merge_base" \ | grep "$path_regex" || true) fi all_files=$( \ printf '%s\n' "$committed_files" "$staged_files" \ | grep -v '^$' | sort -u \ | sed "s~^~$root_folder/~") if [[ -z "$all_files" ]]; then echo "No Python files to format" exit 0 fi num_files=$(echo "$all_files" | wc -l) echo "Formatting ${num_files} files" echo "$all_files" | tr '\n' '\0' | xargs -0 ruff format --config "${root_folder}/ruff.toml" echo "$all_files" | tr '\n' '\0' | xargs -0 ruff check --config "${root_folder}/ruff.toml" --fix colmap-4.2.0/scripts/matlab/000077500000000000000000000000001524536416500157325ustar00rootroot00000000000000colmap-4.2.0/scripts/matlab/cmap2rgb.m000066400000000000000000000036321524536416500176110ustar00rootroot00000000000000% Copyright (c), ETH Zurich and UNC Chapel Hill. % All rights reserved. % % Redistribution and use in source and binary forms, with or without % modification, are permitted provided that the following conditions are met: % % * Redistributions of source code must retain the above copyright % notice, this list of conditions and the following disclaimer. % % * Redistributions in binary form must reproduce the above copyright % notice, this list of conditions and the following disclaimer in the % documentation and/or other materials provided with the distribution. % % * Neither the name of ETH Zurich and UNC Chapel Hill nor the names of % its contributors may be used to endorse or promote products derived % from this software without specific prior written permission. % % THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" % AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE % IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE % ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE % LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR % CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF % SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS % INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN % CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) % ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE % POSSIBILITY OF SUCH DAMAGE. function rgb = cmap2rgb(image, cmap, varargin) if length(varargin) == 1 clim = varargin{1}; image(image <= clim(1)) = clim(1); image(image > clim(2)) = clim(2); end image_min = min(image(:)); image_max = max(image(:)); image = (image - image_min) / (image_max - image_min) * size(cmap, 1); rgb = ind2rgb(uint32(image), cmap); end colmap-4.2.0/scripts/matlab/plot_model.m000077500000000000000000000045711524536416500202600ustar00rootroot00000000000000% Copyright (c), ETH Zurich and UNC Chapel Hill. % All rights reserved. % % Redistribution and use in source and binary forms, with or without % modification, are permitted provided that the following conditions are met: % % * Redistributions of source code must retain the above copyright % notice, this list of conditions and the following disclaimer. % % * Redistributions in binary form must reproduce the above copyright % notice, this list of conditions and the following disclaimer in the % documentation and/or other materials provided with the distribution. % % * Neither the name of ETH Zurich and UNC Chapel Hill nor the names of % its contributors may be used to endorse or promote products derived % from this software without specific prior written permission. % % THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" % AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE % IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE % ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE % LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR % CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF % SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS % INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN % CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) % ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE % POSSIBILITY OF SUCH DAMAGE. function plot_model(cameras, images, points) % Visualize COLMAP model. keys = images.keys; camera_centers = zeros(images.length, 3); view_dirs = zeros(3 * images.length, 3); for i = 1:images.length image_id = keys{i}; image = images(image_id); camera_centers(i,:) = -image.R' * image.t; view_dirs(3 * i - 2,:) = camera_centers(i,:); view_dirs(3 * i - 1,:) = camera_centers(i,:)' + image.R' * [0; 0; 0.3]; view_dirs(3 * i,:) = nan; end keys = points.keys; xyz = zeros(points.length, 3); for i = 1:points.length point_id = keys{i}; point = points(point_id); xyz(i,:) = point.xyz; end hold on; plot3(camera_centers(:,1), camera_centers(:,2), camera_centers(:,3), 'xr'); plot3(view_dirs(:,1), view_dirs(:,2), view_dirs(:,3), '-b'); plot3(xyz(:,1), xyz(:,2), xyz(:,3), '.k'); hold off; end colmap-4.2.0/scripts/matlab/quat2rotmat.m000077500000000000000000000041601524536416500203770ustar00rootroot00000000000000% Copyright (c), ETH Zurich and UNC Chapel Hill. % All rights reserved. % % Redistribution and use in source and binary forms, with or without % modification, are permitted provided that the following conditions are met: % % * Redistributions of source code must retain the above copyright % notice, this list of conditions and the following disclaimer. % % * Redistributions in binary form must reproduce the above copyright % notice, this list of conditions and the following disclaimer in the % documentation and/or other materials provided with the distribution. % % * Neither the name of ETH Zurich and UNC Chapel Hill nor the names of % its contributors may be used to endorse or promote products derived % from this software without specific prior written permission. % % THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" % AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE % IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE % ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE % LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR % CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF % SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS % INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN % CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) % ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE % POSSIBILITY OF SUCH DAMAGE. function rotmat = quat2rotmat(qvec) rotmat = [1 - 2 * qvec(3).^2 - 2 * qvec(4).^2, ... 2 * qvec(2) * qvec(3) - 2 * qvec(1) * qvec(4), ... 2 * qvec(4) * qvec(2) + 2 * qvec(1) * qvec(3); ... 2 * qvec(2) * qvec(3) + 2 * qvec(1) * qvec(4), ... 1 - 2 * qvec(2).^2 - 2 * qvec(4).^2, ... 2 * qvec(3) * qvec(4) - 2 * qvec(1) * qvec(2); ... 2 * qvec(4) * qvec(2) - 2 * qvec(1) * qvec(3), ... 2 * qvec(3) * qvec(4) + 2 * qvec(1) * qvec(2), ... 1 - 2 * qvec(2).^2 - 2 * qvec(3).^2]; end colmap-4.2.0/scripts/matlab/read_array.m000066400000000000000000000037711524536416500202310ustar00rootroot00000000000000% Copyright (c), ETH Zurich and UNC Chapel Hill. % All rights reserved. % % Redistribution and use in source and binary forms, with or without % modification, are permitted provided that the following conditions are met: % % * Redistributions of source code must retain the above copyright % notice, this list of conditions and the following disclaimer. % % * Redistributions in binary form must reproduce the above copyright % notice, this list of conditions and the following disclaimer in the % documentation and/or other materials provided with the distribution. % % * Neither the name of ETH Zurich and UNC Chapel Hill nor the names of % its contributors may be used to endorse or promote products derived % from this software without specific prior written permission. % % THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" % AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE % IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE % ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE % LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR % CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF % SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS % INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN % CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) % ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE % POSSIBILITY OF SUCH DAMAGE. function array = read_array(path, varargin) if length(varargin) == 1 dtype = varargin{1}; else dtype = 'single'; end fid = fopen(path); line = fscanf(fid, '%d&%d&%d&', [1, 3]); width = line(1); height = line(2); channels = line(3); num = width * height * channels; array = fread(fid, num, dtype); array = reshape(array, [width, height, channels]); array = permute(array, [2 1 3]); array = cast(array, dtype); fclose(fid); end colmap-4.2.0/scripts/matlab/read_depth_map.m000066400000000000000000000035031524536416500210450ustar00rootroot00000000000000% Copyright (c), ETH Zurich and UNC Chapel Hill. % All rights reserved. % % Redistribution and use in source and binary forms, with or without % modification, are permitted provided that the following conditions are met: % % * Redistributions of source code must retain the above copyright % notice, this list of conditions and the following disclaimer. % % * Redistributions in binary form must reproduce the above copyright % notice, this list of conditions and the following disclaimer in the % documentation and/or other materials provided with the distribution. % % * Neither the name of ETH Zurich and UNC Chapel Hill nor the names of % its contributors may be used to endorse or promote products derived % from this software without specific prior written permission. % % THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" % AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE % IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE % ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE % LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR % CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF % SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS % INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN % CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) % ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE % POSSIBILITY OF SUCH DAMAGE. function [depth_map, depth_map_rgb] = read_depth_map(path) depth_map = read_array(path); depth_range = prctile(depth_map(depth_map(:) > 0), [2, 98]); depth_map(depth_map<=0) = nan; depth_map_rgb = cmap2rgb(depth_map, [0 0 0; jet(2^15)], depth_range); end colmap-4.2.0/scripts/matlab/read_model.m000077500000000000000000000111541524536416500202100ustar00rootroot00000000000000% Copyright (c), ETH Zurich and UNC Chapel Hill. % All rights reserved. % % Redistribution and use in source and binary forms, with or without % modification, are permitted provided that the following conditions are met: % % * Redistributions of source code must retain the above copyright % notice, this list of conditions and the following disclaimer. % % * Redistributions in binary form must reproduce the above copyright % notice, this list of conditions and the following disclaimer in the % documentation and/or other materials provided with the distribution. % % * Neither the name of ETH Zurich and UNC Chapel Hill nor the names of % its contributors may be used to endorse or promote products derived % from this software without specific prior written permission. % % THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" % AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE % IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE % ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE % LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR % CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF % SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS % INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN % CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) % ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE % POSSIBILITY OF SUCH DAMAGE. function [cameras, images, points3D] = read_model(path) % Read COLMAP model from folder, which contains a % cameras.txt, images.txt, and points3D.txt. if numel(path) > 0 && path(end) ~= '/' path = [path '/']; end cameras = read_cameras([path 'cameras.txt']); images = read_images([path 'images.txt']); points3D = read_points3D([path 'points3D.txt']); end function cameras = read_cameras(path) cameras = containers.Map('KeyType', 'int64', 'ValueType', 'any'); fid = fopen(path); tline = fgets(fid); while ischar(tline) elems = strsplit(tline); if numel(elems) < 4 || strcmp(elems(1), '#') tline = fgets(fid); continue end if mod(cameras.Count, 10) == 0 fprintf('Reading camera %d\n', cameras.length); end camera = struct; camera.camera_id = str2num(elems{1}); camera.model = elems{2}; camera.width = str2num(elems{3}); camera.height = str2num(elems{4}); camera.params = zeros(numel(elems) - 5, 1); for i = 5:numel(elems) - 1 camera.params(i - 4) = str2double(elems{i}); end cameras(camera.camera_id) = camera; tline = fgets(fid); end fclose(fid); end function images = read_images(path) images = containers.Map('KeyType', 'int64', 'ValueType', 'any'); fid = fopen(path); tline = fgets(fid); while ischar(tline) elems = strsplit(tline); if numel(elems) < 4 || strcmp(elems(1), '#') tline = fgets(fid); continue end if mod(images.Count, 10) == 0 fprintf('Reading image %d\n', images.length); end image = struct; image.image_id = str2num(elems{1}); qw = str2double(elems{2}); qx = str2double(elems{3}); qy = str2double(elems{4}); qz = str2double(elems{5}); image.R = quat2rotmat([qw, qx, qy, qz]); tx = str2double(elems{6}); ty = str2double(elems{7}); tz = str2double(elems{8}); image.t = [tx; ty; tz]; image.camera_id = str2num(elems{9}); image.name = elems{10}; tline = fgets(fid); elems = sscanf(tline, '%f'); elems = reshape(elems, [3, numel(elems) / 3]); image.xys = elems(1:2,:)'; image.point3D_ids = elems(3,:)'; images(image.image_id) = image; tline = fgets(fid); end fclose(fid); end function points3D = read_points3D(path) points3D = containers.Map('KeyType', 'int64', 'ValueType', 'any'); fid = fopen(path); tline = fgets(fid); while ischar(tline) if numel(tline) == 0 || strcmp(tline(1), '#') tline = fgets(fid); continue; end elems = sscanf(tline, '%f'); if numel(elems) == 0 tline = fgets(fid); continue; end if mod(points3D.Count, 1000) == 0 fprintf('Reading point %d\n', points3D.length); end point = struct; point.point3D_id = int64(elems(1)); point.xyz = elems(2:4); point.rgb = uint8(elems(5:7)); point.error = elems(8); point.track = int64(elems(9:end)); point.track = reshape(point.track, [2, numel(point.track) / 2])'; point.track(:,2) = point.track(:,2) + 1; points3D(point.point3D_id) = point; tline = fgets(fid); end fclose(fid); end colmap-4.2.0/scripts/matlab/read_normal_map.m000066400000000000000000000040761524536416500212370ustar00rootroot00000000000000% Copyright (c), ETH Zurich and UNC Chapel Hill. % All rights reserved. % % Redistribution and use in source and binary forms, with or without % modification, are permitted provided that the following conditions are met: % % * Redistributions of source code must retain the above copyright % notice, this list of conditions and the following disclaimer. % % * Redistributions in binary form must reproduce the above copyright % notice, this list of conditions and the following disclaimer in the % documentation and/or other materials provided with the distribution. % % * Neither the name of ETH Zurich and UNC Chapel Hill nor the names of % its contributors may be used to endorse or promote products derived % from this software without specific prior written permission. % % THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" % AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE % IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE % ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE % LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR % CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF % SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS % INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN % CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) % ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE % POSSIBILITY OF SUCH DAMAGE. function [normal_map, normal_map_rgb] = read_normal_map(path, varargin) normal_map = read_array(path); normal_map_rgb = -[normal_map(:,:,1) normal_map(:,:,2) ... 2 * normal_map(:,:,3) + 1]; normal_map_rgb = reshape(normal_map_rgb, ... size(normal_map,1), size(normal_map,2), 3); normal_map_rgb = uint8(((normal_map_rgb + 1) ./ 2) .* 255); if length(varargin) == 1 depth_map = varargin{1}; normal_map_rgb(repmat(isnan(depth_map), [1, 1, 3])) = 0; end end colmap-4.2.0/scripts/matlab/read_ply.m000077500000000000000000000044161524536416500177170ustar00rootroot00000000000000% Copyright (c), ETH Zurich and UNC Chapel Hill. % All rights reserved. % % Redistribution and use in source and binary forms, with or without % modification, are permitted provided that the following conditions are met: % % * Redistributions of source code must retain the above copyright % notice, this list of conditions and the following disclaimer. % % * Redistributions in binary form must reproduce the above copyright % notice, this list of conditions and the following disclaimer in the % documentation and/or other materials provided with the distribution. % % * Neither the name of ETH Zurich and UNC Chapel Hill nor the names of % its contributors may be used to endorse or promote products derived % from this software without specific prior written permission. % % THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" % AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE % IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE % ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE % LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR % CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF % SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS % INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN % CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) % ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE % POSSIBILITY OF SUCH DAMAGE. function [xyz, normals, rgb] = read_ply(path) % Read point cloud from PLY text file. file = fopen(path, 'r'); type = fscanf(file, '%s', 1); format = fscanf(file, '%s', 3); data = fscanf(file, '%s', 2); num_points = fscanf(file, '%d', 1); fscanf(file, '%s', 3); fscanf(file, '%s', 3); fscanf(file, '%s', 3); fscanf(file, '%s', 3); fscanf(file, '%s', 3); fscanf(file, '%s', 3); fscanf(file, '%s', 3); fscanf(file, '%s', 3); fscanf(file, '%s', 3); fscanf(file, '%s', 1); points_data = textscan(file, '%f %f %f %f %f %f %f %f %f', num_points); xyz = [points_data{1}, points_data{2}, points_data{3}]; rgb = [points_data{7}, points_data{8}, points_data{9}]; normals = [points_data{4}, points_data{5}, points_data{6}]; end colmap-4.2.0/scripts/matlab/write_array.m000066400000000000000000000034331524536416500204430ustar00rootroot00000000000000% Copyright (c), ETH Zurich and UNC Chapel Hill. % All rights reserved. % % Redistribution and use in source and binary forms, with or without % modification, are permitted provided that the following conditions are met: % % * Redistributions of source code must retain the above copyright % notice, this list of conditions and the following disclaimer. % % * Redistributions in binary form must reproduce the above copyright % notice, this list of conditions and the following disclaimer in the % documentation and/or other materials provided with the distribution. % % * Neither the name of ETH Zurich and UNC Chapel Hill nor the names of % its contributors may be used to endorse or promote products derived % from this software without specific prior written permission. % % THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" % AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE % IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE % ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE % LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR % CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF % SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS % INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN % CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) % ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE % POSSIBILITY OF SUCH DAMAGE. function write_array(path, array) fid = fopen(path, 'w'); fprintf(fid, '%d&%d&%d&', size(array, 2), size(array, 1), size(array, 3)); array = permute(array, [2 1 3]); fwrite(fid, array, class(array)); fclose(fid); end colmap-4.2.0/scripts/matlab/write_ply.m000077500000000000000000000046411524536416500201360ustar00rootroot00000000000000% Copyright (c), ETH Zurich and UNC Chapel Hill. % All rights reserved. % % Redistribution and use in source and binary forms, with or without % modification, are permitted provided that the following conditions are met: % % * Redistributions of source code must retain the above copyright % notice, this list of conditions and the following disclaimer. % % * Redistributions in binary form must reproduce the above copyright % notice, this list of conditions and the following disclaimer in the % documentation and/or other materials provided with the distribution. % % * Neither the name of ETH Zurich and UNC Chapel Hill nor the names of % its contributors may be used to endorse or promote products derived % from this software without specific prior written permission. % % THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" % AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE % IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE % ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE % LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR % CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF % SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS % INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN % CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) % ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE % POSSIBILITY OF SUCH DAMAGE. function write_ply(path, xyz, normals, rgb) % Write point cloud to PLY text file. file = fopen(path, 'W'); fprintf(file,'ply\n'); fprintf(file,'format ascii 1.0\n'); fprintf(file,'element vertex %d\n',size(xyz,1)); fprintf(file,'property float x\n'); fprintf(file,'property float y\n'); fprintf(file,'property float z\n'); fprintf(file,'property float nx\n'); fprintf(file,'property float ny\n'); fprintf(file,'property float nz\n'); fprintf(file,'property uchar diffuse_red\n'); fprintf(file,'property uchar diffuse_green\n'); fprintf(file,'property uchar diffuse_blue\n'); fprintf(file,'end_header\n'); for i = 1:size(xyz, 1) fprintf(file, '%f %f %f %f %f %f %d %d %d\n', ... xyz(i,1), xyz(i,2), xyz(i,3), ... normals(i,1), normals(i,2), normals(i,3), ... uint8(rgb(i,1)), uint8(rgb(i,2)), uint8(rgb(i,3))); end fclose(file); end colmap-4.2.0/scripts/shell/000077500000000000000000000000001524536416500156015ustar00rootroot00000000000000colmap-4.2.0/scripts/shell/build_mac_app.sh000077500000000000000000000102111524536416500207120ustar00rootroot00000000000000#!/usr/bin/env bash # Copyright (c), ETH Zurich and UNC Chapel Hill. # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are met: # # * Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer. # # * Redistributions in binary form must reproduce the above copyright # notice, this list of conditions and the following disclaimer in the # documentation and/or other materials provided with the distribution. # # * Neither the name of ETH Zurich and UNC Chapel Hill nor the names of # its contributors may be used to endorse or promote products derived # from this software without specific prior written permission. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. # This script creates a deployable package of COLMAP for macOS. set -euo pipefail if [[ $# -ne 1 ]]; then echo "Usage: $0 /path/to/colmap" >&2 exit 1 fi BINARY_PATH=$1 BASE_PATH=$(dirname "$BINARY_PATH") APP_PATH="$BASE_PATH/COLMAP.app" APP_BINARY="$APP_PATH/Contents/MacOS/colmap" APP_LAUNCHER="$APP_PATH/Contents/MacOS/colmap_gui.sh" ARCHIVE_PATH="$BASE_PATH/COLMAP-mac.zip" rm -rf "$APP_PATH" "$ARCHIVE_PATH" echo "Creating bundle directory" mkdir -p "$APP_PATH/Contents/MacOS" echo "Copying binary" cp "$BINARY_PATH" "$APP_BINARY" echo "Writing Info.plist" cat <"$APP_PATH/Contents/Info.plist" CFBundlePackageType APPL CFBundleExecutable colmap CFBundleIdentifier COLMAP CFBundleName COLMAP CFBundleDisplayName COLMAP NSHighResolutionCapable NSAppSleepDisabled EOM echo "Linking dynamic libraries" "$(brew --prefix qt)/bin/macdeployqt" "$APP_PATH" -no-codesign echo "Wrapping binary" cat <<'EOM' >"$APP_LAUNCHER" #!/bin/bash script_path="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" exec "$script_path/colmap" gui EOM chmod +x "$APP_LAUNCHER" sed -i '' 's#colmap#colmap_gui.sh#g' "$APP_PATH/Contents/Info.plist" echo "Signing application binaries" BREW_PREFIX=$(brew --prefix) remove_homebrew_rpaths() { local binary_path=$1 while IFS= read -r rpath; do if [[ $rpath == "$BREW_PREFIX/"* ]]; then install_name_tool -delete_rpath "$rpath" "$binary_path" fi done < <(otool -l "$binary_path" | awk '/LC_RPATH/{getline; getline; print $2}') } while IFS= read -r file_path; do if file "$file_path" | grep -q "Mach-O"; then remove_homebrew_rpaths "$file_path" codesign --force --sign - "$file_path" >/dev/null 2>&1 fi done < <(find "$APP_PATH/Contents/Frameworks" "$APP_PATH/Contents/PlugIns" -type f) remove_homebrew_rpaths "$APP_BINARY" codesign --force --sign - "$APP_BINARY" >/dev/null 2>&1 codesign --force --sign - "$APP_PATH" >/dev/null 2>&1 echo "Checking packaged binary" "$APP_BINARY" help >/dev/null codesign --verify --deep --strict "$APP_PATH" echo "Compressing application" ditto -c -k --sequesterRsrc --keepParent "$APP_PATH" "$ARCHIVE_PATH" colmap-4.2.0/scripts/shell/colmap.bat000077500000000000000000000035261524536416500175550ustar00rootroot00000000000000@echo off rem Copyright (c), ETH Zurich and UNC Chapel Hill. rem All rights reserved. rem rem Redistribution and use in source and binary forms, with or without rem modification, are permitted provided that the following conditions are met: rem rem * Redistributions of source code must retain the above copyright rem notice, this list of conditions and the following disclaimer. rem rem * Redistributions in binary form must reproduce the above copyright rem notice, this list of conditions and the following disclaimer in the rem documentation and/or other materials provided with the distribution. rem rem * Neither the name of ETH Zurich and UNC Chapel Hill nor the names of rem its contributors may be used to endorse or promote products derived rem from this software without specific prior written permission. rem rem THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" rem AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE rem IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE rem ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE rem LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR rem CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF rem SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS rem INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN rem CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) rem ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE rem POSSIBILITY OF SUCH DAMAGE. set SCRIPT_PATH=%~dp0 set PATH=%SCRIPT_PATH%\bin;%PATH% set QT_PLUGIN_PATH=%SCRIPT_PATH%\plugins;%QT_PLUGIN_PATH% set ARGUMENTS=%* if "%ARGUMENTS%"=="" set ARGUMENTS=gui "%SCRIPT_PATH%\bin\colmap" %ARGUMENTS% colmap-4.2.0/scripts/shell/enter_vs_dev_shell.ps1000066400000000000000000000043011524536416500220760ustar00rootroot00000000000000if (!$env:VisualStudioDevShell) { $vswhere = "${Env:ProgramFiles(x86)}/Microsoft Visual Studio/Installer/vswhere.exe" if (!(Test-Path $vswhere)) { throw "Failed to find vswhere.exe" } & $vswhere -latest -format json $vsInstance = & $vswhere -latest -format json | ConvertFrom-Json if ($LASTEXITCODE) { throw "vswhere.exe returned exit code $LASTEXITCODE" } Import-Module "$($vsInstance.installationPath)/Common7/Tools/Microsoft.VisualStudio.DevShell.dll" $prevCwd = Get-Location try { Enter-VsDevShell $vsInstance.instanceId -DevCmdArguments "-no_logo -host_arch=amd64 -arch=amd64" } catch { Write-Host $_ Write-Error "Failed to enter Visual Studio Dev Shell" exit 1 } # CI only: the Visual Studio toolchain bundles an LLVM flang Fortran compiler # that miscompiles LAPACK's *gedmd routines (e.g. "'ssum' is not an object # that can appear in an expression"). vcpkg builds each port in its own # environment (regenerated via vcvars), which puts this flang on the PATH, so # vcpkg_find_fortran detects it instead of falling back to a working MinGW # gfortran and the lapack-reference build fails. Remove the bundled flang in CI # so the MinGW gfortran fallback kicks in. COLMAP itself builds with MSVC # (cl.exe) and does not use flang. We gate on CI so local Visual Studio # installations are left untouched. # See https://github.com/llvm/llvm-project/issues/201254 and # https://developercommunity.microsoft.com/t/11105096. if ($env:GITHUB_ACTIONS -eq "true") { $llvmDir = Join-Path $vsInstance.installationPath "VC/Tools/Llvm" if (Test-Path $llvmDir) { Get-ChildItem -Path $llvmDir -Recurse -Filter "flang*.exe" -ErrorAction SilentlyContinue | ForEach-Object { try { Remove-Item -LiteralPath $_.FullName -Force -ErrorAction Stop Write-Host "Removed bundled flang: $($_.FullName)" } catch { Write-Warning "Failed to remove bundled flang $($_.FullName): $_" } } } } Set-Location $prevCwd $env:VisualStudioDevShell = $true } colmap-4.2.0/scripts/shell/generate_coverage_report.sh000077500000000000000000000047741524536416500232140ustar00rootroot00000000000000#!/bin/bash # Copyright (c), ETH Zurich and UNC Chapel Hill. # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are met: # # * Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer. # # * Redistributions in binary form must reproduce the above copyright # notice, this list of conditions and the following disclaimer in the # documentation and/or other materials provided with the distribution. # # * Neither the name of ETH Zurich and UNC Chapel Hill nor the names of # its contributors may be used to endorse or promote products derived # from this software without specific prior written permission. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. # Script to generate a coverage report for the COLMAP code base. Must be # executed from the build directory. The generated HTML report will be available # in the coverage/ directory. # The script assumes that the codebase has been compiled with CMake options: # cmake ... # -DTESTS_ENABLED=ON \ # -DCOVERAGE_ENABLED=ON \ # -DCMAKE_BUILD_TYPE=RelWithDebInfo|Debug # ninja # ctest -j$(nproc) colmap_root_dir=$(git rev-parse --show-toplevel) if [ ! -f "CMakeCache.txt" ]; then echo "Please run this script from the build directory." exit 1 fi rm -rf coverage-html mkdir -p coverage-html gcovr \ --root "$colmap_root_dir" \ --exclude "$colmap_root_dir/src/thirdparty/*" \ --exclude "$(pwd)/_deps/*" \ --exclude "$(pwd)/src/colmap/ui/colmap_ui_autogen/*" \ --cobertura coverage-cobertura.xml \ --cobertura-pretty \ --html-nested coverage-html/index.html \ --html-theme github.blue colmap-4.2.0/scripts/shell/images_to_video.sh000077500000000000000000000033111524536416500212730ustar00rootroot00000000000000# Copyright (c), ETH Zurich and UNC Chapel Hill. # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are met: # # * Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer. # # * Redistributions in binary form must reproduce the above copyright # notice, this list of conditions and the following disclaimer in the # documentation and/or other materials provided with the distribution. # # * Neither the name of ETH Zurich and UNC Chapel Hill nor the names of # its contributors may be used to endorse or promote products derived # from this software without specific prior written permission. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. # Command to produce video from images produced by COLMAP movie grabber tool. ffmpeg -i frame%06d.png -r 30 -vf scale=1680:1050 out.mp4 colmap-4.2.0/scripts/shell/profile_binary.sh000077500000000000000000000047751524536416500211610ustar00rootroot00000000000000#!/bin/bash # Copyright (c), ETH Zurich and UNC Chapel Hill. # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are met: # # * Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer. # # * Redistributions in binary form must reproduce the above copyright # notice, this list of conditions and the following disclaimer in the # documentation and/or other materials provided with the distribution. # # * Neither the name of ETH Zurich and UNC Chapel Hill nor the names of # its contributors may be used to endorse or promote products derived # from this software without specific prior written permission. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. # Script to perform profiling on a command. For example: # ./profile_binary.sh ./src/colmap/exe/colmap automatic_reconstructor ... perf_bin=$(find /usr/lib/linux-tools -name perf | head -1) if [[ -z $perf_bin ]]; then echo "Error: Perf tool not found. Under ubuntu, install as:" echo " sudo apt-get install linux-tools-generic" exit 1 fi "$perf_bin" record -e cycles:u -g "$@" binary_filename=$(basename -- "${binary_path}") profile_path="$binary_filename.perf.data" mv perf.data "$profile_path" echo "#####################################################################" echo "###### Profiling finished. Inspect results using the commands: ######" echo "#####################################################################" echo "If the perf output contains unknown list items, recompile with RelWithDebInfo" echo "$perf_bin report -i $profile_path" echo "$perf_bin report --stdio -g graph,0.5,caller -i $profile_path" colmap-4.2.0/scripts/shell/restore_git_submodules.sh000077500000000000000000000035211524536416500227310ustar00rootroot00000000000000# Copyright (c), ETH Zurich and UNC Chapel Hill. # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are met: # # * Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer. # # * Redistributions in binary form must reproduce the above copyright # notice, this list of conditions and the following disclaimer in the # documentation and/or other materials provided with the distribution. # # * Neither the name of ETH Zurich and UNC Chapel Hill nor the names of # its contributors may be used to endorse or promote products derived # from this software without specific prior written permission. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. set -e git config -f .gitmodules --get-regexp '^submodule\..*\.path$' | while read path_key path do url_key=$(echo $path_key | sed 's/\.path/.url/') url=$(git config -f .gitmodules --get "$url_key") git submodule add -f $url $path done colmap-4.2.0/scripts/shell/run_tests.bat000077500000000000000000000035511524536416500203260ustar00rootroot00000000000000@echo off rem Copyright (c), ETH Zurich and UNC Chapel Hill. rem All rights reserved. rem rem Redistribution and use in source and binary forms, with or without rem modification, are permitted provided that the following conditions are met: rem rem * Redistributions of source code must retain the above copyright rem notice, this list of conditions and the following disclaimer. rem rem * Redistributions in binary form must reproduce the above copyright rem notice, this list of conditions and the following disclaimer in the rem documentation and/or other materials provided with the distribution. rem rem * Neither the name of ETH Zurich and UNC Chapel Hill nor the names of rem its contributors may be used to endorse or promote products derived rem from this software without specific prior written permission. rem rem THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" rem AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE rem IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE rem ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE rem LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR rem CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF rem SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS rem INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN rem CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) rem ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE rem POSSIBILITY OF SUCH DAMAGE. set SCRIPT_PATH=%~dp0 set PATH=%SCRIPT_PATH%\bin;%PATH% set QT_PLUGIN_PATH=%SCRIPT_PATH%\plugins;%QT_PLUGIN_PATH% @echo on for %%i in (%SCRIPT_PATH%\bin\*_test.exe) do ( %%i if %errorlevel% neq 0 goto end ) :end pause colmap-4.2.0/src/000077500000000000000000000000001524536416500135725ustar00rootroot00000000000000colmap-4.2.0/src/colmap/000077500000000000000000000000001524536416500150455ustar00rootroot00000000000000colmap-4.2.0/src/colmap/CMakeLists.txt000066400000000000000000000070071524536416500176110ustar00rootroot00000000000000# Copyright (c), ETH Zurich and UNC Chapel Hill. # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are met: # # * Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer. # # * Redistributions in binary form must reproduce the above copyright # notice, this list of conditions and the following disclaimer in the # documentation and/or other materials provided with the distribution. # # * Neither the name of ETH Zurich and UNC Chapel Hill nor the names of # its contributors may be used to endorse or promote products derived # from this software without specific prior written permission. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. if(IS_MSVC) add_compile_options($<$:/W3>) if(WERROR_ENABLED) add_compile_options($<$:/WX>) endif() # Avoid pulling in too many header files through add_compile_definitions(WIN32_LEAN_AND_MEAN) elseif(IS_GNU OR IS_CLANG) add_compile_options($<$:-Wall>) if(WERROR_ENABLED) add_compile_options($<$:-Werror>) endif() if(IS_GNU AND CMAKE_CXX_COMPILER_VERSION VERSION_GREATER_EQUAL 15 AND CMAKE_CXX_COMPILER_VERSION VERSION_LESS 16) # GCC 15 emits false positives in Eigen 3.4 and libstdc++ internals. add_compile_options( $<$:-Wno-error=array-bounds> $<$:-Wno-error=maybe-uninitialized> $<$:-Wno-error=stringop-overflow>) endif() endif() if(CUDA_ENABLED) set(CMAKE_CUDA_FLAGS "${CMAKE_CUDA_FLAGS} --use_fast_math") # Use a separate stream per thread to allow for concurrent kernel execution # between multiple threads on the same device. set(CMAKE_CUDA_FLAGS "${CMAKE_CUDA_FLAGS} --default-stream per-thread") # Suppress warnings: # ptxas warning : Stack size for entry function X cannot be statically determined. set(CMAKE_CUDA_FLAGS "${CMAKE_CUDA_FLAGS} -Xptxas=-suppress-stack-size-warning") endif() if(COVERAGE_ENABLED) add_compile_options(-coverage -fprofile-update=atomic) add_link_options(-coverage) endif() add_subdirectory(controllers) add_subdirectory(estimators) add_subdirectory(exe) add_subdirectory(feature) add_subdirectory(geometry) add_subdirectory(image) add_subdirectory(math) if(MVS_ENABLED) add_subdirectory(mvs) endif() add_subdirectory(optim) add_subdirectory(retrieval) add_subdirectory(scene) add_subdirectory(sensor) add_subdirectory(sfm) add_subdirectory(tools) add_subdirectory(util) if (GUI_ENABLED) add_subdirectory(ui) endif() colmap-4.2.0/src/colmap/controllers/000077500000000000000000000000001524536416500174135ustar00rootroot00000000000000colmap-4.2.0/src/colmap/controllers/CMakeLists.txt000066400000000000000000000116421524536416500221570ustar00rootroot00000000000000# Copyright (c), ETH Zurich and UNC Chapel Hill. # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are met: # # * Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer. # # * Redistributions in binary form must reproduce the above copyright # notice, this list of conditions and the following disclaimer in the # documentation and/or other materials provided with the distribution. # # * Neither the name of ETH Zurich and UNC Chapel Hill nor the names of # its contributors may be used to endorse or promote products derived # from this software without specific prior written permission. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. set(FOLDER_NAME "controllers") COLMAP_ADD_LIBRARY( NAME colmap_controllers SRCS automatic_reconstruction.h automatic_reconstruction.cc base_option_manager.h base_option_manager.cc bundle_adjustment.h bundle_adjustment.cc hierarchical_pipeline.h hierarchical_pipeline.cc feature_extraction.h feature_extraction.cc feature_matching.h feature_matching.cc feature_matching_utils.h feature_matching_utils.cc matcher_cache.h matcher_cache.cc pairing.h pairing.cc global_pipeline.h global_pipeline.cc image_reader.h image_reader.cc incremental_pipeline.h incremental_pipeline.cc option_manager.h option_manager.cc reconstruction_clustering.h reconstruction_clustering.cc rotation_averaging.h rotation_averaging.cc undistorters.h undistorters.cc PUBLIC_LINK_LIBS colmap_estimators colmap_feature colmap_retrieval colmap_geometry colmap_scene colmap_util Eigen3::Eigen Boost::program_options PRIVATE_LINK_LIBS colmap_image colmap_math colmap_sfm Ceres::ceres Boost::boost faiss ) if(MVS_ENABLED) target_link_libraries(colmap_controllers PRIVATE colmap_mvs) endif() if(CUDA_ENABLED) target_link_libraries(colmap_controllers PRIVATE colmap_util_cuda) if(MVS_ENABLED) target_link_libraries(colmap_controllers PRIVATE colmap_mvs_cuda) endif() endif() COLMAP_ADD_TEST( NAME automatic_reconstruction_test SRCS automatic_reconstruction_test.cc LINK_LIBS colmap_controllers ) COLMAP_ADD_TEST( NAME bundle_adjustment_test SRCS bundle_adjustment_test.cc LINK_LIBS colmap_controllers ) COLMAP_ADD_TEST( NAME hierarchical_pipeline_test SRCS hierarchical_pipeline_test.cc LINK_LIBS colmap_controllers ) COLMAP_ADD_TEST( NAME incremental_pipeline_test SRCS incremental_pipeline_test.cc LINK_LIBS colmap_controllers ) COLMAP_ADD_TEST( NAME image_reader_test SRCS image_reader_test.cc LINK_LIBS colmap_controllers ) COLMAP_ADD_TEST( NAME feature_extraction_test SRCS feature_extraction_test.cc LINK_LIBS colmap_controllers ) COLMAP_ADD_TEST( NAME feature_matching_test SRCS feature_matching_test.cc LINK_LIBS colmap_controllers ) COLMAP_ADD_TEST( NAME feature_matching_utils_test SRCS feature_matching_utils_test.cc LINK_LIBS colmap_controllers ) COLMAP_ADD_TEST( NAME global_pipeline_test SRCS global_pipeline_test.cc LINK_LIBS colmap_controllers ) COLMAP_ADD_TEST( NAME base_option_manager_test SRCS base_option_manager_test.cc LINK_LIBS colmap_controllers ) COLMAP_ADD_TEST( NAME option_manager_test SRCS option_manager_test.cc LINK_LIBS colmap_controllers ) COLMAP_ADD_TEST( NAME rotation_averaging_test SRCS rotation_averaging_test.cc LINK_LIBS colmap_controllers ) COLMAP_ADD_TEST( NAME reconstruction_clustering_test SRCS reconstruction_clustering_test.cc LINK_LIBS colmap_controllers ) COLMAP_ADD_TEST( NAME undistorters_test SRCS undistorters_test.cc LINK_LIBS colmap_controllers ) COLMAP_ADD_TEST( NAME matcher_cache_test SRCS matcher_cache_test.cc LINK_LIBS colmap_controllers ) COLMAP_ADD_TEST( NAME pairing_test SRCS pairing_test.cc LINK_LIBS colmap_controllers ) colmap-4.2.0/src/colmap/controllers/automatic_reconstruction.cc000066400000000000000000000454361524536416500250650ustar00rootroot00000000000000// Copyright (c), ETH Zurich and UNC Chapel Hill. // All rights reserved. // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are met: // // * Redistributions of source code must retain the above copyright // notice, this list of conditions and the following disclaimer. // // * Redistributions in binary form must reproduce the above copyright // notice, this list of conditions and the following disclaimer in the // documentation and/or other materials provided with the distribution. // // * Neither the name of ETH Zurich and UNC Chapel Hill nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE // ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE // LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR // CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF // SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS // INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN // CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) // ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE // POSSIBILITY OF SUCH DAMAGE. #include "colmap/controllers/automatic_reconstruction.h" #include "colmap/controllers/feature_extraction.h" #include "colmap/controllers/feature_matching.h" #include "colmap/controllers/global_pipeline.h" #include "colmap/controllers/hierarchical_pipeline.h" #include "colmap/controllers/incremental_pipeline.h" #include "colmap/controllers/option_manager.h" #include "colmap/controllers/undistorters.h" #include "colmap/estimators/view_graph_calibration.h" #if defined(COLMAP_MVS_ENABLED) #include "colmap/mvs/advancing_front_meshing.h" #include "colmap/mvs/delaunay_meshing.h" #include "colmap/mvs/fusion.h" #include "colmap/mvs/patch_match.h" #include "colmap/mvs/poisson_meshing.h" #endif #include "colmap/retrieval/resources.h" #include "colmap/scene/database.h" #include "colmap/util/logging.h" #include "colmap/util/misc.h" namespace colmap { AutomaticReconstructionController::AutomaticReconstructionController( const Options& options, std::shared_ptr reconstruction_manager) : options_(options), reconstruction_manager_(std::move(reconstruction_manager)), active_thread_(nullptr) { THROW_CHECK_DIR_EXISTS(options_.workspace_path); THROW_CHECK_DIR_EXISTS(options_.image_path); THROW_CHECK_NOTNULL(reconstruction_manager_); option_manager_.AddAllOptions(); *option_manager_.image_path = options_.image_path; option_manager_.image_reader->image_names = options_.image_names; option_manager_.mapper->image_names = {options_.image_names.begin(), options_.image_names.end()}; *option_manager_.database_path = options_.workspace_path / "database.db"; if (options_.data_type == DataType::VIDEO) { option_manager_.ModifyForVideoData(); } else if (options_.data_type == DataType::INDIVIDUAL) { option_manager_.ModifyForIndividualData(); } else if (options_.data_type == DataType::INTERNET) { option_manager_.ModifyForInternetData(); } else { LOG(FATAL_THROW) << "Data type not supported"; } THROW_CHECK(ExistsCameraModelWithName(options_.camera_model)); // Set feature type first so quality modifiers can query EffMaxImageSize(). if (options_.feature == Feature::SIFT) { option_manager_.feature_extraction->type = FeatureExtractorType::SIFT; option_manager_.feature_matching->type = FeatureMatcherType::SIFT_BRUTEFORCE; } else if (options_.feature == Feature::ALIKED) { option_manager_.feature_extraction->type = FeatureExtractorType::ALIKED_N16ROT; option_manager_.feature_matching->type = FeatureMatcherType::ALIKED_BRUTEFORCE; } else if (options_.feature == Feature::LOMA) { option_manager_.feature_extraction->type = FeatureExtractorType::LOMA_B; option_manager_.feature_matching->type = FeatureMatcherType::LOMA_B; } else if (options_.feature == Feature::LOMA128) { option_manager_.feature_extraction->type = FeatureExtractorType::LOMA_B128; option_manager_.feature_matching->type = FeatureMatcherType::LOMA_B128; } // Apply quality preset (scales max_image_size relative to extractor default). if (options_.quality == Quality::LOW) { option_manager_.ModifyForLowQuality(); } else if (options_.quality == Quality::MEDIUM) { option_manager_.ModifyForMediumQuality(); } else if (options_.quality == Quality::HIGH) { option_manager_.ModifyForHighQuality(); } else if (options_.quality == Quality::EXTREME) { option_manager_.ModifyForExtremeQuality(); } // Feature-specific overrides that must come after quality. if (options_.feature == Feature::ALIKED || options_.feature == Feature::LOMA || options_.feature == Feature::LOMA128) { // Guided matching is not supported for ALIKED/LoMa option_manager_.feature_matching->guided_matching = false; } option_manager_.feature_extraction->num_threads = options_.num_threads; option_manager_.feature_matching->num_threads = options_.num_threads; option_manager_.sequential_pairing->num_threads = options_.num_threads; option_manager_.vocab_tree_pairing->num_threads = options_.num_threads; option_manager_.mapper->num_threads = options_.num_threads; #if defined(COLMAP_MVS_ENABLED) option_manager_.patch_match_stereo->num_threads = options_.num_threads; option_manager_.poisson_meshing->num_threads = options_.num_threads; option_manager_.delaunay_meshing->num_threads = options_.num_threads; #endif option_manager_.vocab_tree_pairing->vocab_tree_path = GetVocabTreeUriForFeatureType(option_manager_.feature_extraction->type); option_manager_.sequential_pairing->vocab_tree_path = GetVocabTreeUriForFeatureType(option_manager_.feature_extraction->type); option_manager_.sequential_pairing->loop_detection = true; // Apply mapper-appropriate two-view geometry defaults. // Global uses stricter thresholds; Incremental/Hierarchical use standard. TwoViewGeometryOptions& two_view_geometry_options = *option_manager_.two_view_geometry; two_view_geometry_options.ransac_options.random_seed = options_.random_seed; if (options_.mapper == Mapper::GLOBAL) { two_view_geometry_options.ransac_options.max_error = 1.0; two_view_geometry_options.min_num_inliers = 30; two_view_geometry_options.min_inlier_ratio = 0.25; // Disable guided matching for global mapper to avoid regression issues. // Currently the guided matching leads to significantly worse results of the // global pipeline. // TODO: Write to database matches instead of inlier matches in guided // matching and figure out a good min_num_inliers and min_inlier_ratio // threshold for it. option_manager_.feature_matching->guided_matching = false; } option_manager_.mapper->random_seed = options_.random_seed; #if defined(COLMAP_MVS_ENABLED) if (!options_.mask_path.empty()) { option_manager_.stereo_fusion->mask_path = options_.mask_path; } #endif option_manager_.feature_extraction->use_gpu = options_.use_gpu; option_manager_.feature_matching->use_gpu = options_.use_gpu; option_manager_.mapper->ba_use_gpu = options_.use_gpu; option_manager_.mapper->ba_local_backend = options_.ba_backend; option_manager_.mapper->ba_global_backend = options_.ba_backend; if (option_manager_.bundle_adjustment->ceres) { option_manager_.bundle_adjustment->ceres->use_gpu = options_.use_gpu; } option_manager_.feature_extraction->gpu_index = options_.gpu_index; option_manager_.feature_matching->gpu_index = options_.gpu_index; #if defined(COLMAP_MVS_ENABLED) option_manager_.patch_match_stereo->gpu_index = options_.gpu_index; #endif option_manager_.mapper->ba_gpu_index = options_.gpu_index; if (option_manager_.bundle_adjustment->ceres) { option_manager_.bundle_adjustment->ceres->gpu_index = options_.gpu_index; } } bool AutomaticReconstructionController::RequiresOpenGL() const { return (options_.extraction && option_manager_.feature_extraction->RequiresOpenGL()) || (options_.matching && option_manager_.feature_matching->RequiresOpenGL()); } void AutomaticReconstructionController::Setup() { if (options_.extraction) { ImageReaderOptions& reader_options = *option_manager_.image_reader; reader_options.mask_path = options_.mask_path; reader_options.single_camera = options_.single_camera; reader_options.single_camera_per_folder = options_.single_camera_per_folder; reader_options.camera_model = options_.camera_model; reader_options.camera_params = options_.camera_params; reader_options.image_path = *option_manager_.image_path; reader_options.as_rgb = option_manager_.feature_extraction->RequiresRGB(); feature_extractor_ = CreateFeatureExtractorController(*option_manager_.database_path, reader_options, *option_manager_.feature_extraction); } if (options_.matching) { exhaustive_matcher_ = CreateExhaustiveFeatureMatcher(*option_manager_.exhaustive_pairing, *option_manager_.feature_matching, *option_manager_.two_view_geometry, *option_manager_.database_path); sequential_matcher_ = CreateSequentialFeatureMatcher(*option_manager_.sequential_pairing, *option_manager_.feature_matching, *option_manager_.two_view_geometry, *option_manager_.database_path); if (!options_.vocab_tree_path.empty()) { vocab_tree_matcher_ = CreateVocabTreeFeatureMatcher(*option_manager_.vocab_tree_pairing, *option_manager_.feature_matching, *option_manager_.two_view_geometry, *option_manager_.database_path); } } } void AutomaticReconstructionController::Stop() { if (active_thread_ != nullptr) { active_thread_->Stop(); } Thread::Stop(); } void AutomaticReconstructionController::Run() { if (IsStopped()) { return; } if (options_.extraction) { RunFeatureExtraction(); } if (IsStopped()) { return; } if (options_.matching) { RunFeatureMatching(); } if (IsStopped()) { return; } if (options_.sparse) { RunSparseMapper(); } if (IsStopped()) { return; } if (options_.dense) { RunDenseMapper(); } } void AutomaticReconstructionController::RunFeatureExtraction() { LOG_HEADING1("Feature extraction"); THROW_CHECK_NOTNULL(feature_extractor_); active_thread_ = feature_extractor_.get(); feature_extractor_->Start(); feature_extractor_->Wait(); feature_extractor_.reset(); active_thread_ = nullptr; } void AutomaticReconstructionController::RunFeatureMatching() { LOG_HEADING1("Feature matching"); Thread* matcher = nullptr; if (options_.data_type == DataType::VIDEO) { matcher = sequential_matcher_.get(); } else if (options_.data_type == DataType::INDIVIDUAL || options_.data_type == DataType::INTERNET) { auto database = Database::Open(*option_manager_.database_path); const size_t num_images = database->NumImages(); if (options_.vocab_tree_path.empty() || num_images < 200) { matcher = exhaustive_matcher_.get(); } else { matcher = vocab_tree_matcher_.get(); } } THROW_CHECK_NOTNULL(matcher); active_thread_ = matcher; matcher->Start(); matcher->Wait(); exhaustive_matcher_.reset(); sequential_matcher_.reset(); vocab_tree_matcher_.reset(); active_thread_ = nullptr; } void AutomaticReconstructionController::RunSparseMapper() { LOG_HEADING1("Sparse reconstruction"); const auto sparse_path = options_.workspace_path / "sparse"; if (ExistsDir(sparse_path)) { auto dir_list = GetDirList(sparse_path); std::sort(dir_list.begin(), dir_list.end()); if (dir_list.size() > 0) { LOG(INFO) << "Skipping sparse reconstruction because it is already computed"; for (const auto& dir : dir_list) { reconstruction_manager_->Read(dir); } return; } } std::unique_ptr mapper; auto database = Database::Open(*option_manager_.database_path); switch (options_.mapper) { case Mapper::INCREMENTAL: { auto options = std::make_shared(*option_manager_.mapper); options->image_path = *option_manager_.image_path; mapper = std::make_unique( options, std::move(database), reconstruction_manager_); break; } case Mapper::HIERARCHICAL: { HierarchicalPipelineOptions options; options.image_path = *option_manager_.image_path; options.incremental_options = *option_manager_.mapper; mapper = std::make_unique( options, std::move(database), reconstruction_manager_); break; } case Mapper::GLOBAL: { ViewGraphCalibrationOptions vgc_options; vgc_options.random_seed = options_.random_seed; vgc_options.solver_options.num_threads = options_.num_threads; CalibrateViewGraph(vgc_options, database.get()); GlobalPipelineOptions global_options; global_options.image_path = *option_manager_.image_path; global_options.num_threads = options_.num_threads; global_options.random_seed = options_.random_seed; mapper = std::make_unique(std::move(global_options), std::move(database), reconstruction_manager_); break; } default: LOG(FATAL_THROW) << "Mapper not supported"; } mapper->SetCheckIfStoppedFunc([&]() { return IsStopped(); }); mapper->Run(); CreateDirIfNotExists(sparse_path); reconstruction_manager_->Write(sparse_path); option_manager_.Write(sparse_path / "project.ini"); } void AutomaticReconstructionController::RunDenseMapper() { #if !defined(COLMAP_MVS_ENABLED) LOG(WARNING) << "Skipping dense reconstruction because the MVS module is " "not available"; return; #else LOG_HEADING1("Dense reconstruction"); CreateDirIfNotExists(options_.workspace_path / "dense"); for (size_t i = 0; i < reconstruction_manager_->Size(); ++i) { if (IsStopped()) { return; } const auto dense_path = options_.workspace_path / "dense" / std::to_string(i); const auto fused_path = dense_path / "fused.ply"; std::filesystem::path meshing_path; if (options_.mesher == Mesher::POISSON) { meshing_path = dense_path / "meshed-poisson.ply"; } else if (options_.mesher == Mesher::DELAUNAY) { meshing_path = dense_path / "meshed-delaunay.ply"; } else if (options_.mesher == Mesher::ADVANCING_FRONT) { meshing_path = dense_path / "meshed-advancing-front.ply"; } if (ExistsFile(fused_path) && ExistsFile(meshing_path)) { LOG(INFO) << "Skipping dense reconstruction for model " << i << " as it already exists."; continue; } // Image undistortion. if (!ExistsDir(dense_path)) { CreateDirIfNotExists(dense_path); UndistortCameraOptions undistortion_options; undistortion_options.max_image_size = option_manager_.patch_match_stereo->max_image_size; COLMAPUndistorter::Options undistorter_options; undistorter_options.num_threads = options_.num_threads; COLMAPUndistorter undistorter(std::move(undistorter_options), undistortion_options, *reconstruction_manager_->Get(i), *option_manager_.image_path, dense_path); undistorter.SetCheckIfStoppedFunc([&]() { return IsStopped(); }); undistorter.Run(); } if (IsStopped()) { return; } // Patch match stereo. #if defined(COLMAP_CUDA_ENABLED) { mvs::PatchMatchController patch_match_controller( *option_manager_.patch_match_stereo, dense_path, "COLMAP", ""); patch_match_controller.SetCheckIfStoppedFunc( [&]() { return IsStopped(); }); patch_match_controller.Run(); } #else // COLMAP_CUDA_ENABLED LOG(WARNING) << "Skipping patch match stereo because CUDA is not available"; return; #endif // COLMAP_CUDA_ENABLED if (IsStopped()) { return; } // Stereo fusion. if (!ExistsFile(fused_path)) { auto fusion_options = *option_manager_.stereo_fusion; const int num_reg_images = reconstruction_manager_->Get(i)->NumRegImages(); fusion_options.min_num_pixels = std::min(num_reg_images + 1, fusion_options.min_num_pixels); mvs::StereoFusion fuser( fusion_options, dense_path, "COLMAP", "", option_manager_.patch_match_stereo->geom_consistency ? "geometric" : "photometric"); fuser.SetCheckIfStoppedFunc([&]() { return IsStopped(); }); fuser.Run(); LOG(INFO) << "Writing output: " << fused_path; WriteBinaryPlyPoints(fused_path, fuser.GetFusedPoints()); mvs::WritePointsVisibility(AddFileExtension(fused_path, ".vis"), fuser.GetFusedPointsVisibility()); } if (IsStopped()) { return; } // Surface meshing. if (!ExistsFile(meshing_path)) { if (options_.mesher == Mesher::POISSON) { mvs::PoissonMeshing( *option_manager_.poisson_meshing, fused_path, meshing_path); } else if (options_.mesher == Mesher::DELAUNAY) { #if defined(COLMAP_CGAL_ENABLED) mvs::DenseDelaunayMeshing( *option_manager_.delaunay_meshing, dense_path, meshing_path); #else // COLMAP_CGAL_ENABLED LOG(WARNING) << "Skipping Delaunay meshing because CGAL is not available"; return; #endif // COLMAP_CGAL_ENABLED } else if (options_.mesher == Mesher::ADVANCING_FRONT) { #if defined(COLMAP_CGAL_ENABLED) mvs::AdvancingFrontMeshing( *option_manager_.advancing_front_meshing, dense_path, meshing_path); #else // COLMAP_CGAL_ENABLED LOG(WARNING) << "Skipping advancing front meshing because CGAL is " "not available"; return; #endif // COLMAP_CGAL_ENABLED } } } #endif // COLMAP_MVS_ENABLED } } // namespace colmap colmap-4.2.0/src/colmap/controllers/automatic_reconstruction.h000066400000000000000000000131551524536416500247200ustar00rootroot00000000000000// Copyright (c), ETH Zurich and UNC Chapel Hill. // All rights reserved. // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are met: // // * Redistributions of source code must retain the above copyright // notice, this list of conditions and the following disclaimer. // // * Redistributions in binary form must reproduce the above copyright // notice, this list of conditions and the following disclaimer in the // documentation and/or other materials provided with the distribution. // // * Neither the name of ETH Zurich and UNC Chapel Hill nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE // ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE // LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR // CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF // SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS // INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN // CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) // ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE // POSSIBILITY OF SUCH DAMAGE. #pragma once #include "colmap/controllers/option_manager.h" #include "colmap/estimators/bundle_adjustment.h" #include "colmap/scene/reconstruction_manager.h" #include "colmap/util/enum_utils.h" #include "colmap/util/threading.h" #include #include #include namespace colmap { class AutomaticReconstructionController : public Thread { public: MAKE_ENUM_CLASS(DataType, 0, INDIVIDUAL, VIDEO, INTERNET); MAKE_ENUM_CLASS(Quality, 0, LOW, MEDIUM, HIGH, EXTREME); MAKE_ENUM_CLASS(Feature, 0, SIFT, ALIKED, LOMA, LOMA128); MAKE_ENUM_CLASS(Mapper, 0, INCREMENTAL, HIERARCHICAL, GLOBAL); MAKE_ENUM_CLASS(Mesher, 0, POISSON, DELAUNAY, ADVANCING_FRONT); struct Options { // The path to the workspace folder in which all results are stored. std::filesystem::path workspace_path; // The path to the image folder which are used as input. std::filesystem::path image_path; // Optional list of image names to reconstruct. The list must contain the // relative path of the images with respect to the image_path. std::vector image_names; // The path to the mask folder which are used as input. std::filesystem::path mask_path; // The path to the vocabulary tree for feature matching. std::filesystem::path vocab_tree_path; // The type of input data used to choose optimal mapper settings. DataType data_type = DataType::INDIVIDUAL; // Whether to perform low- or high-quality reconstruction. Quality quality = Quality::HIGH; // Whether to use shared intrinsics or not. bool single_camera = false; // Whether to use shared intrinsics or not for all images in the same // sub-folder. bool single_camera_per_folder = false; // Which camera model to use for images. std::string camera_model = "SIMPLE_RADIAL"; // Initial camera params for all images. std::string camera_params; // Whether to perform feature extraction. bool extraction = true; // Whether to perform feature matching. bool matching = true; // Whether to perform sparse mapping. bool sparse = true; // Whether to perform dense mapping. #if defined(COLMAP_CUDA_ENABLED) && defined(COLMAP_MVS_ENABLED) bool dense = true; #else bool dense = false; #endif // The feature extraction/matching algorithm to be used. Feature feature = Feature::SIFT; // The mapping algorithm to be used. Mapper mapper = Mapper::INCREMENTAL; // The meshing algorithm to be used. Mesher mesher = Mesher::POISSON; // The number of threads to use in all stages. int num_threads = -1; // The random seed to use in all stages. int random_seed = -1; // Whether to use the GPU in feature extraction, feature matching, and // bundle adjustment. bool use_gpu = true; // Index of the GPU used for GPU stages. For multi-GPU computation in // feature extraction/matching, you should separate multiple GPU indices by // comma, e.g., "0,1,2,3". For single-GPU stages only the first GPU will be // used. By default, all available GPUs will be used in all stages. std::string gpu_index = "-1"; // Bundle adjustment solver backend for local and global BA. BundleAdjustmentBackend ba_backend = BundleAdjustmentBackend::CERES; }; AutomaticReconstructionController( const Options& options, std::shared_ptr reconstruction_manager); // Whether any of the selected reconstruction stages requires OpenGL. bool RequiresOpenGL() const; void Setup(); void Stop() override; private: void Run() override; void RunFeatureExtraction(); void RunFeatureMatching(); void RunSparseMapper(); void RunDenseMapper(); const Options options_; OptionManager option_manager_; std::shared_ptr reconstruction_manager_; Thread* active_thread_; std::unique_ptr feature_extractor_; std::unique_ptr exhaustive_matcher_; std::unique_ptr sequential_matcher_; std::unique_ptr vocab_tree_matcher_; }; } // namespace colmap colmap-4.2.0/src/colmap/controllers/automatic_reconstruction_test.cc000066400000000000000000000106251524536416500261140ustar00rootroot00000000000000// Copyright (c), ETH Zurich and UNC Chapel Hill. // All rights reserved. // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are met: // // * Redistributions of source code must retain the above copyright // notice, this list of conditions and the following disclaimer. // // * Redistributions in binary form must reproduce the above copyright // notice, this list of conditions and the following disclaimer in the // documentation and/or other materials provided with the distribution. // // * Neither the name of ETH Zurich and UNC Chapel Hill nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE // ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE // LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR // CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF // SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS // INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN // CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) // ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE // POSSIBILITY OF SUCH DAMAGE. #include "colmap/controllers/automatic_reconstruction.h" #include "colmap/scene/reconstruction_manager.h" #include "colmap/scene/reconstruction_matchers.h" #include "colmap/scene/synthetic.h" #include "colmap/util/file.h" #include "colmap/util/testing.h" #include namespace colmap { namespace { class ParameterizedAutomaticReconstructionTests : public ::testing::TestWithParam< AutomaticReconstructionController::Mapper> {}; TEST_P(ParameterizedAutomaticReconstructionTests, Nominal) { const auto test_dir = CreateTestDir(); const auto workspace_path = test_dir / "workspace"; const auto image_path = test_dir / "images"; CreateDirIfNotExists(workspace_path); CreateDirIfNotExists(image_path); Reconstruction gt_reconstruction; SyntheticDatasetOptions synthetic_dataset_options; synthetic_dataset_options.num_rigs = 5; synthetic_dataset_options.num_cameras_per_rig = 1; synthetic_dataset_options.num_frames_per_rig = 1; synthetic_dataset_options.num_points3D = 200; synthetic_dataset_options.num_points2D_without_point3D = 10; SynthesizeDataset(synthetic_dataset_options, >_reconstruction); SynthesizeImages(SyntheticImageOptions(), gt_reconstruction, image_path); AutomaticReconstructionController::Options options; options.workspace_path = workspace_path; options.image_path = image_path; options.data_type = AutomaticReconstructionController::DataType::INDIVIDUAL; options.quality = AutomaticReconstructionController::Quality::LOW; options.single_camera = false; options.dense = false; // Disable dense reconstruction to avoid GPU options.use_gpu = false; options.random_seed = 1; options.mapper = GetParam(); auto reconstruction_manager = std::make_shared(); AutomaticReconstructionController controller(options, reconstruction_manager); controller.Setup(); controller.Start(); controller.Wait(); EXPECT_EQ(reconstruction_manager->Size(), 1); EXPECT_THAT(*reconstruction_manager->Get(0), ReconstructionNear(gt_reconstruction, /*max_rotation_error_deg=*/0.6, /*max_proj_center_error=*/0.1, /*max_scale_error=*/std::nullopt, /*num_obs_tolerance=*/0.9, /*align=*/true)); } // TODO: Add GLOBAL mapper test. Currently excluded because the test produces // fewer observations than expected. The global pipeline is tested separately // in global_pipeline_test.cc. INSTANTIATE_TEST_SUITE_P( AutomaticReconstructionTests, ParameterizedAutomaticReconstructionTests, ::testing::Values(AutomaticReconstructionController::Mapper::INCREMENTAL, AutomaticReconstructionController::Mapper::HIERARCHICAL)); } // namespace } // namespace colmap colmap-4.2.0/src/colmap/controllers/base_option_manager.cc000066400000000000000000000257641524536416500237340ustar00rootroot00000000000000// Copyright (c), ETH Zurich and UNC Chapel Hill. // All rights reserved. // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are met: // // * Redistributions of source code must retain the above copyright // notice, this list of conditions and the following disclaimer. // // * Redistributions in binary form must reproduce the above copyright // notice, this list of conditions and the following disclaimer in the // documentation and/or other materials provided with the distribution. // // * Neither the name of ETH Zurich and UNC Chapel Hill nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE // ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE // LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR // CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF // SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS // INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN // CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) // ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE // POSSIBILITY OF SUCH DAMAGE. #include "colmap/controllers/base_option_manager.h" #include "colmap/math/random.h" #include "colmap/util/file.h" #include "colmap/util/string.h" #include #include namespace config = boost::program_options; namespace colmap { BaseOptionManager::BaseOptionManager(bool add_project_options) { project_path = std::make_shared(); database_path = std::make_shared(); image_path = std::make_shared(); ResetImpl(/*reset_logging=*/true); desc_->add_options()("help,h", ""); if (add_project_options) { desc_->add_options()("project_path", config::value()); } AddRandomOptions(); AddLogOptions(); } void BaseOptionManager::AddRandomOptions() { if (added_random_options_) { return; } added_random_options_ = true; AddDefaultOption("default_random_seed", &kDefaultPRNGSeed); } void BaseOptionManager::AddLogOptions() { if (added_log_options_) { return; } added_log_options_ = true; AddDefaultOption( "log_target", &log_target_, "{stderr, stdout, file, stderr_and_file}"); // Directory for log files. If empty, glog uses $GOOGLE_LOG_DIR, /tmp, or // %TEMP%. AddDefaultOption("log_path", &FLAGS_log_dir); AddDefaultOption("log_level", &FLAGS_v); AddDefaultOption("log_severity", &FLAGS_minloglevel, "0:INFO, 1:WARNING, 2:ERROR, 3:FATAL"); #if COLMAP_GLOG_HAS_COLOR_SUPPORT AddDefaultOption("log_color", &FLAGS_colorlogtostderr); #endif } void BaseOptionManager::AddDatabaseOptions() { if (added_database_options_) { return; } added_database_options_ = true; AddRequiredOption("database_path", database_path.get()); } void BaseOptionManager::AddImageOptions() { if (added_image_options_) { return; } added_image_options_ = true; AddRequiredOption("image_path", image_path.get()); } void BaseOptionManager::Reset(bool reset_logging) { ResetImpl(reset_logging); } void BaseOptionManager::ResetOptions(const bool reset_paths) { auto saved_project_path = std::move(*project_path); auto saved_database_path = std::move(*database_path); auto saved_image_path = std::move(*image_path); // Re-register all options to update raw pointers, since subclass // ResetOptions() may reallocate internal sub-objects. Reset(/*reset_logging=*/false); AddAllOptions(); if (!reset_paths) { *project_path = std::move(saved_project_path); *database_path = std::move(saved_database_path); *image_path = std::move(saved_image_path); } } void BaseOptionManager::ResetImpl(bool reset_logging) { if (reset_logging) { log_target_ = "stderr_and_file"; FLAGS_log_dir = ""; FLAGS_v = 0; FLAGS_minloglevel = 0; #if COLMAP_GLOG_HAS_COLOR_SUPPORT FLAGS_colorlogtostderr = true; #endif ApplyLogFlags(); } const bool kResetPaths = true; ResetOptionsImpl(kResetPaths); desc_ = std::make_shared(); options_bool_.clear(); options_int_.clear(); options_double_.clear(); options_string_.clear(); options_path_.clear(); added_random_options_ = false; added_log_options_ = false; added_database_options_ = false; added_image_options_ = false; } void BaseOptionManager::ResetOptionsImpl(const bool reset_paths) { if (reset_paths) { *project_path = ""; *database_path = ""; *image_path = ""; } } bool BaseOptionManager::Check() { bool success = true; if (added_database_options_) { const auto database_parent_path = GetParentDir(*database_path); success = success && CHECK_OPTION_IMPL(!ExistsDir(*database_path)) && CHECK_OPTION_IMPL(database_parent_path.empty() || ExistsDir(database_parent_path)); } if (added_image_options_) { success = success && CHECK_OPTION_IMPL(ExistsDir(*image_path)); } return success; } void BaseOptionManager::PostParse() { // Default implementation does nothing. Subclasses can override. } void BaseOptionManager::ApplyEnumConversions() { for (const auto& info : enum_options_) { info->apply(); } } void BaseOptionManager::ApplyLogFlags() { FLAGS_logtostderr = false; #if COLMAP_GLOG_HAS_STDOUT_SUPPORT FLAGS_logtostdout = false; #endif FLAGS_alsologtostderr = false; if (log_target_ == "stderr") { FLAGS_logtostderr = true; } else if (log_target_ == "stdout") { #if COLMAP_GLOG_HAS_STDOUT_SUPPORT FLAGS_logtostdout = true; #else LOG(WARNING) << "log_target=stdout requires glog >= 0.6. " "Falling back to stderr."; FLAGS_logtostderr = true; #endif } else if (log_target_ == "file") { } else if (log_target_ == "stderr_and_file") { FLAGS_alsologtostderr = true; } else { LOG(ERROR) << "Invalid log_target: " << log_target_ << ". Falling back to stderr_and_file."; FLAGS_alsologtostderr = true; } #if COLMAP_GLOG_HAS_STDOUT_SUPPORT FLAGS_colorlogtostdout = FLAGS_colorlogtostderr; #endif if (!FLAGS_log_dir.empty() && (log_target_ == "file" || log_target_ == "stderr_and_file")) { CreateDirIfNotExists(FLAGS_log_dir); } } void BaseOptionManager::PrintHelp() const { LOG(INFO) << "Options can either be specified via command-line or by " "defining them in a .ini project file.\n" << *desc_; } void BaseOptionManager::AddAllOptions() { AddRandomOptions(); AddLogOptions(); AddDatabaseOptions(); AddImageOptions(); } bool BaseOptionManager::Parse(const int argc, char** argv) { config::variables_map vmap; try { config::store(config::parse_command_line(argc, argv, *desc_), vmap); if (vmap.count("help")) { PrintHelp(); // NOLINTNEXTLINE(concurrency-mt-unsafe) exit(EXIT_SUCCESS); } if (vmap.count("project_path")) { *project_path = vmap["project_path"].as(); if (!Read(*project_path)) { return false; } } else { vmap.notify(); } ApplyEnumConversions(); ApplyLogFlags(); PostParse(); } catch (std::exception& exc) { LOG(ERROR) << "Failed to parse options - " << exc.what() << "."; return false; } catch (...) { LOG(ERROR) << "Failed to parse options for unknown reason."; return false; } if (!Check()) { LOG(ERROR) << "Invalid options provided."; return false; } return true; } bool BaseOptionManager::Read(const std::filesystem::path& path, bool allow_unregistered) { config::variables_map vmap; if (!ExistsFile(path)) { LOG(ERROR) << "Configuration file does not exist."; return false; } try { std::ifstream file(path); THROW_CHECK_FILE_OPEN(file, path); const config::parsed_options parsed_options = config::parse_config_file(file, *desc_, allow_unregistered); config::store(parsed_options, vmap); if (allow_unregistered) { for (const auto& option : parsed_options.options) { if (option.unregistered) { LOG(WARNING) << "Unrecognized option key: " << option.string_key; } } } vmap.notify(); ApplyEnumConversions(); } catch (std::exception& e) { LOG(ERROR) << "Failed to parse options " << e.what() << "."; return false; } catch (...) { LOG(ERROR) << "Failed to parse options for unknown reason."; return false; } return true; } bool BaseOptionManager::ReRead(const std::filesystem::path& path, bool reset_logging, bool allow_unregistered) { Reset(reset_logging); AddAllOptions(); return Read(path, allow_unregistered); } void BaseOptionManager::Write(const std::filesystem::path& path) const { boost::property_tree::ptree pt; // First, put all options without a section and then those with a section. // This is necessary as otherwise older Boost versions will write the // options without a section in between other sections and therefore // the errors will be assigned to the wrong section if read later. for (const auto& [key, value] : options_bool_) { if (!StringContains(key, ".")) { pt.put(key, *value); } } for (const auto& [key, value] : options_int_) { if (!StringContains(key, ".")) { pt.put(key, *value); } } for (const auto& [key, value] : options_double_) { if (!StringContains(key, ".")) { pt.put(key, *value); } } for (const auto& [key, value] : options_string_) { if (!StringContains(key, ".")) { pt.put(key, *value); } } for (const auto& [key, value] : options_path_) { if (!StringContains(key, ".")) { pt.put(key, value->string()); } } for (const auto& [key, value] : options_bool_) { if (StringContains(key, ".")) { pt.put(key, *value); } } for (const auto& [key, value] : options_int_) { if (StringContains(key, ".")) { pt.put(key, *value); } } for (const auto& [key, value] : options_double_) { if (StringContains(key, ".")) { pt.put(key, *value); } } for (const auto& [key, value] : options_string_) { if (StringContains(key, ".")) { pt.put(key, *value); } } for (const auto& [key, value] : options_path_) { if (StringContains(key, ".")) { pt.put(key, value->string()); } } std::ofstream file(path); THROW_CHECK_FILE_OPEN(file, path); // Ensure that we don't lose any precision by storing in text. file.precision(17); boost::property_tree::write_ini(file, pt); file.close(); } } // namespace colmap colmap-4.2.0/src/colmap/controllers/base_option_manager.h000066400000000000000000000236551524536416500235730ustar00rootroot00000000000000// Copyright (c), ETH Zurich and UNC Chapel Hill. // All rights reserved. // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are met: // // * Redistributions of source code must retain the above copyright // notice, this list of conditions and the following disclaimer. // // * Redistributions in binary form must reproduce the above copyright // notice, this list of conditions and the following disclaimer in the // documentation and/or other materials provided with the distribution. // // * Neither the name of ETH Zurich and UNC Chapel Hill nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE // ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE // LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR // CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF // SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS // INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN // CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) // ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE // POSSIBILITY OF SUCH DAMAGE. #pragma once #include "colmap/util/logging.h" #include "colmap/util/types.h" #include #include #include #include #include #include #include namespace colmap { // Base class for option managers providing core infrastructure for // command-line parsing, configuration file I/O, and option registration. class BaseOptionManager { public: NON_COPYABLE(BaseOptionManager) explicit BaseOptionManager(bool add_project_options = true); BaseOptionManager(BaseOptionManager&&) = default; BaseOptionManager& operator=(BaseOptionManager&&) = default; virtual ~BaseOptionManager() = default; void AddRandomOptions(); void AddLogOptions(); void AddDatabaseOptions(); void AddImageOptions(); template void AddRequiredOption(const std::string& name, T* option, const std::string& help_text = ""); template void AddDefaultOption(const std::string& name, T* option, const std::string& help_text = ""); // Register an enum option with automatic string-to-enum conversion. // Uses the ToString/FromString functions generated by MAKE_ENUM_CLASS. // The conversion is applied automatically after parsing. // // Example: // MAKE_ENUM_CLASS(MyEnum, 0, VALUE_A, VALUE_B); // AddDefaultEnumOption("my_option", // &options.my_enum, // MyEnumToString, // MyEnumFromString); template void AddDefaultEnumOption(const std::string& name, EnumT* option, std::string_view (*to_string_fn)(EnumT), EnumT (*from_string_fn)(std::string_view), const std::string& help_text = ""); // Reset all internal state. If reset_logging is true, restore glog defaults. // Higher-level applications may override the logging configuration. virtual void Reset(bool reset_logging = true); virtual void ResetOptions(bool reset_paths); virtual bool Check(); bool Parse(int argc, char** argv); virtual bool Read(const std::filesystem::path& path, bool allow_unregistered = true); bool ReRead(const std::filesystem::path& path, bool reset_logging = true, bool allow_unregistered = true); void Write(const std::filesystem::path& path) const; std::shared_ptr project_path; std::shared_ptr database_path; std::shared_ptr image_path; protected: template void RegisterOption(const std::string& name, const T* option); // Hook for subclasses to perform post-parse processing. // Called after successful parsing but before Check(). virtual void PostParse(); // Hook for subclasses to print custom help message. virtual void PrintHelp() const; // Hook for subclasses to add all their options. Called by ReRead(). // Base implementation adds common options (random, log, database, image). // Subclasses should call BaseOptionManager::AddAllOptions() first. virtual void AddAllOptions(); std::shared_ptr desc_; // Log destination choice: {stderr, stdout, file, stderr_and_file}. std::string log_target_ = "stderr_and_file"; std::vector> options_bool_; std::vector> options_int_; std::vector> options_double_; std::vector> options_string_; std::vector> options_path_; // Storage for enum options: string value and conversion callback. // Uses unique_ptr for pointer stability when the vector grows. struct EnumOptionInfo { std::string value; // String value for parsing std::function apply; // Callback to apply string->enum conversion }; std::vector> enum_options_; bool added_random_options_ = false; bool added_log_options_ = false; bool added_database_options_ = false; bool added_image_options_ = false; private: // Non-virtual implementations called from constructor and virtual methods. // These avoid the clang-tidy warning about virtual calls during construction. void ResetImpl(bool reset_logging); void ResetOptionsImpl(bool reset_paths); // Apply string->enum conversions for all registered enum options. void ApplyEnumConversions(); // Map simplified log output options to glog flags. void ApplyLogFlags(); }; template void BaseOptionManager::AddRequiredOption(const std::string& name, T* option, const std::string& help_text) { if constexpr (std::is_same::value) { // Boost program options does not support std::filesystem::path by default. // We treat it as a string and manualy convert it using a notifier. desc_->add_options()( name.c_str(), boost::program_options::value()->required()->notifier( [option](const std::string& val) { *option = val; }), help_text.c_str()); } else { desc_->add_options()(name.c_str(), boost::program_options::value(option)->required(), help_text.c_str()); } RegisterOption(name, option); } template void BaseOptionManager::AddDefaultOption(const std::string& name, T* option, const std::string& help_text) { if constexpr (std::is_floating_point::value) { desc_->add_options()( name.c_str(), boost::program_options::value(option)->default_value( *option, StringPrintf("%.3g", *option)), help_text.c_str()); } else if constexpr (std::is_same::value) { // Boost program options does not support std::filesystem::path by default. // We treat it as a string and manualy convert it using a notifier. desc_->add_options()( name.c_str(), boost::program_options::value() ->default_value(option->string()) ->notifier([option](const std::string& val) { *option = val; }), help_text.c_str()); } else { desc_->add_options()( name.c_str(), boost::program_options::value(option)->default_value(*option), help_text.c_str()); } RegisterOption(name, option); } template void BaseOptionManager::RegisterOption(const std::string& name, const T* option) { if constexpr (std::is_same::value) { options_bool_.emplace_back(name, reinterpret_cast(option)); } else if constexpr (std::is_same::value) { options_int_.emplace_back(name, reinterpret_cast(option)); } else if constexpr (std::is_same::value) { options_double_.emplace_back(name, reinterpret_cast(option)); } else if constexpr (std::is_same::value) { options_string_.emplace_back(name, reinterpret_cast(option)); } else if constexpr (std::is_same::value) { options_path_.emplace_back( name, reinterpret_cast(option)); } else { static_assert(always_false::value, "Unsupported option type"); } } template void BaseOptionManager::AddDefaultEnumOption( const std::string& name, EnumT* option, std::string_view (*to_string_fn)(EnumT), EnumT (*from_string_fn)(std::string_view), const std::string& help_text) { // Create storage for this enum option (unique_ptr for pointer stability) auto info = std::make_unique(); info->value = std::string(to_string_fn(*option)); EnumOptionInfo* info_ptr = info.get(); info->apply = [info_ptr, option, from_string_fn]() { *option = from_string_fn(info_ptr->value); }; // Register as a string option pointing to our storage AddDefaultOption(name, &info->value, help_text); enum_options_.push_back(std::move(info)); } } // namespace colmap colmap-4.2.0/src/colmap/controllers/base_option_manager_test.cc000066400000000000000000000421211524536416500247550ustar00rootroot00000000000000// Copyright (c), ETH Zurich and UNC Chapel Hill. // All rights reserved. // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are met: // // * Redistributions of source code must retain the above copyright // notice, this list of conditions and the following disclaimer. // // * Redistributions in binary form must reproduce the above copyright // notice, this list of conditions and the following disclaimer in the // documentation and/or other materials provided with the distribution. // // * Neither the name of ETH Zurich and UNC Chapel Hill nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE // ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE // LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR // CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF // SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS // INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN // CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) // ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE // POSSIBILITY OF SUCH DAMAGE. #include "colmap/controllers/base_option_manager.h" #include "colmap/util/enum_utils.h" #include "colmap/util/file.h" #include "colmap/util/testing.h" #include #include namespace colmap { namespace { // Test enum for AddDefaultEnumOption tests MAKE_ENUM_CLASS(TestEnumType, 0, VALUE_A, VALUE_B, VALUE_C); TEST(BaseOptionManager, Reset) { BaseOptionManager options; *options.database_path = "/test/path"; *options.image_path = "/test/images"; options.AddDatabaseOptions(); options.AddImageOptions(); EXPECT_EQ(*options.database_path, "/test/path"); EXPECT_EQ(*options.image_path, "/test/images"); options.Reset(); EXPECT_TRUE(options.database_path->empty()); EXPECT_TRUE(options.image_path->empty()); } TEST(BaseOptionManager, ResetOptions) { BaseOptionManager options; *options.database_path = "/test/path"; *options.image_path = "/test/images"; options.ResetOptions(/*reset_paths=*/true); EXPECT_TRUE(options.database_path->empty()); EXPECT_TRUE(options.image_path->empty()); *options.database_path = "/test/path"; *options.image_path = "/test/images"; options.ResetOptions(/*reset_paths=*/false); EXPECT_EQ(*options.database_path, "/test/path"); EXPECT_EQ(*options.image_path, "/test/images"); } TEST(BaseOptionManager, AddOptionsIdempotent) { BaseOptionManager options; // Adding options multiple times should not cause issues options.AddLogOptions(); options.AddLogOptions(); options.AddRandomOptions(); options.AddRandomOptions(); options.AddDatabaseOptions(); options.AddDatabaseOptions(); options.AddImageOptions(); options.AddImageOptions(); // If idempotency is not maintained, the above would cause errors SUCCEED(); } TEST(BaseOptionManager, WriteAndRead) { const auto test_dir = CreateTestDir(); const auto config_path = test_dir / "config.ini"; // Create necessary directories CreateDirIfNotExists(test_dir / "images"); bool bool_option_write = true; int int_option_write = 42; double double_option_write = 3.14; std::string string_option_write = "foobar"; std::string section_option_write = "section"; TestEnumType enum_option_write = TestEnumType::VALUE_B; // Create and configure a BaseOptionManager BaseOptionManager options_write; options_write.AddDatabaseOptions(); options_write.AddImageOptions(); options_write.AddDefaultOption("bool_option", &bool_option_write); options_write.AddDefaultOption("int_option", &int_option_write); options_write.AddDefaultOption("double_option", &double_option_write); options_write.AddDefaultOption("string_option", &string_option_write); options_write.AddDefaultOption("Section.option", §ion_option_write); options_write.AddDefaultEnumOption("enum_option", &enum_option_write, TestEnumTypeToString, TestEnumTypeFromString); *options_write.database_path = test_dir / "database.db"; *options_write.image_path = test_dir / "images"; // Write to file options_write.Write(config_path); EXPECT_TRUE(ExistsFile(config_path)); bool bool_option_read = false; int int_option_read = -1; double double_option_read = 0; std::string string_option_read; std::string section_option_read; TestEnumType enum_option_read = TestEnumType::VALUE_A; // Read from file BaseOptionManager options_read; options_read.AddDatabaseOptions(); options_read.AddImageOptions(); options_read.AddDefaultOption("bool_option", &bool_option_read); options_read.AddDefaultOption("int_option", &int_option_read); options_read.AddDefaultOption("double_option", &double_option_read); options_read.AddDefaultOption("string_option", &string_option_read); options_read.AddDefaultOption("Section.option", §ion_option_read); options_read.AddDefaultEnumOption("enum_option", &enum_option_read, TestEnumTypeToString, TestEnumTypeFromString); EXPECT_TRUE(options_read.Read(config_path)); // Verify that values were read correctly EXPECT_EQ(*options_read.database_path, *options_write.database_path); EXPECT_EQ(*options_read.image_path, *options_write.image_path); EXPECT_EQ(bool_option_read, bool_option_write); EXPECT_EQ(int_option_read, int_option_write); EXPECT_EQ(double_option_read, double_option_write); EXPECT_EQ(string_option_read, string_option_write); EXPECT_EQ(section_option_read, section_option_write); EXPECT_EQ(enum_option_read, enum_option_write); } TEST(BaseOptionManager, ReadWithUnregisteredOptions) { const auto test_dir = CreateTestDir(); const auto config_path = test_dir / "config.ini"; CreateDirIfNotExists(test_dir / "images"); std::ofstream file(config_path); file << "database_path=" << (test_dir / "database.db").string() << "\n"; file << "image_path=" << (test_dir / "images").string() << "\n"; file << "unknown_option=foobar\n"; file.close(); BaseOptionManager options; options.AddDatabaseOptions(); options.AddImageOptions(); EXPECT_TRUE(options.Read(config_path, /*allow_unregistered=*/true)); EXPECT_FALSE(options.Read(config_path, /*allow_unregistered=*/false)); EXPECT_EQ(*options.database_path, test_dir / "database.db"); EXPECT_EQ(*options.image_path, test_dir / "images"); } TEST(BaseOptionManager, ReRead) { const auto test_dir = CreateTestDir(); const auto config_path = test_dir / "config.ini"; // Create necessary directories CreateDirIfNotExists(test_dir / "images"); // Create and write initial config BaseOptionManager options_write; options_write.AddDatabaseOptions(); options_write.AddImageOptions(); *options_write.database_path = test_dir / "database.db"; *options_write.image_path = test_dir / "images"; options_write.Write(config_path); // Read with ReRead BaseOptionManager options_read; EXPECT_TRUE(options_read.ReRead(config_path)); // Verify values EXPECT_EQ(*options_read.database_path, *options_write.database_path); EXPECT_EQ(*options_read.image_path, *options_write.image_path); } TEST(BaseOptionManager, ReadNonExistentFile) { BaseOptionManager options; options.AddDatabaseOptions(); options.AddImageOptions(); EXPECT_FALSE(options.Read("/path/that/does/not/exist.ini")); } TEST(BaseOptionManager, Check) { const auto test_dir = CreateTestDir(); BaseOptionManager options; options.AddDatabaseOptions(); options.AddImageOptions(); // Should fail with non-existent paths *options.database_path = test_dir / "database.db"; *options.image_path = "/path/that/does/not/exist"; EXPECT_FALSE(options.Check()); // Should succeed with valid paths CreateDirIfNotExists(test_dir / "images"); *options.image_path = test_dir / "images"; EXPECT_TRUE(options.Check()); } TEST(BaseOptionManager, CheckDatabaseParentDir) { const auto test_dir = CreateTestDir(); BaseOptionManager options; options.AddDatabaseOptions(); // Should succeed when database parent dir exists *options.database_path = test_dir / "database.db"; EXPECT_TRUE(options.Check()); // Should fail when database path is a directory CreateDirIfNotExists(test_dir / "bad_database"); *options.database_path = test_dir / "bad_database"; EXPECT_FALSE(options.Check()); } TEST(BaseOptionManager, ParseWithOptions) { const auto test_dir = CreateTestDir(); CreateDirIfNotExists(test_dir / "images"); BaseOptionManager options; options.AddDatabaseOptions(); options.AddImageOptions(); const auto database_path = test_dir / "database.db"; const auto image_path = test_dir / "images"; // Create argv with additional options const std::vector args = { "colmap", "--database_path", database_path.string(), "--image_path", image_path.string(), }; std::vector argv; argv.reserve(args.size()); for (auto& arg : args) { argv.push_back(const_cast(arg.c_str())); } EXPECT_TRUE(options.Parse(argv.size(), argv.data())); // Verify parsed values EXPECT_EQ(*options.database_path, database_path); EXPECT_EQ(*options.image_path, image_path); } TEST(BaseOptionManager, ParseWithProjectPath) { const auto test_dir = CreateTestDir(); const auto config_path = test_dir / "config.ini"; CreateDirIfNotExists(test_dir / "images"); // Create and write a config file BaseOptionManager options_write; options_write.AddDatabaseOptions(); options_write.AddImageOptions(); *options_write.database_path = test_dir / "database.db"; *options_write.image_path = test_dir / "images"; options_write.Write(config_path); // Parse using project_path BaseOptionManager options; options.AddDatabaseOptions(); options.AddImageOptions(); const std::vector args = { "colmap", "--project_path", config_path.string(), }; std::vector argv; argv.reserve(args.size()); for (auto& arg : args) { argv.push_back(const_cast(arg.c_str())); } EXPECT_TRUE(options.Parse(argv.size(), argv.data())); // Verify values were loaded from config file EXPECT_EQ(*options.database_path, *options_write.database_path); EXPECT_EQ(*options.image_path, *options_write.image_path); } TEST(BaseOptionManager, ParseEmptyArguments) { BaseOptionManager options; const std::vector args = {"colmap"}; std::vector argv; argv.reserve(args.size()); for (auto& arg : args) { argv.push_back(const_cast(arg.c_str())); } // Should succeed with no required options EXPECT_TRUE(options.Parse(argv.size(), argv.data())); } TEST(BaseOptionManager, ParseUnknownArgumentsFails) { const auto test_dir = CreateTestDir(); BaseOptionManager options; options.AddDatabaseOptions(); const auto database_path = test_dir / "database.db"; // Create argv with an unknown option const std::vector args = { "colmap", "--database_path", database_path.string(), "--unknown_option", "value", }; std::vector argv; argv.reserve(args.size()); for (auto& arg : args) { argv.push_back(const_cast(arg.c_str())); } // Should return false when encountering unknown option EXPECT_FALSE(options.Parse(argv.size(), argv.data())); } // Helper class to test enum options through BaseOptionManager class TestEnumOptionManager : public BaseOptionManager { public: TestEnumOptionManager() : BaseOptionManager(/*add_project_options=*/false) { AddDefaultEnumOption("test_enum", &test_enum_value, TestEnumTypeToString, TestEnumTypeFromString); } TestEnumType test_enum_value = TestEnumType::VALUE_A; }; TEST(BaseOptionManager, EnumOptionDefaultValue) { TestEnumOptionManager options; // Default value should be VALUE_A EXPECT_EQ(options.test_enum_value, TestEnumType::VALUE_A); // Parse with no enum option specified const std::vector args = {"test"}; std::vector argv; argv.reserve(args.size()); for (auto& arg : args) { argv.push_back(const_cast(arg.c_str())); } EXPECT_TRUE(options.Parse(argv.size(), argv.data())); // Should still be default value EXPECT_EQ(options.test_enum_value, TestEnumType::VALUE_A); } TEST(BaseOptionManager, EnumOptionParseFromCommandLine) { TestEnumOptionManager options; // Parse with enum option set to VALUE_B const std::vector args = {"test", "--test_enum", "VALUE_B"}; std::vector argv; argv.reserve(args.size()); for (auto& arg : args) { argv.push_back(const_cast(arg.c_str())); } EXPECT_TRUE(options.Parse(argv.size(), argv.data())); // Should be VALUE_B after parsing EXPECT_EQ(options.test_enum_value, TestEnumType::VALUE_B); } TEST(BaseOptionManager, EnumOptionParseFromCommandLineValueC) { TestEnumOptionManager options; // Parse with enum option set to VALUE_C const std::vector args = {"test", "--test_enum", "VALUE_C"}; std::vector argv; argv.reserve(args.size()); for (auto& arg : args) { argv.push_back(const_cast(arg.c_str())); } EXPECT_TRUE(options.Parse(argv.size(), argv.data())); // Should be VALUE_C after parsing EXPECT_EQ(options.test_enum_value, TestEnumType::VALUE_C); } TEST(BaseOptionManager, EnumOptionInvalidValue) { TestEnumOptionManager options; // Parse with invalid enum value const std::vector args = { "test", "--test_enum", "INVALID_VALUE"}; std::vector argv; argv.reserve(args.size()); for (auto& arg : args) { argv.push_back(const_cast(arg.c_str())); } // Should fail due to invalid enum value EXPECT_FALSE(options.Parse(argv.size(), argv.data())); } // Helper class to test enum options with non-default initial value class TestEnumOptionManagerWithValueB : public BaseOptionManager { public: TestEnumOptionManagerWithValueB() : BaseOptionManager(/*add_project_options=*/false) { AddDefaultEnumOption("test_enum", &test_enum_value, TestEnumTypeToString, TestEnumTypeFromString); } TestEnumType test_enum_value = TestEnumType::VALUE_B; }; TEST(BaseOptionManager, EnumOptionNonDefaultInitialValue) { TestEnumOptionManagerWithValueB options; // Default value should be VALUE_B (non-default) EXPECT_EQ(options.test_enum_value, TestEnumType::VALUE_B); // Parse with no enum option specified const std::vector args = {"test"}; std::vector argv; argv.reserve(args.size()); for (auto& arg : args) { argv.push_back(const_cast(arg.c_str())); } EXPECT_TRUE(options.Parse(argv.size(), argv.data())); // Should still be VALUE_B (the initial value) EXPECT_EQ(options.test_enum_value, TestEnumType::VALUE_B); } TEST(BaseOptionManager, LogOptions) { BaseOptionManager options; options.AddLogOptions(); auto VerifyLogState = [&](const std::string& output, bool expect_stderr, bool expect_stdout, bool expect_stderr_and_file) { const std::vector args = {"colmap", "--log_target", output}; std::vector argv; argv.reserve(args.size()); for (const auto& arg : args) { argv.push_back(const_cast(arg.c_str())); } EXPECT_TRUE(options.Parse(argv.size(), argv.data())); EXPECT_EQ(FLAGS_logtostderr, expect_stderr); #if COLMAP_GLOG_HAS_STDOUT_SUPPORT EXPECT_EQ(FLAGS_logtostdout, expect_stdout); #endif EXPECT_EQ(FLAGS_alsologtostderr, expect_stderr_and_file); }; VerifyLogState("stderr", /*expect_stderr=*/true, /*expect_stdout=*/false, /*expect_and_file=*/false); #if COLMAP_GLOG_HAS_STDOUT_SUPPORT VerifyLogState("stdout", /*expect_stderr=*/false, /*expect_stdout=*/true, /*expect_and_file=*/false); #else // glog < 0.6 does not support FLAGS_logtostdout, falls back to stderr. VerifyLogState("stdout", /*expect_stderr=*/true, /*expect_stdout=*/false, /*expect_and_file=*/false); #endif VerifyLogState("file", /*expect_stderr=*/false, /*expect_stdout=*/false, /*expect_and_file=*/false); VerifyLogState("stderr_and_file", /*expect_stderr=*/false, /*expect_stdout=*/false, /*expect_and_file=*/true); VerifyLogState("invalid", /*expect_stderr=*/false, /*expect_stdout=*/false, /*expect_and_file=*/true); } } // namespace } // namespace colmap colmap-4.2.0/src/colmap/controllers/bundle_adjustment.cc000066400000000000000000000065771524536416500234500ustar00rootroot00000000000000// Copyright (c), ETH Zurich and UNC Chapel Hill. // All rights reserved. // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are met: // // * Redistributions of source code must retain the above copyright // notice, this list of conditions and the following disclaimer. // // * Redistributions in binary form must reproduce the above copyright // notice, this list of conditions and the following disclaimer in the // documentation and/or other materials provided with the distribution. // // * Neither the name of ETH Zurich and UNC Chapel Hill nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE // ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE // LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR // CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF // SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS // INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN // CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) // ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE // POSSIBILITY OF SUCH DAMAGE. #include "colmap/controllers/bundle_adjustment.h" #include "colmap/estimators/bundle_adjustment_ceres.h" #include "colmap/sfm/observation_manager.h" #include "colmap/util/misc.h" #include "colmap/util/timer.h" namespace colmap { BundleAdjustmentController::BundleAdjustmentController( const OptionManager& options, std::shared_ptr reconstruction) : options_(options), reconstruction_(std::move(reconstruction)) {} void BundleAdjustmentController::Run() { THROW_CHECK_NOTNULL(reconstruction_); LOG_HEADING1("Global bundle adjustment"); Timer run_timer; run_timer.Start(); if (reconstruction_->NumRegFrames() == 0) { LOG(ERROR) << "Need at least one registered frame."; return; } if (CheckIfStopped()) { return; } // Avoid degeneracies in bundle adjustment. ObservationManager(*reconstruction_).FilterObservationsWithNegativeDepth(); BundleAdjustmentOptions ba_options = *options_.bundle_adjustment; ba_options.check_if_stopped = [this]() { return CheckIfStopped(); }; // Configure bundle adjustment. BundleAdjustmentConfig ba_config; for (const image_t image_id : reconstruction_->RegImageIds()) { ba_config.AddImage(image_id); } // Fixing the gauge with two cameras leads to a more stable optimization // with fewer steps as compared to fixing three points. // TODO(jsch): Investigate whether it is safe to not fix the gauge at all, // as initial experiments show that it is even faster. ba_config.FixGauge(BundleAdjustmentGauge::TWO_CAMS_FROM_WORLD); // Run bundle adjustment. std::unique_ptr bundle_adjuster = CreateDefaultBundleAdjuster(ba_options, ba_config, *reconstruction_); bundle_adjuster->Solve(); reconstruction_->UpdatePoint3DErrors(); run_timer.PrintMinutes(); } } // namespace colmap colmap-4.2.0/src/colmap/controllers/bundle_adjustment.h000066400000000000000000000042241524536416500232750ustar00rootroot00000000000000// Copyright (c), ETH Zurich and UNC Chapel Hill. // All rights reserved. // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are met: // // * Redistributions of source code must retain the above copyright // notice, this list of conditions and the following disclaimer. // // * Redistributions in binary form must reproduce the above copyright // notice, this list of conditions and the following disclaimer in the // documentation and/or other materials provided with the distribution. // // * Neither the name of ETH Zurich and UNC Chapel Hill nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE // ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE // LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR // CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF // SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS // INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN // CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) // ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE // POSSIBILITY OF SUCH DAMAGE. #pragma once #include "colmap/controllers/option_manager.h" #include "colmap/scene/reconstruction.h" #include "colmap/util/base_controller.h" namespace colmap { // Class that controls the global bundle adjustment procedure. class BundleAdjustmentController : public BaseController { public: BundleAdjustmentController(const OptionManager& options, std::shared_ptr reconstruction); void Run(); private: const OptionManager& options_; std::shared_ptr reconstruction_; }; } // namespace colmap colmap-4.2.0/src/colmap/controllers/bundle_adjustment_test.cc000066400000000000000000000101641524536416500244720ustar00rootroot00000000000000// Copyright (c), ETH Zurich and UNC Chapel Hill. // All rights reserved. // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are met: // // * Redistributions of source code must retain the above copyright // notice, this list of conditions and the following disclaimer. // // * Redistributions in binary form must reproduce the above copyright // notice, this list of conditions and the following disclaimer in the // documentation and/or other materials provided with the distribution. // // * Neither the name of ETH Zurich and UNC Chapel Hill nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE // ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE // LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR // CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF // SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS // INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN // CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) // ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE // POSSIBILITY OF SUCH DAMAGE. #include "colmap/controllers/bundle_adjustment.h" #include "colmap/controllers/option_manager.h" #include "colmap/scene/reconstruction_matchers.h" #include "colmap/scene/synthetic.h" #include namespace colmap { namespace { TEST(BundleAdjustmentController, EmptyReconstruction) { auto reconstruction = std::make_shared(); OptionManager options; BundleAdjustmentController controller(options, reconstruction); EXPECT_NO_THROW(controller.Run()); EXPECT_EQ(reconstruction->NumRegImages(), 0); EXPECT_EQ(reconstruction->NumPoints3D(), 0); } TEST(BundleAdjustmentController, StopsBeforeOptimization) { Reconstruction gt_reconstruction; SyntheticDatasetOptions synthetic_options; synthetic_options.num_rigs = 1; synthetic_options.num_cameras_per_rig = 1; synthetic_options.num_frames_per_rig = 3; synthetic_options.num_points3D = 50; SynthesizeDataset(synthetic_options, >_reconstruction); auto reconstruction = std::make_shared(gt_reconstruction); OptionManager options; BundleAdjustmentController controller(options, reconstruction); bool stop_checked = false; controller.SetCheckIfStoppedFunc([&stop_checked]() { stop_checked = true; return true; }); controller.Run(); EXPECT_TRUE(stop_checked); EXPECT_THAT(*reconstruction, ReconstructionEq(gt_reconstruction)); } TEST(BundleAdjustmentController, Reconstruction) { Reconstruction gt_reconstruction; SyntheticDatasetOptions synthetic_options; synthetic_options.num_rigs = 1; synthetic_options.num_cameras_per_rig = 2; synthetic_options.num_frames_per_rig = 3; synthetic_options.num_points3D = 100; SynthesizeDataset(synthetic_options, >_reconstruction); auto reconstruction = std::make_shared(gt_reconstruction); SyntheticNoiseOptions noise_options; noise_options.point2D_stddev = 0.1; noise_options.point3D_stddev = 0.1; noise_options.rig_from_world_rotation_stddev = 0.1; noise_options.rig_from_world_translation_stddev = 0.1; SynthesizeNoise(noise_options, reconstruction.get()); OptionManager options; BundleAdjustmentController controller(options, reconstruction); controller.Run(); EXPECT_THAT(gt_reconstruction, ReconstructionNear(*reconstruction, /*max_rotation_error_deg=*/0.1, /*max_proj_center_error=*/0.1, /*max_scale_error=*/std::nullopt, /*num_obs_tolerance=*/0.0)); } } // namespace } // namespace colmap colmap-4.2.0/src/colmap/controllers/feature_extraction.cc000066400000000000000000000606471524536416500236320ustar00rootroot00000000000000// Copyright (c), ETH Zurich and UNC Chapel Hill. // All rights reserved. // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are met: // // * Redistributions of source code must retain the above copyright // notice, this list of conditions and the following disclaimer. // // * Redistributions in binary form must reproduce the above copyright // notice, this list of conditions and the following disclaimer in the // documentation and/or other materials provided with the distribution. // // * Neither the name of ETH Zurich and UNC Chapel Hill nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE // ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE // LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR // CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF // SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS // INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN // CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) // ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE // POSSIBILITY OF SUCH DAMAGE. #include "colmap/controllers/feature_extraction.h" #include "colmap/feature/sift.h" #include "colmap/scene/database.h" #include "colmap/util/cuda.h" #include "colmap/util/file.h" #include "colmap/util/misc.h" #include "colmap/util/opengl_utils.h" #include "colmap/util/timer.h" #include namespace colmap { namespace { void ScaleKeypoints(int bitmap_width, int bitmap_height, size_t camera_width, size_t camera_height, FeatureKeypoints* keypoints) { if (static_cast(bitmap_width) != camera_width || static_cast(bitmap_height) != camera_height) { const float scale_x = static_cast(camera_width) / bitmap_width; const float scale_y = static_cast(camera_height) / bitmap_height; for (auto& keypoint : *keypoints) { keypoint.Rescale(scale_x, scale_y); } } } void MaskFeatures(const Bitmap& mask, FeatureKeypoints* keypoints, FeatureDescriptors* descriptors) { size_t out_index = 0; for (size_t i = 0; i < keypoints->size(); ++i) { const auto color = mask.GetPixel(static_cast(keypoints->at(i).x), static_cast(keypoints->at(i).y)); if (!color || color->r == 0) { // Delete this keypoint by not copying it to the output. } else { // Retain this keypoint by copying it to the output index (in case this // index differs from its current position). if (out_index != i) { keypoints->at(out_index) = keypoints->at(i); for (int col = 0; col < descriptors->data.cols(); ++col) { descriptors->data(out_index, col) = descriptors->data(i, col); } } out_index += 1; } } keypoints->resize(out_index); descriptors->data.conservativeResize(out_index, descriptors->data.cols()); } struct ImageData { ImageReader::Status status = ImageReader::Status::FAILURE; Rig rig; Camera camera; Image image; PosePrior pose_prior; std::unique_ptr bitmap; std::unique_ptr mask; FeatureKeypoints keypoints; FeatureDescriptors descriptors; }; class ImageResizerThread : public Thread { public: ImageResizerThread(int max_image_size, JobQueue* input_queue, JobQueue* output_queue) : max_image_size_(max_image_size), input_queue_(input_queue), output_queue_(output_queue) { THROW_CHECK_GT(max_image_size_, 0); } private: void Run() override { while (true) { if (IsStopped()) { break; } auto input_job = input_queue_->Pop(); if (input_job.IsValid()) { auto& image_data = input_job.Data(); if (image_data.status == ImageReader::Status::SUCCESS) { image_data.bitmap->Thumbnail(max_image_size_); } output_queue_->Push(std::move(image_data)); } else { break; } } } const int max_image_size_; JobQueue* input_queue_; JobQueue* output_queue_; }; class FeatureExtractorThread : public Thread { public: FeatureExtractorThread(const FeatureExtractionOptions& extraction_options, const std::shared_ptr& camera_mask, JobQueue* input_queue, JobQueue* output_queue) : extraction_options_(extraction_options), camera_mask_(camera_mask), input_queue_(input_queue), output_queue_(output_queue) { THROW_CHECK(extraction_options_.Check()); if (extraction_options_.RequiresOpenGL()) { opengl_context_ = std::make_unique(); } } private: void Run() override { if (opengl_context_ != nullptr) { THROW_CHECK(opengl_context_->MakeCurrent()); } std::unique_ptr extractor = FeatureExtractor::Create(extraction_options_); if (extractor == nullptr) { LOG(ERROR) << "Failed to create feature extractor."; SignalInvalidSetup(); return; } SignalValidSetup(); while (true) { if (IsStopped()) { break; } auto input_job = input_queue_->Pop(); if (input_job.IsValid()) { auto& image_data = input_job.Data(); if (image_data.status == ImageReader::Status::SUCCESS) { const int orig_width = image_data.bitmap->Width(); const int orig_height = image_data.bitmap->Height(); const int rot90 = image_data.pose_prior.HasGravity() ? ComputeRot90FromGravity(image_data.pose_prior.gravity) : 0; if (rot90 > 0) { image_data.bitmap->Rot90(rot90); } if (extractor->Extract(*image_data.bitmap, &image_data.keypoints, &image_data.descriptors)) { if (rot90 > 0) { const int w = image_data.bitmap->Width(); const int h = image_data.bitmap->Height(); for (auto& kp : image_data.keypoints) { kp.Rot90(4 - rot90, w, h); } } ScaleKeypoints(orig_width, orig_height, image_data.camera.width, image_data.camera.height, &image_data.keypoints); if (camera_mask_) { MaskFeatures(*camera_mask_, &image_data.keypoints, &image_data.descriptors); } if (image_data.mask) { MaskFeatures(*image_data.mask, &image_data.keypoints, &image_data.descriptors); } } else { image_data.status = ImageReader::Status::FAILURE; } } // Release the memory, since it is not used afterwards. // Warning: Do not reset the pointer, as we use it later // to check if a mask exists for logging purposes. *image_data.bitmap = Bitmap(); if (image_data.mask) { *image_data.mask = Bitmap(); } output_queue_->Push(std::move(image_data)); } else { break; } } } const FeatureExtractionOptions extraction_options_; std::shared_ptr camera_mask_; std::unique_ptr opengl_context_; JobQueue* input_queue_; JobQueue* output_queue_; }; class FeatureWriterThread : public Thread { public: FeatureWriterThread(FeatureExtractorType extractor_type, size_t num_images, Database* database, JobQueue* input_queue) : extractor_type_str_(FeatureExtractorTypeToString(extractor_type)), num_images_(num_images), database_(database), input_queue_(input_queue) {} private: void Run() override { size_t image_index = 0; while (true) { if (IsStopped()) { break; } auto input_job = input_queue_->Pop(); if (input_job.IsValid()) { auto& image_data = input_job.Data(); image_index += 1; LOG(INFO) << StringPrintf( "Processed file [%d/%d]", image_index, num_images_); LOG(INFO) << StringPrintf(" Name: %s", image_data.image.Name().c_str()); if (image_data.status != ImageReader::Status::SUCCESS) { LOG(WARNING) << image_data.image.Name() << " " << ImageReader::StatusToString(image_data.status); continue; } LOG(INFO) << StringPrintf(" Dimensions: %d x %d", image_data.camera.width, image_data.camera.height); LOG(INFO) << StringPrintf(" Camera: #%d - %s", image_data.camera.camera_id, image_data.camera.ModelName().c_str()); if (image_data.camera.IsPerspective()) { LOG(INFO) << StringPrintf( " Focal Length: %.2fpx%s", image_data.camera.MeanFocalLength(), image_data.camera.has_prior_focal_length ? " (Prior)" : ""); } LOG(INFO) << " Features: " << image_data.keypoints.size() << " (" << extractor_type_str_ << ")"; if (image_data.mask) { LOG(INFO) << " Mask: Yes"; } DatabaseTransaction database_transaction(database_); if (image_data.image.ImageId() == kInvalidImageId) { image_data.image.SetImageId(database_->WriteImage(image_data.image)); if (image_data.pose_prior.HasPosition() || image_data.pose_prior.HasGravity()) { if (image_data.pose_prior.HasPosition()) { LOG(INFO) << StringPrintf( " GPS: LAT=%.3f, LON=%.3f, ALT=%.3f", image_data.pose_prior.position.x(), image_data.pose_prior.position.y(), image_data.pose_prior.position.z()); } if (image_data.pose_prior.HasGravity()) { LOG(INFO) << StringPrintf( " Gravity: X=%.3f, Y=%.3f, Z=%.3f", image_data.pose_prior.gravity.x(), image_data.pose_prior.gravity.y(), image_data.pose_prior.gravity.z()); } image_data.pose_prior.corr_data_id = image_data.image.DataId(); image_data.pose_prior.pose_prior_id = database_->WritePosePrior(image_data.pose_prior); } Frame frame; frame.SetRigId(image_data.rig.RigId()); frame.AddDataId(image_data.image.DataId()); database_->WriteFrame(frame); } if (!database_->ExistsKeypoints(image_data.image.ImageId())) { database_->WriteKeypoints(image_data.image.ImageId(), image_data.keypoints); } if (!database_->ExistsDescriptors(image_data.image.ImageId())) { database_->WriteDescriptors(image_data.image.ImageId(), image_data.descriptors); } } else { break; } } } const std::string extractor_type_str_; const size_t num_images_; Database* database_; JobQueue* input_queue_; }; // Feature extraction class to extract features for all images in a directory. class FeatureExtractorController : public Thread { public: FeatureExtractorController(const std::filesystem::path& database_path, const ImageReaderOptions& reader_options, const FeatureExtractionOptions& extraction_options) : reader_options_(reader_options), extraction_options_(extraction_options), database_(Database::Open(database_path)), image_reader_(reader_options_, database_.get()) { THROW_CHECK(reader_options_.Check()); THROW_CHECK(extraction_options_.Check()); std::shared_ptr camera_mask; if (!reader_options_.camera_mask_path.empty()) { if (ExistsFile(reader_options_.camera_mask_path)) { camera_mask = std::make_shared(); if (!camera_mask->Read(reader_options_.camera_mask_path, /*as_rgb=*/false)) { LOG(ERROR) << "Failed to read invalid mask file at: " << reader_options_.camera_mask_path << ". No mask is going to be used."; camera_mask.reset(); } } else { LOG(ERROR) << "Mask at " << reader_options_.camera_mask_path << " does not exist."; } } const int num_threads = GetEffectiveNumThreads(extraction_options_.num_threads); THROW_CHECK_GT(num_threads, 0); // Make sure that we only have limited number of objects in the queue to // avoid excess in memory usage since images and features take lots of // memory. constexpr int kQueueSize = 1; resizer_queue_ = std::make_unique>(kQueueSize); extractor_queue_ = std::make_unique>(kQueueSize); writer_queue_ = std::make_unique>(kQueueSize); const int max_image_size = extraction_options_.EffMaxImageSize(); for (int i = 0; i < num_threads; ++i) { resizers_.emplace_back(std::make_unique( max_image_size, resizer_queue_.get(), extractor_queue_.get())); } // Determine if GPU extraction should be used. SIFT GPU extraction is not // supported with domain_size_pooling or estimate_affine_shape, which // require CPU-based covariant SIFT. auto worker_extraction_options = extraction_options_; if (extraction_options_.type == FeatureExtractorType::SIFT && (extraction_options_.sift->domain_size_pooling || extraction_options_.sift->estimate_affine_shape)) { worker_extraction_options.use_gpu = false; } if (worker_extraction_options.use_gpu) { std::vector gpu_indices = CSVToVector(extraction_options_.gpu_index); THROW_CHECK_GT(gpu_indices.size(), 0); #if defined(COLMAP_CUDA_ENABLED) if (gpu_indices.size() == 1 && gpu_indices[0] == -1) { const int num_cuda_devices = GetNumCudaDevices(); THROW_CHECK_GT(num_cuda_devices, 0); gpu_indices.resize(num_cuda_devices); std::iota(gpu_indices.begin(), gpu_indices.end(), 0); } #endif // COLMAP_CUDA_ENABLED // Prevent nested threading, as we multi-thread at the controller level. worker_extraction_options.num_threads = std::max(num_threads / static_cast(gpu_indices.size()), 1); for (const int gpu_index : gpu_indices) { worker_extraction_options.gpu_index = std::to_string(gpu_index); extractors_.emplace_back( std::make_unique(worker_extraction_options, camera_mask, extractor_queue_.get(), writer_queue_.get())); } } else { const static FeatureExtractionOptions kDefaultExtractionOptions; if (extraction_options_.num_threads == -1 && extraction_options_.type == FeatureExtractorType::SIFT && extraction_options_.max_image_size == kDefaultExtractionOptions.max_image_size && extraction_options_.sift->first_octave == kDefaultExtractionOptions.sift->first_octave) { LOG(WARNING) << "Your current options use the maximum number of " "threads on the machine to extract features. Extracting SIFT " "features on the CPU can consume a lot of RAM per thread for " "large images. Consider reducing the maximum image size and/or " "the first octave or manually limit the number of extraction " "threads. Ignore this warning, if your machine has sufficient " "memory for the current settings."; } int num_extractors = 0; switch (extraction_options_.type) { case FeatureExtractorType::SIFT: // Prevent nested threading, as we multi-thread at the controller // level as SIFT extraction doesn't require much RAM per extractor. num_extractors = num_threads; worker_extraction_options.num_threads = 1; break; case FeatureExtractorType::ALIKED_N16ROT: case FeatureExtractorType::ALIKED_N32: case FeatureExtractorType::LOMA_B: case FeatureExtractorType::LOMA_B128: // Use a single extractor with parallelization per image because // ALIKED/LoMa require a lot of RAM per extractor (LoMa's DeDoDe-G // descriptor graph alone is ~1.3GB) and would otherwise OOM with // one extractor instance per CPU thread. num_extractors = 1; worker_extraction_options.num_threads = num_threads; break; default: LOG(FATAL_THROW) << "Unknown feature extractor type: " << FeatureExtractorTypeToString( extraction_options_.type); } THROW_CHECK_GT(num_extractors, 0); for (int i = 0; i < num_extractors; ++i) { extractors_.emplace_back( std::make_unique(worker_extraction_options, camera_mask, extractor_queue_.get(), writer_queue_.get())); } } writer_ = std::make_unique(extraction_options_.type, image_reader_.NumImages(), database_.get(), writer_queue_.get()); } private: void Run() override { LOG_HEADING1("Feature extraction"); Timer run_timer; run_timer.Start(); for (auto& resizer : resizers_) { resizer->Start(); } for (auto& extractor : extractors_) { extractor->Start(); } writer_->Start(); for (auto& extractor : extractors_) { if (!extractor->CheckValidSetup()) { return; } } while (image_reader_.NextIndex() < image_reader_.NumImages()) { if (IsStopped()) { resizer_queue_->Stop(); extractor_queue_->Stop(); resizer_queue_->Clear(); extractor_queue_->Clear(); break; } ImageData image_data; image_data.bitmap = std::make_unique(); Bitmap mask; image_data.status = image_reader_.Next(&image_data.rig, &image_data.camera, &image_data.image, &image_data.pose_prior, image_data.bitmap.get(), &mask); if (!mask.IsEmpty()) { image_data.mask = std::make_unique(std::move(mask)); } if (image_data.status != ImageReader::Status::SUCCESS) { // Release the memory, since it is not used afterwards. *image_data.bitmap = Bitmap(); if (image_data.mask) { *image_data.mask = Bitmap(); } } THROW_CHECK(resizer_queue_->Push(std::move(image_data))); } resizer_queue_->Wait(); resizer_queue_->Stop(); for (auto& resizer : resizers_) { resizer->Wait(); } extractor_queue_->Wait(); extractor_queue_->Stop(); for (auto& extractor : extractors_) { extractor->Wait(); } writer_queue_->Wait(); writer_queue_->Stop(); writer_->Wait(); run_timer.PrintMinutes(); } const ImageReaderOptions reader_options_; const FeatureExtractionOptions extraction_options_; std::shared_ptr database_; ImageReader image_reader_; std::vector> resizers_; std::vector> extractors_; std::unique_ptr writer_; std::unique_ptr> resizer_queue_; std::unique_ptr> extractor_queue_; std::unique_ptr> writer_queue_; }; // Import features from text files. Each image must have a corresponding text // file with the same name and an additional ".txt" suffix. // Currently hard-coded to support SIFT features. class FeatureImporterController : public Thread { public: FeatureImporterController(const std::filesystem::path& database_path, const ImageReaderOptions& reader_options, const std::filesystem::path& import_path) : database_path_(database_path), reader_options_(reader_options), import_path_(import_path) {} private: void Run() override { LOG_HEADING1("Feature import"); Timer run_timer; run_timer.Start(); if (!ExistsDir(import_path_)) { LOG(ERROR) << "Import directory does not exist."; return; } auto database = Database::Open(database_path_); ImageReader image_reader(reader_options_, database.get()); while (image_reader.NextIndex() < image_reader.NumImages()) { if (IsStopped()) { break; } LOG(INFO) << StringPrintf("Processing file [%d/%d]", image_reader.NextIndex() + 1, image_reader.NumImages()); // Load image data and possibly save camera to database. Rig rig; Camera camera; Image image; PosePrior pose_prior; Bitmap bitmap; if (image_reader.Next( &rig, &camera, &image, &pose_prior, &bitmap, nullptr) != ImageReader::Status::SUCCESS) { continue; } const auto path = import_path_ / (image.Name() + ".txt"); if (ExistsFile(path)) { FeatureKeypoints keypoints; FeatureDescriptors descriptors; LoadSiftFeaturesFromTextFile(path, &keypoints, &descriptors); LOG(INFO) << "Features: " << keypoints.size() << "(Imported SIFT)"; DatabaseTransaction database_transaction(database.get()); if (image.ImageId() == kInvalidImageId) { image.SetImageId(database->WriteImage(image)); if (pose_prior.HasPosition() || pose_prior.HasGravity()) { pose_prior.corr_data_id = image.DataId(); pose_prior.pose_prior_id = database->WritePosePrior(pose_prior); } Frame frame; frame.SetRigId(rig.RigId()); frame.AddDataId(image.DataId()); database->WriteFrame(frame); } if (!database->ExistsKeypoints(image.ImageId())) { database->WriteKeypoints(image.ImageId(), keypoints); } if (!database->ExistsDescriptors(image.ImageId())) { database->WriteDescriptors(image.ImageId(), descriptors); } } else { LOG(INFO) << "SKIP: No features found at " << path; } } run_timer.PrintMinutes(); } const std::filesystem::path database_path_; const ImageReaderOptions reader_options_; const std::filesystem::path import_path_; }; } // namespace std::unique_ptr CreateFeatureExtractorController( const std::filesystem::path& database_path, const ImageReaderOptions& reader_options, const FeatureExtractionOptions& extraction_options) { return std::make_unique( database_path, reader_options, extraction_options); } std::unique_ptr CreateFeatureImporterController( const std::filesystem::path& database_path, const ImageReaderOptions& reader_options, const std::filesystem::path& import_path) { return std::make_unique( database_path, reader_options, import_path); } } // namespace colmap colmap-4.2.0/src/colmap/controllers/feature_extraction.h000066400000000000000000000046241524536416500234650ustar00rootroot00000000000000// Copyright (c), ETH Zurich and UNC Chapel Hill. // All rights reserved. // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are met: // // * Redistributions of source code must retain the above copyright // notice, this list of conditions and the following disclaimer. // // * Redistributions in binary form must reproduce the above copyright // notice, this list of conditions and the following disclaimer in the // documentation and/or other materials provided with the distribution. // // * Neither the name of ETH Zurich and UNC Chapel Hill nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE // ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE // LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR // CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF // SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS // INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN // CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) // ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE // POSSIBILITY OF SUCH DAMAGE. #pragma once #include "colmap/controllers/image_reader.h" #include "colmap/feature/extractor.h" #include "colmap/util/threading.h" #include namespace colmap { // Reads images from a folder, extracts features, and writes them to database. std::unique_ptr CreateFeatureExtractorController( const std::filesystem::path& database_path, const ImageReaderOptions& reader_options, const FeatureExtractionOptions& extraction_options); // Import features from text files. Each image must have a corresponding text // file with the same name and an additional ".txt" suffix. std::unique_ptr CreateFeatureImporterController( const std::filesystem::path& database_path, const ImageReaderOptions& reader_options, const std::filesystem::path& import_path); } // namespace colmap colmap-4.2.0/src/colmap/controllers/feature_extraction_test.cc000066400000000000000000000230441524536416500246570ustar00rootroot00000000000000// Copyright (c), ETH Zurich and UNC Chapel Hill. // All rights reserved. // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are met: // // * Redistributions of source code must retain the above copyright // notice, this list of conditions and the following disclaimer. // // * Redistributions in binary form must reproduce the above copyright // notice, this list of conditions and the following disclaimer in the // documentation and/or other materials provided with the distribution. // // * Neither the name of ETH Zurich and UNC Chapel Hill nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE // ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE // LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR // CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF // SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS // INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN // CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) // ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE // POSSIBILITY OF SUCH DAMAGE. #include "colmap/controllers/feature_extraction.h" #include "colmap/scene/database.h" #include "colmap/util/file.h" #include "colmap/util/testing.h" #include #include namespace colmap { namespace { Bitmap CreateTestBitmap() { Bitmap bitmap(100, 100, /*as_rgb=*/false); bitmap.Fill(BitmapColor(0)); for (int y = 30; y < 70; ++y) { for (int x = 30; x < 70; ++x) { bitmap.SetPixel(x, y, BitmapColor(255)); } } return bitmap; } TEST(CreateFeatureExtractorController, Nominal) { const auto test_dir = CreateTestDir(); const auto database_path = test_dir / "database.db"; const auto image_path = test_dir / "images"; CreateDirIfNotExists(image_path); // Create test images const int kNumImages = 2; const Bitmap test_bitmap = CreateTestBitmap(); for (int i = 0; i < kNumImages; ++i) { test_bitmap.Write(image_path / (std::to_string(i) + ".png")); } // Set up options ImageReaderOptions reader_options; reader_options.image_path = image_path; FeatureExtractionOptions extraction_options; extraction_options.use_gpu = false; extraction_options.num_threads = kNumImages; // Create and run the controller auto controller = CreateFeatureExtractorController( database_path, reader_options, extraction_options); ASSERT_NE(controller, nullptr); controller->Start(); controller->Wait(); // Verify results in database auto database = Database::Open(database_path); const std::vector images = database->ReadAllImages(); EXPECT_EQ(images.size(), kNumImages); for (const auto& image : images) { EXPECT_TRUE(database->ExistsKeypoints(image.ImageId())); EXPECT_TRUE(database->ExistsDescriptors(image.ImageId())); const FeatureKeypoints keypoints = database->ReadKeypoints(image.ImageId()); const FeatureDescriptors descriptors = database->ReadDescriptors(image.ImageId()); // Check that features were extracted EXPECT_GT(keypoints.size(), 0); EXPECT_EQ(keypoints.size(), descriptors.data.rows()); EXPECT_EQ(descriptors.type, FeatureExtractorType::SIFT); EXPECT_EQ(descriptors.data.cols(), 128); } } TEST(CreateFeatureExtractorController, WithCameraMask) { const auto test_dir = CreateTestDir(); const auto database_path = test_dir / "database.db"; const auto image_path = test_dir / "images"; const auto mask_path = test_dir / "mask.png"; CreateDirIfNotExists(image_path); // Create test image with features const Bitmap test_bitmap = CreateTestBitmap(); test_bitmap.Write(image_path / "test.png"); // Create a mask that only allows the center region (white = keep, black = // mask) The test bitmap has a white square from (30,30) to (70,70) We'll // create a mask that only keeps a smaller region Bitmap mask_bitmap(100, 100, /*as_rgb=*/false); mask_bitmap.Fill(BitmapColor(0)); // Start with all black (masked) // Only keep center region (40,40) to (60,60) for (int y = 40; y < 60; ++y) { for (int x = 40; x < 60; ++x) { mask_bitmap.SetPixel(x, y, BitmapColor(255)); // White = keep } } mask_bitmap.Write(mask_path); // Extract features without mask first to get baseline ImageReaderOptions reader_options_no_mask; reader_options_no_mask.image_path = image_path; FeatureExtractionOptions extraction_options; extraction_options.use_gpu = false; extraction_options.num_threads = 1; auto controller = CreateFeatureExtractorController( database_path, reader_options_no_mask, extraction_options); ASSERT_NE(controller, nullptr); controller->Start(); controller->Wait(); auto database = Database::Open(database_path); std::vector images = database->ReadAllImages(); ASSERT_EQ(images.size(), 1); const size_t num_features_no_mask = database->ReadKeypoints(images[0].ImageId()).size(); EXPECT_GT(num_features_no_mask, 0); // Now extract with mask const auto database_path_masked = test_dir / "database_masked.db"; ImageReaderOptions reader_options_masked; reader_options_masked.image_path = image_path; reader_options_masked.camera_mask_path = mask_path; controller = CreateFeatureExtractorController( database_path_masked, reader_options_masked, extraction_options); ASSERT_NE(controller, nullptr); controller->Start(); controller->Wait(); auto database_masked = Database::Open(database_path_masked); images = database_masked->ReadAllImages(); ASSERT_EQ(images.size(), 1); const FeatureKeypoints keypoints_masked = database_masked->ReadKeypoints(images[0].ImageId()); const FeatureDescriptors descriptors_masked = database_masked->ReadDescriptors(images[0].ImageId()); const size_t num_features_masked = keypoints_masked.size(); // With mask, should have fewer features EXPECT_LT(num_features_masked, num_features_no_mask); EXPECT_GT(num_features_masked, 0); // But should still have some features // All remaining keypoints should be within the unmasked region (40-60, 40-60) for (const auto& kp : keypoints_masked) { EXPECT_GE(kp.x, 40.0f); EXPECT_LT(kp.x, 60.0f); EXPECT_GE(kp.y, 40.0f); EXPECT_LT(kp.y, 60.0f); } // Descriptors should match keypoints count EXPECT_EQ(descriptors_masked.data.rows(), keypoints_masked.size()); EXPECT_EQ(descriptors_masked.type, FeatureExtractorType::SIFT); EXPECT_EQ(descriptors_masked.data.cols(), 128); } TEST(CreateFeatureImporterController, Nominal) { const auto test_dir = CreateTestDir(); const auto database_path = test_dir / "database.db"; const auto image_path = test_dir / "images"; const auto import_path = test_dir / "features"; CreateDirIfNotExists(image_path); CreateDirIfNotExists(import_path); const int kNumImages = 2; const int kNumFeatures = 3; // Create test images const Bitmap test_bitmap = CreateTestBitmap(); for (int i = 0; i < kNumImages; ++i) { test_bitmap.Write(image_path / (std::to_string(i) + ".png")); } // Create feature text files for each image for (int i = 0; i < kNumImages; ++i) { const auto feature_file = import_path / (std::to_string(i) + ".png.txt"); std::ofstream file(feature_file); ASSERT_TRUE(file.is_open()); // Write header: num_features dimension const int kDimension = 128; file << kNumFeatures << " " << kDimension << "\n"; // Write features: x y scale orientation descriptor[0..127] for (int j = 0; j < kNumFeatures; ++j) { // Keypoint data file << (10.0f + j * 5.0f) << " " // x << (20.0f + j * 5.0f) << " " // y << (1.5f + j * 0.1f) << " " // scale << (0.5f + j * 0.2f); // orientation // Descriptor data (128 values) for (int k = 0; k < kDimension; ++k) { file << " " << ((j * kDimension + k) % 256); } file << "\n"; } } // Set up options ImageReaderOptions reader_options; reader_options.image_path = image_path; // Create and run the controller auto controller = CreateFeatureImporterController( database_path, reader_options, import_path); ASSERT_NE(controller, nullptr); controller->Start(); controller->Wait(); // Verify results in database auto database = Database::Open(database_path); const std::vector images = database->ReadAllImages(); EXPECT_EQ(images.size(), kNumImages); for (const auto& image : images) { EXPECT_TRUE(database->ExistsKeypoints(image.ImageId())); EXPECT_TRUE(database->ExistsDescriptors(image.ImageId())); const FeatureKeypoints keypoints = database->ReadKeypoints(image.ImageId()); const FeatureDescriptors descriptors = database->ReadDescriptors(image.ImageId()); // Check that features were imported correctly EXPECT_EQ(keypoints.size(), kNumFeatures); EXPECT_EQ(descriptors.type, FeatureExtractorType::SIFT); EXPECT_EQ(descriptors.data.rows(), kNumFeatures); EXPECT_EQ(descriptors.data.cols(), 128); // Verify some keypoint values EXPECT_FLOAT_EQ(keypoints[0].x, 10.0f); EXPECT_FLOAT_EQ(keypoints[0].y, 20.0f); } } } // namespace } // namespace colmap colmap-4.2.0/src/colmap/controllers/feature_matching.cc000066400000000000000000000503541524536416500232360ustar00rootroot00000000000000// Copyright (c), ETH Zurich and UNC Chapel Hill. // All rights reserved. // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are met: // // * Redistributions of source code must retain the above copyright // notice, this list of conditions and the following disclaimer. // // * Redistributions in binary form must reproduce the above copyright // notice, this list of conditions and the following disclaimer in the // documentation and/or other materials provided with the distribution. // // * Neither the name of ETH Zurich and UNC Chapel Hill nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE // ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE // LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR // CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF // SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS // INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN // CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) // ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE // POSSIBILITY OF SUCH DAMAGE. #include "colmap/controllers/feature_matching.h" #include "colmap/controllers/feature_matching_utils.h" #include "colmap/estimators/two_view_geometry.h" #include "colmap/feature/matcher.h" #include "colmap/feature/utils.h" #include "colmap/scene/database.h" #include "colmap/util/file.h" #include "colmap/util/hash_containers.h" #include "colmap/util/misc.h" #include "colmap/util/timer.h" #include namespace colmap { namespace { void RigVerification(const std::shared_ptr& database, const std::shared_ptr& cache, const TwoViewGeometryOptions& geometry_options, const int num_threads) { NodeHashMap rigs; for (auto& rig : database->ReadAllRigs()) { rigs[rig.RigId()] = std::move(rig); } NodeHashMap image_to_frame_ids; for (const auto& frame : database->ReadAllFrames()) { for (const data_t& data_id : frame.ImageIds()) { image_to_frame_ids[data_id.id] = frame.FrameId(); } } struct FramePairStats { int num_image_pairs = 0; int num_matches = 0; }; std::map, FramePairStats> frame_pair_stats; for (const auto& [image_pair_id, pair_num_matches] : database->ReadNumMatches()) { if (pair_num_matches == 0) { continue; } const auto [image_id1, image_id2] = PairIdToImagePair(image_pair_id); frame_t frame_id1 = image_to_frame_ids.at(image_id1); frame_t frame_id2 = image_to_frame_ids.at(image_id2); if (frame_id1 > frame_id2) { std::swap(frame_id1, frame_id2); } auto& stats = frame_pair_stats[{frame_id1, frame_id2}]; stats.num_image_pairs += 1; stats.num_matches += pair_num_matches; } ThreadPool thread_pool(num_threads); for (const auto& [frame_pair, stats] : frame_pair_stats) { // If the frame pair has only matches between one pair of images, then // there is no need to run rig verification, as there are no rig // constraints. if (stats.num_image_pairs <= 1 || stats.num_matches < geometry_options.min_num_inliers) { continue; } thread_pool.AddTask([&cache, &rigs, geometry_options, frame_id1 = frame_pair.first, frame_id2 = frame_pair.second]() { const Frame& frame1 = cache->GetFrame(frame_id1); const Frame& frame2 = cache->GetFrame(frame_id2); const Rig& rig1 = rigs.at(frame1.RigId()); const Rig& rig2 = rigs.at(frame2.RigId()); NodeHashMap images; images.reserve(frame1.NumDataIds() + frame2.NumDataIds()); NodeHashMap cameras; cameras.reserve(images.size()); auto add_images_and_cameras = [&cache, &images, &cameras]( const Frame& frame) { for (const data_t& data_id : frame.ImageIds()) { Image& image = images[data_id.id]; image = cache->GetImage(data_id.id); image.SetPoints2D( FeatureKeypointsToPointsVector(*cache->GetKeypoints(data_id.id))); cameras[image.CameraId()] = cache->GetCamera(image.CameraId()); } }; add_images_and_cameras(frame1); add_images_and_cameras(frame2); std::vector, FeatureMatches>> matches; matches.reserve(frame1.NumDataIds() * frame2.NumDataIds()); for (const data_t& data_id1 : frame1.ImageIds()) { const image_t image_id1 = data_id1.id; for (const data_t& data_id2 : frame2.ImageIds()) { const image_t image_id2 = data_id2.id; // If verifying within the same frame, then skip redundant image // pairs, whereas different frames are guaranteed to have different // image pairs. Note that verifying within the same frame can be // useful when the images have some overlap but the matches between // image pairs are not enough alone but accumulating them over the // whole frame can lead to a successful verification. if ((frame_id1 == frame_id2 && image_id1 <= image_id2) || !cache->ExistsMatches(image_id1, image_id2)) { continue; } matches.emplace_back(std::make_pair(image_id1, image_id2), cache->GetMatches(image_id1, image_id2)); } } for (const auto& [image_pair, two_view_geometry] : EstimateRigTwoViewGeometries( rig1, rig2, images, cameras, matches, geometry_options)) { const auto& [image_id1, image_id2] = image_pair; cache->DeleteTwoViewGeometry(image_id1, image_id2); cache->WriteTwoViewGeometry(image_id1, image_id2, two_view_geometry); } }); } thread_pool.Wait(); } class FeatureMatcherThread : public Thread { public: template static std::unique_ptr Create( const typename PairGeneratorType::PairingOptions& pairing_options, const FeatureMatchingOptions& matching_options, const TwoViewGeometryOptions& geometry_options, const std::filesystem::path& database_path) { auto database = Database::Open(database_path); auto cache = std::make_shared( pairing_options.CacheSize(), database); return std::make_unique( matching_options, geometry_options, database, cache, [pairing_options, cache]() { return std::make_unique(pairing_options, cache); }); } using PairGeneratorFactory = std::function()>; FeatureMatcherThread(const FeatureMatchingOptions& matching_options, const TwoViewGeometryOptions& geometry_options, std::shared_ptr database, std::shared_ptr cache, PairGeneratorFactory pair_generator_factory) : matching_options_(matching_options), geometry_options_(geometry_options), database_(std::move(database)), cache_(std::move(cache)), pair_generator_factory_(std::move(pair_generator_factory)), matcher_(matching_options, geometry_options, cache_) { THROW_CHECK(matching_options.Check()); THROW_CHECK(geometry_options.Check()); } private: void Run() override { LOG_HEADING1("Feature matching & geometric verification"); Timer run_timer; run_timer.Start(); if (!matcher_.Setup()) { return; } std::unique_ptr pair_generator = THROW_CHECK_NOTNULL(pair_generator_factory_()); while (!pair_generator->HasFinished()) { if (IsStopped()) { run_timer.PrintMinutes(); return; } Timer timer; timer.Start(); const std::vector> image_pairs = pair_generator->Next(); matcher_.Match(image_pairs); LOG(INFO) << StringPrintf("in %.3fs", timer.ElapsedSeconds()); } run_timer.PrintMinutes(); // Notice that we run rig verification after feature matching, because // feature matching operates on pairs of images instead of pairs of frames. // Rig verification operates on pairs of frames and we require all image // pairs between two frames to be matched before running rig verification. if (!matching_options_.skip_geometric_verification && matching_options_.rig_verification) { run_timer.Restart(); LOG_HEADING1("Rig verification"); RigVerification( database_, cache_, geometry_options_, matching_options_.num_threads); run_timer.PrintMinutes(); } } const FeatureMatchingOptions matching_options_; const TwoViewGeometryOptions geometry_options_; const std::shared_ptr database_; const std::shared_ptr cache_; const PairGeneratorFactory pair_generator_factory_; FeatureMatcherController matcher_; }; class GeometricVerifierThread : public Thread { public: template static std::unique_ptr Create( const GeometricVerifierOptions& verifier_options, const typename PairGeneratorType::PairingOptions& pairing_options, const TwoViewGeometryOptions& geometry_options, const std::filesystem::path& database_path) { auto database = Database::Open(database_path); auto cache = std::make_shared( pairing_options.CacheSize(), database); return std::make_unique( verifier_options, geometry_options, database, cache, [pairing_options, cache]() { return std::make_unique(pairing_options, cache); }); } using PairGeneratorFactory = std::function()>; GeometricVerifierThread(const GeometricVerifierOptions& verifier_options, const TwoViewGeometryOptions& geometry_options, std::shared_ptr database, std::shared_ptr cache, PairGeneratorFactory pair_generator_factory) : geometry_options_(geometry_options), database_(std::move(database)), cache_(std::move(cache)), pair_generator_factory_(std::move(pair_generator_factory)), verifier_(verifier_options, geometry_options, cache_) { THROW_CHECK(geometry_options.Check()); } private: void Run() override { LOG_HEADING1("Geometric verification"); Timer run_timer; run_timer.Start(); if (!verifier_.Setup()) { return; } std::unique_ptr pair_generator = THROW_CHECK_NOTNULL(pair_generator_factory_()); while (!pair_generator->HasFinished()) { if (IsStopped()) { run_timer.PrintMinutes(); return; } Timer timer; timer.Start(); const std::vector> image_pairs = pair_generator->Next(); verifier_.Verify(image_pairs); LOG(INFO) << StringPrintf("in %.3fs", timer.ElapsedSeconds()); } if (verifier_.Options().rig_verification) { run_timer.Restart(); LOG_HEADING1("Rig verification"); RigVerification(database_, cache_, geometry_options_, verifier_.Options().num_threads); run_timer.PrintMinutes(); } run_timer.PrintMinutes(); } const TwoViewGeometryOptions geometry_options_; const std::shared_ptr database_; const std::shared_ptr cache_; const PairGeneratorFactory pair_generator_factory_; GeometricVerifierController verifier_; }; } // namespace std::unique_ptr CreateExhaustiveFeatureMatcher( const ExhaustivePairingOptions& pairing_options, const FeatureMatchingOptions& matching_options, const TwoViewGeometryOptions& geometry_options, const std::filesystem::path& database_path) { return FeatureMatcherThread::Create( pairing_options, matching_options, geometry_options, database_path); } std::unique_ptr CreateVocabTreeFeatureMatcher( const VocabTreePairingOptions& pairing_options, const FeatureMatchingOptions& matching_options, const TwoViewGeometryOptions& geometry_options, const std::filesystem::path& database_path) { return FeatureMatcherThread::Create( pairing_options, matching_options, geometry_options, database_path); } std::unique_ptr CreateSequentialFeatureMatcher( const SequentialPairingOptions& pairing_options, const FeatureMatchingOptions& matching_options, const TwoViewGeometryOptions& geometry_options, const std::filesystem::path& database_path) { return FeatureMatcherThread::Create( pairing_options, matching_options, geometry_options, database_path); } std::unique_ptr CreateSpatialFeatureMatcher( const SpatialPairingOptions& pairing_options, const FeatureMatchingOptions& matching_options, const TwoViewGeometryOptions& geometry_options, const std::filesystem::path& database_path) { return FeatureMatcherThread::Create( pairing_options, matching_options, geometry_options, database_path); } std::unique_ptr CreateTransitiveFeatureMatcher( const TransitivePairingOptions& pairing_options, const FeatureMatchingOptions& matching_options, const TwoViewGeometryOptions& geometry_options, const std::filesystem::path& database_path) { return FeatureMatcherThread::Create( pairing_options, matching_options, geometry_options, database_path); } std::unique_ptr CreateImagePairsFeatureMatcher( const ImportedPairingOptions& pairing_options, const FeatureMatchingOptions& matching_options, const TwoViewGeometryOptions& geometry_options, const std::filesystem::path& database_path) { return FeatureMatcherThread::Create( pairing_options, matching_options, geometry_options, database_path); } namespace { class FeaturePairsFeatureMatcher : public Thread { public: FeaturePairsFeatureMatcher(const FeaturePairsMatchingOptions& pairing_options, const FeatureMatchingOptions& matching_options, const TwoViewGeometryOptions& geometry_options, const std::filesystem::path& database_path) : options_(pairing_options), matching_options_(matching_options), geometry_options_(geometry_options), database_(Database::Open(database_path)), cache_(std::make_shared(/*cache_size=*/100, database_)) { THROW_CHECK(pairing_options.Check()); THROW_CHECK(matching_options.Check()); THROW_CHECK(geometry_options.Check()); } private: void Run() override { LOG_HEADING1("Importing matches"); Timer run_timer; run_timer.Start(); NodeHashMap image_name_to_image; image_name_to_image.reserve(cache_->GetImageIds().size()); for (const auto image_id : cache_->GetImageIds()) { const auto& image = cache_->GetImage(image_id); image_name_to_image.emplace(image.Name(), &image); } std::ifstream file(options_.match_list_path); THROW_CHECK_FILE_OPEN(file, options_.match_list_path); std::string line; while (std::getline(file, line)) { if (IsStopped()) { run_timer.PrintMinutes(); return; } StringTrim(&line); if (line.empty()) { continue; } std::istringstream line_stream(line); std::string image_name1, image_name2; try { line_stream >> image_name1 >> image_name2; } catch (...) { LOG(ERROR) << "Could not read image pair."; break; } LOG(INFO) << StringPrintf( "%s - %s", image_name1.c_str(), image_name2.c_str()); if (image_name_to_image.count(image_name1) == 0) { LOG(INFO) << StringPrintf("SKIP: Image %s not found in database.", image_name1.c_str()); break; } if (image_name_to_image.count(image_name2) == 0) { LOG(INFO) << StringPrintf("SKIP: Image %s not found in database.", image_name2.c_str()); break; } const Image& image1 = *image_name_to_image[image_name1]; const Image& image2 = *image_name_to_image[image_name2]; bool skip_pair = false; if (database_->ExistsTwoViewGeometry(image1.ImageId(), image2.ImageId())) { LOG(INFO) << "SKIP: Matches for image pair already exist in database."; skip_pair = true; } FeatureMatches matches; while (std::getline(file, line)) { StringTrim(&line); if (line.empty()) { break; } std::istringstream line_stream(line); FeatureMatch match; try { line_stream >> match.point2D_idx1 >> match.point2D_idx2; } catch (...) { LOG(ERROR) << "Cannot read feature matches."; break; } matches.push_back(match); } if (skip_pair) { continue; } const Camera& camera1 = cache_->GetCamera(image1.CameraId()); const Camera& camera2 = cache_->GetCamera(image2.CameraId()); TwoViewGeometry two_view_geometry; if (options_.verify_matches) { database_->WriteMatches(image1.ImageId(), image2.ImageId(), matches); const std::shared_ptr keypoints1 = cache_->GetKeypoints(image1.ImageId()); const std::shared_ptr keypoints2 = cache_->GetKeypoints(image2.ImageId()); two_view_geometry = EstimateTwoViewGeometry(camera1, FeatureKeypointsToPointsVector(*keypoints1), camera2, FeatureKeypointsToPointsVector(*keypoints2), std::move(matches), geometry_options_); } else { if (camera1.has_prior_focal_length && camera2.has_prior_focal_length) { two_view_geometry.config = TwoViewGeometry::CALIBRATED; } else { two_view_geometry.config = TwoViewGeometry::UNCALIBRATED; } two_view_geometry.inlier_matches = std::move(matches); } database_->WriteTwoViewGeometry( image1.ImageId(), image2.ImageId(), two_view_geometry); } run_timer.PrintMinutes(); } const FeaturePairsMatchingOptions options_; const FeatureMatchingOptions matching_options_; const TwoViewGeometryOptions geometry_options_; const std::shared_ptr database_; const std::shared_ptr cache_; }; } // namespace std::unique_ptr CreateFeaturePairsFeatureMatcher( const FeaturePairsMatchingOptions& pairing_options, const FeatureMatchingOptions& matching_options, const TwoViewGeometryOptions& geometry_options, const std::filesystem::path& database_path) { return std::make_unique( pairing_options, matching_options, geometry_options, database_path); } std::unique_ptr CreateGeometricVerifier( const GeometricVerifierOptions& verifier_options, const ExistingMatchedPairingOptions& pairing_options, const TwoViewGeometryOptions& geometry_options, const std::filesystem::path& database_path) { return GeometricVerifierThread::Create( verifier_options, pairing_options, geometry_options, database_path); } } // namespace colmap colmap-4.2.0/src/colmap/controllers/feature_matching.h000066400000000000000000000171051524536416500230750ustar00rootroot00000000000000// Copyright (c), ETH Zurich and UNC Chapel Hill. // All rights reserved. // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are met: // // * Redistributions of source code must retain the above copyright // notice, this list of conditions and the following disclaimer. // // * Redistributions in binary form must reproduce the above copyright // notice, this list of conditions and the following disclaimer in the // documentation and/or other materials provided with the distribution. // // * Neither the name of ETH Zurich and UNC Chapel Hill nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE // ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE // LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR // CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF // SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS // INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN // CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) // ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE // POSSIBILITY OF SUCH DAMAGE. #pragma once #include "colmap/controllers/pairing.h" #include "colmap/estimators/two_view_geometry.h" #include "colmap/feature/matcher.h" #include "colmap/util/threading.h" #include namespace colmap { // Exhaustively match images by processing each block in the exhaustive match // matrix in one batch: // // +----+----+-----------------> images[i] // |#000|0000| // |1#00|1000| <- Above the main diagonal, the block diagonal is not matched // |11#0|1100| ^ // |111#|1110| | // +----+----+ | // |1000|#000|\ | // |1100|1#00| \ One block | // |1110|11#0| / of image pairs | // |1111|111#|/ | // +----+----+ | // | ^ | // | | | // | Below the main diagonal, the block diagonal is matched <--------------+ // | // v // images[i] // // Pairs will only be matched if 1, to avoid duplicate pairs. Pairs with # // are on the main diagonal and denote pairs of the same image. std::unique_ptr CreateExhaustiveFeatureMatcher( const ExhaustivePairingOptions& pairing_options, const FeatureMatchingOptions& matching_options, const TwoViewGeometryOptions& geometry_options, const std::filesystem::path& database_path); // Match each image against its nearest neighbors using a vocabulary tree. std::unique_ptr CreateVocabTreeFeatureMatcher( const VocabTreePairingOptions& pairing_options, const FeatureMatchingOptions& matching_options, const TwoViewGeometryOptions& geometry_options, const std::filesystem::path& database_path); // Sequentially match images within neighborhood: // // +-------------------------------+-----------------------> images[i] // ^ | ^ // | Current image[i] | // | | | // +----------+-----------+ // | // Match image_i against // // image_[i - o, i + o] with o = [1 .. overlap] // image_[i - 2^o, i + 2^o] (for quadratic overlap) // // Sequential order is determined based on the image names in ascending order. // // Invoke loop detection if `(i mod loop_detection_period) == 0`, retrieve // most similar `loop_detection_num_images` images from vocabulary tree that // are at least `loop_detection_min_index_distance` away in the sequential // order, and perform matching and verification. std::unique_ptr CreateSequentialFeatureMatcher( const SequentialPairingOptions& pairing_options, const FeatureMatchingOptions& matching_options, const TwoViewGeometryOptions& geometry_options, const std::filesystem::path& database_path); // Match images against spatial nearest neighbors using prior location // information, e.g. provided manually or extracted from EXIF. std::unique_ptr CreateSpatialFeatureMatcher( const SpatialPairingOptions& pairing_options, const FeatureMatchingOptions& matching_options, const TwoViewGeometryOptions& geometry_options, const std::filesystem::path& database_path); // Match transitive image pairs in a database with existing feature matches. // This matcher transitively closes loops/triplets. For example, if image pairs // A-B and B-C match but A-C has not been matched, then this matcher attempts to // match A-C. This procedure is performed for multiple iterations. std::unique_ptr CreateTransitiveFeatureMatcher( const TransitivePairingOptions& pairing_options, const FeatureMatchingOptions& matching_options, const TwoViewGeometryOptions& geometry_options, const std::filesystem::path& database_path); // Match images manually specified in a list of image pairs. // // Read matches file with the following format: // // image_name1 image_name2 // image_name1 image_name3 // image_name2 image_name3 // ... // std::unique_ptr CreateImagePairsFeatureMatcher( const ImportedPairingOptions& pairing_options, const FeatureMatchingOptions& matching_options, const TwoViewGeometryOptions& geometry_options, const std::filesystem::path& database_path); // Import feature matches from a text file. // // Read matches file with the following format: // // image_name1 image_name2 // 0 1 // 1 2 // 2 3 // // image_name1 image_name3 // 0 1 // 1 2 // 2 3 // ... // std::unique_ptr CreateFeaturePairsFeatureMatcher( const FeaturePairsMatchingOptions& pairing_options, const FeatureMatchingOptions& matching_options, const TwoViewGeometryOptions& geometry_options, const std::filesystem::path& database_path); // Options for CreateGeometricVerifier struct GeometricVerifierOptions { // Number of threads for geometric verification. int num_threads = -1; // Whether to perform rig verification at the end. Unnecessary when we have // existing relative poses for each matched pair. bool rig_verification = false; // Whether to use the existing relative pose stored in TwoViewGeometry in the // database. If no TwoViewGeometry is found we will fall back to geometric // verification with RANSAC. bool use_existing_relative_pose = false; }; // Perform geometric verification of existing matched image pairs. std::unique_ptr CreateGeometricVerifier( const GeometricVerifierOptions& verifier_options, const ExistingMatchedPairingOptions& pairing_options, const TwoViewGeometryOptions& geometry_options, const std::filesystem::path& database_path); } // namespace colmap colmap-4.2.0/src/colmap/controllers/feature_matching_test.cc000066400000000000000000000440101524536416500242650ustar00rootroot00000000000000// Copyright (c), ETH Zurich and UNC Chapel Hill. // All rights reserved. // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are met: // // * Redistributions of source code must retain the above copyright // notice, this list of conditions and the following disclaimer. // // * Redistributions in binary form must reproduce the above copyright // notice, this list of conditions and the following disclaimer in the // documentation and/or other materials provided with the distribution. // // * Neither the name of ETH Zurich and UNC Chapel Hill nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE // ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE // LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR // CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF // SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS // INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN // CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) // ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE // POSSIBILITY OF SUCH DAMAGE. #include "colmap/controllers/feature_matching.h" #include "colmap/feature/types.h" #include "colmap/retrieval/visual_index.h" #include "colmap/scene/synthetic.h" #include "colmap/util/testing.h" #include #include namespace colmap { namespace { void CreateTestDatabase(int num_images, Database& database) { Reconstruction unused_reconstruction; SyntheticDatasetOptions synthetic_dataset_options; synthetic_dataset_options.num_rigs = num_images; synthetic_dataset_options.num_cameras_per_rig = 1; synthetic_dataset_options.num_frames_per_rig = 1; synthetic_dataset_options.num_points3D = 20; synthetic_dataset_options.num_points2D_without_point3D = 3; synthetic_dataset_options.prior_position = true; SynthesizeDataset( synthetic_dataset_options, &unused_reconstruction, &database); } std::unique_ptr CreateSyntheticVisualIndex() { auto visual_index = retrieval::VisualIndex::Create(); retrieval::VisualIndex::BuildOptions build_options; build_options.num_visual_words = 5; visual_index->Build( build_options, FeatureDescriptorsFloat(FeatureExtractorType::SIFT, FeatureDescriptorsFloatData::Random(50, 128))); return visual_index; } TEST(CreateExhaustiveFeatureMatcher, Nominal) { const auto test_dir = CreateTestDir(); const auto database_path = test_dir / "database.db"; auto database = Database::Open(database_path); CreateTestDatabase(/*num_images=*/4, *database); database->ClearMatches(); database->ClearTwoViewGeometries(); ExhaustivePairingOptions pairing_options; FeatureMatchingOptions matching_options; matching_options.use_gpu = false; matching_options.num_threads = 1; TwoViewGeometryOptions geometry_options; auto matcher = CreateExhaustiveFeatureMatcher( pairing_options, matching_options, geometry_options, database_path); ASSERT_NE(matcher, nullptr); matcher->Start(); matcher->Wait(); EXPECT_EQ(database->ReadAllMatches().size(), 6); EXPECT_EQ(database->ReadTwoViewGeometries().size(), 6); } TEST(CreateVocabTreeFeatureMatcher, Nominal) { const auto test_dir = CreateTestDir(); const auto database_path = test_dir / "database.db"; const auto vocab_tree_path = test_dir / "vocab_tree.bin"; auto database = Database::Open(database_path); CreateTestDatabase(/*num_images=*/4, *database); database->ClearMatches(); database->ClearTwoViewGeometries(); // Create vocab tree CreateSyntheticVisualIndex()->Write(vocab_tree_path); VocabTreePairingOptions pairing_options; pairing_options.vocab_tree_path = vocab_tree_path; pairing_options.num_images = 2; FeatureMatchingOptions matching_options; matching_options.use_gpu = false; matching_options.num_threads = 1; TwoViewGeometryOptions geometry_options; auto matcher = CreateVocabTreeFeatureMatcher( pairing_options, matching_options, geometry_options, database_path); ASSERT_NE(matcher, nullptr); matcher->Start(); matcher->Wait(); // Each image should match with num_images others, // while some of the pairs may be redundant. EXPECT_GE(database->ReadAllMatches().size(), 4); EXPECT_GE(database->ReadTwoViewGeometries().size(), 4); } TEST(CreateSequentialFeatureMatcher, Nominal) { const auto test_dir = CreateTestDir(); const auto database_path = test_dir / "database.db"; auto database = Database::Open(database_path); CreateTestDatabase(/*num_images=*/5, *database); database->ClearMatches(); database->ClearTwoViewGeometries(); SequentialPairingOptions pairing_options; pairing_options.overlap = 2; pairing_options.quadratic_overlap = false; FeatureMatchingOptions matching_options; matching_options.use_gpu = false; matching_options.num_threads = 1; TwoViewGeometryOptions geometry_options; auto matcher = CreateSequentialFeatureMatcher( pairing_options, matching_options, geometry_options, database_path); ASSERT_NE(matcher, nullptr); matcher->Start(); matcher->Wait(); // With 5 images and overlap=2: // (0,1), (0,2), (1,2), (1,3), (2,3), (2,4), (3,4) EXPECT_EQ(database->ReadAllMatches().size(), 7); EXPECT_EQ(database->ReadTwoViewGeometries().size(), 7); } TEST(CreateSpatialFeatureMatcher, Nominal) { const auto test_dir = CreateTestDir(); const auto database_path = test_dir / "database.db"; auto database = Database::Open(database_path); CreateTestDatabase(/*num_images=*/4, *database); database->ClearMatches(); database->ClearTwoViewGeometries(); SpatialPairingOptions pairing_options; pairing_options.max_num_neighbors = 2; pairing_options.max_distance = 1e6; FeatureMatchingOptions matching_options; matching_options.use_gpu = false; matching_options.num_threads = 1; TwoViewGeometryOptions geometry_options; auto matcher = CreateSpatialFeatureMatcher( pairing_options, matching_options, geometry_options, database_path); ASSERT_NE(matcher, nullptr); matcher->Start(); matcher->Wait(); EXPECT_GT(database->ReadAllMatches().size(), 0); EXPECT_GT(database->ReadTwoViewGeometries().size(), 0); } TEST(CreateTransitiveFeatureMatcher, Nominal) { const auto test_dir = CreateTestDir(); const auto database_path = test_dir / "database.db"; auto database = Database::Open(database_path); CreateTestDatabase(/*num_images=*/4, *database); database->ClearMatches(); database->ClearTwoViewGeometries(); const std::vector images = database->ReadAllImages(); ASSERT_GE(images.size(), 3); // Create initial matches: 1-2 and 2-3 TwoViewGeometry two_view_geometry; two_view_geometry.config = TwoViewGeometry::CALIBRATED; two_view_geometry.inlier_matches = FeatureMatches(10); database->WriteTwoViewGeometry( images[0].ImageId(), images[1].ImageId(), two_view_geometry); database->WriteTwoViewGeometry( images[1].ImageId(), images[2].ImageId(), two_view_geometry); TransitivePairingOptions pairing_options; pairing_options.batch_size = 100; pairing_options.num_iterations = 1; FeatureMatchingOptions matching_options; matching_options.use_gpu = false; matching_options.num_threads = 1; TwoViewGeometryOptions geometry_options; auto matcher = CreateTransitiveFeatureMatcher( pairing_options, matching_options, geometry_options, database_path); ASSERT_NE(matcher, nullptr); matcher->Start(); matcher->Wait(); // Should create transitive match 1-3 const size_t final_matches = database->ReadTwoViewGeometries().size(); EXPECT_GE(final_matches, 2); // At least the original 2 matches } TEST(CreateImagePairsFeatureMatcher, Nominal) { const auto test_dir = CreateTestDir(); const auto database_path = test_dir / "database.db"; const auto match_list_path = test_dir / "match_list.txt"; auto database = Database::Open(database_path); CreateTestDatabase(/*num_images=*/4, *database); database->ClearMatches(); database->ClearTwoViewGeometries(); const std::vector images = database->ReadAllImages(); ASSERT_GE(images.size(), 3); // Create match list file with specific image pairs std::ofstream file(match_list_path); file << images[0].Name() << " " << images[1].Name() << "\n"; file << images[1].Name() << " " << images[2].Name() << "\n"; file << images[2].Name() << " " << images[3].Name() << "\n"; file.close(); ImportedPairingOptions pairing_options; pairing_options.match_list_path = match_list_path; FeatureMatchingOptions matching_options; matching_options.use_gpu = false; matching_options.num_threads = 1; TwoViewGeometryOptions geometry_options; auto matcher = CreateImagePairsFeatureMatcher( pairing_options, matching_options, geometry_options, database_path); ASSERT_NE(matcher, nullptr); matcher->Start(); matcher->Wait(); EXPECT_EQ(database->ReadAllMatches().size(), 3); EXPECT_EQ(database->ReadTwoViewGeometries().size(), 3); } TEST(CreateFeaturePairsFeatureMatcher, Nominal) { const auto test_dir = CreateTestDir(); const auto database_path = test_dir / "database.db"; const auto match_list_path = test_dir / "feature_match_list.txt"; auto database = Database::Open(database_path); CreateTestDatabase(/*num_images=*/3, *database); database->ClearMatches(); database->ClearTwoViewGeometries(); const std::vector images = database->ReadAllImages(); ASSERT_GE(images.size(), 2); // Create feature match list file with many matches for better verification std::ofstream file(match_list_path); file << images[0].Name() << " " << images[1].Name() << "\n"; for (int i = 0; i < 15; ++i) { file << i << " " << i << "\n"; } file << "\n"; // Empty line separates pairs file << images[1].Name() << " " << images[2].Name() << "\n"; for (int i = 0; i < 15; ++i) { file << i << " " << i << "\n"; } file << "\n"; file.close(); FeaturePairsMatchingOptions pairing_options; pairing_options.match_list_path = match_list_path; pairing_options.verify_matches = true; FeatureMatchingOptions matching_options; matching_options.use_gpu = false; matching_options.num_threads = 1; TwoViewGeometryOptions geometry_options; geometry_options.min_num_inliers = 5; // Lower threshold for testing auto matcher = CreateFeaturePairsFeatureMatcher( pairing_options, matching_options, geometry_options, database_path); ASSERT_NE(matcher, nullptr); matcher->Start(); matcher->Wait(); // Should have imported and verified the matches EXPECT_GE(database->ReadTwoViewGeometries().size(), 2); } TEST(CreateGeometricVerifier, Nominal) { const auto test_dir = CreateTestDir(); const auto database_path = test_dir / "database.db"; auto database = Database::Open(database_path); CreateTestDatabase(/*num_images=*/4, *database); database->ClearTwoViewGeometries(); ExistingMatchedPairingOptions pairing_options; GeometricVerifierOptions verifier_options; verifier_options.num_threads = 1; TwoViewGeometryOptions geometry_options; auto verifier = CreateGeometricVerifier( verifier_options, pairing_options, geometry_options, database_path); ASSERT_NE(verifier, nullptr); verifier->Start(); verifier->Wait(); EXPECT_GE(database->ReadAllMatches().size(), 3); EXPECT_GE(database->ReadTwoViewGeometries().size(), 3); } void ExpectRigVerificationResults(const Database& database, int num_expected_matches, int num_expected_calibrated, int num_expected_calibrated_rig) { // Verify that two-view geometries were created. int num_calibrated = 0; int num_calibrated_rig = 0; int num_others = 0; for (const auto& [pair_id, two_view_geometry] : database.ReadTwoViewGeometries()) { EXPECT_EQ(two_view_geometry.inlier_matches.size(), num_expected_matches); switch (two_view_geometry.config) { case TwoViewGeometry::CALIBRATED: ++num_calibrated; break; case TwoViewGeometry::CALIBRATED_RIG: ++num_calibrated_rig; break; default: ++num_others; } } // Two calibrated pairs between images in the same frames. EXPECT_EQ(num_calibrated, num_expected_calibrated); // Four calibrated pairs between images in different frames. EXPECT_EQ(num_calibrated_rig, num_expected_calibrated_rig); EXPECT_EQ(num_others, 0); } TEST(CreateGeometricVerifier, RigVerificationWithNonTrivialFrames) { const auto test_dir = CreateTestDir(); const auto database_path = test_dir / "database.db"; auto database = Database::Open(database_path); Reconstruction reconstruction; SyntheticDatasetOptions synthetic_dataset_options; synthetic_dataset_options.num_rigs = 1; synthetic_dataset_options.num_cameras_per_rig = 3; synthetic_dataset_options.num_frames_per_rig = 2; synthetic_dataset_options.num_points3D = 25; synthetic_dataset_options.match_config = SyntheticDatasetOptions::MatchConfig::EXHAUSTIVE; synthetic_dataset_options.camera_has_prior_focal_length = true; SynthesizeDataset(synthetic_dataset_options, &reconstruction, database.get()); ExistingMatchedPairingOptions pairing_options; GeometricVerifierOptions verifier_options; verifier_options.num_threads = -1; verifier_options.rig_verification = true; TwoViewGeometryOptions geometry_options; geometry_options.min_num_inliers = 5; auto verifier = CreateGeometricVerifier( verifier_options, pairing_options, geometry_options, database_path); ASSERT_NE(verifier, nullptr); verifier->Start(); verifier->Wait(); // All pairs should be overwritten with calibrated rig pairs. ExpectRigVerificationResults(*database, synthetic_dataset_options.num_points3D, /*num_expected_calibrated=*/0, /*num_expected_calibrated_rig=*/15); } TEST(CreateGeometricVerifier, RigVerificationWithTrivialFrames) { const auto test_dir = CreateTestDir(); const auto database_path = test_dir / "database.db"; auto database = Database::Open(database_path); Reconstruction reconstruction; SyntheticDatasetOptions synthetic_dataset_options; synthetic_dataset_options.num_rigs = 1; synthetic_dataset_options.num_cameras_per_rig = 1; synthetic_dataset_options.num_frames_per_rig = 2; synthetic_dataset_options.num_points3D = 25; synthetic_dataset_options.match_config = SyntheticDatasetOptions::MatchConfig::EXHAUSTIVE; synthetic_dataset_options.camera_has_prior_focal_length = true; SynthesizeDataset(synthetic_dataset_options, &reconstruction, database.get()); ExistingMatchedPairingOptions pairing_options; GeometricVerifierOptions verifier_options; verifier_options.num_threads = 1; verifier_options.rig_verification = true; TwoViewGeometryOptions geometry_options; geometry_options.min_num_inliers = 5; auto verifier = CreateGeometricVerifier( verifier_options, pairing_options, geometry_options, database_path); ASSERT_NE(verifier, nullptr); verifier->Start(); verifier->Wait(); // Trivial frames should be skipped and unmodified. ExpectRigVerificationResults(*database, synthetic_dataset_options.num_points3D, /*num_expected_calibrated=*/1, /*num_expected_calibrated_rig=*/0); } TEST(CreateGeometricVerifier, Guided) { const auto test_dir = CreateTestDir(); const auto database_path = test_dir / "database.db"; auto database = Database::Open(database_path); Reconstruction gt_reconstruction; SyntheticDatasetOptions synthetic_dataset_options; synthetic_dataset_options.num_rigs = 2; synthetic_dataset_options.num_cameras_per_rig = 1; synthetic_dataset_options.num_frames_per_rig = 5; synthetic_dataset_options.num_points3D = 50; synthetic_dataset_options.inlier_match_ratio = 0.6; synthetic_dataset_options.two_view_geometry_has_relative_pose = true; SynthesizeDataset( synthetic_dataset_options, >_reconstruction, database.get()); // Clear all inlier matches. cam2_from_cam1 is already gt from the synthesized // database. std::vector> gt_two_view_geometries = database->ReadTwoViewGeometries(); for (const auto& [pair_id, _] : gt_two_view_geometries) { const auto [image_id1, image_id2] = PairIdToImagePair(pair_id); database->DeleteInlierMatches(image_id1, image_id2); } ExistingMatchedPairingOptions pairing_options; GeometricVerifierOptions verifier_options; verifier_options.num_threads = 1; verifier_options.use_existing_relative_pose = true; TwoViewGeometryOptions geometry_options; auto verifier = CreateGeometricVerifier( verifier_options, pairing_options, geometry_options, database_path); ASSERT_NE(verifier, nullptr); verifier->Start(); verifier->Wait(); // Check validity after guided geometric verification. std::vector> two_view_geometries = database->ReadTwoViewGeometries(); EXPECT_GE(two_view_geometries.size(), gt_two_view_geometries.size()); for (size_t i = 0; i < two_view_geometries.size(); ++i) { EXPECT_EQ(two_view_geometries[i].first, gt_two_view_geometries[i].first); EXPECT_EQ(two_view_geometries[i].second.cam2_from_cam1, gt_two_view_geometries[i].second.cam2_from_cam1); EXPECT_TRUE(gt_two_view_geometries[i].second.E.value().isApprox( two_view_geometries[i].second.E.value())); // Should at least have all the original inliers. Some generated outliers // can be accidentally inliers as well. EXPECT_GE(two_view_geometries[i].second.inlier_matches.size(), gt_two_view_geometries[i].second.inlier_matches.size()); } } } // namespace } // namespace colmap colmap-4.2.0/src/colmap/controllers/feature_matching_utils.cc000066400000000000000000000536051524536416500244600ustar00rootroot00000000000000// Copyright (c), ETH Zurich and UNC Chapel Hill. // All rights reserved. // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are met: // // * Redistributions of source code must retain the above copyright // notice, this list of conditions and the following disclaimer. // // * Redistributions in binary form must reproduce the above copyright // notice, this list of conditions and the following disclaimer in the // documentation and/or other materials provided with the distribution. // // * Neither the name of ETH Zurich and UNC Chapel Hill nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE // ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE // LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR // CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF // SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS // INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN // CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) // ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE // POSSIBILITY OF SUCH DAMAGE. #include "colmap/controllers/feature_matching_utils.h" #include "colmap/estimators/two_view_geometry.h" #include "colmap/feature/sift.h" #include "colmap/feature/utils.h" #include "colmap/util/cuda.h" #include "colmap/util/hash_containers.h" #include "colmap/util/misc.h" #if defined(COLMAP_CUDA_ENABLED) #include #endif namespace colmap { FeatureMatcherWorker::FeatureMatcherWorker( const FeatureMatchingOptions& matching_options, const TwoViewGeometryOptions& geometry_options, const std::shared_ptr& cache, JobQueue* input_queue, JobQueue* output_queue) : matching_options_(matching_options), geometry_options_(geometry_options), cache_(cache), input_queue_(input_queue), output_queue_(output_queue) { THROW_CHECK(matching_options_.Check()); if (matching_options_.RequiresOpenGL()) { opengl_context_ = std::make_unique(); } } void FeatureMatcherWorker::Run() { if (opengl_context_ != nullptr) { THROW_CHECK(opengl_context_->MakeCurrent()); } #if defined(COLMAP_CUDA_ENABLED) if (matching_options_.use_gpu) { // Initialize CUDA device for this worker thread const std::vector gpu_indices = CSVToVector(matching_options_.gpu_index); THROW_CHECK_EQ(gpu_indices.size(), 1) << "Each matching worker can only use one GPU"; const int gpu_index = gpu_indices[0]; if (gpu_index >= 0) { SetBestCudaDevice(gpu_index); LOG(INFO) << "Bind FeatureMatcherWorker to GPU device " << gpu_index; } } #endif if (matching_options_.type == FeatureMatcherType::SIFT_BRUTEFORCE) { // TODO(jsch): This is a bit ugly, but currently cannot think of a better // way to inject the shared descriptor index cache. THROW_CHECK_NOTNULL(matching_options_.sift)->cpu_descriptor_index_cache = &cache_->GetFeatureDescriptorIndexCache(); THROW_CHECK_NOTNULL(matching_options_.sift->cpu_descriptor_index_cache); } // Minimize the amount of allocated GPU memory by computing the maximum number // of descriptors for any image over the whole database. matching_options_.max_num_matches = std::min( matching_options_.max_num_matches, cache_->MaxNumKeypoints()); std::unique_ptr matcher = FeatureMatcher::Create(matching_options_); if (matcher == nullptr) { LOG(ERROR) << "Failed to create feature matcher."; SignalInvalidSetup(); return; } SignalValidSetup(); while (true) { if (IsStopped()) { break; } auto input_job = input_queue_->Pop(); if (input_job.IsValid()) { auto& data = input_job.Data(); if (!cache_->ExistsDescriptors(data.image_id1) || !cache_->ExistsDescriptors(data.image_id2)) { THROW_CHECK(output_queue_->Push(std::move(data))); continue; } const auto& camera1 = cache_->GetCamera(cache_->GetImage(data.image_id1).CameraId()); const auto& camera2 = cache_->GetCamera(cache_->GetImage(data.image_id2).CameraId()); if (matching_options_.guided_matching) { matcher->MatchGuided( geometry_options_.ransac_options.max_error, { data.image_id1, &camera1, cache_->GetKeypoints(data.image_id1), cache_->GetDescriptors(data.image_id1), cache_->FindImagePosePriorOrNull(data.image_id1), }, { data.image_id2, &camera2, cache_->GetKeypoints(data.image_id2), cache_->GetDescriptors(data.image_id2), cache_->FindImagePosePriorOrNull(data.image_id2), }, &data.two_view_geometry); } else { matcher->Match( { data.image_id1, &camera1, cache_->GetKeypoints(data.image_id1), cache_->GetDescriptors(data.image_id1), cache_->FindImagePosePriorOrNull(data.image_id1), }, { data.image_id2, &camera2, cache_->GetKeypoints(data.image_id2), cache_->GetDescriptors(data.image_id2), cache_->FindImagePosePriorOrNull(data.image_id2), }, &data.matches); } THROW_CHECK(output_queue_->Push(std::move(data))); } } } namespace { class VerifierWorker : public Thread { public: using Input = FeatureMatcherData; using Output = FeatureMatcherData; VerifierWorker(const TwoViewGeometryOptions& options, std::shared_ptr cache, JobQueue* input_queue, JobQueue* output_queue, const bool use_existing_relative_pose = false) : options_(options), cache_(std::move(cache)), use_existing_relative_pose_(use_existing_relative_pose), input_queue_(input_queue), output_queue_(output_queue) { THROW_CHECK(options_.Check()); } protected: void Run() override { while (true) { if (IsStopped()) { break; } auto input_job = input_queue_->Pop(); if (input_job.IsValid()) { auto& data = input_job.Data(); if (data.matches.size() < static_cast(options_.min_num_inliers)) { THROW_CHECK(output_queue_->Push(std::move(data))); continue; } const auto& camera1 = cache_->GetCamera(cache_->GetImage(data.image_id1).CameraId()); const auto& camera2 = cache_->GetCamera(cache_->GetImage(data.image_id2).CameraId()); const auto keypoints1 = cache_->GetKeypoints(data.image_id1); const auto keypoints2 = cache_->GetKeypoints(data.image_id2); const std::vector points1 = FeatureKeypointsToPointsVector(*keypoints1); const std::vector points2 = FeatureKeypointsToPointsVector(*keypoints2); if (use_existing_relative_pose_ && data.two_view_geometry.cam2_from_cam1.has_value()) { data.two_view_geometry = TwoViewGeometryFromKnownRelativePose( camera1, points1, camera2, points2, *data.two_view_geometry.cam2_from_cam1, data.matches, options_.min_num_inliers, options_.ransac_options.max_error); } else { data.two_view_geometry = EstimateTwoViewGeometry( camera1, points1, camera2, points2, data.matches, options_); } THROW_CHECK(output_queue_->Push(std::move(data))); } } } private: const TwoViewGeometryOptions options_; std::shared_ptr cache_; const bool use_existing_relative_pose_; JobQueue* input_queue_; JobQueue* output_queue_; }; } // namespace FeatureMatcherController::FeatureMatcherController( const FeatureMatchingOptions& matching_options, const TwoViewGeometryOptions& geometry_options, std::shared_ptr cache) : matching_options_(matching_options), geometry_options_(geometry_options), cache_(std::move(cache)), is_setup_(false) { THROW_CHECK(matching_options_.Check()); THROW_CHECK(geometry_options_.Check()); THROW_CHECK_EQ(geometry_options_.ransac_options.num_threads, 1) << "Parallel RANSAC is not supported inside multi-threaded matching"; const int num_threads = GetEffectiveNumThreads(matching_options_.num_threads); THROW_CHECK_GT(num_threads, 0); std::vector gpu_indices = CSVToVector(matching_options_.gpu_index); THROW_CHECK_GT(gpu_indices.size(), 0); #if defined(COLMAP_CUDA_ENABLED) if (matching_options_.use_gpu && gpu_indices.size() == 1 && gpu_indices[0] == -1) { const int num_cuda_devices = GetNumCudaDevices(); THROW_CHECK_GT(num_cuda_devices, 0); gpu_indices.resize(num_cuda_devices); std::iota(gpu_indices.begin(), gpu_indices.end(), 0); } #endif // COLMAP_CUDA_ENABLED // If skip_geometric_verification, match directly to output_queue_. const bool skip_geometric_verification = matching_options_.skip_geometric_verification && !matching_options_.guided_matching; JobQueue* matcher_output_queue = skip_geometric_verification ? &output_queue_ : &verifier_queue_; if (matching_options_.use_gpu) { auto worker_matching_options = matching_options_; // The first matching is always without guided matching. worker_matching_options.guided_matching = false; matchers_.reserve(gpu_indices.size()); for (const auto& gpu_index : gpu_indices) { worker_matching_options.gpu_index = std::to_string(gpu_index); matchers_.emplace_back( std::make_unique(worker_matching_options, geometry_options_, cache_, &matcher_queue_, matcher_output_queue)); } } else { auto worker_matching_options = matching_options_; // Prevent nested threading. worker_matching_options.num_threads = 1; // The first matching is always without guided matching. worker_matching_options.guided_matching = false; matchers_.reserve(num_threads); for (int i = 0; i < num_threads; ++i) { matchers_.emplace_back( std::make_unique(worker_matching_options, geometry_options_, cache_, &matcher_queue_, matcher_output_queue)); } } verifiers_.reserve(num_threads); if (matching_options_.guided_matching) { // Redirect the verification output to final round of guided matching. for (int i = 0; i < num_threads; ++i) { verifiers_.emplace_back(std::make_unique( geometry_options_, cache_, &verifier_queue_, &guided_matcher_queue_)); } if (matching_options_.use_gpu) { auto worker_matching_options = matching_options_; guided_matchers_.reserve(gpu_indices.size()); for (const auto& gpu_index : gpu_indices) { worker_matching_options.gpu_index = std::to_string(gpu_index); guided_matchers_.emplace_back( std::make_unique(worker_matching_options, geometry_options_, cache_, &guided_matcher_queue_, &output_queue_)); } } else { auto worker_matching_options = matching_options_; // Prevent nested threading. worker_matching_options.num_threads = 1; guided_matchers_.reserve(num_threads); for (int i = 0; i < num_threads; ++i) { guided_matchers_.emplace_back( std::make_unique(worker_matching_options, geometry_options_, cache_, &guided_matcher_queue_, &output_queue_)); } } } else if (!matching_options.skip_geometric_verification) { for (int i = 0; i < num_threads; ++i) { verifiers_.emplace_back(std::make_unique( geometry_options_, cache_, &verifier_queue_, &output_queue_)); } } } FeatureMatcherController::~FeatureMatcherController() { matcher_queue_.Wait(); verifier_queue_.Wait(); guided_matcher_queue_.Wait(); output_queue_.Wait(); for (auto& matcher : matchers_) { matcher->Stop(); } for (auto& verifier : verifiers_) { verifier->Stop(); } for (auto& guided_matcher : guided_matchers_) { guided_matcher->Stop(); } matcher_queue_.Stop(); verifier_queue_.Stop(); guided_matcher_queue_.Stop(); output_queue_.Stop(); for (auto& matcher : matchers_) { matcher->Wait(); } for (auto& verifier : verifiers_) { verifier->Wait(); } for (auto& guided_matcher : guided_matchers_) { guided_matcher->Wait(); } } bool FeatureMatcherController::Setup() { for (auto& matcher : matchers_) { matcher->Start(); } for (auto& verifier : verifiers_) { verifier->Start(); } for (auto& guided_matcher : guided_matchers_) { guided_matcher->Start(); } for (auto& matcher : matchers_) { if (!matcher->CheckValidSetup()) { return false; } } for (auto& guided_matcher : guided_matchers_) { if (!guided_matcher->CheckValidSetup()) { return false; } } is_setup_ = true; return true; } void FeatureMatcherController::Match( const std::vector>& image_pairs) { THROW_CHECK_NOTNULL(cache_); THROW_CHECK(is_setup_); if (image_pairs.empty()) { return; } ////////////////////////////////////////////////////////////////////////////// // Match the image pairs ////////////////////////////////////////////////////////////////////////////// FlatHashSet image_pair_ids; image_pair_ids.reserve(image_pairs.size()); size_t num_outputs = 0; for (const auto& [image_id1, image_id2] : image_pairs) { // Avoid self-matches. if (image_id1 == image_id2) { continue; } // Avoid duplicate image pairs. const image_pair_t pair_id = ImagePairToPairId(image_id1, image_id2); if (!image_pair_ids.insert(pair_id).second) { continue; } // Avoid self-matches within a frame. if (matching_options_.skip_image_pairs_in_same_frame) { const Image& image1 = cache_->GetImage(image_id1); const Image& image2 = cache_->GetImage(image_id2); if (image1.HasFrameId() && image2.HasFrameId() && image1.FrameId() == image2.FrameId()) { continue; } } const bool exists_matches = cache_->ExistsMatches(image_id1, image_id2); const bool exists_two_view_geometry = cache_->ExistsTwoViewGeometry(image_id1, image_id2); if (exists_matches && exists_two_view_geometry) { continue; } num_outputs += 1; // If only one of the matches or inlier matches exist, we recompute them // from scratch and delete the existing results. This must be done before // pushing the jobs to the queue, otherwise database constraints might fail // when writing an existing result into the database. if (exists_two_view_geometry) { cache_->DeleteTwoViewGeometry(image_id1, image_id2); } FeatureMatcherData data; data.image_id1 = image_id1; data.image_id2 = image_id2; if (exists_matches) { data.matches = cache_->GetMatches(image_id1, image_id2); cache_->DeleteMatches(image_id1, image_id2); THROW_CHECK(verifier_queue_.Push(std::move(data))); } else { THROW_CHECK(matcher_queue_.Push(std::move(data))); } } ////////////////////////////////////////////////////////////////////////////// // Write results to database ////////////////////////////////////////////////////////////////////////////// for (size_t i = 0; i < num_outputs; ++i) { auto output_job = output_queue_.Pop(); THROW_CHECK(output_job.IsValid()); auto& output = output_job.Data(); if (output.matches.size() < static_cast(geometry_options_.min_num_inliers)) { output.matches = {}; } if (output.two_view_geometry.inlier_matches.size() < static_cast(geometry_options_.min_num_inliers)) { output.two_view_geometry = TwoViewGeometry(); } cache_->WriteMatches(output.image_id1, output.image_id2, output.matches); cache_->WriteTwoViewGeometry( output.image_id1, output.image_id2, output.two_view_geometry); } THROW_CHECK_EQ(output_queue_.Size(), 0); } GeometricVerifierController::GeometricVerifierController( const GeometricVerifierOptions& options, const TwoViewGeometryOptions& geometry_options, std::shared_ptr cache) : geometry_options_(geometry_options), cache_(std::move(cache)), options_(options), is_setup_(false) { THROW_CHECK(geometry_options_.Check()); const int num_threads = GetEffectiveNumThreads(options_.num_threads); // Run geometric verification for (int i = 0; i < num_threads; ++i) { verifiers_.emplace_back( std::make_unique(geometry_options_, cache_, &verifier_queue_, &output_queue_, options_.use_existing_relative_pose)); } } GeometricVerifierController::~GeometricVerifierController() { verifier_queue_.Wait(); output_queue_.Wait(); for (auto& verifier : verifiers_) { verifier->Stop(); } verifier_queue_.Stop(); output_queue_.Stop(); for (auto& verifier : verifiers_) { verifier->Wait(); } } const GeometricVerifierOptions& GeometricVerifierController::Options() const { return options_; } GeometricVerifierOptions& GeometricVerifierController::Options() { return options_; } bool GeometricVerifierController::Setup() { for (auto& verifier : verifiers_) { verifier->Start(); } is_setup_ = true; return true; } void GeometricVerifierController::Verify( const std::vector>& image_pairs) { THROW_CHECK_NOTNULL(cache_); THROW_CHECK(is_setup_); if (image_pairs.empty()) { return; } ////////////////////////////////////////////////////////////////////////////// // Verify the matches from the image pairs ////////////////////////////////////////////////////////////////////////////// FlatHashSet image_pair_ids; image_pair_ids.reserve(image_pairs.size()); size_t num_outputs = 0; for (const auto& [image_id1, image_id2] : image_pairs) { // Avoid self-matches. if (image_id1 == image_id2) { continue; } // Avoid duplicate image pairs. const image_pair_t pair_id = ImagePairToPairId(image_id1, image_id2); if (!image_pair_ids.insert(pair_id).second) { continue; } const bool exists_matches = cache_->ExistsMatches(image_id1, image_id2); const bool exists_inlier_matches = cache_->ExistsInlierMatches(image_id1, image_id2); if (exists_matches && exists_inlier_matches) { continue; } num_outputs += 1; // If only one of the matches or inlier matches exist, we recompute them // from scratch and delete the existing results. This must be done before // pushing the jobs to the queue, otherwise database constraints might fail // when writing an existing result into the database. if (exists_inlier_matches) { cache_->DeleteTwoViewGeometry(image_id1, image_id2); } FeatureMatcherData data; data.image_id1 = image_id1; data.image_id2 = image_id2; if (exists_matches) { data.matches = cache_->GetMatches(image_id1, image_id2); // There exists a two view geometry without inlier matches. if (cache_->ExistsTwoViewGeometry(image_id1, image_id2)) { data.two_view_geometry = cache_->GetTwoViewGeometry(image_id1, image_id2); } THROW_CHECK(verifier_queue_.Push(std::move(data))); } } ////////////////////////////////////////////////////////////////////////////// // Write results to database ////////////////////////////////////////////////////////////////////////////// for (size_t i = 0; i < num_outputs; ++i) { auto output_job = output_queue_.Pop(); THROW_CHECK(output_job.IsValid()); auto& output = output_job.Data(); if (output.matches.size() < static_cast(geometry_options_.min_num_inliers)) { output.matches = {}; } if (output.two_view_geometry.inlier_matches.size() < static_cast(geometry_options_.min_num_inliers)) { output.two_view_geometry = TwoViewGeometry(); } if (cache_->ExistsTwoViewGeometry(output.image_id1, output.image_id2)) { cache_->DeleteTwoViewGeometry(output.image_id1, output.image_id2); } cache_->WriteTwoViewGeometry( output.image_id1, output.image_id2, output.two_view_geometry); } THROW_CHECK_EQ(output_queue_.Size(), 0); } } // namespace colmap colmap-4.2.0/src/colmap/controllers/feature_matching_utils.h000066400000000000000000000120701524536416500243110ustar00rootroot00000000000000// Copyright (c), ETH Zurich and UNC Chapel Hill. // All rights reserved. // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are met: // // * Redistributions of source code must retain the above copyright // notice, this list of conditions and the following disclaimer. // // * Redistributions in binary form must reproduce the above copyright // notice, this list of conditions and the following disclaimer in the // documentation and/or other materials provided with the distribution. // // * Neither the name of ETH Zurich and UNC Chapel Hill nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE // ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE // LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR // CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF // SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS // INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN // CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) // ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE // POSSIBILITY OF SUCH DAMAGE. #pragma once #include "colmap/controllers/feature_matching.h" #include "colmap/estimators/two_view_geometry.h" #include "colmap/feature/matcher.h" #include "colmap/util/opengl_utils.h" #include "colmap/util/threading.h" #include #include namespace colmap { struct FeatureMatcherData { image_t image_id1 = kInvalidImageId; image_t image_id2 = kInvalidImageId; FeatureMatches matches; TwoViewGeometry two_view_geometry; }; class FeatureMatcherWorker : public Thread { public: using Input = FeatureMatcherData; using Output = FeatureMatcherData; FeatureMatcherWorker(const FeatureMatchingOptions& matching_options, const TwoViewGeometryOptions& geometry_options, const std::shared_ptr& cache, JobQueue* input_queue, JobQueue* output_queue); private: void Run() override; FeatureMatchingOptions matching_options_; TwoViewGeometryOptions geometry_options_; std::shared_ptr cache_; JobQueue* input_queue_; JobQueue* output_queue_; std::unique_ptr opengl_context_; }; // Multi-threaded and multi-GPU SIFT feature matcher, which writes the computed // results to the database and skips already matched image pairs. To improve // performance of the matching by taking advantage of caching and database // transactions, pass multiple images to the `Match` function. Note that the // database should be in an active transaction while calling `Match`. class FeatureMatcherController { public: FeatureMatcherController(const FeatureMatchingOptions& matching_options, const TwoViewGeometryOptions& geometry_options, std::shared_ptr cache); ~FeatureMatcherController(); // Setup the matchers and return if successful. bool Setup(); // Match one batch of multiple image pairs. void Match(const std::vector>& image_pairs); private: FeatureMatchingOptions matching_options_; TwoViewGeometryOptions geometry_options_; std::shared_ptr cache_; bool is_setup_; std::vector> matchers_; std::vector> guided_matchers_; std::vector> verifiers_; JobQueue matcher_queue_; JobQueue verifier_queue_; JobQueue guided_matcher_queue_; JobQueue output_queue_; }; class GeometricVerifierController { public: GeometricVerifierController(const GeometricVerifierOptions& verifier_options, const TwoViewGeometryOptions& geometry_options, std::shared_ptr cache); const GeometricVerifierOptions& Options() const; GeometricVerifierOptions& Options(); ~GeometricVerifierController(); // Setup the verifiers and return if successful. bool Setup(); // Verify one batch of multiple image pairs. void Verify(const std::vector>& image_pairs); private: TwoViewGeometryOptions geometry_options_; std::shared_ptr cache_; GeometricVerifierOptions options_; bool is_setup_; std::vector> verifiers_; JobQueue verifier_queue_; JobQueue output_queue_; }; } // namespace colmap colmap-4.2.0/src/colmap/controllers/feature_matching_utils_test.cc000066400000000000000000000273731524536416500255220ustar00rootroot00000000000000// Copyright (c), ETH Zurich and UNC Chapel Hill. // All rights reserved. // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are met: // // * Redistributions of source code must retain the above copyright // notice, this list of conditions and the following disclaimer. // // * Redistributions in binary form must reproduce the above copyright // notice, this list of conditions and the following disclaimer in the // documentation and/or other materials provided with the distribution. // // * Neither the name of ETH Zurich and UNC Chapel Hill nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE // ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE // LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR // CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF // SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS // INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN // CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) // ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE // POSSIBILITY OF SUCH DAMAGE. #include "colmap/controllers/feature_matching_utils.h" #include "colmap/controllers/matcher_cache.h" #include "colmap/scene/synthetic.h" #include "colmap/util/testing.h" #include namespace colmap { namespace { struct TestData { std::filesystem::path test_dir; std::shared_ptr database; std::shared_ptr cache; std::vector image_ids; }; TestData CreateTestData(int num_images) { TestData data; data.test_dir = CreateTestDir(); const auto database_path = data.test_dir / "database.db"; data.database = Database::Open(database_path); Reconstruction reconstruction; SyntheticDatasetOptions options; options.num_rigs = num_images; options.num_cameras_per_rig = 1; options.num_frames_per_rig = 1; options.num_points3D = 20; options.num_points2D_without_point3D = 3; SynthesizeDataset(options, &reconstruction, data.database.get()); data.cache = std::make_shared(100, data.database); data.image_ids = data.cache->GetImageIds(); return data; } FeatureMatchingOptions DefaultMatchingOptions() { FeatureMatchingOptions options; options.use_gpu = false; options.num_threads = 1; return options; } std::vector> AllPairs( const std::vector& image_ids) { std::vector> pairs; for (size_t i = 0; i < image_ids.size(); ++i) { for (size_t j = i + 1; j < image_ids.size(); ++j) { pairs.emplace_back(image_ids[i], image_ids[j]); } } return pairs; } // Match pairs without geometric verification, then clear TVGs. // Leaves matches in the database ready for a GeometricVerifierController. void MatchPairsWithoutVerification( TestData& data, const std::vector>& pairs) { data.database->ClearMatches(); data.database->ClearTwoViewGeometries(); FeatureMatchingOptions matching_options = DefaultMatchingOptions(); matching_options.skip_geometric_verification = true; TwoViewGeometryOptions geometry_options; FeatureMatcherController matcher( matching_options, geometry_options, data.cache); ASSERT_TRUE(matcher.Setup()); matcher.Match(pairs); data.database->ClearTwoViewGeometries(); } TEST(FeatureMatcherController, MatchEmptyPairs) { auto data = CreateTestData(3); data.database->ClearMatches(); data.database->ClearTwoViewGeometries(); FeatureMatchingOptions matching_options = DefaultMatchingOptions(); TwoViewGeometryOptions geometry_options; FeatureMatcherController controller( matching_options, geometry_options, data.cache); ASSERT_TRUE(controller.Setup()); // Matching empty pairs should return without error controller.Match({}); EXPECT_EQ(data.database->ReadAllMatches().size(), 0); } TEST(FeatureMatcherController, MatchSkipsSelfMatches) { auto data = CreateTestData(3); data.database->ClearMatches(); data.database->ClearTwoViewGeometries(); FeatureMatchingOptions matching_options = DefaultMatchingOptions(); TwoViewGeometryOptions geometry_options; FeatureMatcherController controller( matching_options, geometry_options, data.cache); ASSERT_TRUE(controller.Setup()); // Self-match pairs should be skipped std::vector> pairs; pairs.reserve(data.image_ids.size()); for (const auto id : data.image_ids) { pairs.emplace_back(id, id); } controller.Match(pairs); EXPECT_EQ(data.database->ReadAllMatches().size(), 0); } TEST(FeatureMatcherController, MatchSkipsDuplicatePairs) { auto data = CreateTestData(3); data.database->ClearMatches(); data.database->ClearTwoViewGeometries(); FeatureMatchingOptions matching_options = DefaultMatchingOptions(); TwoViewGeometryOptions geometry_options; FeatureMatcherController controller( matching_options, geometry_options, data.cache); ASSERT_TRUE(controller.Setup()); ASSERT_GE(data.image_ids.size(), 2); const image_t id1 = data.image_ids[0]; const image_t id2 = data.image_ids[1]; // Submit same pair multiple times — should only process once controller.Match({{id1, id2}, {id1, id2}, {id1, id2}}); const auto matches = data.database->ReadAllMatches(); EXPECT_EQ(matches.size(), 1); } TEST(FeatureMatcherController, MatchSkipsExistingResults) { auto data = CreateTestData(3); FeatureMatchingOptions matching_options = DefaultMatchingOptions(); TwoViewGeometryOptions geometry_options; FeatureMatcherController controller( matching_options, geometry_options, data.cache); ASSERT_TRUE(controller.Setup()); ASSERT_GE(data.image_ids.size(), 2); const image_t id1 = data.image_ids[0]; const image_t id2 = data.image_ids[1]; // Clear and match once data.database->ClearMatches(); data.database->ClearTwoViewGeometries(); controller.Match({{id1, id2}}); const auto matches_before = data.database->ReadAllMatches(); const auto tvg_before = data.database->ReadTwoViewGeometries(); EXPECT_EQ(matches_before.size(), 1); EXPECT_EQ(tvg_before.size(), 1); // Match same pair again — should skip since both matches and TVG exist controller.Match({{id1, id2}}); const auto matches_after = data.database->ReadAllMatches(); const auto tvg_after = data.database->ReadTwoViewGeometries(); EXPECT_EQ(matches_after.size(), matches_before.size()); EXPECT_EQ(tvg_after.size(), tvg_before.size()); // Match with reversed pair — should also be skipped controller.Match({{id2, id1}}); const auto matches_reversed = data.database->ReadAllMatches(); const auto tvg_reversed = data.database->ReadTwoViewGeometries(); EXPECT_EQ(matches_reversed.size(), matches_before.size()); EXPECT_EQ(tvg_reversed.size(), tvg_before.size()); } TEST(FeatureMatcherController, MatchMultiplePairs) { auto data = CreateTestData(4); data.database->ClearMatches(); data.database->ClearTwoViewGeometries(); FeatureMatchingOptions matching_options = DefaultMatchingOptions(); TwoViewGeometryOptions geometry_options; FeatureMatcherController controller( matching_options, geometry_options, data.cache); ASSERT_TRUE(controller.Setup()); // Match all pairs const auto pairs = AllPairs(data.image_ids); controller.Match(pairs); // 4 choose 2 = 6 pairs EXPECT_EQ(data.database->ReadAllMatches().size(), 6); EXPECT_EQ(data.database->ReadTwoViewGeometries().size(), 6); } TEST(FeatureMatcherController, MatchSkipGeometricVerification) { auto data = CreateTestData(3); data.database->ClearMatches(); data.database->ClearTwoViewGeometries(); FeatureMatchingOptions matching_options = DefaultMatchingOptions(); matching_options.skip_geometric_verification = true; TwoViewGeometryOptions geometry_options; FeatureMatcherController controller( matching_options, geometry_options, data.cache); ASSERT_TRUE(controller.Setup()); ASSERT_GE(data.image_ids.size(), 2); controller.Match({{data.image_ids[0], data.image_ids[1]}}); // Matches should be written even without geometric verification EXPECT_EQ(data.database->ReadAllMatches().size(), 1); // Verify geometric verification was skipped: TVG should have UNDEFINED config const auto tvg = data.database->ReadTwoViewGeometry(data.image_ids[0], data.image_ids[1]); EXPECT_EQ(tvg.config, TwoViewGeometry::UNDEFINED); EXPECT_TRUE(tvg.inlier_matches.empty()); } TEST(GeometricVerifierController, OptionsAccessor) { auto data = CreateTestData(3); GeometricVerifierOptions verifier_options; verifier_options.num_threads = 1; TwoViewGeometryOptions geometry_options; GeometricVerifierController controller( verifier_options, geometry_options, data.cache); EXPECT_EQ(controller.Options().num_threads, 1); controller.Options().num_threads = 2; EXPECT_EQ(controller.Options().num_threads, 2); } TEST(GeometricVerifierController, VerifyEmptyPairs) { auto data = CreateTestData(3); data.database->ClearTwoViewGeometries(); GeometricVerifierOptions verifier_options; verifier_options.num_threads = 1; TwoViewGeometryOptions geometry_options; GeometricVerifierController controller( verifier_options, geometry_options, data.cache); ASSERT_TRUE(controller.Setup()); // Verifying empty pairs should return without error controller.Verify({}); EXPECT_EQ(data.database->ReadTwoViewGeometries().size(), 0); } TEST(GeometricVerifierController, VerifySkipsSelfMatches) { auto data = CreateTestData(3); data.database->ClearTwoViewGeometries(); GeometricVerifierOptions verifier_options; verifier_options.num_threads = 1; TwoViewGeometryOptions geometry_options; GeometricVerifierController controller( verifier_options, geometry_options, data.cache); ASSERT_TRUE(controller.Setup()); std::vector> pairs; pairs.reserve(data.image_ids.size()); for (const auto id : data.image_ids) { pairs.emplace_back(id, id); } controller.Verify(pairs); EXPECT_EQ(data.database->ReadTwoViewGeometries().size(), 0); } TEST(GeometricVerifierController, VerifySkipsDuplicatePairs) { auto data = CreateTestData(3); ASSERT_GE(data.image_ids.size(), 2); MatchPairsWithoutVerification(data, {{data.image_ids[0], data.image_ids[1]}}); GeometricVerifierOptions verifier_options; verifier_options.num_threads = 1; TwoViewGeometryOptions geometry_options; GeometricVerifierController controller( verifier_options, geometry_options, data.cache); ASSERT_TRUE(controller.Setup()); const image_t id1 = data.image_ids[0]; const image_t id2 = data.image_ids[1]; // Submit same pair multiple times — should only process once controller.Verify({{id1, id2}, {id1, id2}, {id1, id2}}); const auto tvgs = data.database->ReadTwoViewGeometries(); EXPECT_EQ(tvgs.size(), 1); } TEST(GeometricVerifierController, VerifyWithExistingMatches) { auto data = CreateTestData(4); const auto pairs = AllPairs(data.image_ids); MatchPairsWithoutVerification(data, pairs); GeometricVerifierOptions verifier_options; verifier_options.num_threads = 1; TwoViewGeometryOptions geometry_options; GeometricVerifierController controller( verifier_options, geometry_options, data.cache); ASSERT_TRUE(controller.Setup()); controller.Verify(pairs); // All 6 pairs should now have TVGs EXPECT_EQ(data.database->ReadTwoViewGeometries().size(), 6); } } // namespace } // namespace colmap colmap-4.2.0/src/colmap/controllers/global_pipeline.cc000066400000000000000000000327211524536416500230540ustar00rootroot00000000000000// Copyright (c), ETH Zurich and UNC Chapel Hill. // All rights reserved. // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are met: // // * Redistributions of source code must retain the above copyright // notice, this list of conditions and the following disclaimer. // // * Redistributions in binary form must reproduce the above copyright // notice, this list of conditions and the following disclaimer in the // documentation and/or other materials provided with the distribution. // // * Neither the name of ETH Zurich and UNC Chapel Hill nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE // ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE // LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR // CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF // SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS // INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN // CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) // ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE // POSSIBILITY OF SUCH DAMAGE. #include "colmap/controllers/global_pipeline.h" #include "colmap/estimators/alignment.h" #include "colmap/estimators/rotation_averaging.h" #include "colmap/estimators/two_view_geometry.h" #include "colmap/scene/database_cache.h" #include "colmap/scene/pose_graph.h" #include "colmap/scene/reconstruction_manager.h" #include "colmap/sfm/global_mapper.h" #include "colmap/util/misc.h" #include "colmap/util/timer.h" #include namespace colmap { namespace { constexpr double kMinPriorFocalLengthRatio = 0.5; bool HasInsufficientPriorFocalLengths(const DatabaseCache& database_cache) { const auto& cameras = database_cache.Cameras(); if (cameras.empty()) { return false; } const size_t num_with_prior = std::count_if(cameras.begin(), cameras.end(), [](const auto& camera) { return camera.second.has_prior_focal_length; }); return num_with_prior < kMinPriorFocalLengthRatio * cameras.size(); } void WarnInsufficientPriorFocalLengths() { LOG(WARNING) << "Less than " << kMinPriorFocalLengthRatio * 100 << "% of cameras have prior focal lengths. The " "global mapper depends on reasonably good focal length " "priors to perform well. Consider running " "'colmap view_graph_calibrator' before 'colmap " "global_mapper' or providing camera calibrations " "manually."; } size_t NumFramesForImages(const Reconstruction& reconstruction, const FlatHashSet& image_ids) { FlatHashSet frame_ids; frame_ids.reserve(image_ids.size()); for (const image_t image_id : image_ids) { frame_ids.insert(reconstruction.Image(image_id).FrameId()); } return frame_ids.size(); } struct ComponentDecomposition { std::vector> components; size_t num_failed = 0; size_t num_too_small = 0; }; // Split every input view-graph component once using the same rotation // averaging and relative-rotation filtering as the global mapper. Components // that are already too small are discarded without running rotation averaging. ComponentDecomposition ComputeComponentsByRotationAveraging( const RotationEstimatorOptions& options, const PoseGraph& pose_graph, const Reconstruction& base, const std::vector& pose_priors, int min_model_size) { ComponentDecomposition result; const std::vector> input_components = pose_graph.ConnectedImageIdsForFrameComponents( base, /*filter_unregistered=*/false); for (const auto& input_component : input_components) { if (static_cast(NumFramesForImages(base, input_component)) < min_model_size) { ++result.num_too_small; continue; } Reconstruction reconstruction = base; PoseGraph component_pose_graph = pose_graph; component_pose_graph.InvalidatePairsOutsideActiveImageIds(input_component); RotationEstimatorOptions decomposition_options = options; decomposition_options.filter_unregistered = false; if (!RunRotationAveragingOnComponent(decomposition_options, component_pose_graph, input_component, reconstruction, pose_priors)) { ++result.num_failed; continue; } if (decomposition_options.max_rotation_error_deg > 0) { FilterEdgesByRelativeRotation( component_pose_graph, reconstruction, decomposition_options.max_rotation_error_deg); } std::vector> sub_components = component_pose_graph.ConnectedImageIdsForFrameComponents( reconstruction, /*filter_unregistered=*/true); for (auto& sub_component : sub_components) { result.components.push_back(std::move(sub_component)); } } return result; } } // namespace GlobalPipeline::GlobalPipeline( GlobalPipelineOptions options, std::shared_ptr database, std::shared_ptr reconstruction_manager) : options_(std::move(options)), reconstruction_manager_( std::move(THROW_CHECK_NOTNULL(reconstruction_manager))) { THROW_CHECK_NOTNULL(database); THROW_CHECK_GE(options_.min_model_size, 0); // Create database cache with relative poses for pose graph. DatabaseCache::Options database_cache_options; database_cache_options.min_num_matches = options_.min_num_matches; database_cache_options.ignore_watermarks = options_.ignore_watermarks; database_cache_options.image_names = {options_.image_names.begin(), options_.image_names.end()}; database_cache_ = DatabaseCache::Create(*database, database_cache_options); if (options_.decompose_relative_pose) { MaybeDecomposeRelativePoses(database_cache_.get()); } RegisterCallback(MODEL_UPDATE_CALLBACK); } std::optional> GlobalPipeline::ReconstructSingleComponent( const std::shared_ptr& database_cache, const GlobalMapperOptions& mapper_options) { auto reconstruction = reconstruction_manager_->Get(reconstruction_manager_->Add()); GlobalMapper global_mapper(database_cache); global_mapper.BeginReconstruction(reconstruction); Timer run_timer; run_timer.Start(); const bool success = global_mapper.Solve(mapper_options, [this]() { Callback(MODEL_UPDATE_CALLBACK); return CheckIfStopped(); }); LOG(INFO) << "Reconstruction done in " << run_timer.ElapsedSeconds() << " seconds"; // A stop requested through the callback is reported as success, so false // only denotes a genuine mapping failure. The caller removes failed // reconstructions from the output manager. if (!success) { LOG(ERROR) << "Global mapping failed"; return std::nullopt; } // Align reconstruction to the original metric scales in rig extrinsics. AlignReconstructionToOrigRigScales(database_cache->Rigs(), reconstruction.get()); return reconstruction; } void GlobalPipeline::Run() { const bool has_insufficient_prior_focal_lengths = HasInsufficientPriorFocalLengths(*database_cache_); if (has_insufficient_prior_focal_lengths) { WarnInsufficientPriorFocalLengths(); } // Prepare mapper options with top-level options. GlobalMapperOptions mapper_options = options_.mapper; mapper_options.image_path = options_.image_path; mapper_options.num_threads = options_.num_threads; mapper_options.random_seed = options_.random_seed; const size_t first_reconstruction_idx = reconstruction_manager_->Size(); ReconstructionStats stats; if (options_.multiple_models) { stats = ReconstructMultiComponents(mapper_options); } else { const std::optional> reconstruction = ReconstructSingleComponent(database_cache_, mapper_options); if (!reconstruction.has_value()) { reconstruction_manager_->Delete(reconstruction_manager_->Size() - 1); ++stats.num_failed; } else if (static_cast((*reconstruction)->NumRegFrames()) < options_.min_model_size) { reconstruction_manager_->Delete(reconstruction_manager_->Size() - 1); ++stats.num_too_small; } } // Sort newly created reconstructions by registered frame count. Keep any // reconstructions that were already managed before this run untouched. std::vector> reconstructions; reconstructions.reserve(reconstruction_manager_->Size() - first_reconstruction_idx); for (size_t i = first_reconstruction_idx; i < reconstruction_manager_->Size(); ++i) { reconstructions.push_back(reconstruction_manager_->Get(i)); } std::sort(reconstructions.begin(), reconstructions.end(), [](const std::shared_ptr& lhs, const std::shared_ptr& rhs) { return lhs->NumRegFrames() > rhs->NumRegFrames(); }); for (size_t i = 0; i < reconstructions.size(); ++i) { reconstruction_manager_->Get(first_reconstruction_idx + i) = std::move(reconstructions[i]); } for (size_t i = first_reconstruction_idx; i < reconstruction_manager_->Size(); ++i) { if (!options_.image_path.empty()) { LOG(INFO) << "Extracting colors ..."; reconstruction_manager_->Get(i)->ExtractColorsForAllImages( options_.image_path, options_.num_threads); } } LOG(INFO) << "Kept " << reconstruction_manager_->Size() - first_reconstruction_idx << " reconstruction(s), discarded " << stats.num_too_small << " with fewer than " << options_.min_model_size << " registered frames, and failed to reconstruct " << stats.num_failed; if (has_insufficient_prior_focal_lengths) { // Intentionally logging this warning before and after the reconstruction // to make sure it is not missed. WarnInsufficientPriorFocalLengths(); } } GlobalPipeline::ReconstructionStats GlobalPipeline::ReconstructMultiComponents( const GlobalMapperOptions& mapper_options) { ReconstructionStats stats; // Build the base reconstruction, pose graph, and pose priors from the cache. Reconstruction base; base.Load(*database_cache_); PoseGraph pose_graph; pose_graph.Load(*database_cache_->CorrespondenceGraph()); const std::vector& pose_priors = database_cache_->PosePriors(); if (pose_graph.Empty()) { LOG(ERROR) << "Cannot continue with empty pose graph"; return stats; } // Decompose the view graph once after rotation filtering. The full mapper is // then run at most once per resulting component; any additional fragments // rejected by its refinement pass are not recursively retried. ComponentDecomposition decomposition = ComputeComponentsByRotationAveraging(mapper_options.RotationAveraging(), pose_graph, base, pose_priors, options_.min_model_size); stats.num_failed += decomposition.num_failed; stats.num_too_small += decomposition.num_too_small; std::vector>& components = decomposition.components; LOG(INFO) << "Found " << components.size() << " connected component(s) after rotation filtering"; for (size_t component_idx = 0; component_idx < components.size(); ++component_idx) { if (CheckIfStopped()) { return stats; } const FlatHashSet& image_ids = components[component_idx]; if (static_cast(NumFramesForImages(base, image_ids)) < options_.min_model_size) { ++stats.num_too_small; continue; } LOG_HEADING1(StringPrintf("Reconstructing component %d / %d with %d images", static_cast(component_idx + 1), static_cast(components.size()), static_cast(image_ids.size()))); DatabaseCache::Options cache_options; cache_options.image_names.reserve(image_ids.size()); for (const image_t image_id : image_ids) { cache_options.image_names.insert(base.Image(image_id).Name()); } const std::shared_ptr component_cache = DatabaseCache::CreateFromCache(*database_cache_, cache_options); const std::optional> reconstruction = ReconstructSingleComponent(component_cache, mapper_options); if (!reconstruction.has_value()) { reconstruction_manager_->Delete(reconstruction_manager_->Size() - 1); ++stats.num_failed; } else if (static_cast((*reconstruction)->NumRegFrames()) < options_.min_model_size) { reconstruction_manager_->Delete(reconstruction_manager_->Size() - 1); ++stats.num_too_small; } } return stats; } } // namespace colmap colmap-4.2.0/src/colmap/controllers/global_pipeline.h000066400000000000000000000110401524536416500227050ustar00rootroot00000000000000// Copyright (c), ETH Zurich and UNC Chapel Hill. // All rights reserved. // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are met: // // * Redistributions of source code must retain the above copyright // notice, this list of conditions and the following disclaimer. // // * Redistributions in binary form must reproduce the above copyright // notice, this list of conditions and the following disclaimer in the // documentation and/or other materials provided with the distribution. // // * Neither the name of ETH Zurich and UNC Chapel Hill nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE // ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE // LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR // CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF // SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS // INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN // CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) // ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE // POSSIBILITY OF SUCH DAMAGE. #pragma once #include "colmap/scene/reconstruction_manager.h" #include "colmap/sfm/global_mapper.h" #include "colmap/util/base_controller.h" #include #include #include #include namespace colmap { struct GlobalPipelineOptions { // The minimum number of matches for inlier matches to be considered. int min_num_matches = 15; // Whether to ignore the inlier matches of watermark image pairs. bool ignore_watermarks = false; // Names of images to reconstruct. If empty, all images are used. std::vector image_names; // The image path at which to find the images to extract point colors. std::filesystem::path image_path; // Number of threads for parallel processing. int num_threads = -1; // Random seed for reproducibility. int random_seed = -1; // Whether to decompose relative poses from two-view geometries. bool decompose_relative_pose = true; // If true (default), reconstruct every connected component of the view graph // (one model per component). If false, reconstruct only the largest connected // component. bool multiple_models = true; // Minimum number of registered frames for a reconstruction to be kept. // Reconstructions with fewer registered frames are discarded. int min_model_size = 3; // Options for the global mapper. GlobalMapperOptions mapper; }; class GlobalPipeline : public BaseController { public: enum CallbackType { // Triggered after global positioning, after each global refinement // iteration, and after retriangulation, so the in-progress reconstruction // can be rendered. MODEL_UPDATE_CALLBACK, }; GlobalPipeline(GlobalPipelineOptions options, std::shared_ptr database, std::shared_ptr reconstruction_manager); void Run() override; private: struct ReconstructionStats { // Number of components that failed during rotation averaging or mapping. size_t num_failed = 0; // Number of components discarded for having too few registered frames. size_t num_too_small = 0; }; // Run the full global SfM pipeline on the given database cache and return // the resulting reconstruction, or nullopt if mapping fails. The in-progress // reconstruction is added to the manager so callbacks can render it. The // caller decides whether to keep it. std::optional> ReconstructSingleComponent( const std::shared_ptr& database_cache, const GlobalMapperOptions& mapper_options); // Partition the input view graph once using rotation averaging and // reconstruct each resulting component at most once. ReconstructionStats ReconstructMultiComponents( const GlobalMapperOptions& mapper_options); const GlobalPipelineOptions options_; std::shared_ptr database_cache_; std::shared_ptr reconstruction_manager_; }; } // namespace colmap colmap-4.2.0/src/colmap/controllers/global_pipeline_test.cc000066400000000000000000000775301524536416500241220ustar00rootroot00000000000000// Copyright (c), ETH Zurich and UNC Chapel Hill. // All rights reserved. // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are met: // // * Redistributions of source code must retain the above copyright // notice, this list of conditions and the following disclaimer. // // * Redistributions in binary form must reproduce the above copyright // notice, this list of conditions and the following disclaimer in the // documentation and/or other materials provided with the distribution. // // * Neither the name of ETH Zurich and UNC Chapel Hill nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE // ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE // LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR // CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF // SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS // INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN // CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) // ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE // POSSIBILITY OF SUCH DAMAGE. #include "colmap/controllers/global_pipeline.h" #include "colmap/estimators/view_graph_calibration.h" #include "colmap/math/random_eigen.h" #include "colmap/scene/database.h" #include "colmap/scene/reconstruction_matchers.h" #include "colmap/scene/synthetic.h" #include "colmap/util/hash_containers.h" #include "colmap/util/testing.h" #include #include #include #include #include namespace colmap { namespace { // TODO(jsch): Create parameterized tests for the different mapper // implementations (incremental, hierarchical, global) TEST(GlobalPipeline, Nominal) { const auto database_path = CreateTestDir() / "database.db"; auto database = Database::Open(database_path); Reconstruction gt_reconstruction; SyntheticDatasetOptions synthetic_dataset_options; synthetic_dataset_options.num_rigs = 2; synthetic_dataset_options.num_cameras_per_rig = 1; synthetic_dataset_options.num_frames_per_rig = 7; synthetic_dataset_options.num_points3D = 50; synthetic_dataset_options.camera_has_prior_focal_length = false; SynthesizeDataset( synthetic_dataset_options, >_reconstruction, database.get()); auto reconstruction_manager = std::make_shared(); GlobalPipelineOptions options; ViewGraphCalibrationOptions vgc_options; CalibrateViewGraph(vgc_options, database.get()); GlobalPipeline mapper(std::move(options), database, reconstruction_manager); mapper.Run(); ASSERT_EQ(reconstruction_manager->Size(), 1); auto reconstruction = reconstruction_manager->Get(0); EXPECT_THAT(gt_reconstruction, ReconstructionNear(*reconstruction, /*max_rotation_error_deg=*/1e-2, /*max_proj_center_error=*/1e-4)); // After the pipeline runs, point3D.error must be in pixel units, i.e. // equal to what UpdatePoint3DErrors would recompute. ASSERT_GT(reconstruction->NumPoints3D(), 0u); const double mean_after_run = reconstruction->ComputeMeanReprojectionError(); reconstruction->UpdatePoint3DErrors(); EXPECT_DOUBLE_EQ(mean_after_run, reconstruction->ComputeMeanReprojectionError()); } TEST(GlobalPipeline, SfMWithRandomSeedStability) { const auto database_path = CreateTestDir() / "database.db"; auto database = Database::Open(database_path); Reconstruction gt_reconstruction; SyntheticDatasetOptions synthetic_dataset_options; synthetic_dataset_options.num_rigs = 2; synthetic_dataset_options.num_cameras_per_rig = 1; synthetic_dataset_options.num_frames_per_rig = 4; synthetic_dataset_options.num_points3D = 100; SynthesizeDataset( synthetic_dataset_options, >_reconstruction, database.get()); SyntheticNoiseOptions synthetic_noise_options; synthetic_noise_options.point2D_stddev = 0.5; SynthesizeNoise(synthetic_noise_options, >_reconstruction, database.get()); auto run_mapper = [&](int num_threads, int random_seed) { GlobalPipelineOptions options; options.num_threads = num_threads; options.random_seed = random_seed; ViewGraphCalibrationOptions vgc_options; vgc_options.random_seed = random_seed; vgc_options.solver_options.num_threads = num_threads; CalibrateViewGraph(vgc_options, database.get()); auto reconstruction_manager = std::make_shared(); GlobalPipeline mapper(std::move(options), database, reconstruction_manager); mapper.Run(); EXPECT_EQ(reconstruction_manager->Size(), 1); return reconstruction_manager; }; constexpr int kRandomSeed = 42; // Single-threaded execution. { auto reconstruction_manager0 = run_mapper(/*num_threads=*/1, /*random_seed=*/kRandomSeed); auto reconstruction_manager1 = run_mapper(/*num_threads=*/1, /*random_seed=*/kRandomSeed); EXPECT_THAT(*reconstruction_manager0->Get(0), ReconstructionEq(*reconstruction_manager1->Get(0))); } // Multi-threaded execution. { auto reconstruction_manager0 = run_mapper(/*num_threads=*/3, /*random_seed=*/kRandomSeed); auto reconstruction_manager1 = run_mapper(/*num_threads=*/3, /*random_seed=*/kRandomSeed); // Same seed should produce similar results, up to floating-point variations // in optimization. EXPECT_THAT(*reconstruction_manager0->Get(0), ReconstructionNear(*reconstruction_manager1->Get(0), /*max_rotation_error_deg=*/1e-9, /*max_proj_center_error=*/1e-9, /*max_scale_error=*/std::nullopt, /*num_obs_tolerance=*/0.01, /*align=*/false)); } } TEST(GlobalPipeline, WithExistingRelativePoses) { const auto database_path = CreateTestDir() / "database.db"; auto database = Database::Open(database_path); Reconstruction gt_reconstruction; SyntheticDatasetOptions synthetic_dataset_options; synthetic_dataset_options.num_rigs = 2; synthetic_dataset_options.num_cameras_per_rig = 1; synthetic_dataset_options.num_frames_per_rig = 7; synthetic_dataset_options.num_points3D = 50; synthetic_dataset_options.camera_has_prior_focal_length = false; synthetic_dataset_options.two_view_geometry_has_relative_pose = true; SynthesizeDataset( synthetic_dataset_options, >_reconstruction, database.get()); auto reconstruction_manager = std::make_shared(); GlobalPipelineOptions options; ViewGraphCalibrationOptions vgc_options; CalibrateViewGraph(vgc_options, database.get()); GlobalPipeline mapper(std::move(options), database, reconstruction_manager); mapper.Run(); ASSERT_EQ(reconstruction_manager->Size(), 1); EXPECT_THAT(gt_reconstruction, ReconstructionNear(*reconstruction_manager->Get(0), /*max_rotation_error_deg=*/1e-2, /*max_proj_center_error=*/1e-4)); } // To test relative pose re-estimation from view graph calibration. TEST(GlobalPipeline, WithNoisyExistingRelativePoses) { const auto database_path = CreateTestDir() / "database.db"; auto database = Database::Open(database_path); Reconstruction gt_reconstruction; SyntheticDatasetOptions synthetic_dataset_options; synthetic_dataset_options.num_rigs = 2; synthetic_dataset_options.num_cameras_per_rig = 1; synthetic_dataset_options.num_frames_per_rig = 7; synthetic_dataset_options.num_points3D = 50; synthetic_dataset_options.camera_has_prior_focal_length = false; synthetic_dataset_options.two_view_geometry_has_relative_pose = true; SynthesizeDataset( synthetic_dataset_options, >_reconstruction, database.get()); // Replace relative poses with completely random values. for (auto& [pair_id, two_view_geometry] : database->ReadTwoViewGeometries()) { if (!two_view_geometry.cam2_from_cam1.has_value()) { continue; } two_view_geometry.cam2_from_cam1->rotation() = RandomEigenQuaterniond(); two_view_geometry.cam2_from_cam1->translation() = RandomEigenVectord<3>().normalized(); const auto [image_id1, image_id2] = PairIdToImagePair(pair_id); database->UpdateTwoViewGeometry(image_id1, image_id2, two_view_geometry); } auto reconstruction_manager = std::make_shared(); GlobalPipelineOptions options; ViewGraphCalibrationOptions vgc_options; CalibrateViewGraph(vgc_options, database.get()); GlobalPipeline mapper(std::move(options), database, reconstruction_manager); mapper.Run(); ASSERT_EQ(reconstruction_manager->Size(), 1); // Expect slightly worse accuracy due to noisy input poses. EXPECT_THAT(gt_reconstruction, ReconstructionNear(*reconstruction_manager->Get(0), /*max_rotation_error_deg=*/1e-2, /*max_proj_center_error=*/1e-4)); } // Returns the set of registered image ids for each reconstruction managed by // `reconstruction_manager`. std::vector> RegImageIdSetsPerReconstruction( const ReconstructionManager& reconstruction_manager) { std::vector> image_id_sets; for (size_t i = 0; i < reconstruction_manager.Size(); ++i) { const std::vector reg_image_ids = reconstruction_manager.Get(i)->RegImageIds(); image_id_sets.emplace_back(reg_image_ids.begin(), reg_image_ids.end()); } return image_id_sets; } // Groups the registered images of `reconstruction` by their rig. std::vector> GroupImageIdsByRig( const Reconstruction& reconstruction) { FlatHashMap> images_by_rig; for (const auto& [frame_id, frame] : reconstruction.Frames()) { for (const data_t& data_id : frame.ImageIds()) { images_by_rig[frame.RigId()].insert(data_id.id); } } std::vector> groups; groups.reserve(images_by_rig.size()); for (auto& [rig_id, image_ids] : images_by_rig) { groups.push_back(std::move(image_ids)); } return groups; } // Builds a ground-truth sub-reconstruction restricted to `group_image_ids` by // de-registering all frames outside the group and tearing down the leftover // images, frames, rigs, and cameras. The group must align with frame/rig // boundaries (e.g. one full rig) so the result is internally consistent. Reconstruction ExtractGroundTruthSubset( const Reconstruction& gt_reconstruction, const FlatHashSet& group_image_ids) { Reconstruction subset = gt_reconstruction; std::vector frames_to_deregister; for (const auto& [frame_id, frame] : subset.Frames()) { const bool in_group = std::any_of(frame.ImageIds().begin(), frame.ImageIds().end(), [&](const data_t& data_id) { return group_image_ids.count(data_id.id) > 0; }); if (!in_group) { frames_to_deregister.push_back(frame_id); } } for (const frame_t frame_id : frames_to_deregister) { subset.DeRegisterFrame(frame_id); } subset.TearDown(); return subset; } // Deletes all two-view geometries and matches connecting images from different // groups so the view graph in the database splits into disconnected components. void DisconnectDatabaseComponents( const std::vector>& groups, Database& database) { FlatHashMap image_to_group; for (int group = 0; group < static_cast(groups.size()); ++group) { for (const image_t image_id : groups[group]) { image_to_group[image_id] = group; } } for (const auto& [pair_id, two_view_geometry] : database.ReadTwoViewGeometries()) { const auto [image_id1, image_id2] = PairIdToImagePair(pair_id); if (image_to_group.at(image_id1) != image_to_group.at(image_id2)) { database.DeleteTwoViewGeometry(image_id1, image_id2); database.DeleteInlierMatches(image_id1, image_id2); database.DeleteMatches(image_id1, image_id2); } } } // Bridges the given image groups with `num_outlier_edges` cross-group two-view // geometries whose relative rotations are randomized (outliers), and deletes // all other cross-group edges. The kept outlier edges connect the groups into a // single initial connected component that rotation averaging must split by // filtering the outliers. void BridgeGroupsWithOutlierEdges( const std::vector>& groups, int num_outlier_edges, Database& database) { FlatHashMap image_to_group; for (int group = 0; group < static_cast(groups.size()); ++group) { for (const image_t image_id : groups[group]) { image_to_group[image_id] = group; } } int num_kept = 0; for (auto [pair_id, two_view_geometry] : database.ReadTwoViewGeometries()) { const auto [image_id1, image_id2] = PairIdToImagePair(pair_id); if (image_to_group.at(image_id1) == image_to_group.at(image_id2)) { continue; // Keep intra-group edges untouched. } if (num_kept < num_outlier_edges && two_view_geometry.cam2_from_cam1.has_value()) { // Corrupt the relative rotation so this bridge edge is an outlier. two_view_geometry.cam2_from_cam1->rotation() = RandomEigenQuaterniond(); database.UpdateTwoViewGeometry(image_id1, image_id2, two_view_geometry); ++num_kept; } else { database.DeleteTwoViewGeometry(image_id1, image_id2); database.DeleteInlierMatches(image_id1, image_id2); database.DeleteMatches(image_id1, image_id2); } } } // End-to-end: a database whose view graph splits into two disconnected // components should yield one reconstruction per component, each registering // exactly that component's images. TEST(GlobalPipeline, MultiComponents) { SetPRNGSeed(1); const auto database_path = CreateTestDir() / "database.db"; auto database = Database::Open(database_path); Reconstruction gt_reconstruction; SyntheticDatasetOptions synthetic_dataset_options; synthetic_dataset_options.num_rigs = 2; synthetic_dataset_options.num_cameras_per_rig = 1; synthetic_dataset_options.num_frames_per_rig = 5; synthetic_dataset_options.num_points3D = 100; synthetic_dataset_options.camera_has_prior_focal_length = false; synthetic_dataset_options.two_view_geometry_has_relative_pose = true; SynthesizeDataset( synthetic_dataset_options, >_reconstruction, database.get()); // Split the images into two groups (one per rig) and cut all cross-group // matches so the view graph decomposes into two connected components. // Grouping by rig keeps each component's ground truth well-defined. const std::vector> expected_components = GroupImageIdsByRig(gt_reconstruction); ASSERT_EQ(expected_components.size(), 2); ASSERT_EQ(expected_components[0].size(), 5); ASSERT_EQ(expected_components[1].size(), 5); DisconnectDatabaseComponents(expected_components, *database); auto reconstruction_manager = std::make_shared(); GlobalPipelineOptions options; ASSERT_TRUE(options.multiple_models); ViewGraphCalibrationOptions vgc_options; CalibrateViewGraph(vgc_options, database.get()); GlobalPipeline mapper(std::move(options), database, reconstruction_manager); mapper.Run(); // Expect one reconstruction per component, each covering its own images. ASSERT_EQ(reconstruction_manager->Size(), 2); EXPECT_THAT(RegImageIdSetsPerReconstruction(*reconstruction_manager), testing::UnorderedElementsAreArray(expected_components)); // Each recovered component must also match the ground truth of its cluster. for (size_t i = 0; i < reconstruction_manager->Size(); ++i) { const Reconstruction& reconstruction = *reconstruction_manager->Get(i); const std::vector reg_image_ids = reconstruction.RegImageIds(); const FlatHashSet reconstruction_image_ids(reg_image_ids.begin(), reg_image_ids.end()); const auto group_it = std::find(expected_components.begin(), expected_components.end(), reconstruction_image_ids); ASSERT_NE(group_it, expected_components.end()); const Reconstruction gt_subset = ExtractGroundTruthSubset(gt_reconstruction, *group_it); EXPECT_THAT(gt_subset, ReconstructionNear(reconstruction, /*max_rotation_error_deg=*/1e-2, /*max_proj_center_error=*/1e-4)); } } TEST(GlobalPipeline, ReconstructOnlyLargestComponent) { SetPRNGSeed(1); const auto database_path = CreateTestDir() / "database.db"; auto database = Database::Open(database_path); Reconstruction gt_reconstruction; SyntheticDatasetOptions synthetic_dataset_options; synthetic_dataset_options.num_rigs = 2; synthetic_dataset_options.num_cameras_per_rig = 1; synthetic_dataset_options.num_frames_per_rig = 5; synthetic_dataset_options.num_points3D = 100; synthetic_dataset_options.camera_has_prior_focal_length = true; synthetic_dataset_options.two_view_geometry_has_relative_pose = true; SynthesizeDataset( synthetic_dataset_options, >_reconstruction, database.get()); const std::vector> expected_components = GroupImageIdsByRig(gt_reconstruction); DisconnectDatabaseComponents(expected_components, *database); auto reconstruction_manager = std::make_shared(); GlobalPipelineOptions options; options.multiple_models = false; GlobalPipeline mapper(std::move(options), database, reconstruction_manager); mapper.Run(); ASSERT_EQ(reconstruction_manager->Size(), 1); EXPECT_EQ(reconstruction_manager->Get(0)->NumRegFrames(), 5); } // The current component must be visible through the reconstruction manager // while callbacks run, and a stop request must prevent subsequent components // from starting. TEST(GlobalPipeline, MultiComponentsStopAfterFirstComponent) { SetPRNGSeed(1); const auto database_path = CreateTestDir() / "database.db"; auto database = Database::Open(database_path); Reconstruction gt_reconstruction; SyntheticDatasetOptions synthetic_dataset_options; synthetic_dataset_options.num_rigs = 2; synthetic_dataset_options.num_cameras_per_rig = 1; synthetic_dataset_options.num_frames_per_rig = 5; synthetic_dataset_options.num_points3D = 100; synthetic_dataset_options.camera_has_prior_focal_length = true; synthetic_dataset_options.two_view_geometry_has_relative_pose = true; SynthesizeDataset( synthetic_dataset_options, >_reconstruction, database.get()); const std::vector> expected_components = GroupImageIdsByRig(gt_reconstruction); DisconnectDatabaseComponents(expected_components, *database); auto reconstruction_manager = std::make_shared(); GlobalPipelineOptions options; GlobalPipeline mapper(std::move(options), database, reconstruction_manager); bool stop_requested = false; bool callback_saw_in_progress_reconstruction = false; mapper.AddCallback(GlobalPipeline::MODEL_UPDATE_CALLBACK, [&]() { callback_saw_in_progress_reconstruction = reconstruction_manager->Size() == 1 && reconstruction_manager->Get(0)->NumRegFrames() > 0; stop_requested = true; }); mapper.SetCheckIfStoppedFunc([&]() { return stop_requested; }); mapper.Run(); EXPECT_TRUE(callback_saw_in_progress_reconstruction); EXPECT_TRUE(stop_requested); EXPECT_EQ(reconstruction_manager->Size(), 1); } // Components that cannot meet min_model_size are discarded before invoking // the expensive global mapper. TEST(GlobalPipeline, MultiComponentsBelowMinModelSize) { SetPRNGSeed(1); const auto database_path = CreateTestDir() / "database.db"; auto database = Database::Open(database_path); Reconstruction gt_reconstruction; SyntheticDatasetOptions synthetic_dataset_options; synthetic_dataset_options.num_rigs = 2; synthetic_dataset_options.num_cameras_per_rig = 1; synthetic_dataset_options.num_frames_per_rig = 2; synthetic_dataset_options.num_points3D = 20; synthetic_dataset_options.two_view_geometry_has_relative_pose = true; SynthesizeDataset( synthetic_dataset_options, >_reconstruction, database.get()); const std::vector> expected_components = GroupImageIdsByRig(gt_reconstruction); DisconnectDatabaseComponents(expected_components, *database); auto reconstruction_manager = std::make_shared(); GlobalPipelineOptions options; options.min_model_size = 3; GlobalPipeline mapper(std::move(options), database, reconstruction_manager); bool callback_called = false; mapper.AddCallback(GlobalPipeline::MODEL_UPDATE_CALLBACK, [&]() { callback_called = true; }); mapper.Run(); EXPECT_FALSE(callback_called); EXPECT_EQ(reconstruction_manager->Size(), 0); } // Components that become too small only after rotation filtering must also be // discarded before invoking the full global mapper. In particular, the // rejected bridge edges must not reconnect the residual components and cause // repeated mapping attempts. TEST(GlobalPipeline, MultiComponentsBelowMinModelSizeAfterRotationFiltering) { SetPRNGSeed(1); const auto database_path = CreateTestDir() / "database.db"; auto database = Database::Open(database_path); Reconstruction gt_reconstruction; SyntheticDatasetOptions synthetic_dataset_options; synthetic_dataset_options.num_rigs = 2; synthetic_dataset_options.num_cameras_per_rig = 1; synthetic_dataset_options.num_frames_per_rig = 3; synthetic_dataset_options.num_points3D = 100; synthetic_dataset_options.camera_has_prior_focal_length = true; synthetic_dataset_options.two_view_geometry_has_relative_pose = true; synthetic_dataset_options.prior_gravity = true; SynthesizeDataset( synthetic_dataset_options, >_reconstruction, database.get()); const std::vector> expected_components = GroupImageIdsByRig(gt_reconstruction); ASSERT_EQ(expected_components.size(), 2); ASSERT_EQ(expected_components[0].size(), 3); ASSERT_EQ(expected_components[1].size(), 3); // Keep and corrupt every cross-component edge. Gravity anchors the two // groups, so rotation filtering rejects the bridges and recovers two // three-frame components from one initial six-frame component. BridgeGroupsWithOutlierEdges(expected_components, /*num_outlier_edges=*/ static_cast(gt_reconstruction.NumImages() * gt_reconstruction.NumImages()), *database); // First verify that the setup is successfully decomposed into the expected // components when they meet the minimum size. auto baseline_manager = std::make_shared(); GlobalPipelineOptions baseline_options; baseline_options.random_seed = 1; baseline_options.min_model_size = 3; baseline_options.mapper.rotation_averaging.use_gravity = true; GlobalPipeline baseline_mapper( std::move(baseline_options), database, baseline_manager); baseline_mapper.Run(); ASSERT_EQ(baseline_manager->Size(), 2); EXPECT_THAT(RegImageIdSetsPerReconstruction(*baseline_manager), testing::UnorderedElementsAreArray(expected_components)); auto reconstruction_manager = std::make_shared(); GlobalPipelineOptions options; options.random_seed = 1; options.min_model_size = 4; options.mapper.rotation_averaging.use_gravity = true; GlobalPipeline mapper(std::move(options), database, reconstruction_manager); bool callback_called = false; mapper.AddCallback(GlobalPipeline::MODEL_UPDATE_CALLBACK, [&]() { callback_called = true; }); mapper.Run(); EXPECT_FALSE(callback_called); EXPECT_EQ(reconstruction_manager->Size(), 0); } // Multi-camera rigs with unknown sensor_from_rig must be calibrated // independently in each disconnected component. TEST(GlobalPipeline, MultiComponentsWithUnknownSensorFromRig) { SetPRNGSeed(1); const auto database_path = CreateTestDir() / "database.db"; auto database = Database::Open(database_path); Reconstruction gt_reconstruction; SyntheticDatasetOptions synthetic_dataset_options; synthetic_dataset_options.num_rigs = 2; synthetic_dataset_options.num_cameras_per_rig = 2; synthetic_dataset_options.num_frames_per_rig = 5; synthetic_dataset_options.num_points3D = 100; synthetic_dataset_options.camera_has_prior_focal_length = true; synthetic_dataset_options.two_view_geometry_has_relative_pose = true; SynthesizeDataset( synthetic_dataset_options, >_reconstruction, database.get()); const std::vector> expected_components = GroupImageIdsByRig(gt_reconstruction); ASSERT_EQ(expected_components.size(), 2); DisconnectDatabaseComponents(expected_components, *database); for (Rig rig : database->ReadAllRigs()) { for (const sensor_t sensor_id : rig.SensorIds()) { if (!rig.IsRefSensor(sensor_id)) { rig.ResetSensorFromRig(sensor_id); } } database->UpdateRig(rig); } auto reconstruction_manager = std::make_shared(); GlobalPipelineOptions options; GlobalPipeline mapper(std::move(options), database, reconstruction_manager); mapper.Run(); ASSERT_EQ(reconstruction_manager->Size(), 2); EXPECT_THAT(RegImageIdSetsPerReconstruction(*reconstruction_manager), testing::UnorderedElementsAreArray(expected_components)); } // End-to-end: two clusters bridged only by a couple of outlier edges (with // bogus relative rotations) are still recovered as two separate // reconstructions. The two mutually-inconsistent bridges cannot both be // satisfied by any global rotation solution, so rotation averaging leaves each // with a large residual and FilterEdgesByRelativeRotation removes them, // splitting the view graph. TEST(GlobalPipeline, MultiComponentsWithOutlierEdges) { SetPRNGSeed(1); const auto database_path = CreateTestDir() / "database.db"; auto database = Database::Open(database_path); Reconstruction gt_reconstruction; SyntheticDatasetOptions synthetic_dataset_options; synthetic_dataset_options.num_rigs = 2; synthetic_dataset_options.num_cameras_per_rig = 1; synthetic_dataset_options.num_frames_per_rig = 5; synthetic_dataset_options.num_points3D = 100; // Known focal lengths let us skip view graph calibration, which would // otherwise re-estimate (and thereby "fix") the injected outlier edges. synthetic_dataset_options.camera_has_prior_focal_length = true; synthetic_dataset_options.two_view_geometry_has_relative_pose = true; SynthesizeDataset( synthetic_dataset_options, >_reconstruction, database.get()); // Split the images into two groups. std::vector image_ids = gt_reconstruction.RegImageIds(); std::sort(image_ids.begin(), image_ids.end()); ASSERT_EQ(image_ids.size(), 10); const std::vector> expected_components = { {image_ids.begin(), image_ids.begin() + 5}, {image_ids.begin() + 5, image_ids.end()}}; // Connect the two groups only through two outlier bridge edges. BridgeGroupsWithOutlierEdges( expected_components, /*num_outlier_edges=*/2, *database); auto reconstruction_manager = std::make_shared(); GlobalPipelineOptions options; ASSERT_TRUE(options.multiple_models); GlobalPipeline mapper(std::move(options), database, reconstruction_manager); mapper.Run(); // The outlier bridges must be rejected, recovering the two clusters. ASSERT_EQ(reconstruction_manager->Size(), 2); EXPECT_THAT(RegImageIdSetsPerReconstruction(*reconstruction_manager), testing::UnorderedElementsAreArray(expected_components)); } // End-to-end (gravity variant): even when *every* cross-cluster edge is a // bogus-rotation outlier - a regime the default gravity-free solver cannot // resolve because the inter-cluster orientation gauge is free - gravity priors // anchor each cluster to the vertical. The random bridge rotations then exceed // the rotation error threshold and are filtered, recovering the two clusters. TEST(GlobalPipeline, MultiComponentsWithOutlierEdgesUsingGravity) { SetPRNGSeed(1); const auto database_path = CreateTestDir() / "database.db"; auto database = Database::Open(database_path); Reconstruction gt_reconstruction; SyntheticDatasetOptions synthetic_dataset_options; synthetic_dataset_options.num_rigs = 2; synthetic_dataset_options.num_cameras_per_rig = 1; synthetic_dataset_options.num_frames_per_rig = 5; synthetic_dataset_options.num_points3D = 100; // Known focal lengths let us skip view graph calibration, which would // otherwise re-estimate (and thereby "fix") the injected outlier edges. synthetic_dataset_options.camera_has_prior_focal_length = true; synthetic_dataset_options.two_view_geometry_has_relative_pose = true; synthetic_dataset_options.prior_gravity = true; SynthesizeDataset( synthetic_dataset_options, >_reconstruction, database.get()); // Split the images into two groups. std::vector image_ids = gt_reconstruction.RegImageIds(); std::sort(image_ids.begin(), image_ids.end()); ASSERT_EQ(image_ids.size(), 10); const std::vector> expected_components = { {image_ids.begin(), image_ids.begin() + 5}, {image_ids.begin() + 5, image_ids.end()}}; // Corrupt every cross-cluster edge into an outlier bridge (passing a count // larger than the number of cross pairs keeps and randomizes all of them). BridgeGroupsWithOutlierEdges( expected_components, /*num_outlier_edges=*/ static_cast(image_ids.size() * image_ids.size()), *database); auto reconstruction_manager = std::make_shared(); GlobalPipelineOptions options; ASSERT_TRUE(options.multiple_models); // Gravity priors pin each cluster to the vertical, making the random-rotation // bridges detectable regardless of the otherwise free inter-cluster gauge. options.mapper.rotation_averaging.use_gravity = true; GlobalPipeline mapper(std::move(options), database, reconstruction_manager); mapper.Run(); // Despite every cross edge being an outlier, gravity lets rotation averaging // filter them all and recover the two clusters. ASSERT_EQ(reconstruction_manager->Size(), 2); EXPECT_THAT(RegImageIdSetsPerReconstruction(*reconstruction_manager), testing::UnorderedElementsAreArray(expected_components)); } // End-to-end: with no matches at all, the view graph is empty and the pipeline // produces no reconstructions. TEST(GlobalPipeline, MultiComponentsEmptyViewGraph) { SetPRNGSeed(1); const auto database_path = CreateTestDir() / "database.db"; auto database = Database::Open(database_path); Reconstruction gt_reconstruction; SyntheticDatasetOptions synthetic_dataset_options; synthetic_dataset_options.num_rigs = 1; synthetic_dataset_options.num_cameras_per_rig = 1; synthetic_dataset_options.num_frames_per_rig = 3; synthetic_dataset_options.num_points3D = 20; synthetic_dataset_options.two_view_geometry_has_relative_pose = true; SynthesizeDataset( synthetic_dataset_options, >_reconstruction, database.get()); // Remove all matches so the view graph is empty. database->ClearTwoViewGeometries(); database->ClearMatches(); auto reconstruction_manager = std::make_shared(); GlobalPipelineOptions options; GlobalPipeline mapper(std::move(options), database, reconstruction_manager); mapper.Run(); EXPECT_EQ(reconstruction_manager->Size(), 0); } } // namespace } // namespace colmap colmap-4.2.0/src/colmap/controllers/hierarchical_pipeline.cc000066400000000000000000000264011524536416500242300ustar00rootroot00000000000000// Copyright (c), ETH Zurich and UNC Chapel Hill. // All rights reserved. // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are met: // // * Redistributions of source code must retain the above copyright // notice, this list of conditions and the following disclaimer. // // * Redistributions in binary form must reproduce the above copyright // notice, this list of conditions and the following disclaimer in the // documentation and/or other materials provided with the distribution. // // * Neither the name of ETH Zurich and UNC Chapel Hill nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE // ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE // LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR // CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF // SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS // INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN // CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) // ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE // POSSIBILITY OF SUCH DAMAGE. #include "colmap/controllers/hierarchical_pipeline.h" #include "colmap/scene/database.h" #include "colmap/scene/scene_clustering.h" #include "colmap/sfm/observation_manager.h" #include "colmap/util/hash_containers.h" #include "colmap/util/misc.h" #include "colmap/util/threading.h" #include "colmap/util/timer.h" namespace colmap { namespace { void MergeClusters(const SceneClustering::Cluster& cluster, NodeHashMap>* reconstruction_managers) { // Extract all reconstructions from all child clusters. std::vector> reconstructions; for (const auto& child_cluster : cluster.child_clusters) { if (!child_cluster.child_clusters.empty()) { MergeClusters(child_cluster, reconstruction_managers); } auto& reconstruction_manager = reconstruction_managers->at(&child_cluster); for (size_t i = 0; i < reconstruction_manager->Size(); ++i) { reconstructions.push_back(reconstruction_manager->Get(i)); } } // Try to merge all child cluster reconstruction. while (reconstructions.size() > 1) { bool merge_success = false; for (size_t i = 0; i < reconstructions.size(); ++i) { const int num_reg_images_i = reconstructions[i]->NumRegImages(); for (size_t j = 0; j < i; ++j) { const double kMaxReprojError = 8.0; const int num_reg_images_j = reconstructions[j]->NumRegImages(); if (MergeAndFilterReconstructions( kMaxReprojError, *reconstructions[j], *reconstructions[i])) { LOG(INFO) << StringPrintf( "=> Merged clusters with %d and %d images into %d images", num_reg_images_i, num_reg_images_j, reconstructions[i]->NumRegImages()); reconstructions.erase(reconstructions.begin() + j); merge_success = true; break; } } if (merge_success) { break; } } if (!merge_success) { break; } } // Insert a new reconstruction manager for merged cluster. auto& reconstruction_manager = (*reconstruction_managers)[&cluster]; reconstruction_manager = std::make_shared(); for (const auto& reconstruction : reconstructions) { reconstruction_manager->Get(reconstruction_manager->Add()) = reconstruction; } // Delete all merged child cluster reconstruction managers. for (const auto& child_cluster : cluster.child_clusters) { reconstruction_managers->erase(&child_cluster); } } } // namespace bool HierarchicalPipelineOptions::Check() const { CHECK_OPTION_GT(init_num_trials, -1); CHECK_OPTION_GE(num_threads, -1); CHECK_OPTION_GE(num_workers, -1); clustering_options.Check(); THROW_CHECK_EQ(clustering_options.branching, 2); incremental_options.Check(); return true; } HierarchicalPipeline::HierarchicalPipeline( const HierarchicalPipelineOptions& options, std::shared_ptr database, std::shared_ptr reconstruction_manager) : options_(options), reconstruction_manager_( std::move(THROW_CHECK_NOTNULL(reconstruction_manager))) { THROW_CHECK(options_.Check()); THROW_CHECK_NOTNULL(database); LOG(INFO) << "Loading database"; Timer timer; timer.Start(); DatabaseCache::Options database_cache_options; database_cache_options.min_num_matches = static_cast(options_.incremental_options.min_num_matches); database_cache_options.ignore_watermarks = options_.incremental_options.ignore_watermarks; database_cache_ = DatabaseCache::Create(*database, database_cache_options); timer.PrintMinutes(); if (options_.incremental_options.ba_refine_sensor_from_rig) { LOG(WARNING) << "The hierarchical reconstruction pipeline currently does not work " "robustly when refining the rig extrinsics, because overlapping " "frames in different child clusters are optimized independently and " "can thus diverge significantly. The merging of clusters oftentimes " "fails in these cases."; } } void HierarchicalPipeline::Run() { LOG_HEADING1("Partitioning scene"); Timer run_timer; run_timer.Start(); ////////////////////////////////////////////////////////////////////////////// // Cluster scene graph ////////////////////////////////////////////////////////////////////////////// NodeHashMap image_id_to_name; image_id_to_name.reserve(database_cache_->NumImages()); for (const auto& [image_id, image] : database_cache_->Images()) { image_id_to_name.emplace(image_id, image.Name()); } SceneClustering scene_clustering = SceneClustering::Create(options_.clustering_options, *database_cache_); auto leaf_clusters = scene_clustering.GetLeafClusters(); size_t total_num_images = 0; for (size_t i = 0; i < leaf_clusters.size(); ++i) { total_num_images += leaf_clusters[i]->image_ids.size(); LOG(INFO) << StringPrintf(" Cluster %d with %d images", i + 1, leaf_clusters[i]->image_ids.size()); } LOG(INFO) << StringPrintf("Clusters have %d images", total_num_images); ////////////////////////////////////////////////////////////////////////////// // Reconstruct clusters ////////////////////////////////////////////////////////////////////////////// LOG_HEADING1("Reconstructing clusters"); // Determine the number of workers and threads per worker. The total thread // budget is divided across workers to avoid oversubscription. if (options_.incremental_options.num_threads > 0) { LOG(WARNING) << "Mapper.num_threads is ignored in hierarchical mapping. Use " "num_threads to control the total thread budget instead."; } const int num_total_threads = GetEffectiveNumThreads(options_.num_threads); const int kDefaultNumWorkers = 8; const int num_eff_workers = std::max( 1, std::min(static_cast(leaf_clusters.size()), std::min(options_.num_workers < 1 ? kDefaultNumWorkers : options_.num_workers, num_total_threads))); const int num_threads_per_worker = std::max(1, num_total_threads / num_eff_workers); // Function to reconstruct one cluster using incremental mapping. auto ReconstructCluster = [this, &image_id_to_name, num_threads_per_worker]( const SceneClustering::Cluster& cluster, std::shared_ptr reconstruction_manager) { if (cluster.image_ids.empty()) { return; } auto incremental_options = std::make_shared( options_.incremental_options); incremental_options->image_path = options_.image_path; incremental_options->max_model_overlap = 3; incremental_options->init_num_trials = options_.init_num_trials; incremental_options->num_threads = num_threads_per_worker; FlatHashSet cluster_image_names; cluster_image_names.reserve(cluster.image_ids.size()); for (const image_t image_id : cluster.image_ids) { cluster_image_names.insert(image_id_to_name.at(image_id)); } // Create a filtered database cache for this cluster. DatabaseCache::Options cluster_cache_options; cluster_cache_options.min_num_matches = static_cast(options_.incremental_options.min_num_matches); cluster_cache_options.image_names = cluster_image_names; auto cluster_database_cache = DatabaseCache::CreateFromCache( *database_cache_, cluster_cache_options); IncrementalPipeline mapper(std::move(incremental_options), std::move(cluster_database_cache), std::move(reconstruction_manager)); mapper.Run(); }; // Start reconstructing the bigger clusters first for better resource usage. // NOLINTNEXTLINE(bugprone-nondeterministic-pointer-iteration-order) std::sort(leaf_clusters.begin(), leaf_clusters.end(), [](const SceneClustering::Cluster* cluster1, const SceneClustering::Cluster* cluster2) { return cluster1->image_ids.size() > cluster2->image_ids.size(); }); // Start the reconstruction workers. Use a separate reconstruction manager per // thread to avoid race conditions. NodeHashMap> reconstruction_managers; reconstruction_managers.reserve(leaf_clusters.size()); ThreadPool thread_pool(num_eff_workers); for (const auto& cluster : leaf_clusters) { reconstruction_managers[cluster] = std::make_shared(); thread_pool.AddTask( ReconstructCluster, *cluster, reconstruction_managers[cluster]); } thread_pool.Wait(); ////////////////////////////////////////////////////////////////////////////// // Merge clusters ////////////////////////////////////////////////////////////////////////////// if (leaf_clusters.size() > 1) { LOG_HEADING1("Merging clusters"); MergeClusters(*scene_clustering.GetRootCluster(), &reconstruction_managers); } THROW_CHECK_EQ(reconstruction_managers.size(), 1); THROW_CHECK_GT( reconstruction_managers.begin()->second->Get(0)->NumRegImages(), 0); *reconstruction_manager_ = *reconstruction_managers.begin()->second; for (size_t i = 0; i < reconstruction_manager_->Size(); ++i) { auto reconstruction = reconstruction_manager_->Get(i); reconstruction->UpdatePoint3DErrors(); } run_timer.PrintMinutes(); } } // namespace colmap colmap-4.2.0/src/colmap/controllers/hierarchical_pipeline.h000066400000000000000000000066751524536416500241050ustar00rootroot00000000000000// Copyright (c), ETH Zurich and UNC Chapel Hill. // All rights reserved. // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are met: // // * Redistributions of source code must retain the above copyright // notice, this list of conditions and the following disclaimer. // // * Redistributions in binary form must reproduce the above copyright // notice, this list of conditions and the following disclaimer in the // documentation and/or other materials provided with the distribution. // // * Neither the name of ETH Zurich and UNC Chapel Hill nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE // ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE // LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR // CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF // SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS // INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN // CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) // ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE // POSSIBILITY OF SUCH DAMAGE. #pragma once #include "colmap/controllers/incremental_pipeline.h" #include "colmap/scene/reconstruction_manager.h" #include "colmap/scene/scene_clustering.h" #include "colmap/util/base_controller.h" #include #include namespace colmap { struct HierarchicalPipelineOptions { // The image path at which to find the images to extract point colors. // If not specified, all point colors will be black. std::filesystem::path image_path; // The maximum number of trials to initialize a cluster. int init_num_trials = 10; // The total number of threads for the hierarchical pipeline. This budget // is divided across workers to avoid thread oversubscription. int num_threads = -1; // The number of workers used to reconstruct clusters in parallel. int num_workers = -1; // Options for clustering the scene graph. SceneClustering::Options clustering_options; // Options used to reconstruction each cluster individually. IncrementalPipelineOptions incremental_options; bool Check() const; }; // Hierarchical mapping first hierarchically partitions the scene into multiple // overlapping clusters, then reconstructs them separately using incremental // mapping, and finally merges them all into a globally consistent // reconstruction. This is especially useful for larger-scale scenes, since // incremental mapping becomes slow with an increasing number of images. class HierarchicalPipeline : public BaseController { public: HierarchicalPipeline( const HierarchicalPipelineOptions& options, std::shared_ptr database, std::shared_ptr reconstruction_manager); void Run() override; private: const HierarchicalPipelineOptions options_; std::shared_ptr database_cache_; std::shared_ptr reconstruction_manager_; }; } // namespace colmap colmap-4.2.0/src/colmap/controllers/hierarchical_pipeline_test.cc000066400000000000000000000250141524536416500252660ustar00rootroot00000000000000// Copyright (c), ETH Zurich and UNC Chapel Hill. // All rights reserved. // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are met: // // * Redistributions of source code must retain the above copyright // notice, this list of conditions and the following disclaimer. // // * Redistributions in binary form must reproduce the above copyright // notice, this list of conditions and the following disclaimer in the // documentation and/or other materials provided with the distribution. // // * Neither the name of ETH Zurich and UNC Chapel Hill nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE // ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE // LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR // CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF // SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS // INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN // CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) // ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE // POSSIBILITY OF SUCH DAMAGE. #include "colmap/controllers/hierarchical_pipeline.h" #include "colmap/estimators/alignment.h" #include "colmap/scene/database.h" #include "colmap/scene/synthetic.h" #include "colmap/util/testing.h" #include namespace colmap { namespace { void ExpectEqualReconstructions(const Reconstruction& gt, const Reconstruction& computed, const double max_rotation_error_deg, const double max_proj_center_error, const double num_obs_tolerance) { EXPECT_EQ(computed.NumCameras(), gt.NumCameras()); EXPECT_EQ(computed.NumImages(), gt.NumImages()); EXPECT_EQ(computed.NumRegImages(), gt.NumRegImages()); EXPECT_GE(computed.ComputeNumObservations(), (1 - num_obs_tolerance) * gt.ComputeNumObservations()); Sim3d gt_from_computed; ASSERT_TRUE(AlignReconstructionsViaProjCenters(computed, gt, /*max_proj_center_error=*/0.1, >_from_computed)); const std::vector errors = ComputeImageAlignmentError(computed, gt, gt_from_computed); EXPECT_EQ(errors.size(), gt.NumImages()); for (const auto& error : errors) { EXPECT_LT(error.rotation_error_deg, max_rotation_error_deg); EXPECT_LT(error.proj_center_error, max_proj_center_error); } } TEST(HierarchicalPipeline, WithoutNoise) { const auto database_path = CreateTestDir() / "database.db"; auto database = Database::Open(database_path); Reconstruction gt_reconstruction; SyntheticDatasetOptions synthetic_dataset_options; synthetic_dataset_options.num_rigs = 2; synthetic_dataset_options.num_cameras_per_rig = 1; synthetic_dataset_options.num_frames_per_rig = 20; synthetic_dataset_options.num_points3D = 100; SynthesizeDataset( synthetic_dataset_options, >_reconstruction, database.get()); auto reconstruction_manager = std::make_shared(); HierarchicalPipelineOptions mapper_options; mapper_options.clustering_options.leaf_max_num_images = 5; mapper_options.clustering_options.image_overlap = 3; HierarchicalPipeline mapper(mapper_options, database, reconstruction_manager); mapper.Run(); ASSERT_EQ(reconstruction_manager->Size(), 1); auto reconstruction = reconstruction_manager->Get(0); ExpectEqualReconstructions(gt_reconstruction, *reconstruction, /*max_rotation_error_deg=*/1e-2, /*max_proj_center_error=*/5e-4, /*num_obs_tolerance=*/0); // After the pipeline runs, point3D.error must be in pixel units, i.e. // equal to what UpdatePoint3DErrors would recompute. ASSERT_GT(reconstruction->NumPoints3D(), 0u); const double mean_after_run = reconstruction->ComputeMeanReprojectionError(); reconstruction->UpdatePoint3DErrors(); EXPECT_DOUBLE_EQ(mean_after_run, reconstruction->ComputeMeanReprojectionError()); } TEST(HierarchicalPipeline, WithoutNoiseAndNonTrivialFrames) { const auto database_path = CreateTestDir() / "database.db"; auto database = Database::Open(database_path); Reconstruction gt_reconstruction; SyntheticDatasetOptions synthetic_dataset_options; synthetic_dataset_options.num_rigs = 2; synthetic_dataset_options.num_cameras_per_rig = 2; synthetic_dataset_options.num_frames_per_rig = 10; synthetic_dataset_options.num_points3D = 100; synthetic_dataset_options.sensor_from_rig_translation_stddev = 0.05; synthetic_dataset_options.sensor_from_rig_rotation_stddev = 30; SynthesizeDataset( synthetic_dataset_options, >_reconstruction, database.get()); auto reconstruction_manager = std::make_shared(); HierarchicalPipelineOptions mapper_options; mapper_options.clustering_options.leaf_max_num_images = 10; mapper_options.clustering_options.image_overlap = 3; // Note that the hierarchical mapper does not work well when the // sensor_from_rig poses are inconsistently refined in different clusters, // because then the merging does not work well. mapper_options.incremental_options.ba_refine_sensor_from_rig = false; HierarchicalPipeline mapper(mapper_options, database, reconstruction_manager); mapper.Run(); ASSERT_EQ(reconstruction_manager->Size(), 1); ExpectEqualReconstructions(gt_reconstruction, *reconstruction_manager->Get(0), /*max_rotation_error_deg=*/1e-2, /*max_proj_center_error=*/1e-3, /*num_obs_tolerance=*/0); } TEST(HierarchicalPipeline, WithoutNoiseAndPanoramicNonTrivialFrames) { const auto database_path = CreateTestDir() / "database.db"; auto database = Database::Open(database_path); Reconstruction gt_reconstruction; SyntheticDatasetOptions synthetic_dataset_options; synthetic_dataset_options.num_rigs = 2; synthetic_dataset_options.num_cameras_per_rig = 3; synthetic_dataset_options.num_frames_per_rig = 10; synthetic_dataset_options.num_points3D = 100; synthetic_dataset_options.sensor_from_rig_translation_stddev = 0; synthetic_dataset_options.sensor_from_rig_rotation_stddev = 30; SynthesizeDataset( synthetic_dataset_options, >_reconstruction, database.get()); auto reconstruction_manager = std::make_shared(); HierarchicalPipelineOptions mapper_options; mapper_options.clustering_options.leaf_max_num_images = 10; mapper_options.clustering_options.image_overlap = 3; // Note that the hierarchical mapper does not work well when the // sensor_from_rig poses are inconsistently refined in different clusters, // because then the merging does not work well. mapper_options.incremental_options.ba_refine_sensor_from_rig = false; HierarchicalPipeline mapper(mapper_options, database, reconstruction_manager); mapper.Run(); ASSERT_EQ(reconstruction_manager->Size(), 1); ExpectEqualReconstructions(gt_reconstruction, *reconstruction_manager->Get(0), /*max_rotation_error_deg=*/1e-2, /*max_proj_center_error=*/1e-3, /*num_obs_tolerance=*/0); } TEST(HierarchicalPipeline, MultiReconstruction) { const auto database_path = CreateTestDir() / "database.db"; auto database = Database::Open(database_path); Reconstruction gt_reconstruction1; Reconstruction gt_reconstruction2; SyntheticDatasetOptions synthetic_dataset_options; synthetic_dataset_options.num_rigs = 1; synthetic_dataset_options.num_cameras_per_rig = 1; synthetic_dataset_options.num_frames_per_rig = 5; synthetic_dataset_options.num_points3D = 50; SynthesizeDataset( synthetic_dataset_options, >_reconstruction1, database.get()); synthetic_dataset_options.num_frames_per_rig = 4; SynthesizeDataset( synthetic_dataset_options, >_reconstruction2, database.get()); auto reconstruction_manager = std::make_shared(); HierarchicalPipelineOptions mapper_options; mapper_options.clustering_options.leaf_max_num_images = 5; mapper_options.clustering_options.image_overlap = 3; HierarchicalPipeline mapper(mapper_options, database, reconstruction_manager); mapper.Run(); ASSERT_EQ(reconstruction_manager->Size(), 2); Reconstruction* computed_reconstruction1 = nullptr; Reconstruction* computed_reconstruction2 = nullptr; if (reconstruction_manager->Get(0)->NumRegImages() == 5) { computed_reconstruction1 = reconstruction_manager->Get(0).get(); computed_reconstruction2 = reconstruction_manager->Get(1).get(); } else { computed_reconstruction1 = reconstruction_manager->Get(1).get(); computed_reconstruction2 = reconstruction_manager->Get(0).get(); } ExpectEqualReconstructions(gt_reconstruction1, *computed_reconstruction1, /*max_rotation_error_deg=*/1e-2, /*max_proj_center_error=*/1e-4, /*num_obs_tolerance=*/0); ExpectEqualReconstructions(gt_reconstruction2, *computed_reconstruction2, /*max_rotation_error_deg=*/1e-2, /*max_proj_center_error=*/1e-4, /*num_obs_tolerance=*/0); // After the pipeline runs, point3D.error must be in pixel units for every // reconstruction in the manager, i.e. equal to what UpdatePoint3DErrors // would recompute. for (Reconstruction* reconstruction : {computed_reconstruction1, computed_reconstruction2}) { ASSERT_GT(reconstruction->NumPoints3D(), 0u); const double mean_after_run = reconstruction->ComputeMeanReprojectionError(); reconstruction->UpdatePoint3DErrors(); EXPECT_DOUBLE_EQ(mean_after_run, reconstruction->ComputeMeanReprojectionError()); } } } // namespace } // namespace colmap colmap-4.2.0/src/colmap/controllers/image_reader.cc000066400000000000000000000351721524536416500223360ustar00rootroot00000000000000// Copyright (c), ETH Zurich and UNC Chapel Hill. // All rights reserved. // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are met: // // * Redistributions of source code must retain the above copyright // notice, this list of conditions and the following disclaimer. // // * Redistributions in binary form must reproduce the above copyright // notice, this list of conditions and the following disclaimer in the // documentation and/or other materials provided with the distribution. // // * Neither the name of ETH Zurich and UNC Chapel Hill nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE // ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE // LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR // CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF // SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS // INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN // CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) // ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE // POSSIBILITY OF SUCH DAMAGE. #include "colmap/controllers/image_reader.h" #include "colmap/sensor/models.h" #include "colmap/util/file.h" #include "colmap/util/misc.h" namespace colmap { bool ImageReaderOptions::Check() const { CHECK_OPTION_GT(default_focal_length_factor, 0.0); CHECK_OPTION(ExistsCameraModelWithName(camera_model)); const CameraModelId model_id = CameraModelNameToId(camera_model); if (!camera_params.empty()) { CHECK_OPTION( CameraModelVerifyParams(model_id, CSVToVector(camera_params))); } return true; } ImageReader::ImageReader(const ImageReaderOptions& options, Database* database) : options_(options), database_(database), image_index_(0) { THROW_CHECK(options_.Check()); // Get a list of all files in the image path, sorted by image name. if (options_.image_names.empty()) { auto image_paths = GetRecursiveFileList(options_.image_path); std::sort(image_paths.begin(), image_paths.end()); options_.image_names.reserve(image_paths.size()); for (const auto& image_path : image_paths) { options_.image_names.push_back( GetNormalizedRelativePath(image_path, options_.image_path)); } } else { if (!std::is_sorted(options_.image_names.begin(), options_.image_names.end())) { std::sort(options_.image_names.begin(), options_.image_names.end()); } } if (static_cast(options_.existing_camera_id) != kInvalidCameraId) { THROW_CHECK(database->ExistsCamera(options_.existing_camera_id)); prev_camera_ = database->ReadCamera(options_.existing_camera_id); if (std::optional rig = database->ReadRigWithSensor(prev_camera_.SensorId()); rig.has_value()) { prev_rig_ = std::move(*rig); } else { // For backwards compatibility with old databases without rigs. prev_rig_.AddRefSensor(prev_camera_.SensorId()); prev_rig_.SetRigId(database_->WriteRig(prev_rig_)); } } else { // Set the manually specified camera parameters. prev_camera_.camera_id = kInvalidCameraId; THROW_CHECK(ExistsCameraModelWithName(options_.camera_model)); prev_camera_.model_id = CameraModelNameToId(options_.camera_model); prev_camera_.params.resize(CameraModelNumParams(prev_camera_.model_id), 0.); if (!options_.camera_params.empty()) { THROW_CHECK(prev_camera_.SetParamsFromString(options_.camera_params)); prev_camera_.has_prior_focal_length = true; } } } ImageReader::Status ImageReader::Next(Rig* rig, Camera* camera, Image* image, PosePrior* pose_prior, Bitmap* bitmap, Bitmap* mask) { THROW_CHECK_NOTNULL(camera); THROW_CHECK_NOTNULL(image); THROW_CHECK_NOTNULL(bitmap); image_index_ += 1; THROW_CHECK_LE(image_index_, options_.image_names.size()); const std::string image_name = options_.image_names.at(image_index_ - 1); const std::filesystem::path image_path = options_.image_path / image_name; DatabaseTransaction database_transaction(database_); ////////////////////////////////////////////////////////////////////////////// // Set the image name. ////////////////////////////////////////////////////////////////////////////// image->SetName(image_name); const std::string image_folder = GetParentDir(image->Name()).string(); ////////////////////////////////////////////////////////////////////////////// // Check if image already read. ////////////////////////////////////////////////////////////////////////////// const bool exists_image = database_->ExistsImageWithName(image->Name()); if (exists_image) { *image = database_->ReadImageWithName(image->Name()).value(); const bool exists_keypoints = database_->ExistsKeypoints(image->ImageId()); const bool exists_descriptors = database_->ExistsDescriptors(image->ImageId()); if (exists_keypoints && exists_descriptors) { return Status::IMAGE_EXISTS; } } ////////////////////////////////////////////////////////////////////////////// // Read image. ////////////////////////////////////////////////////////////////////////////// if (!bitmap->Read(image_path, /*as_rgb=*/options_.as_rgb)) { return Status::BITMAP_ERROR; } ////////////////////////////////////////////////////////////////////////////// // Read mask. ////////////////////////////////////////////////////////////////////////////// if (mask && !options_.mask_path.empty()) { auto mask_path = options_.mask_path / (image->Name() + ".png"); if (!ExistsFile(mask_path)) { bool exists_mask = false; // Try replacing extension with .png const std::string& base_name = image->Name(); const size_t last_dot = base_name.find_last_of('.'); if (last_dot != std::string::npos) { auto alt_mask_path = options_.mask_path / (base_name.substr(0, last_dot) + ".png"); if (ExistsFile(alt_mask_path)) { mask_path = std::move(alt_mask_path); exists_mask = true; } } if (!exists_mask) { LOG(ERROR) << "Mask at " << mask_path << " does not exist."; return Status::MASK_ERROR; } } if (!mask->Read(mask_path, false)) { LOG(ERROR) << "Failed to read invalid mask file at: " << mask_path; return Status::MASK_ERROR; } } ////////////////////////////////////////////////////////////////////////////// // Check for well-formed data. ////////////////////////////////////////////////////////////////////////////// if (exists_image) { Camera current_camera = database_->ReadCamera(image->CameraId()); if (options_.single_camera && prev_camera_.camera_id != kInvalidCameraId && (current_camera.width != prev_camera_.width || current_camera.height != prev_camera_.height)) { return Status::CAMERA_SINGLE_DIM_ERROR; } if (static_cast(bitmap->Width()) != current_camera.width || static_cast(bitmap->Height()) != current_camera.height) { return Status::CAMERA_EXIST_DIM_ERROR; } prev_camera_ = std::move(current_camera); if (std::optional rig = database_->ReadRigWithSensor(prev_camera_.SensorId()); rig.has_value()) { prev_rig_ = std::move(rig.value()); } else { // For backwards compatibility with old databases, we create a rig. prev_rig_ = Rig(); prev_rig_.AddRefSensor(prev_camera_.SensorId()); prev_rig_.SetRigId(database_->WriteRig(prev_rig_)); } } else { ////////////////////////////////////////////////////////////////////////////// // Check image dimensions. ////////////////////////////////////////////////////////////////////////////// if (prev_camera_.camera_id != kInvalidCameraId && ((options_.single_camera && !options_.single_camera_per_folder) || (options_.single_camera_per_folder && image_folder == prev_image_folder_)) && (prev_camera_.width != static_cast(bitmap->Width()) || prev_camera_.height != static_cast(bitmap->Height()))) { return Status::CAMERA_SINGLE_DIM_ERROR; } ////////////////////////////////////////////////////////////////////////////// // Read camera model and check for consistency if it exists ////////////////////////////////////////////////////////////////////////////// const std::optional camera_model = bitmap->ExifCameraModel(); if (camera_model.has_value() && camera_model_to_id_.count(*camera_model) > 0) { Camera camera = database_->ReadCamera(camera_model_to_id_.at(*camera_model)); if (camera.width != static_cast(bitmap->Width()) || camera.height != static_cast(bitmap->Height())) { return Status::CAMERA_EXIST_DIM_ERROR; } prev_camera_ = std::move(camera); if (std::optional rig = database_->ReadRigWithSensor(prev_camera_.SensorId()); rig.has_value()) { prev_rig_ = std::move(rig.value()); } else { // For backwards compatibility with old databases, we create a rig. prev_rig_ = Rig(); prev_rig_.AddRefSensor(prev_camera_.SensorId()); prev_rig_.SetRigId(database_->WriteRig(prev_rig_)); } } ////////////////////////////////////////////////////////////////////////////// // Extract camera model and focal length ////////////////////////////////////////////////////////////////////////////// if (prev_camera_.camera_id == kInvalidCameraId || options_.single_camera_per_image || (!options_.single_camera && !options_.single_camera_per_folder && static_cast(options_.existing_camera_id) == kInvalidCameraId && (!camera_model.has_value() || camera_model_to_id_.count(camera_model.value()) == 0)) || (options_.single_camera_per_folder && image_folders_.count(image_folder) == 0)) { if (options_.camera_params.empty()) { // Extract focal length. const std::optional maybe_focal_length = bitmap->ExifFocalLength(); const double focal_length = maybe_focal_length.value_or( options_.default_focal_length_factor * std::max(bitmap->Width(), bitmap->Height())); prev_camera_ = Camera::CreateFromModelId(prev_camera_.camera_id, prev_camera_.model_id, focal_length, bitmap->Width(), bitmap->Height()); prev_camera_.has_prior_focal_length = maybe_focal_length.has_value(); } prev_camera_.width = static_cast(bitmap->Width()); prev_camera_.height = static_cast(bitmap->Height()); if (!prev_camera_.VerifyParams()) { return Status::CAMERA_PARAM_ERROR; } prev_camera_.camera_id = database_->WriteCamera(prev_camera_); // By default we create a separate rig per camera. Grouping of different // cameras into the same rig is expected to be done with the // "rig_configurator" after feature extraction. if (!database_->ReadRigWithSensor(prev_camera_.SensorId()).has_value()) { prev_rig_ = Rig(); prev_rig_.AddRefSensor(prev_camera_.SensorId()); prev_rig_.SetRigId(database_->WriteRig(prev_rig_)); } if (camera_model.has_value()) { camera_model_to_id_[*camera_model] = prev_camera_.camera_id; } } image->SetCameraId(prev_camera_.camera_id); ////////////////////////////////////////////////////////////////////////////// // Extract GPS data. ////////////////////////////////////////////////////////////////////////////// const std::optional latitude = bitmap->ExifLatitude(); const std::optional longitude = bitmap->ExifLongitude(); const std::optional altitude = bitmap->ExifAltitude(); if (latitude.has_value() && longitude.has_value() && altitude.has_value()) { pose_prior->position = Eigen::Vector3d(*latitude, *longitude, *altitude); pose_prior->coordinate_system = PosePrior::CoordinateSystem::WGS84; } ////////////////////////////////////////////////////////////////////////////// // Extract Gravity from Orientation. ////////////////////////////////////////////////////////////////////////////// const std::optional orientation = bitmap->ExifOrientation(); if (orientation.has_value()) { const auto gravity = GravityFromExifOrientation(orientation.value()); if (gravity.has_value()) { pose_prior->gravity = gravity.value(); } } } *camera = prev_camera_; *rig = prev_rig_; image_folders_.insert(image_folder); prev_image_folder_ = image_folder; return Status::SUCCESS; } size_t ImageReader::NextIndex() const { return image_index_; } size_t ImageReader::NumImages() const { return options_.image_names.size(); } std::string ImageReader::StatusToString(const ImageReader::Status status) { switch (status) { case ImageReader::Status::SUCCESS: return "SUCCESS"; case ImageReader::Status::FAILURE: return "FAILURE: Failed to process the image."; case ImageReader::Status::IMAGE_EXISTS: return "IMAGE_EXISTS: Features for image were already extracted."; case ImageReader::Status::BITMAP_ERROR: return "BITMAP_ERROR: Failed to read the image file format."; case ImageReader::Status::MASK_ERROR: return "MASK_ERROR: Failed to read the mask file."; case ImageReader::Status::CAMERA_SINGLE_DIM_ERROR: return "CAMERA_SINGLE_DIM_ERROR: Single camera specified, but images " "have different dimensions."; case ImageReader::Status::CAMERA_EXIST_DIM_ERROR: return "CAMERA_EXIST_DIM_ERROR: Image previously processed, but current " "image has different dimensions."; case ImageReader::Status::CAMERA_PARAM_ERROR: return "CAMERA_PARAM_ERROR: Camera has invalid parameters."; default: return "Unknown"; } } } // namespace colmap colmap-4.2.0/src/colmap/controllers/image_reader.h000066400000000000000000000120451524536416500221720ustar00rootroot00000000000000// Copyright (c), ETH Zurich and UNC Chapel Hill. // All rights reserved. // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are met: // // * Redistributions of source code must retain the above copyright // notice, this list of conditions and the following disclaimer. // // * Redistributions in binary form must reproduce the above copyright // notice, this list of conditions and the following disclaimer in the // documentation and/or other materials provided with the distribution. // // * Neither the name of ETH Zurich and UNC Chapel Hill nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE // ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE // LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR // CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF // SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS // INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN // CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) // ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE // POSSIBILITY OF SUCH DAMAGE. #pragma once #include "colmap/scene/database.h" #include "colmap/sensor/bitmap.h" #include "colmap/util/hash_containers.h" #include #include namespace colmap { struct ImageReaderOptions { // Root path to folder which contains the images. std::filesystem::path image_path; // Optional root path to folder which contains image masks. For a given image, // the corresponding mask must have the same sub-path below this root as the // image has below image_path. The filename must be equal, aside from the // added extension .png. For example, for an image image_path/abc/012.jpg, the // mask would be mask_path/abc/012.jpg.png. No features will be extracted in // regions where the mask image is black (pixel intensity value 0 in // grayscale). std::filesystem::path mask_path; // Optional path to an image file specifying a mask for all images. No // features will be extracted in regions where the mask is black (pixel // intensity value 0 in grayscale). std::filesystem::path camera_mask_path; // Optional list of images to read. The list must contain the relative path // of the images with respect to the image_path. std::vector image_names; // Name of the camera model. std::string camera_model = "SIMPLE_RADIAL"; // Manual specification of camera parameters. If empty, camera parameters // will be extracted from EXIF, i.e. principal point and focal length. std::string camera_params; // Whether to use the same camera for all images. bool single_camera = false; // Whether to use the same camera for all images in the same sub-folder. bool single_camera_per_folder = false; // Whether to use a different camera for each image. bool single_camera_per_image = false; // Whether to explicitly use an existing camera for all images. Note that in // this case the specified camera model and parameters are ignored. int existing_camera_id = kInvalidCameraId; // If camera parameters are not specified manually and the image does not // have focal length EXIF information, the focal length is set to the // value `default_focal_length_factor * max(width, height)`. double default_focal_length_factor = 1.2; // Whether to read images as grayscale or RGB. bool as_rgb = false; bool Check() const; }; // Recursively iterate over the images in a directory. Skips an image if it // already exists in the database. Extracts the camera intrinsics from EXIF and // writes the camera information to the database. class ImageReader { public: enum class Status { FAILURE, SUCCESS, IMAGE_EXISTS, BITMAP_ERROR, MASK_ERROR, CAMERA_SINGLE_DIM_ERROR, CAMERA_EXIST_DIM_ERROR, CAMERA_PARAM_ERROR }; ImageReader(const ImageReaderOptions& options, Database* database); Status Next(Rig* rig, Camera* camera, Image* image, PosePrior* pose_prior, Bitmap* bitmap, Bitmap* mask); size_t NextIndex() const; size_t NumImages() const; static std::string StatusToString(Status status); private: // Image reader options. ImageReaderOptions options_; Database* database_; // Index of previously processed image. size_t image_index_; // Previously processed rig/camera. Rig prev_rig_; Camera prev_camera_; NodeHashMap camera_model_to_id_; // Names of image sub-folders. std::string prev_image_folder_; FlatHashSet image_folders_; }; } // namespace colmap colmap-4.2.0/src/colmap/controllers/image_reader_test.cc000066400000000000000000000464121524536416500233740ustar00rootroot00000000000000// Copyright (c), ETH Zurich and UNC Chapel Hill. // All rights reserved. // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are met: // // * Redistributions of source code must retain the above copyright // notice, this list of conditions and the following disclaimer. // // * Redistributions in binary form must reproduce the above copyright // notice, this list of conditions and the following disclaimer in the // documentation and/or other materials provided with the distribution. // // * Neither the name of ETH Zurich and UNC Chapel Hill nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE // ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE // LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR // CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF // SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS // INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN // CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) // ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE // POSSIBILITY OF SUCH DAMAGE. #include "colmap/controllers/image_reader.h" #include "colmap/scene/database_sqlite.h" #include "colmap/sensor/models.h" #include "colmap/util/file.h" #include "colmap/util/hash_containers.h" #include "colmap/util/testing.h" #include #include #include namespace colmap { namespace { Bitmap CreateTestBitmap(bool as_rgb) { Bitmap bitmap(1, 3, as_rgb); bitmap.SetPixel(0, 0, BitmapColor(1)); bitmap.SetPixel(1, 0, BitmapColor(2)); bitmap.SetPixel(2, 0, BitmapColor(3)); return bitmap; } class ParameterizedImageReaderTests : public ::testing::TestWithParam> {}; TEST_P(ParameterizedImageReaderTests, Nominal) { const auto [kNumImages, kWithMasks, kWithExistingImages, kAsRGB, kExtension] = GetParam(); auto database = Database::Open(kInMemorySqliteDatabasePath); const auto test_dir = CreateTestDir(); ImageReaderOptions options; options.image_path = test_dir / "images"; options.as_rgb = kAsRGB; CreateDirIfNotExists(options.image_path); if (kWithMasks) { options.mask_path = test_dir / "masks"; CreateDirIfNotExists(options.mask_path); } const Bitmap test_bitmap = CreateTestBitmap(kAsRGB); for (int i = 0; i < kNumImages; ++i) { const std::string stem = std::to_string(i); const std::string image_name = stem + kExtension; test_bitmap.Write(options.image_path / image_name); if (kWithMasks) { if (i == 0) { // append .png to image_name test_bitmap.Write(options.mask_path / (image_name + ".png")); } else { // replace mask extension by .png test_bitmap.Write(options.mask_path / (stem + ".png")); } } if (kWithExistingImages) { Image image; image.SetName(image_name); image.SetCameraId(database->WriteCamera( Camera::CreateFromModelName(i + 1, options.camera_model, /*focal_length=*/1, test_bitmap.Width(), test_bitmap.Height()))); image.SetImageId(database->WriteImage(image)); database->WriteKeypoints(image.ImageId(), FeatureKeypoints()); database->WriteDescriptors(image.ImageId(), FeatureDescriptors()); Rig rig; rig.AddRefSensor(sensor_t(SensorType::CAMERA, image.CameraId())); database->WriteRig(rig); } } ImageReader image_reader(options, database.get()); EXPECT_EQ(image_reader.NumImages(), kNumImages); Rig rig; Camera camera; Image image; PosePrior pose_prior; Bitmap bitmap; Bitmap mask; for (int i = 0; i < kNumImages; ++i) { EXPECT_EQ(image_reader.NextIndex(), i); const auto status = image_reader.Next(&rig, &camera, &image, &pose_prior, &bitmap, &mask); if (kWithExistingImages) { EXPECT_EQ(status, ImageReader::Status::IMAGE_EXISTS); continue; } ASSERT_EQ(status, ImageReader::Status::SUCCESS); EXPECT_EQ(rig.RigId(), i + 1); EXPECT_EQ(camera.camera_id, i + 1); EXPECT_EQ(camera.ModelName(), options.camera_model); EXPECT_EQ(camera.width, test_bitmap.Width()); EXPECT_EQ(camera.height, test_bitmap.Height()); EXPECT_EQ(image.Name(), std::to_string(i) + kExtension); EXPECT_EQ(bitmap.IsRGB(), kAsRGB); EXPECT_EQ(bitmap.RowMajorData(), test_bitmap.RowMajorData()); if (kWithExistingImages) { EXPECT_EQ(database->NumRigs(), kNumImages); EXPECT_EQ(database->NumCameras(), kNumImages); } else { EXPECT_EQ(database->NumRigs(), i + 1); EXPECT_EQ(database->NumCameras(), i + 1); } } EXPECT_THROW( image_reader.Next(&rig, &camera, &image, &pose_prior, &bitmap, &mask), std::invalid_argument); EXPECT_EQ(database->NumRigs(), kNumImages); EXPECT_EQ(database->NumCameras(), kNumImages); } INSTANTIATE_TEST_SUITE_P( ImageReaderTests, ParameterizedImageReaderTests, ::testing::Values(std::make_tuple(/*num_images=*/0, /*with_masks=*/false, /*with_existing_images=*/true, /*as_rgb=*/true, /*extension=*/".png"), std::make_tuple(/*num_images=*/5, /*with_masks=*/false, /*with_existing_images=*/false, /*as_rgb=*/true, /*extension=*/".png"), std::make_tuple(/*num_images=*/5, /*with_masks=*/true, /*with_existing_images=*/false, /*as_rgb=*/true, /*extension=*/".png"), std::make_tuple(/*num_images=*/5, /*with_masks=*/true, /*with_existing_images=*/false, /*as_rgb=*/true, /*extension=*/".bmp"), std::make_tuple(/*num_images=*/5, /*with_masks=*/true, /*with_existing_images=*/false, /*as_rgb=*/false, /*extension=*/".png"), std::make_tuple(/*num_images=*/5, /*with_masks=*/false, /*with_existing_images=*/true, /*as_rgb=*/true, /*extension=*/".png"))); TEST(ImageReaderTest, SingleCamera) { auto database = Database::Open(kInMemorySqliteDatabasePath); const auto test_dir = CreateTestDir(); ImageReaderOptions options; options.image_path = test_dir / "images"; options.single_camera = true; CreateDirIfNotExists(options.image_path); // Create 3 test images with same dimensions Bitmap test_bitmap(10, 20, true); test_bitmap.Write(options.image_path / "0.png"); test_bitmap.Write(options.image_path / "1.png"); test_bitmap.Write(options.image_path / "2.png"); ImageReader image_reader(options, database.get()); EXPECT_EQ(image_reader.NumImages(), 3); Rig rig; Camera camera; Image image; PosePrior pose_prior; Bitmap bitmap; Bitmap mask; for (int i = 0; i < 3; ++i) { const auto status = image_reader.Next(&rig, &camera, &image, &pose_prior, &bitmap, &mask); ASSERT_EQ(status, ImageReader::Status::SUCCESS); } EXPECT_EQ(database->NumRigs(), 1); EXPECT_EQ(database->NumCameras(), 1); } TEST(ImageReaderTest, SingleCameraDimensionError) { auto database = Database::Open(kInMemorySqliteDatabasePath); const auto test_dir = CreateTestDir(); ImageReaderOptions options; options.image_path = test_dir / "images"; options.single_camera = true; CreateDirIfNotExists(options.image_path); // Create images with different dimensions Bitmap bitmap1(10, 20, true); Bitmap bitmap2(30, 40, true); bitmap1.Write(options.image_path / "0.png"); bitmap2.Write(options.image_path / "1.png"); ImageReader image_reader(options, database.get()); Rig rig; Camera camera; Image image; PosePrior pose_prior; Bitmap bitmap; Bitmap mask; // First image succeeds auto status = image_reader.Next(&rig, &camera, &image, &pose_prior, &bitmap, &mask); ASSERT_EQ(status, ImageReader::Status::SUCCESS); // Second image fails due to dimension mismatch status = image_reader.Next(&rig, &camera, &image, &pose_prior, &bitmap, &mask); EXPECT_EQ(status, ImageReader::Status::CAMERA_SINGLE_DIM_ERROR); } TEST(ImageReaderTest, SingleCameraPerFolder) { auto database = Database::Open(kInMemorySqliteDatabasePath); const auto test_dir = CreateTestDir(); ImageReaderOptions options; options.image_path = test_dir / "images"; options.single_camera_per_folder = true; CreateDirIfNotExists(options.image_path); CreateDirIfNotExists(options.image_path / "folder1"); CreateDirIfNotExists(options.image_path / "folder2"); // Create 2 images in each folder Bitmap test_bitmap(10, 20, true); test_bitmap.Write(options.image_path / "folder1" / "0.png"); test_bitmap.Write(options.image_path / "folder1" / "1.png"); test_bitmap.Write(options.image_path / "folder2" / "0.png"); test_bitmap.Write(options.image_path / "folder2" / "1.png"); ImageReader image_reader(options, database.get()); EXPECT_EQ(image_reader.NumImages(), 4); Rig rig; Camera camera; Image image; PosePrior pose_prior; Bitmap bitmap; Bitmap mask; NodeHashMap folder_cameras; for (int i = 0; i < 4; ++i) { const auto status = image_reader.Next(&rig, &camera, &image, &pose_prior, &bitmap, &mask); ASSERT_EQ(status, ImageReader::Status::SUCCESS); const std::string folder = GetParentDir(image.Name()).string(); if (folder_cameras.count(folder) == 0) { folder_cameras[folder] = camera.camera_id; } else { EXPECT_EQ(camera.camera_id, folder_cameras[folder]); } } // Should have 2 cameras (one per folder) EXPECT_EQ(database->NumCameras(), 2); } TEST(ImageReaderTest, SingleCameraPerImage) { auto database = Database::Open(kInMemorySqliteDatabasePath); const auto test_dir = CreateTestDir(); ImageReaderOptions options; options.image_path = test_dir / "images"; options.single_camera_per_image = true; CreateDirIfNotExists(options.image_path); // Create 3 images with same dimensions Bitmap test_bitmap(10, 20, true); test_bitmap.Write(options.image_path / "0.png"); test_bitmap.Write(options.image_path / "1.png"); test_bitmap.Write(options.image_path / "2.png"); ImageReader image_reader(options, database.get()); Rig rig; Camera camera; Image image; PosePrior pose_prior; Bitmap bitmap; Bitmap mask; for (int i = 0; i < 3; ++i) { const auto status = image_reader.Next(&rig, &camera, &image, &pose_prior, &bitmap, &mask); ASSERT_EQ(status, ImageReader::Status::SUCCESS); EXPECT_EQ(camera.camera_id, i + 1); // Each image gets its own camera } // Should have 3 cameras (one per image) EXPECT_EQ(database->NumCameras(), 3); } TEST(ImageReaderTest, ExistingCameraId) { auto database = Database::Open(kInMemorySqliteDatabasePath); const auto test_dir = CreateTestDir(); // Create an existing camera in the database Camera existing_camera; existing_camera.model_id = CameraModelNameToId("SIMPLE_RADIAL"); existing_camera.width = 10; existing_camera.height = 20; existing_camera.params = {1.0, 5.0, 10.0, 0.0}; existing_camera.camera_id = database->WriteCamera(existing_camera); Rig existing_rig; existing_rig.AddRefSensor(existing_camera.SensorId()); database->WriteRig(existing_rig); ImageReaderOptions options; options.image_path = test_dir / "images"; options.existing_camera_id = existing_camera.camera_id; CreateDirIfNotExists(options.image_path); Bitmap test_bitmap(10, 20, true); test_bitmap.Write(options.image_path / "0.png"); test_bitmap.Write(options.image_path / "1.png"); ImageReader image_reader(options, database.get()); Rig rig; Camera camera; Image image; PosePrior pose_prior; Bitmap bitmap; Bitmap mask; for (int i = 0; i < 2; ++i) { const auto status = image_reader.Next(&rig, &camera, &image, &pose_prior, &bitmap, &mask); ASSERT_EQ(status, ImageReader::Status::SUCCESS); EXPECT_EQ(camera.camera_id, existing_camera.camera_id); EXPECT_EQ(camera.params, existing_camera.params); } // No new cameras created EXPECT_EQ(database->NumCameras(), 1); } TEST(ImageReaderTest, ManualCameraParams) { auto database = Database::Open(kInMemorySqliteDatabasePath); const auto test_dir = CreateTestDir(); ImageReaderOptions options; options.image_path = test_dir / "images"; options.camera_model = "PINHOLE"; options.camera_params = "500.0, 500.0, 320.0, 240.0"; CreateDirIfNotExists(options.image_path); Bitmap test_bitmap(640, 480, true); test_bitmap.Write(options.image_path / "test.png"); ImageReader image_reader(options, database.get()); Rig rig; Camera camera; Image image; PosePrior pose_prior; Bitmap bitmap; Bitmap mask; const auto status = image_reader.Next(&rig, &camera, &image, &pose_prior, &bitmap, &mask); ASSERT_EQ(status, ImageReader::Status::SUCCESS); EXPECT_EQ(camera.model_id, PinholeCameraModel::model_id); EXPECT_EQ(camera.params[0], 500.0); EXPECT_EQ(camera.params[1], 500.0); EXPECT_EQ(camera.params[2], 320.0); EXPECT_EQ(camera.params[3], 240.0); EXPECT_TRUE(camera.has_prior_focal_length); } TEST(ImageReaderTest, ExplicitImageNames) { auto database = Database::Open(kInMemorySqliteDatabasePath); const auto test_dir = CreateTestDir(); ImageReaderOptions options; options.image_path = test_dir / "images"; CreateDirIfNotExists(options.image_path); // Create 5 images Bitmap test_bitmap(10, 20, true); for (int i = 0; i < 5; ++i) { test_bitmap.Write(options.image_path / (std::to_string(i) + ".png")); } // Only select a subset of images options.image_names = {"1.png", "3.png"}; ImageReader image_reader(options, database.get()); EXPECT_EQ(image_reader.NumImages(), 2); Rig rig; Camera camera; Image image; PosePrior pose_prior; Bitmap bitmap; Bitmap mask; auto status = image_reader.Next(&rig, &camera, &image, &pose_prior, &bitmap, &mask); ASSERT_EQ(status, ImageReader::Status::SUCCESS); EXPECT_EQ(image.Name(), "1.png"); status = image_reader.Next(&rig, &camera, &image, &pose_prior, &bitmap, &mask); ASSERT_EQ(status, ImageReader::Status::SUCCESS); EXPECT_EQ(image.Name(), "3.png"); } TEST(ImageReaderTest, BitmapError) { auto database = Database::Open(kInMemorySqliteDatabasePath); const auto test_dir = CreateTestDir(); ImageReaderOptions options; options.image_path = test_dir / "images"; CreateDirIfNotExists(options.image_path); // Create a file that is not a valid image std::ofstream file(options.image_path / "invalid.png"); file << "not an image"; file.close(); ImageReader image_reader(options, database.get()); Rig rig; Camera camera; Image image; PosePrior pose_prior; Bitmap bitmap; Bitmap mask; const auto status = image_reader.Next(&rig, &camera, &image, &pose_prior, &bitmap, &mask); EXPECT_EQ(status, ImageReader::Status::BITMAP_ERROR); } TEST(ImageReaderTest, MaskErrorMissing) { auto database = Database::Open(kInMemorySqliteDatabasePath); const auto test_dir = CreateTestDir(); ImageReaderOptions options; options.image_path = test_dir / "images"; options.mask_path = test_dir / "masks"; CreateDirIfNotExists(options.image_path); CreateDirIfNotExists(options.mask_path); Bitmap test_bitmap(10, 20, true); test_bitmap.Write(options.image_path / "test.png"); // Don't create mask file ImageReader image_reader(options, database.get()); Rig rig; Camera camera; Image image; PosePrior pose_prior; Bitmap bitmap; Bitmap mask; const auto status = image_reader.Next(&rig, &camera, &image, &pose_prior, &bitmap, &mask); EXPECT_EQ(status, ImageReader::Status::MASK_ERROR); } TEST(ImageReaderTest, MaskErrorInvalid) { auto database = Database::Open(kInMemorySqliteDatabasePath); const auto test_dir = CreateTestDir(); ImageReaderOptions options; options.image_path = test_dir / "images"; options.mask_path = test_dir / "masks"; CreateDirIfNotExists(options.image_path); CreateDirIfNotExists(options.mask_path); Bitmap test_bitmap(10, 20, true); test_bitmap.Write(options.image_path / "test.png"); // Create invalid mask file std::ofstream file(options.mask_path / "test.png.png"); file << "not an image"; file.close(); ImageReader image_reader(options, database.get()); Rig rig; Camera camera; Image image; PosePrior pose_prior; Bitmap bitmap; Bitmap mask; const auto status = image_reader.Next(&rig, &camera, &image, &pose_prior, &bitmap, &mask); EXPECT_EQ(status, ImageReader::Status::MASK_ERROR); } TEST(ImageReaderTest, ImageExistsWithKeypoints) { auto database = Database::Open(kInMemorySqliteDatabasePath); const auto test_dir = CreateTestDir(); ImageReaderOptions options; options.image_path = test_dir / "images"; CreateDirIfNotExists(options.image_path); Bitmap test_bitmap(10, 20, true); test_bitmap.Write(options.image_path / "test.png"); // Add existing image with keypoints and descriptors Camera existing_camera; existing_camera.model_id = CameraModelNameToId("SIMPLE_RADIAL"); existing_camera.width = 10; existing_camera.height = 20; existing_camera.params = {1.0, 5.0, 10.0, 0.0}; existing_camera.camera_id = database->WriteCamera(existing_camera); Rig existing_rig; existing_rig.AddRefSensor(existing_camera.SensorId()); database->WriteRig(existing_rig); Image existing_image; existing_image.SetName("test.png"); existing_image.SetCameraId(existing_camera.camera_id); existing_image.SetImageId(database->WriteImage(existing_image)); database->WriteKeypoints(existing_image.ImageId(), FeatureKeypoints()); database->WriteDescriptors(existing_image.ImageId(), FeatureDescriptors()); ImageReader image_reader(options, database.get()); Rig rig; Camera camera; Image image; PosePrior pose_prior; Bitmap bitmap; Bitmap mask; const auto status = image_reader.Next(&rig, &camera, &image, &pose_prior, &bitmap, &mask); EXPECT_EQ(status, ImageReader::Status::IMAGE_EXISTS); } } // namespace } // namespace colmap colmap-4.2.0/src/colmap/controllers/incremental_pipeline.cc000066400000000000000000001077651524536416500241300ustar00rootroot00000000000000// Copyright (c), ETH Zurich and UNC Chapel Hill. // All rights reserved. // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are met: // // * Redistributions of source code must retain the above copyright // notice, this list of conditions and the following disclaimer. // // * Redistributions in binary form must reproduce the above copyright // notice, this list of conditions and the following disclaimer in the // documentation and/or other materials provided with the distribution. // // * Neither the name of ETH Zurich and UNC Chapel Hill nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE // ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE // LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR // CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF // SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS // INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN // CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) // ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE // POSSIBILITY OF SUCH DAMAGE. #include "colmap/controllers/incremental_pipeline.h" #include "colmap/estimators/alignment.h" #include "colmap/estimators/bundle_adjustment_caspar.h" #include "colmap/estimators/bundle_adjustment_ceres.h" #include "colmap/scene/database.h" #include "colmap/util/file.h" #include "colmap/util/hash_containers.h" #include "colmap/util/timer.h" namespace colmap { namespace { // Default maximum number of bundle adjustment iterations for the Ceres // backend, used when ba_{local,global}_max_num_iterations is -1. The Caspar // backend has different convergence behavior and instead falls back to its // own tuned default in CasparBundleAdjustmentOptions::solver_iter_max. constexpr int kDefaultCeresLocalMaxNumIterations = 25; constexpr int kDefaultCeresGlobalMaxNumIterations = 50; void CustomizeIncrementalPipelineOptions(const DatabaseCache& database_cache, IncrementalPipelineOptions& options) { // If the total number of images is small then do not enforce the // minimum model size so that we can reconstruct small image // collections, i.e., if the model is at least half of the total number // of images, we always keep it. options.min_model_size = std::min(0.5 * database_cache.NumImages(), options.min_model_size); } DatabaseCache::Options CreateDatabaseCacheOptions( const IncrementalPipelineOptions& options, const ReconstructionManager& reconstruction_manager) { DatabaseCache::Options database_cache_options; database_cache_options.min_num_matches = static_cast(options.min_num_matches); database_cache_options.ignore_watermarks = options.ignore_watermarks; database_cache_options.image_names = {options.image_names.begin(), options.image_names.end()}; // Make sure images of the given reconstruction are also included when // manually specifying images for the reconstruction procedure. if (reconstruction_manager.Size() == 1 && !options.image_names.empty()) { const auto& reconstruction = reconstruction_manager.Get(0); for (const image_t image_id : reconstruction->RegImageIds()) { const auto& image = reconstruction->Image(image_id); database_cache_options.image_names.insert(image.Name()); } } database_cache_options.load_all_images = options.load_all_images; database_cache_options.convert_pose_priors_to_enu = options.use_prior_position; return database_cache_options; } void IterativeGlobalRefinement(const IncrementalPipelineOptions& options, const IncrementalMapper::Options& mapper_options, IncrementalMapper& mapper, std::function check_if_stopped) { LOG(INFO) << "Retriangulation and Global bundle adjustment"; BundleAdjustmentOptions ba_options = options.GlobalBundleAdjustment(); ba_options.check_if_stopped = std::move(check_if_stopped); mapper.IterativeGlobalRefinement(options.ba_global_max_refinements, options.ba_global_max_refinement_change, mapper_options, ba_options, options.Triangulation()); if (!ba_options.check_if_stopped || !ba_options.check_if_stopped()) { mapper.FilterFrames(mapper_options); } } void ExtractColors(const std::filesystem::path& image_path, const image_t image_id, Reconstruction& reconstruction) { if (!reconstruction.ExtractColorsForImage(image_id, image_path)) { LOG(WARNING) << "Could not read image " << reconstruction.Image(image_id).Name() << " at path " << image_path << "."; } } void WriteSnapshot(const Reconstruction& reconstruction, const std::filesystem::path& snapshot_path) { LOG(INFO) << "Creating snapshot"; // Get the current timestamp in milliseconds. const size_t timestamp = std::chrono::duration_cast( std::chrono::high_resolution_clock::now().time_since_epoch()) .count(); // Write reconstruction to unique path with current timestamp. const auto path = snapshot_path / StringPrintf("%010zu", timestamp); CreateDirIfNotExists(path); VLOG(1) << "=> Writing to " << path; reconstruction.Write(path); } bool HasUnknownSensorFromRig(const Reconstruction& reconstruction) { FlatHashSet parameterized_rigs; for (const auto& [_, image] : reconstruction.Images()) { parameterized_rigs.insert(image.FramePtr()->RigPtr()); } for (const Rig* rig : parameterized_rigs) { for (const auto& [sensor_id, sensor_from_rig] : rig->NonRefSensors()) { if (sensor_id.type == SensorType::CAMERA && !sensor_from_rig.has_value()) { return true; } } } return false; } void AlignReconstructionToPriorsOrRigScale( const IncrementalPipelineOptions& options, const DatabaseCache& database_cache, Reconstruction* reconstruction) { if (options.use_prior_position) { PosePriorBundleAdjustmentOptions prior_options; prior_options.alignment_ransac_options.random_seed = options.random_seed; Sim3d metric_from_reconstruction; if (AlignReconstructionToPosePriors( *reconstruction, database_cache.PosePriors(), prior_options.alignment_ransac_options, prior_options.prior_position_fallback_stddev, &metric_from_reconstruction)) { reconstruction->Transform(metric_from_reconstruction); return; } LOG(WARNING) << "Final alignment w.r.t. prior positions failed; restoring " "the original rig scale instead"; } AlignReconstructionToOrigRigScales(database_cache.Rigs(), reconstruction); } } // namespace IncrementalMapper::Options IncrementalPipelineOptions::Mapper() const { IncrementalMapper::Options options = mapper; options.abs_pose_refine_focal_length = ba_refine_focal_length; options.abs_pose_refine_extra_params = ba_refine_extra_params; options.min_focal_length_ratio = min_focal_length_ratio; options.max_focal_length_ratio = max_focal_length_ratio; options.max_extra_param = max_extra_param; options.num_threads = num_threads; options.fix_existing_frames = fix_existing_frames; options.constant_rigs = constant_rigs; options.constant_cameras = constant_cameras; options.use_prior_position = use_prior_position; options.use_robust_loss_on_prior_position = use_robust_loss_on_prior_position; options.prior_position_loss_scale = prior_position_loss_scale; options.random_seed = random_seed; return options; } IncrementalTriangulator::Options IncrementalPipelineOptions::Triangulation() const { IncrementalTriangulator::Options options = triangulation; options.min_focal_length_ratio = min_focal_length_ratio; options.max_focal_length_ratio = max_focal_length_ratio; options.max_extra_param = max_extra_param; options.random_seed = random_seed; return options; } BundleAdjustmentOptions IncrementalPipelineOptions::LocalBundleAdjustment() const { BundleAdjustmentOptions options; options.print_summary = false; options.backend = ba_local_backend; options.refine_focal_length = ba_refine_focal_length; options.refine_principal_point = ba_refine_principal_point; options.refine_extra_params = ba_refine_extra_params; options.refine_sensor_from_rig = ba_refine_sensor_from_rig; if (options.ceres) { options.ceres->solver_options.function_tolerance = ba_local_function_tolerance; options.ceres->solver_options.gradient_tolerance = 10.0; options.ceres->solver_options.parameter_tolerance = 0.0; options.ceres->solver_options.max_num_iterations = (ba_local_max_num_iterations >= 0) ? ba_local_max_num_iterations : kDefaultCeresLocalMaxNumIterations; options.ceres->solver_options.max_linear_solver_iterations = 100; options.ceres->solver_options.logging_type = ceres::LoggingType::SILENT; options.ceres->solver_options.num_threads = num_threads; #if CERES_VERSION_MAJOR < 2 options.ceres->solver_options.num_linear_solver_threads = num_threads; #endif // CERES_VERSION_MAJOR options.ceres->min_num_residuals_for_cpu_multi_threading = ba_min_num_residuals_for_cpu_multi_threading; options.ceres->loss_function_scale = 1.0; options.ceres->loss_function_type = CeresBundleAdjustmentOptions::LossFunctionType::SOFT_L1; options.ceres->use_gpu = ba_use_gpu; options.ceres->gpu_index = ba_gpu_index; } if (options.caspar) { // Only forward the iteration bound when the user explicitly set it // (i.e. it is not the -1 sentinel), leaving Caspar's own tuned // solver_iter_max default untouched otherwise, as Caspar has different // dampening and convergence criteria than Ceres. if (ba_local_max_num_iterations >= 0) { options.caspar->solver_iter_max = ba_local_max_num_iterations; } options.caspar->gpu_index = ba_gpu_index; } return options; } BundleAdjustmentOptions IncrementalPipelineOptions::GlobalBundleAdjustment() const { BundleAdjustmentOptions options; options.print_summary = false; options.backend = ba_global_backend; options.refine_focal_length = ba_refine_focal_length; options.refine_principal_point = ba_refine_principal_point; options.refine_extra_params = ba_refine_extra_params; options.refine_sensor_from_rig = ba_refine_sensor_from_rig; if (options.ceres) { options.ceres->solver_options.function_tolerance = ba_global_function_tolerance; options.ceres->solver_options.gradient_tolerance = 1.0; options.ceres->solver_options.parameter_tolerance = 0.0; options.ceres->solver_options.max_num_iterations = (ba_global_max_num_iterations >= 0) ? ba_global_max_num_iterations : kDefaultCeresGlobalMaxNumIterations; options.ceres->solver_options.max_linear_solver_iterations = 100; options.ceres->solver_options.logging_type = ceres::LoggingType::SILENT; if (VLOG_IS_ON(2)) { options.ceres->solver_options.minimizer_progress_to_stdout = true; options.ceres->solver_options.logging_type = ceres::LoggingType::PER_MINIMIZER_ITERATION; } options.ceres->solver_options.num_threads = num_threads; #if CERES_VERSION_MAJOR < 2 options.ceres->solver_options.num_linear_solver_threads = num_threads; #endif // CERES_VERSION_MAJOR options.ceres->min_num_residuals_for_cpu_multi_threading = ba_min_num_residuals_for_cpu_multi_threading; options.ceres->loss_function_type = CeresBundleAdjustmentOptions::LossFunctionType::TRIVIAL; options.ceres->use_gpu = ba_use_gpu; options.ceres->gpu_index = ba_gpu_index; } if (options.caspar) { // See LocalBundleAdjustment(): only forward the iteration bound to the // Caspar backend when the user explicitly set it, so Caspar's own tuned // solver_iter_max default is preserved otherwise. if (ba_global_max_num_iterations >= 0) { options.caspar->solver_iter_max = ba_global_max_num_iterations; } options.caspar->gpu_index = ba_gpu_index; } return options; } int IncrementalPipelineOptions::EffBaLocalMaxNumIterations() const { if (ba_local_max_num_iterations >= 0) { return ba_local_max_num_iterations; } if (ba_local_backend == BundleAdjustmentBackend::CASPAR) { return CasparBundleAdjustmentOptions().solver_iter_max; } return kDefaultCeresLocalMaxNumIterations; } int IncrementalPipelineOptions::EffBaGlobalMaxNumIterations() const { if (ba_global_max_num_iterations >= 0) { return ba_global_max_num_iterations; } if (ba_global_backend == BundleAdjustmentBackend::CASPAR) { return CasparBundleAdjustmentOptions().solver_iter_max; } return kDefaultCeresGlobalMaxNumIterations; } bool IncrementalPipelineOptions::Check() const { CHECK_OPTION_GT(min_num_matches, 0); CHECK_OPTION_GT(max_num_models, 0); CHECK_OPTION_GT(max_model_overlap, 0); CHECK_OPTION_GE(min_model_size, 0); CHECK_OPTION_GT(init_num_trials, 0); CHECK_OPTION_GT(min_focal_length_ratio, 0); CHECK_OPTION_GT(max_focal_length_ratio, 0); CHECK_OPTION_GE(max_extra_param, 0); CHECK_OPTION_GE(ba_local_max_num_iterations, -1); CHECK_OPTION_GT(ba_global_frames_ratio, 1.0); CHECK_OPTION_GT(ba_global_points_ratio, 1.0); CHECK_OPTION_GT(ba_global_frames_freq, 0); CHECK_OPTION_GT(ba_global_points_freq, 0); CHECK_OPTION_GE(ba_global_max_num_iterations, -1); CHECK_OPTION_NE(ba_global_max_num_iterations, 0); CHECK_OPTION_GT(ba_local_max_refinements, 0); CHECK_OPTION_GE(ba_local_max_refinement_change, 0); CHECK_OPTION_GE(ba_global_max_refinements, 0); CHECK_OPTION_GE(ba_global_max_refinement_change, 0); CHECK_OPTION_GE(snapshot_frames_freq, 0); CHECK_OPTION_GT(prior_position_loss_scale, 0.); CHECK_OPTION_GE(num_threads, -1); CHECK_OPTION_GE(random_seed, -1); #ifndef CASPAR_ENABLED CHECK_OPTION(ba_local_backend != BundleAdjustmentBackend::CASPAR); CHECK_OPTION(ba_global_backend != BundleAdjustmentBackend::CASPAR); #endif CHECK_OPTION(Mapper().Check()); CHECK_OPTION(Triangulation().Check()); return true; } IncrementalPipeline::IncrementalPipeline( std::shared_ptr options, std::shared_ptr database, std::shared_ptr reconstruction_manager) : options_(std::move(THROW_CHECK_NOTNULL(options))), reconstruction_manager_( THROW_CHECK_NOTNULL(std::move(reconstruction_manager))), total_run_timer_(std::make_shared()) { THROW_CHECK(options_->Check()); THROW_CHECK_NOTNULL(database); LOG(INFO) << "Loading database"; Timer timer; timer.Start(); database_cache_ = DatabaseCache::Create( *database, CreateDatabaseCacheOptions(*options_, *reconstruction_manager_)); timer.PrintMinutes(); CustomizeIncrementalPipelineOptions(*database_cache_, *options_); RegisterCallbacks(); } IncrementalPipeline::IncrementalPipeline( std::shared_ptr options, std::shared_ptr database_cache, std::shared_ptr reconstruction_manager) : options_(std::move(THROW_CHECK_NOTNULL(options))), reconstruction_manager_( THROW_CHECK_NOTNULL(std::move(reconstruction_manager))), total_run_timer_(std::make_shared()) { THROW_CHECK(options_->Check()); THROW_CHECK_NOTNULL(database_cache); database_cache_ = DatabaseCache::CreateFromCache( *database_cache, CreateDatabaseCacheOptions(*options_, *reconstruction_manager_)); CustomizeIncrementalPipelineOptions(*database_cache_, *options_); RegisterCallbacks(); } void IncrementalPipeline::Run() { total_run_timer_->Start(); if (database_cache_->NumImages() == 0) { LOG(WARNING) << "No images with matches"; return; } if (options_->use_prior_position && database_cache_->NumPosePriors() == 0) { LOG(WARNING) << "No pose priors"; return; } // Is there a sub-reconstruction before we start the reconstruction? I.e. the // user has imported an existing reconstruction. const bool continue_reconstruction = reconstruction_manager_->Size() > 0; THROW_CHECK_LE(reconstruction_manager_->Size(), 1) << "Can only continue from a single reconstruction, " "but multiple are given."; const size_t num_images = database_cache_->NumImages(); IncrementalMapper::Options mapper_options = options_->Mapper(); IncrementalMapper mapper(database_cache_); if (Reconstruct(mapper, mapper_options, /*continue_reconstruction=*/continue_reconstruction) == Status::STOP) { total_run_timer_->PrintMinutes(); return; } auto ShouldStop = [this, &mapper, &num_images]() { return mapper.NumTotalRegImages() == num_images || CheckIfStopped() || CheckReachedMaxRuntime(); }; const size_t kNumInitRelaxations = 2; for (size_t i = 0; i < kNumInitRelaxations; ++i) { if (ShouldStop()) { break; } LOG(INFO) << "=> Relaxing the initialization constraints."; mapper_options.init_min_num_inliers /= 2; mapper.ResetInitializationStats(); if (Reconstruct(mapper, mapper_options, /*continue_reconstruction=*/false) == Status::STOP) { break; } if (ShouldStop()) { break; } LOG(INFO) << "=> Relaxing the initialization constraints."; mapper_options.init_min_tri_angle /= 2; mapper.ResetInitializationStats(); if (Reconstruct(mapper, mapper_options, /*continue_reconstruction=*/false) == Status::STOP) { break; } } total_run_timer_->PrintMinutes(); } IncrementalPipeline::Status IncrementalPipeline::InitializeReconstruction( IncrementalMapper& mapper, const IncrementalMapper::Options& mapper_options, Reconstruction& reconstruction) { image_t image_id1 = static_cast(options_->init_image_id1); image_t image_id2 = static_cast(options_->init_image_id2); // Try to find good initial pair. Rigid3d cam2_from_cam1; if (!options_->IsInitialPairProvided()) { LOG(INFO) << "Finding good initial image pair"; const bool find_init_success = mapper.FindInitialImagePair( mapper_options, image_id1, image_id2, cam2_from_cam1); if (CheckIfStopped() || CheckReachedMaxRuntime()) { return Status::INTERRUPTED; } if (!find_init_success) { LOG(INFO) << "=> No good initial image pair found."; return Status::NO_INITIAL_PAIR; } } else { if (!reconstruction.ExistsImage(image_id1) || !reconstruction.ExistsImage(image_id2)) { LOG(INFO) << StringPrintf( "=> Initial image pair #%d and #%d does not exist.", image_id1, image_id2); return Status::NO_INITIAL_PAIR; } const bool provided_init_success = mapper.EstimateInitialTwoViewGeometry( mapper_options, image_id1, image_id2, cam2_from_cam1); if (!provided_init_success) { LOG(INFO) << "=> Provided pair is unsuitable for initialization."; return Status::BAD_INITIAL_PAIR; } } LOG(INFO) << StringPrintf( "Registering initial image pair #%d and #%d", image_id1, image_id2); mapper.RegisterInitialImagePair( mapper_options, image_id1, image_id2, cam2_from_cam1); IncrementalTriangulator::Options tri_options = options_->Triangulation(); tri_options.min_angle = mapper_options.init_min_tri_angle; for (const image_t image_id : {image_id1, image_id2}) { const Image& image = reconstruction.Image(image_id); for (const data_t& data_id : image.FramePtr()->ImageIds()) { mapper.TriangulateImage(tri_options, data_id.id); } } if (reconstruction.NumPoints3D() == 0) { return Status::BAD_INITIAL_PAIR; } LOG(INFO) << "Global bundle adjustment"; BundleAdjustmentOptions ba_options = options_->GlobalBundleAdjustment(); ba_options.check_if_stopped = [this]() { return CheckIfStopped(); }; mapper.AdjustGlobalBundle(mapper_options, ba_options); if (CheckIfStopped() || CheckReachedMaxRuntime()) { return Status::INTERRUPTED; } reconstruction.Normalize(); mapper.FilterPoints(mapper_options); mapper.FilterFrames(mapper_options); // Initial image pair failed to register. if (reconstruction.NumRegFrames() == 0 || reconstruction.NumPoints3D() == 0) { return Status::BAD_INITIAL_PAIR; } // Number of triangulated points not enough for registering future images. if (static_cast(reconstruction.NumPoints3D()) < mapper_options.abs_pose_min_num_inliers) { return Status::BAD_INITIAL_PAIR; } if (options_->extract_colors) { for (const image_t image_id : {image_id1, image_id2}) { const Image& image = reconstruction.Image(image_id); for (const data_t& data_id : image.FramePtr()->ImageIds()) { ExtractColors(options_->image_path, data_id.id, reconstruction); } } } return Status::SUCCESS; } bool IncrementalPipeline::CheckRunGlobalRefinement( const Reconstruction& reconstruction, const size_t ba_prev_num_reg_frames, const size_t ba_prev_num_points) { return reconstruction.NumRegFrames() >= options_->ba_global_frames_ratio * ba_prev_num_reg_frames || reconstruction.NumRegFrames() >= options_->ba_global_frames_freq + ba_prev_num_reg_frames || reconstruction.NumPoints3D() >= options_->ba_global_points_ratio * ba_prev_num_points || reconstruction.NumPoints3D() >= options_->ba_global_points_freq + ba_prev_num_points; } IncrementalPipeline::Status IncrementalPipeline::ReconstructSubModel( IncrementalMapper& mapper, const IncrementalMapper::Options& mapper_options, const std::shared_ptr& reconstruction) { mapper.BeginReconstruction(reconstruction); if (HasUnknownSensorFromRig(*reconstruction)) { return Status::UNKNOWN_SENSOR_FROM_RIG; } //////////////////////////////////////////////////////////////////////////// // Register initial pair //////////////////////////////////////////////////////////////////////////// if (reconstruction->NumRegFrames() == 0) { const Status init_status = IncrementalPipeline::InitializeReconstruction( mapper, mapper_options, *reconstruction); if (init_status != Status::SUCCESS) { return init_status; } } Callback(INITIAL_IMAGE_PAIR_REG_CALLBACK); //////////////////////////////////////////////////////////////////////////// // Incremental mapping //////////////////////////////////////////////////////////////////////////// size_t snapshot_prev_num_reg_frames = reconstruction->NumRegFrames(); size_t ba_prev_num_reg_frames = reconstruction->NumRegFrames(); size_t ba_prev_num_points = reconstruction->NumPoints3D(); std::vector structure_less_flags; if (options_->structure_less_registration_only) { structure_less_flags = {true}; } else { if (options_->structure_less_registration_fallback) { structure_less_flags = {false, true}; } else { structure_less_flags = {false}; } } bool reg_next_success = true; bool prev_reg_next_success = true; do { if (CheckIfStopped() || CheckReachedMaxRuntime()) { break; } prev_reg_next_success = reg_next_success; reg_next_success = false; image_t next_image_id = kInvalidImageId; // Try to register next image. Always prefer structure-based registration // first, and if that fails, try (less reliable) structure-less // registration. for (const bool structure_less : structure_less_flags) { const std::vector next_images = mapper.FindNextImages( mapper_options, /*structure_less=*/structure_less); for (size_t reg_trial = 0; reg_trial < next_images.size(); ++reg_trial) { next_image_id = next_images[reg_trial]; LOG(INFO) << StringPrintf("Registering image #%d (num_reg_frames=%d)", next_image_id, reconstruction->NumRegFrames()); LOG(INFO) << StringPrintf( "=> Image sees %d / %d points", mapper.ObservationManager().NumVisiblePoints3D(next_image_id), mapper.ObservationManager().NumObservations(next_image_id)); if (structure_less) { LOG(INFO) << StringPrintf( "Registering image with structure-less fallback"); LOG(INFO) << StringPrintf( "=> Image sees %d / %d correspondences", mapper.ObservationManager().NumVisibleCorrespondences( next_image_id), mapper.ObservationManager().NumCorrespondences(next_image_id)); reg_next_success = mapper.RegisterNextStructureLessImage( mapper_options, next_image_id); } else { reg_next_success = mapper.RegisterNextImage(mapper_options, next_image_id); } if (reg_next_success) { break; } else { LOG(INFO) << "=> Could not register, trying another image."; // If initial model fails to continue for some time, // abort and try different initial pair. const size_t kMinNumInitialRegTrials = 30; if (reg_trial >= kMinNumInitialRegTrials && reconstruction->NumRegImages() < static_cast(options_->min_model_size)) { break; } } } if (reg_next_success) { break; } } if (reg_next_success) { const Image& image = reconstruction->Image(next_image_id); for (const data_t& data_id : image.FramePtr()->ImageIds()) { mapper.TriangulateImage(options_->Triangulation(), data_id.id); } BundleAdjustmentOptions ba_options = options_->LocalBundleAdjustment(); ba_options.check_if_stopped = [this]() { return CheckIfStopped(); }; mapper.IterativeLocalRefinement(options_->ba_local_max_refinements, options_->ba_local_max_refinement_change, mapper_options, ba_options, options_->Triangulation(), next_image_id); if (CheckIfStopped() || CheckReachedMaxRuntime()) { break; } if (CheckRunGlobalRefinement( *reconstruction, ba_prev_num_reg_frames, ba_prev_num_points)) { IterativeGlobalRefinement(*options_, mapper_options, mapper, [this]() { return CheckIfStopped(); }); if (CheckIfStopped() || CheckReachedMaxRuntime()) { break; } ba_prev_num_points = reconstruction->NumPoints3D(); ba_prev_num_reg_frames = reconstruction->NumRegFrames(); } if (options_->extract_colors) { for (const data_t& data_id : image.FramePtr()->ImageIds()) { ExtractColors(options_->image_path, data_id.id, *reconstruction); } } if (options_->snapshot_frames_freq > 0 && reconstruction->NumRegFrames() >= options_->snapshot_frames_freq + snapshot_prev_num_reg_frames) { snapshot_prev_num_reg_frames = reconstruction->NumRegFrames(); WriteSnapshot(*reconstruction, options_->snapshot_path); } Callback(NEXT_IMAGE_REG_CALLBACK); } const size_t max_model_overlap = static_cast(options_->max_model_overlap); if (mapper.NumSharedRegImages() >= max_model_overlap) { break; } // If no image could be registered, try a single final global iterative // bundle adjustment and try again to register one image. If this fails // once, then exit the incremental mapping. if (!reg_next_success && prev_reg_next_success) { IterativeGlobalRefinement(*options_, mapper_options, mapper, [this]() { return CheckIfStopped(); }); } } while (reg_next_success || prev_reg_next_success); if (CheckIfStopped() || CheckReachedMaxRuntime()) { return Status::INTERRUPTED; } // Only run final global BA, if last incremental BA was not global. if (reconstruction->NumRegFrames() > 0 && reconstruction->NumRegFrames() != ba_prev_num_reg_frames && reconstruction->NumPoints3D() != ba_prev_num_points) { IterativeGlobalRefinement(*options_, mapper_options, mapper, [this]() { return CheckIfStopped(); }); } return Status::SUCCESS; } IncrementalPipeline::Status IncrementalPipeline::Reconstruct( IncrementalMapper& mapper, const IncrementalMapper::Options& mapper_options, bool continue_reconstruction) { for (int num_trials = 0; num_trials < options_->init_num_trials; ++num_trials) { if (CheckIfStopped() || CheckReachedMaxRuntime()) { return Status::STOP; } const size_t reconstruction_idx = (!continue_reconstruction || num_trials > 0) ? reconstruction_manager_->Add() : 0; std::shared_ptr reconstruction = reconstruction_manager_->Get(reconstruction_idx); const Status status = ReconstructSubModel(mapper, mapper_options, reconstruction); switch (status) { case Status::INTERRUPTED: { if (reconstruction->NumRegFrames() == 0) { mapper.EndReconstruction(/*discard=*/true); reconstruction_manager_->Delete(reconstruction_idx); return Status::STOP; } reconstruction->UpdatePoint3DErrors(); LOG(INFO) << "Keeping reconstruction due to interrupt"; mapper.EndReconstruction(/*discard=*/false); AlignReconstructionToPriorsOrRigScale( *options_, *database_cache_, reconstruction.get()); return Status::STOP; } case Status::UNKNOWN_SENSOR_FROM_RIG: { LOG(ERROR) << "Discarding reconstruction due to unknown sensor_from_rig " "poses. Either explicitly define the poses by configuring the " "rigs or first run reconstruction without configured rigs and " "then derive the poses from the initial reconstruction for a " "subsequent reconstruction with rig constraints. See " "documentation for detailed instructions."; mapper.EndReconstruction(/*discard=*/true); reconstruction_manager_->Delete(reconstruction_idx); // If the reconstruction was discarded due to an unknown sensor from // rig, we can stop the outer trial loop, because all trials will fail. return Status::STOP; } case Status::BAD_INITIAL_PAIR: { LOG(INFO) << "Discarding reconstruction due to bad initial pair"; mapper.EndReconstruction(/*discard=*/true); reconstruction_manager_->Delete(reconstruction_idx); // If an initial pair was found but it was bad, we discard and attempt // to initialize from any of the remaining pairs in the next trials. break; } case Status::NO_INITIAL_PAIR: { LOG(INFO) << "Discarding reconstruction due to no initial pair"; mapper.EndReconstruction(/*discard=*/true); reconstruction_manager_->Delete(reconstruction_idx); // If no pair could be found, we can exit the trial loop, because // the next trials in this loop will not find anything unless the // initialization thresholds are relaxed. However, by relaxing the // constraints in the outer loop we can succeed. return Status::CONTINUE; } case Status::SUCCESS: { // Remember the total number of registered images before potentially // discarding it below due to small size, so we can exit out of the main // loop, if all images were registered. const size_t num_reg_images = reconstruction->NumRegImages(); const size_t total_num_reg_images = mapper.NumTotalRegImages(); // Always keep the first reconstruction, independent of size. if ((options_->multiple_models && reconstruction_manager_->Size() > 1 && num_reg_images < static_cast(options_->min_model_size)) || num_reg_images == 0) { LOG(WARNING) << "Discarding reconstruction due to insufficient size"; mapper.EndReconstruction(/*discard=*/true); reconstruction_manager_->Delete(reconstruction_idx); } else { reconstruction->UpdatePoint3DErrors(); LOG(INFO) << "Keeping successful reconstruction"; mapper.EndReconstruction(/*discard=*/false); AlignReconstructionToPriorsOrRigScale( *options_, *database_cache_, reconstruction.get()); } Callback(LAST_IMAGE_REG_CALLBACK); // Check if we should or can reconstruct another sub-model. if (!options_->multiple_models || reconstruction_manager_->Size() >= static_cast(options_->max_num_models) || total_num_reg_images >= database_cache_->NumImages() - 1) { return Status::STOP; } // In case the reconstruction was successful and there are remaining // images, we try to reconstruct another sub-model in the next trial. break; } default: LOG(FATAL_THROW) << "Unknown reconstruction status."; } } return Status::CONTINUE; } void IncrementalPipeline::TriangulateReconstruction( const std::shared_ptr& reconstruction) { THROW_CHECK_GT(database_cache_->NumImages(), 0) << "No images with matches found in the database"; IncrementalMapper mapper(database_cache_); mapper.BeginReconstruction(reconstruction); LOG(INFO) << "Iterative triangulation"; size_t image_idx = 0; for (const image_t image_id : reconstruction->RegImageIds()) { if (CheckIfStopped()) { break; } const auto& image = reconstruction->Image(image_id); LOG(INFO) << StringPrintf( "Triangulating image #%d (%d)", image_id, image_idx++); const size_t num_existing_points3D = image.NumPoints3D(); LOG(INFO) << "=> Image sees " << num_existing_points3D << " / " << mapper.ObservationManager().NumObservations(image_id) << " points"; mapper.TriangulateImage(options_->Triangulation(), image_id); VLOG(1) << "=> Triangulated " << (image.NumPoints3D() - num_existing_points3D) << " points"; } if (!CheckIfStopped()) { LOG(INFO) << "Retriangulation and Global bundle adjustment"; BundleAdjustmentOptions ba_options = options_->GlobalBundleAdjustment(); ba_options.check_if_stopped = [this]() { return CheckIfStopped(); }; mapper.IterativeGlobalRefinement(options_->ba_global_max_refinements, options_->ba_global_max_refinement_change, options_->Mapper(), ba_options, options_->Triangulation(), /*normalize_reconstruction=*/false); } mapper.EndReconstruction(/*discard=*/false); reconstruction->UpdatePoint3DErrors(); if (!CheckIfStopped()) { LOG(INFO) << "Extracting colors"; reconstruction->ExtractColorsForAllImages(options_->image_path, options_->num_threads); } } void IncrementalPipeline::RegisterCallbacks() { RegisterCallback(INITIAL_IMAGE_PAIR_REG_CALLBACK); RegisterCallback(NEXT_IMAGE_REG_CALLBACK); RegisterCallback(LAST_IMAGE_REG_CALLBACK); } bool IncrementalPipeline::CheckReachedMaxRuntime() const { if (options_->max_runtime_seconds > 0 && total_run_timer_->ElapsedSeconds() > options_->max_runtime_seconds) { LOG(INFO) << "Reached maximum runtime of " << options_->max_runtime_seconds << " seconds."; return true; } return false; } } // namespace colmap colmap-4.2.0/src/colmap/controllers/incremental_pipeline.h000066400000000000000000000255151524536416500237620ustar00rootroot00000000000000// Copyright (c), ETH Zurich and UNC Chapel Hill. // All rights reserved. // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are met: // // * Redistributions of source code must retain the above copyright // notice, this list of conditions and the following disclaimer. // // * Redistributions in binary form must reproduce the above copyright // notice, this list of conditions and the following disclaimer in the // documentation and/or other materials provided with the distribution. // // * Neither the name of ETH Zurich and UNC Chapel Hill nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE // ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE // LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR // CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF // SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS // INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN // CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) // ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE // POSSIBILITY OF SUCH DAMAGE. #pragma once #include "colmap/scene/reconstruction_manager.h" #include "colmap/sfm/incremental_mapper.h" #include "colmap/util/base_controller.h" #include "colmap/util/hash_containers.h" #include #include #include #include namespace colmap { class Timer; // NOLINTNEXTLINE(clang-analyzer-optin.performance.Padding) struct IncrementalPipelineOptions { // The minimum number of matches for inlier matches to be considered. int min_num_matches = 15; // Whether to ignore the inlier matches of watermark image pairs. bool ignore_watermarks = false; // Whether to reconstruct multiple sub-models. bool multiple_models = true; // The number of sub-models to reconstruct. int max_num_models = 50; // The maximum number of overlapping images between sub-models. If the // current sub-models shares more than this number of images with another // model, then the reconstruction is stopped. int max_model_overlap = 20; // The minimum number of registered images of a sub-model, otherwise the // sub-model is discarded. Note that the first sub-model is always kept // independent of size. If the model contains at least half of the total // number of images, we also always keep it. int min_model_size = 10; // The image identifiers used to initialize the reconstruction. Note that // only one or both image identifiers can be specified. In the former case, // the second image is automatically determined. int init_image_id1 = -1; int init_image_id2 = -1; // The number of trials to initialize the reconstruction. int init_num_trials = 200; // Enable fallback to structure-less image registration using 2D-2D // correspondences, if structured-based registration fails using 2D-3D // correspondences. bool structure_less_registration_fallback = true; // Only use structure-less and skip structure-based image registration. bool structure_less_registration_only = false; // Whether to extract colors for reconstructed points. bool extract_colors = true; // The number of threads to use during reconstruction. int num_threads = -1; // PRNG seed for all stochastic methods during reconstruction. int random_seed = -1; // Thresholds for filtering images with degenerate intrinsics. double min_focal_length_ratio = 0.1; double max_focal_length_ratio = 10.0; double max_extra_param = 1.0; // Which camera parameters to optimize during the reconstruction. bool ba_refine_focal_length = true; bool ba_refine_principal_point = false; bool ba_refine_extra_params = true; // Whether to optimize rig poses during the reconstruction. bool ba_refine_sensor_from_rig = true; // The minimum number of residuals per bundle adjustment problem to // enable multi-threading solving of the problems. int ba_min_num_residuals_for_cpu_multi_threading = 50000; // Ceres solver function tolerance for local bundle adjustment double ba_local_function_tolerance = 0.0; // The maximum number of local bundle adjustment iterations. If -1, the // default of the configured bundle adjustment backend is used. int ba_local_max_num_iterations = -1; // The growth rates after which to perform global bundle adjustment. double ba_global_frames_ratio = 1.1; double ba_global_points_ratio = 1.1; int ba_global_frames_freq = 500; int ba_global_points_freq = 250000; // Ceres solver function tolerance for global bundle adjustment double ba_global_function_tolerance = 0.0; // The maximum number of global bundle adjustment iterations. If -1, the // default of the configured bundle adjustment backend is used. int ba_global_max_num_iterations = -1; // The thresholds for iterative bundle adjustment refinements. int ba_local_max_refinements = 2; double ba_local_max_refinement_change = 0.001; int ba_global_max_refinements = 5; double ba_global_max_refinement_change = 0.0005; // Whether to use Ceres' CUDA sparse linear algebra library, if available. bool ba_use_gpu = false; // GPU device index for bundle adjustment (-1 = auto-select). std::string ba_gpu_index = "-1"; // Bundle adjustment solver backend for local bundle adjustment. BundleAdjustmentBackend ba_local_backend = BundleAdjustmentBackend::CERES; // Bundle adjustment solver backend for global bundle adjustment. BundleAdjustmentBackend ba_global_backend = BundleAdjustmentBackend::CERES; // Whether to use priors on the camera positions. bool use_prior_position = false; // Whether to use a robust loss on prior camera positions. bool use_robust_loss_on_prior_position = false; // Threshold on the residual for the robust position prior loss // (chi2 for 3DOF at 95% = 7.815). double prior_position_loss_scale = 7.815; // Path to a folder with reconstruction snapshots during incremental // reconstruction. Snapshots will be saved according to the specified // frequency of registered images. std::filesystem::path snapshot_path; int snapshot_frames_freq = 0; // The image path at which to find the images to extract point colors. // If not specified, all point colors will be black. std::filesystem::path image_path; // Optional list of image names to reconstruct. If no images are specified, // all images will be reconstructed by default. std::vector image_names; // Whether to load all images from the database, including those without // correspondences. Only useful for triangulation where all images are // already registered and should retain their keypoints. Should not be // enabled for incremental SfM. bool load_all_images = false; // If reconstruction is provided as input, fix the existing frame poses. bool fix_existing_frames = false; // List of rigs for which to fix the sensor_from_rig transformation, // independent of ba_refine_sensor_from_rig. FlatHashSet constant_rigs; // List of cameras for which to fix the camera parameters independent // of refine_focal_length, refine_principal_point, and refine_extra_params. FlatHashSet constant_cameras; // Maximum runtime in seconds for the reconstruction process. // If set to a non-positive value, the process will run until completion. int max_runtime_seconds = -1; IncrementalMapper::Options mapper; IncrementalTriangulator::Options triangulation; IncrementalMapper::Options Mapper() const; IncrementalTriangulator::Options Triangulation() const; BundleAdjustmentOptions LocalBundleAdjustment() const; BundleAdjustmentOptions GlobalBundleAdjustment() const; // Returns the effective maximum number of local/global bundle adjustment // iterations. If the respective option is set to -1, the default of the // configured bundle adjustment backend is returned. int EffBaLocalMaxNumIterations() const; int EffBaGlobalMaxNumIterations() const; inline bool IsInitialPairProvided() const { return init_image_id1 != -1 && init_image_id2 != -1; } bool Check() const; }; // Class that controls the incremental mapping procedure by iteratively // initializing reconstructions from the same scene graph. class IncrementalPipeline : public BaseController { public: enum CallbackType { INITIAL_IMAGE_PAIR_REG_CALLBACK, NEXT_IMAGE_REG_CALLBACK, LAST_IMAGE_REG_CALLBACK, }; enum class Status { SUCCESS, INTERRUPTED, CONTINUE, STOP, NO_INITIAL_PAIR, BAD_INITIAL_PAIR, UNKNOWN_SENSOR_FROM_RIG, }; IncrementalPipeline( std::shared_ptr options, std::shared_ptr database, std::shared_ptr reconstruction_manager); IncrementalPipeline( std::shared_ptr options, std::shared_ptr database_cache, std::shared_ptr reconstruction_manager); void Run() override; // Getter functions for python pipelines. std::shared_ptr Options() const { return options_; } const std::shared_ptr& ReconstructionManager() const { return reconstruction_manager_; } const std::shared_ptr& DatabaseCache() const { return database_cache_; } Status Reconstruct(IncrementalMapper& mapper, const IncrementalMapper::Options& mapper_options, bool continue_reconstruction); Status ReconstructSubModel( IncrementalMapper& mapper, const IncrementalMapper::Options& mapper_options, const std::shared_ptr& reconstruction); Status InitializeReconstruction( IncrementalMapper& mapper, const IncrementalMapper::Options& mapper_options, Reconstruction& reconstruction); void TriangulateReconstruction( const std::shared_ptr& reconstruction); bool CheckRunGlobalRefinement(const Reconstruction& reconstruction, size_t ba_prev_num_reg_images, size_t ba_prev_num_points); bool CheckReachedMaxRuntime() const; private: void RegisterCallbacks(); const std::shared_ptr options_; std::shared_ptr reconstruction_manager_; std::shared_ptr database_cache_; std::shared_ptr total_run_timer_; }; } // namespace colmap colmap-4.2.0/src/colmap/controllers/incremental_pipeline_test.cc000066400000000000000000001036711524536416500251570ustar00rootroot00000000000000// Copyright (c), ETH Zurich and UNC Chapel Hill. // All rights reserved. // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are met: // // * Redistributions of source code must retain the above copyright // notice, this list of conditions and the following disclaimer. // // * Redistributions in binary form must reproduce the above copyright // notice, this list of conditions and the following disclaimer in the // documentation and/or other materials provided with the distribution. // // * Neither the name of ETH Zurich and UNC Chapel Hill nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE // ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE // LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR // CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF // SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS // INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN // CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) // ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE // POSSIBILITY OF SUCH DAMAGE. #include "colmap/controllers/incremental_pipeline.h" #include "colmap/estimators/bundle_adjustment_caspar.h" #include "colmap/estimators/bundle_adjustment_ceres.h" #include "colmap/geometry/rigid3_matchers.h" #include "colmap/scene/database.h" #include "colmap/scene/reconstruction_matchers.h" #include "colmap/scene/synthetic.h" #include "colmap/util/testing.h" #include namespace colmap { namespace { TEST(IncrementalPipeline, WithoutNoise) { const auto database_path = CreateTestDir() / "database.db"; auto database = Database::Open(database_path); Reconstruction gt_reconstruction; SyntheticDatasetOptions synthetic_dataset_options; synthetic_dataset_options.num_rigs = 2; synthetic_dataset_options.num_cameras_per_rig = 1; synthetic_dataset_options.num_frames_per_rig = 7; synthetic_dataset_options.num_points3D = 50; synthetic_dataset_options.camera_has_prior_focal_length = false; SynthesizeDataset( synthetic_dataset_options, >_reconstruction, database.get()); auto reconstruction_manager = std::make_shared(); IncrementalPipeline mapper(std::make_shared(), database, reconstruction_manager); mapper.Run(); ASSERT_EQ(reconstruction_manager->Size(), 1); EXPECT_THAT(gt_reconstruction, ReconstructionNear(*reconstruction_manager->Get(0), /*max_rotation_error_deg=*/1e-2, /*max_proj_center_error=*/1e-4)); } TEST(IncrementalPipeline, WithoutNoiseSphericalCameras) { const auto database_path = CreateTestDir() / "database.db"; auto database = Database::Open(database_path); Reconstruction gt_reconstruction; SyntheticDatasetOptions synthetic_dataset_options; synthetic_dataset_options.num_rigs = 5; synthetic_dataset_options.num_cameras_per_rig = 1; synthetic_dataset_options.num_frames_per_rig = 1; synthetic_dataset_options.num_points3D = 100; synthetic_dataset_options.camera_model_id = EquirectangularCameraModel::model_id; synthetic_dataset_options.camera_width = 1000; synthetic_dataset_options.camera_height = 500; synthetic_dataset_options.camera_params = {1000, 500}; SynthesizeDataset( synthetic_dataset_options, >_reconstruction, database.get()); auto reconstruction_manager = std::make_shared(); IncrementalPipeline mapper(std::make_shared(), database, reconstruction_manager); mapper.Run(); ASSERT_EQ(reconstruction_manager->Size(), 1); const Reconstruction& reconstruction = *reconstruction_manager->Get(0); EXPECT_EQ(reconstruction.NumRegImages(), gt_reconstruction.NumImages()); EXPECT_THAT(gt_reconstruction, ReconstructionNear(reconstruction, /*max_rotation_error_deg=*/1e-2, /*max_proj_center_error=*/1e-4)); } TEST(IncrementalPipeline, WithoutNoiseAndWithNonTrivialFrames) { const auto database_path = CreateTestDir() / "database.db"; auto database = Database::Open(database_path); Reconstruction gt_reconstruction; SyntheticDatasetOptions synthetic_dataset_options; synthetic_dataset_options.num_rigs = 2; synthetic_dataset_options.num_cameras_per_rig = 2; synthetic_dataset_options.num_frames_per_rig = 7; synthetic_dataset_options.num_points3D = 100; synthetic_dataset_options.camera_has_prior_focal_length = false; synthetic_dataset_options.sensor_from_rig_translation_stddev = 0.05; synthetic_dataset_options.sensor_from_rig_rotation_stddev = 30; SynthesizeDataset( synthetic_dataset_options, >_reconstruction, database.get()); for (const bool refine_sensor_from_rig : {true, false}) { auto reconstruction_manager = std::make_shared(); auto options = std::make_shared(); options->ba_refine_sensor_from_rig = refine_sensor_from_rig; IncrementalPipeline mapper(options, database, reconstruction_manager); mapper.Run(); ASSERT_EQ(reconstruction_manager->Size(), 1); EXPECT_THAT(gt_reconstruction, ReconstructionNear( *reconstruction_manager->Get(0), /*max_rotation_error_deg=*/1e-2, /*max_proj_center_error=*/1e-3, /*max_scale_error=*/refine_sensor_from_rig ? 1e-2 : 1e-4)); } } TEST(IncrementalPipeline, UnknownSensorFromRigExitsGracefully) { const auto database_path = CreateTestDir() / "database.db"; auto database = Database::Open(database_path); Reconstruction gt_reconstruction; SyntheticDatasetOptions synthetic_dataset_options; synthetic_dataset_options.num_rigs = 2; synthetic_dataset_options.num_cameras_per_rig = 3; synthetic_dataset_options.num_frames_per_rig = 5; synthetic_dataset_options.num_points3D = 50; synthetic_dataset_options.camera_has_prior_focal_length = false; synthetic_dataset_options.sensor_from_rig_translation_stddev = 0.05; synthetic_dataset_options.sensor_from_rig_rotation_stddev = 30; SynthesizeDataset( synthetic_dataset_options, >_reconstruction, database.get()); // Set one of the sensor from rig to unknown. auto rig = database->ReadAllRigs()[0]; rig.NonRefSensors().begin()->second.reset(); database->UpdateRig(rig); auto reconstruction_manager = std::make_shared(); auto options = std::make_shared(); IncrementalPipeline mapper(options, database, reconstruction_manager); mapper.Run(); ASSERT_EQ(reconstruction_manager->Size(), 0); } TEST(IncrementalPipeline, WithNonTrivialFramesAndConstantRigsAndCameras) { const auto database_path = CreateTestDir() / "database.db"; auto database = Database::Open(database_path); Reconstruction gt_reconstruction; SyntheticDatasetOptions synthetic_dataset_options; synthetic_dataset_options.num_rigs = 2; synthetic_dataset_options.num_cameras_per_rig = 2; synthetic_dataset_options.num_frames_per_rig = 7; synthetic_dataset_options.num_points3D = 100; synthetic_dataset_options.camera_has_prior_focal_length = false; synthetic_dataset_options.sensor_from_rig_translation_stddev = 0.05; synthetic_dataset_options.sensor_from_rig_rotation_stddev = 30; SynthesizeDataset( synthetic_dataset_options, >_reconstruction, database.get()); constexpr int kConstantRigId = 1; constexpr int kConstantCameraId = 1; auto reconstruction_manager = std::make_shared(); auto options = std::make_shared(); options->constant_rigs.insert(kConstantRigId); options->constant_cameras.insert(kConstantCameraId); IncrementalPipeline mapper(options, database, reconstruction_manager); mapper.Run(); ASSERT_EQ(reconstruction_manager->Size(), 1); auto& reconstruction = *reconstruction_manager->Get(0); EXPECT_THAT(gt_reconstruction, ReconstructionNear(reconstruction, /*max_rotation_error_deg=*/1e-2, /*max_proj_center_error=*/1e-3)); for (const auto& [sensor_id, sensor_from_rig] : reconstruction.Rig(kConstantRigId).NonRefSensors()) { EXPECT_THAT( sensor_from_rig.value(), Rigid3dNear( gt_reconstruction.Rig(kConstantRigId).SensorFromRig(sensor_id), /*rtol=*/1e-4, /*ttol=*/1e-4)); } EXPECT_EQ(reconstruction.Camera(kConstantCameraId).params, gt_reconstruction.Camera(kConstantCameraId).params); } TEST(IncrementalPipeline, WithoutNoiseAndWithPanoramicNonTrivialFrames) { const auto database_path = CreateTestDir() / "database.db"; auto database = Database::Open(database_path); Reconstruction gt_reconstruction; SyntheticDatasetOptions synthetic_dataset_options; synthetic_dataset_options.num_rigs = 2; synthetic_dataset_options.num_cameras_per_rig = 3; synthetic_dataset_options.num_frames_per_rig = 7; synthetic_dataset_options.num_points3D = 100; synthetic_dataset_options.camera_has_prior_focal_length = false; synthetic_dataset_options.sensor_from_rig_translation_stddev = 0; synthetic_dataset_options.sensor_from_rig_rotation_stddev = 30; SynthesizeDataset( synthetic_dataset_options, >_reconstruction, database.get()); for (const bool refine_sensor_from_rig : {true, false}) { auto reconstruction_manager = std::make_shared(); auto options = std::make_shared(); options->ba_refine_sensor_from_rig = refine_sensor_from_rig; IncrementalPipeline mapper(options, database, reconstruction_manager); mapper.Run(); ASSERT_EQ(reconstruction_manager->Size(), 1); EXPECT_THAT(gt_reconstruction, ReconstructionNear(*reconstruction_manager->Get(0), /*max_rotation_error_deg=*/1e-2, /*max_proj_center_error=*/1e-3)); } } TEST(IncrementalPipeline, WithPriorFocalLength) { const auto database_path = CreateTestDir() / "database.db"; auto database = Database::Open(database_path); Reconstruction gt_reconstruction; SyntheticDatasetOptions synthetic_dataset_options; synthetic_dataset_options.num_rigs = 2; synthetic_dataset_options.num_cameras_per_rig = 1; synthetic_dataset_options.num_frames_per_rig = 7; synthetic_dataset_options.num_points3D = 50; synthetic_dataset_options.camera_has_prior_focal_length = true; SynthesizeDataset( synthetic_dataset_options, >_reconstruction, database.get()); auto reconstruction_manager = std::make_shared(); IncrementalPipeline mapper(std::make_shared(), database, reconstruction_manager); mapper.Run(); ASSERT_EQ(reconstruction_manager->Size(), 1); EXPECT_THAT(gt_reconstruction, ReconstructionNear(*reconstruction_manager->Get(0), /*max_rotation_error_deg=*/1e-2, /*max_proj_center_error=*/1e-4)); } TEST(IncrementalPipeline, WithNoise) { const auto database_path = CreateTestDir() / "database.db"; auto database = Database::Open(database_path); Reconstruction gt_reconstruction; SyntheticDatasetOptions synthetic_dataset_options; synthetic_dataset_options.num_rigs = 2; synthetic_dataset_options.num_cameras_per_rig = 1; synthetic_dataset_options.num_frames_per_rig = 7; synthetic_dataset_options.num_points3D = 100; SynthesizeDataset( synthetic_dataset_options, >_reconstruction, database.get()); SyntheticNoiseOptions synthetic_noise_options; synthetic_noise_options.point2D_stddev = 0.5; SynthesizeNoise(synthetic_noise_options, >_reconstruction, database.get()); auto reconstruction_manager = std::make_shared(); IncrementalPipeline mapper(std::make_shared(), database, reconstruction_manager); mapper.Run(); ASSERT_EQ(reconstruction_manager->Size(), 1); auto reconstruction = reconstruction_manager->Get(0); EXPECT_THAT(gt_reconstruction, ReconstructionNear(*reconstruction, /*max_rotation_error_deg=*/1e-1, /*max_proj_center_error=*/1e-1, /*max_scale_error=*/std::nullopt, /*num_obs_tolerance=*/0.02)); // After the pipeline runs, point3D.error must be in pixel units, i.e. // equal to what UpdatePoint3DErrors would recompute. ASSERT_GT(reconstruction->NumPoints3D(), 0u); const double mean_after_run = reconstruction->ComputeMeanReprojectionError(); reconstruction->UpdatePoint3DErrors(); EXPECT_DOUBLE_EQ(mean_after_run, reconstruction->ComputeMeanReprojectionError()); } TEST(IncrementalPipeline, IgnoreRedundantPoints3D) { const auto database_path = CreateTestDir() / "database.db"; auto database = Database::Open(database_path); Reconstruction gt_reconstruction; SyntheticDatasetOptions synthetic_dataset_options; synthetic_dataset_options.num_rigs = 2; synthetic_dataset_options.num_cameras_per_rig = 1; synthetic_dataset_options.num_frames_per_rig = 7; synthetic_dataset_options.num_points3D = 50; SynthesizeDataset( synthetic_dataset_options, >_reconstruction, database.get()); auto reconstruction_manager = std::make_shared(); auto options = std::make_shared(); options->mapper.ba_global_ignore_redundant_points3D = true; options->mapper.ba_global_ignore_redundant_points3D_min_coverage_gain = 0.5; IncrementalPipeline mapper(options, database, reconstruction_manager); mapper.Run(); ASSERT_EQ(reconstruction_manager->Size(), 1); EXPECT_THAT(gt_reconstruction, ReconstructionNear(*reconstruction_manager->Get(0), /*max_rotation_error_deg=*/1e-2, /*max_proj_center_error=*/1e-4)); } TEST(IncrementalPipeline, StructureLessRegistrationOnly) { const auto database_path = CreateTestDir() / "database.db"; auto database = Database::Open(database_path); Reconstruction gt_reconstruction; SyntheticDatasetOptions synthetic_dataset_options; synthetic_dataset_options.num_rigs = 2; synthetic_dataset_options.num_cameras_per_rig = 1; synthetic_dataset_options.num_frames_per_rig = 7; synthetic_dataset_options.num_points3D = 50; SynthesizeDataset( synthetic_dataset_options, >_reconstruction, database.get()); auto reconstruction_manager = std::make_shared(); auto options = std::make_shared(); options->structure_less_registration_only = true; IncrementalPipeline mapper(options, database, reconstruction_manager); mapper.Run(); ASSERT_EQ(reconstruction_manager->Size(), 1); EXPECT_THAT(gt_reconstruction, ReconstructionNear(*reconstruction_manager->Get(0), /*max_rotation_error_deg=*/1e-3, /*max_proj_center_error=*/1e-4)); } TEST(IncrementalPipeline, MultiReconstruction) { const auto database_path = CreateTestDir() / "database.db"; auto database = Database::Open(database_path); Reconstruction gt_reconstruction1; Reconstruction gt_reconstruction2; SyntheticDatasetOptions synthetic_dataset_options; synthetic_dataset_options.num_rigs = 1; synthetic_dataset_options.num_cameras_per_rig = 1; synthetic_dataset_options.num_frames_per_rig = 5; synthetic_dataset_options.num_points3D = 50; SynthesizeDataset( synthetic_dataset_options, >_reconstruction1, database.get()); synthetic_dataset_options.num_frames_per_rig = 4; SynthesizeDataset( synthetic_dataset_options, >_reconstruction2, database.get()); auto reconstruction_manager = std::make_shared(); auto mapper_options = std::make_shared(); mapper_options->min_model_size = 4; IncrementalPipeline mapper(mapper_options, database, reconstruction_manager); mapper.Run(); ASSERT_EQ(reconstruction_manager->Size(), 2); Reconstruction* computed_reconstruction1 = nullptr; Reconstruction* computed_reconstruction2 = nullptr; if (reconstruction_manager->Get(0)->NumRegImages() == 5) { computed_reconstruction1 = reconstruction_manager->Get(0).get(); computed_reconstruction2 = reconstruction_manager->Get(1).get(); } else { computed_reconstruction1 = reconstruction_manager->Get(1).get(); computed_reconstruction2 = reconstruction_manager->Get(0).get(); } EXPECT_THAT(gt_reconstruction1, ReconstructionNear(*computed_reconstruction1, /*max_rotation_error_deg=*/1e-2, /*max_proj_center_error=*/1e-4)); EXPECT_THAT(gt_reconstruction2, ReconstructionNear(*computed_reconstruction2, /*max_rotation_error_deg=*/1e-2, /*max_proj_center_error=*/1e-4)); } TEST(IncrementalPipeline, FixExistingFrames) { const auto database_path = CreateTestDir() / "database.db"; auto database = Database::Open(database_path); Reconstruction gt_reconstruction; SyntheticDatasetOptions synthetic_dataset_options; synthetic_dataset_options.num_rigs = 1; synthetic_dataset_options.num_cameras_per_rig = 1; synthetic_dataset_options.num_frames_per_rig = 7; synthetic_dataset_options.num_points3D = 50; synthetic_dataset_options.camera_has_prior_focal_length = false; SynthesizeDataset( synthetic_dataset_options, >_reconstruction, database.get()); auto reconstruction_manager = std::make_shared(); auto options = std::make_shared(); for (const bool fix_existing_frames : {false, true}) { if (fix_existing_frames) { ASSERT_EQ(reconstruction_manager->Size(), 1); Reconstruction& reconstruction = *reconstruction_manager->Get(0); // De-register a frame that expect to be re-registered in the second run. reconstruction.DeRegisterFrame(1); // Clear all the observations of one image but keep it registered. We do // not expect fixed images to be filtered (due to insufficient // observations). Image& image2 = reconstruction.Image(2); for (point2D_t point2D_idx = 0; point2D_idx < image2.NumPoints2D(); ++point2D_idx) { if (image2.Point2D(point2D_idx).HasPoint3D()) { reconstruction.DeleteObservation(image2.ImageId(), point2D_idx); } } } options->fix_existing_frames = fix_existing_frames; IncrementalPipeline mapper(options, database, reconstruction_manager); mapper.Run(); ASSERT_EQ(reconstruction_manager->Size(), 1); EXPECT_THAT(gt_reconstruction, ReconstructionNear(*reconstruction_manager->Get(0), /*max_rotation_error_deg=*/1e-2, /*max_proj_center_error=*/1e-4)); } } TEST(IncrementalPipeline, ChainedMatches) { const auto database_path = CreateTestDir() / "database.db"; auto database = Database::Open(database_path); Reconstruction gt_reconstruction; SyntheticDatasetOptions synthetic_dataset_options; synthetic_dataset_options.match_config = SyntheticDatasetOptions::MatchConfig::CHAINED; synthetic_dataset_options.num_rigs = 1; synthetic_dataset_options.num_cameras_per_rig = 1; synthetic_dataset_options.num_frames_per_rig = 4; synthetic_dataset_options.num_points3D = 100; SynthesizeDataset( synthetic_dataset_options, >_reconstruction, database.get()); auto reconstruction_manager = std::make_shared(); auto options = std::make_shared(); options->num_threads = 1; IncrementalPipeline mapper(options, database, reconstruction_manager); mapper.Run(); ASSERT_EQ(reconstruction_manager->Size(), 1); EXPECT_THAT(gt_reconstruction, ReconstructionNear(*reconstruction_manager->Get(0), /*max_rotation_error_deg=*/1e-2, /*max_proj_center_error=*/1e-4)); } TEST(IncrementalPipeline, PriorBasedSfMWithoutNoise) { const auto database_path = CreateTestDir() / "database.db"; auto database = Database::Open(database_path); Reconstruction gt_reconstruction; SyntheticDatasetOptions synthetic_dataset_options; synthetic_dataset_options.num_rigs = 2; synthetic_dataset_options.num_cameras_per_rig = 1; synthetic_dataset_options.num_frames_per_rig = 10; synthetic_dataset_options.num_points3D = 100; synthetic_dataset_options.prior_position = true; SynthesizeDataset( synthetic_dataset_options, >_reconstruction, database.get()); SyntheticNoiseOptions synthetic_noise_options; synthetic_noise_options.point2D_stddev = 0.5; synthetic_noise_options.prior_position_stddev = 0.0; SynthesizeNoise(synthetic_noise_options, >_reconstruction, database.get()); std::shared_ptr mapper_options = std::make_shared(); mapper_options->use_prior_position = true; auto reconstruction_manager = std::make_shared(); IncrementalPipeline mapper(mapper_options, database, reconstruction_manager); mapper.Run(); ASSERT_EQ(reconstruction_manager->Size(), 1); // No noise on prior so do not align gt & computed (expected to be aligned // from PositionPriorBundleAdjustment) EXPECT_THAT(gt_reconstruction, ReconstructionNear(*reconstruction_manager->Get(0), /*max_rotation_error_deg=*/1e-1, /*max_proj_center_error=*/1e-1, /*max_scale_error=*/std::nullopt, /*num_obs_tolerance=*/0.02, /*align=*/false)); } TEST(IncrementalPipeline, PriorBasedSfMWithoutNoiseAndWithNonTrivialFrames) { const auto database_path = CreateTestDir() / "database.db"; auto database = Database::Open(database_path); Reconstruction gt_reconstruction; SyntheticDatasetOptions synthetic_dataset_options; synthetic_dataset_options.num_rigs = 2; synthetic_dataset_options.num_cameras_per_rig = 2; synthetic_dataset_options.num_frames_per_rig = 7; synthetic_dataset_options.num_points3D = 100; synthetic_dataset_options.camera_has_prior_focal_length = false; synthetic_dataset_options.prior_position = true; SynthesizeDataset( synthetic_dataset_options, >_reconstruction, database.get()); // Match the common rig setup where only the reference sensor has absolute // positions. Registering two frames then yields many images but only two // usable pose priors. FlatHashSet ref_sensor_ids; for (const auto& [_, rig] : gt_reconstruction.Rigs()) { ref_sensor_ids.insert(rig.RefSensorId()); } const std::vector pose_priors = database->ReadAllPosePriors(); database->ClearPosePriors(); for (const PosePrior& pose_prior : pose_priors) { if (ref_sensor_ids.count(pose_prior.corr_data_id.sensor_id)) { database->WritePosePrior(pose_prior); } } std::shared_ptr mapper_options = std::make_shared(); mapper_options->use_prior_position = true; mapper_options->use_robust_loss_on_prior_position = true; auto reconstruction_manager = std::make_shared(); IncrementalPipeline mapper(mapper_options, database, reconstruction_manager); mapper.Run(); ASSERT_EQ(reconstruction_manager->Size(), 1); EXPECT_THAT(gt_reconstruction, ReconstructionNear(*reconstruction_manager->Get(0), /*max_rotation_error_deg=*/1e-1, /*max_proj_center_error=*/1e-1, /*max_scale_error=*/std::nullopt, /*num_obs_tolerance=*/0.02, /*align=*/false)); } TEST(IncrementalPipeline, PriorBasedSfMWithNoise) { const auto database_path = CreateTestDir() / "database.db"; auto database = Database::Open(database_path); Reconstruction gt_reconstruction; SyntheticDatasetOptions synthetic_dataset_options; synthetic_dataset_options.num_rigs = 2; synthetic_dataset_options.num_cameras_per_rig = 1; synthetic_dataset_options.num_frames_per_rig = 7; synthetic_dataset_options.num_points3D = 100; synthetic_dataset_options.prior_position = true; SynthesizeDataset( synthetic_dataset_options, >_reconstruction, database.get()); SyntheticNoiseOptions synthetic_noise_options; synthetic_noise_options.point2D_stddev = 0.5; synthetic_noise_options.prior_position_stddev = 1.5; SynthesizeNoise(synthetic_noise_options, >_reconstruction, database.get()); std::shared_ptr mapper_options = std::make_shared(); mapper_options->use_prior_position = true; mapper_options->use_robust_loss_on_prior_position = true; auto reconstruction_manager = std::make_shared(); IncrementalPipeline mapper(mapper_options, database, reconstruction_manager); mapper.Run(); ASSERT_EQ(reconstruction_manager->Size(), 1); EXPECT_THAT(gt_reconstruction, ReconstructionNear(*reconstruction_manager->Get(0), /*max_rotation_error_deg=*/1e-1, /*max_proj_center_error=*/1e-1, /*max_scale_error=*/std::nullopt, /*num_obs_tolerance=*/0.02)); } TEST(IncrementalPipeline, GPSPriorBasedSfMWithNoise) { const auto database_path = CreateTestDir() / "database.db"; auto database = Database::Open(database_path); Reconstruction gt_reconstruction; SyntheticDatasetOptions synthetic_dataset_options; synthetic_dataset_options.num_rigs = 2; synthetic_dataset_options.num_cameras_per_rig = 1; synthetic_dataset_options.num_frames_per_rig = 10; synthetic_dataset_options.num_points3D = 100; synthetic_dataset_options.prior_position = true; synthetic_dataset_options.prior_position_coordinate_system = PosePrior::CoordinateSystem::WGS84; SynthesizeDataset( synthetic_dataset_options, >_reconstruction, database.get()); SyntheticNoiseOptions synthetic_noise_options; synthetic_noise_options.point2D_stddev = 0.5; synthetic_noise_options.prior_position_stddev = 1.5; SynthesizeNoise(synthetic_noise_options, >_reconstruction, database.get()); std::shared_ptr mapper_options = std::make_shared(); mapper_options->use_prior_position = true; mapper_options->use_robust_loss_on_prior_position = true; auto reconstruction_manager = std::make_shared(); IncrementalPipeline mapper(mapper_options, database, reconstruction_manager); mapper.Run(); ASSERT_EQ(reconstruction_manager->Size(), 1); EXPECT_THAT(gt_reconstruction, ReconstructionNear(*reconstruction_manager->Get(0), /*max_rotation_error_deg=*/1e-1, /*max_proj_center_error=*/1e-1, /*max_scale_error=*/std::nullopt, /*num_obs_tolerance=*/0.02)); } TEST(IncrementalPipeline, SfMWithRandomSeedStability) { const auto database_path = CreateTestDir() / "database.db"; auto database = Database::Open(database_path); Reconstruction gt_reconstruction; SyntheticDatasetOptions synthetic_dataset_options; synthetic_dataset_options.num_rigs = 2; synthetic_dataset_options.num_cameras_per_rig = 1; synthetic_dataset_options.num_frames_per_rig = 3; synthetic_dataset_options.num_points3D = 50; synthetic_dataset_options.prior_position = false; SynthesizeDataset( synthetic_dataset_options, >_reconstruction, database.get()); SyntheticNoiseOptions synthetic_noise_options; synthetic_noise_options.point2D_stddev = 0.1; SynthesizeNoise(synthetic_noise_options, >_reconstruction, database.get()); auto run_mapper = [&](int num_threads, int random_seed) { auto mapper_options = std::make_shared(); mapper_options->use_prior_position = false; mapper_options->num_threads = num_threads; mapper_options->random_seed = random_seed; auto reconstruction_manager = std::make_shared(); IncrementalPipeline mapper( mapper_options, database, reconstruction_manager); mapper.Run(); EXPECT_EQ(reconstruction_manager->Size(), 1); return reconstruction_manager; }; constexpr int kRandomSeed = 42; auto reconstruction_manager0 = run_mapper(/*num_threads=*/1, /*random_seed=*/kRandomSeed); auto reconstruction_manager1 = run_mapper(/*num_threads=*/1, /*random_seed=*/kRandomSeed); EXPECT_THAT(*reconstruction_manager0->Get(0), ReconstructionEq(*reconstruction_manager1->Get(0))); } TEST(IncrementalPipeline, PriorBasedSfMWithRandomSeedStability) { const auto database_path = CreateTestDir() / "database.db"; auto database = Database::Open(database_path); Reconstruction gt_reconstruction; SyntheticDatasetOptions synthetic_dataset_options; synthetic_dataset_options.num_rigs = 2; synthetic_dataset_options.num_cameras_per_rig = 1; synthetic_dataset_options.num_frames_per_rig = 5; synthetic_dataset_options.num_points3D = 50; synthetic_dataset_options.prior_position = true; SynthesizeDataset( synthetic_dataset_options, >_reconstruction, database.get()); SyntheticNoiseOptions synthetic_noise_options; synthetic_noise_options.point2D_stddev = 0.1; synthetic_noise_options.prior_position_stddev = 0.1; SynthesizeNoise(synthetic_noise_options, >_reconstruction, database.get()); auto run_mapper = [&](int num_threads, int random_seed) { auto mapper_options = std::make_shared(); mapper_options->use_prior_position = true; mapper_options->num_threads = num_threads; mapper_options->random_seed = random_seed; auto reconstruction_manager = std::make_shared(); IncrementalPipeline mapper( mapper_options, database, reconstruction_manager); mapper.Run(); EXPECT_EQ(reconstruction_manager->Size(), 1); return reconstruction_manager; }; constexpr int kRandomSeed = 42; auto reconstruction_manager0 = run_mapper(/*num_threads=*/1, /*random_seed=*/kRandomSeed); auto reconstruction_manager1 = run_mapper(/*num_threads=*/1, /*random_seed=*/kRandomSeed); EXPECT_THAT(*reconstruction_manager0->Get(0), ReconstructionEq(*reconstruction_manager1->Get(0))); } TEST(IncrementalPipelineOptions, PropagatesExplicitMaxNumIterations) { // Explicitly set iteration bounds must be forwarded to both backends, // including values that coincide with the previous compiled-in defaults. IncrementalPipelineOptions options; options.ba_local_max_num_iterations = 25; options.ba_global_max_num_iterations = 50; const BundleAdjustmentOptions local_options = options.LocalBundleAdjustment(); ASSERT_TRUE(local_options.ceres); EXPECT_EQ(local_options.ceres->solver_options.max_num_iterations, 25); ASSERT_TRUE(local_options.caspar); EXPECT_EQ(local_options.caspar->solver_iter_max, 25); const BundleAdjustmentOptions global_options = options.GlobalBundleAdjustment(); ASSERT_TRUE(global_options.ceres); EXPECT_EQ(global_options.ceres->solver_options.max_num_iterations, 50); ASSERT_TRUE(global_options.caspar); EXPECT_EQ(global_options.caspar->solver_iter_max, 50); } TEST(IncrementalPipelineOptions, DefaultMaxNumIterationsUsesBackendDefaults) { // With the -1 sentinel default, each backend must keep its own default: // Ceres the previous 25/50 mapper defaults, Caspar its own tuned // solver_iter_max (see review discussion on #4382/PR #4527). const IncrementalPipelineOptions options; ASSERT_EQ(options.ba_local_max_num_iterations, -1); ASSERT_EQ(options.ba_global_max_num_iterations, -1); const int caspar_default = CasparBundleAdjustmentOptions().solver_iter_max; const BundleAdjustmentOptions local_options = options.LocalBundleAdjustment(); ASSERT_TRUE(local_options.ceres); EXPECT_EQ(local_options.ceres->solver_options.max_num_iterations, 25); ASSERT_TRUE(local_options.caspar); EXPECT_EQ(local_options.caspar->solver_iter_max, caspar_default); const BundleAdjustmentOptions global_options = options.GlobalBundleAdjustment(); ASSERT_TRUE(global_options.ceres); EXPECT_EQ(global_options.ceres->solver_options.max_num_iterations, 50); ASSERT_TRUE(global_options.caspar); EXPECT_EQ(global_options.caspar->solver_iter_max, caspar_default); } TEST(IncrementalPipelineOptions, EffBaMaxNumIterations) { IncrementalPipelineOptions options; const int caspar_default = CasparBundleAdjustmentOptions().solver_iter_max; // Sentinel resolves to the configured backend's default. EXPECT_EQ(options.EffBaLocalMaxNumIterations(), 25); EXPECT_EQ(options.EffBaGlobalMaxNumIterations(), 50); options.ba_local_backend = BundleAdjustmentBackend::CASPAR; options.ba_global_backend = BundleAdjustmentBackend::CASPAR; EXPECT_EQ(options.EffBaLocalMaxNumIterations(), caspar_default); EXPECT_EQ(options.EffBaGlobalMaxNumIterations(), caspar_default); // Explicit values win regardless of backend. options.ba_local_max_num_iterations = 7; options.ba_global_max_num_iterations = 13; EXPECT_EQ(options.EffBaLocalMaxNumIterations(), 7); EXPECT_EQ(options.EffBaGlobalMaxNumIterations(), 13); } } // namespace } // namespace colmap colmap-4.2.0/src/colmap/controllers/matcher_cache.cc000066400000000000000000000261061524536416500224750ustar00rootroot00000000000000// Copyright (c), ETH Zurich and UNC Chapel Hill. // All rights reserved. // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are met: // // * Redistributions of source code must retain the above copyright // notice, this list of conditions and the following disclaimer. // // * Redistributions in binary form must reproduce the above copyright // notice, this list of conditions and the following disclaimer in the // documentation and/or other materials provided with the distribution. // // * Neither the name of ETH Zurich and UNC Chapel Hill nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE // ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE // LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR // CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF // SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS // INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN // CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) // ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE // POSSIBILITY OF SUCH DAMAGE. #include "colmap/controllers/matcher_cache.h" #include "colmap/util/hash_containers.h" namespace colmap { FeatureMatcherCache::FeatureMatcherCache( const size_t cache_size, const std::shared_ptr& database) : cache_size_(cache_size), database_(THROW_CHECK_NOTNULL(database)), descriptor_index_cache_(cache_size_, [this](const image_t image_id) { auto descriptors = GetDescriptors(image_id); auto index = FeatureDescriptorIndex::Create(); index->Build(descriptors->ToFloat()); return index; }) { keypoints_cache_ = std::make_unique>( cache_size_, [this](const image_t image_id) { std::lock_guard lock(database_mutex_); return std::make_shared( database_->ReadKeypoints(image_id)); }); descriptors_cache_ = std::make_unique>( cache_size_, [this](const image_t image_id) { std::lock_guard lock(database_mutex_); return std::make_shared( database_->ReadDescriptors(image_id)); }); keypoints_exists_cache_ = std::make_unique>( cache_size_, [this](const image_t image_id) { std::lock_guard lock(database_mutex_); return std::make_shared(database_->ExistsKeypoints(image_id)); }); descriptors_exists_cache_ = std::make_unique>( cache_size_, [this](const image_t image_id) { std::lock_guard lock(database_mutex_); return std::make_shared( database_->ExistsDescriptors(image_id)); }); } void FeatureMatcherCache::AccessDatabase( const std::function& func) { std::lock_guard lock(database_mutex_); func(*database_); } const Camera& FeatureMatcherCache::GetCamera(const camera_t camera_id) { MaybeLoadCameras(); return cameras_cache_->at(camera_id); } const Frame& FeatureMatcherCache::GetFrame(const frame_t frame_id) { MaybeLoadFrames(); return frames_cache_->at(frame_id); } const Image& FeatureMatcherCache::GetImage(const image_t image_id) { MaybeLoadImages(); return images_cache_->at(image_id); } const PosePrior* FeatureMatcherCache::FindImagePosePriorOrNull( const image_t image_id) { MaybeLoadPosePriors(); const auto it = pose_priors_cache_->find(image_id); if (it != pose_priors_cache_->end()) { return &it->second; } return nullptr; } std::shared_ptr FeatureMatcherCache::GetKeypoints( const image_t image_id) { return keypoints_cache_->Get(image_id); } std::shared_ptr FeatureMatcherCache::GetDescriptors( const image_t image_id) { return descriptors_cache_->Get(image_id); } FeatureMatches FeatureMatcherCache::GetMatches(const image_t image_id1, const image_t image_id2) { std::lock_guard lock(database_mutex_); return database_->ReadMatches(image_id1, image_id2); } TwoViewGeometry FeatureMatcherCache::GetTwoViewGeometry( const image_t image_id1, const image_t image_id2) { std::lock_guard lock(database_mutex_); return database_->ReadTwoViewGeometry(image_id1, image_id2); } std::vector FeatureMatcherCache::GetFrameIds() { MaybeLoadFrames(); std::vector frame_ids; frame_ids.reserve(frames_cache_->size()); for (const auto& frame : *frames_cache_) { frame_ids.push_back(frame.first); } // Sort the frames for deterministic behavior. Note that the frames_cache_ is // an unordered_map, which does not guarantee a deterministic order across // different standard library implementations. std::sort(frame_ids.begin(), frame_ids.end()); return frame_ids; } std::vector FeatureMatcherCache::GetImageIds() { MaybeLoadImages(); std::vector image_ids; image_ids.reserve(images_cache_->size()); for (const auto& image : *images_cache_) { image_ids.push_back(image.first); } // Sort the images for deterministic behavior. Note that the images_cache_ is // an unordered_map, which does not guarantee a deterministic order across // different standard library implementations. std::sort(image_ids.begin(), image_ids.end()); return image_ids; } ThreadSafeLRUCache& FeatureMatcherCache::GetFeatureDescriptorIndexCache() { return descriptor_index_cache_; } bool FeatureMatcherCache::ExistsKeypoints(const image_t image_id) { return *keypoints_exists_cache_->Get(image_id); } bool FeatureMatcherCache::ExistsDescriptors(const image_t image_id) { return *descriptors_exists_cache_->Get(image_id); } bool FeatureMatcherCache::ExistsMatches(const image_t image_id1, const image_t image_id2) { std::lock_guard lock(database_mutex_); return database_->ExistsMatches(image_id1, image_id2); } bool FeatureMatcherCache::ExistsTwoViewGeometry(const image_t image_id1, const image_t image_id2) { std::lock_guard lock(database_mutex_); return database_->ExistsTwoViewGeometry(image_id1, image_id2); } bool FeatureMatcherCache::ExistsInlierMatches(const image_t image_id1, const image_t image_id2) { std::lock_guard lock(database_mutex_); if (!database_->ExistsTwoViewGeometry(image_id1, image_id2)) { return false; } auto two_view_geometry = database_->ReadTwoViewGeometry(image_id1, image_id2); return !two_view_geometry.inlier_matches.empty(); } void FeatureMatcherCache::UpdateTwoViewGeometry( const image_t image_id1, const image_t image_id2, const TwoViewGeometry& two_view_geometry) { std::lock_guard lock(database_mutex_); database_->UpdateTwoViewGeometry(image_id1, image_id2, two_view_geometry); } void FeatureMatcherCache::WriteMatches(const image_t image_id1, const image_t image_id2, const FeatureMatches& matches) { std::lock_guard lock(database_mutex_); database_->WriteMatches(image_id1, image_id2, matches); } void FeatureMatcherCache::WriteTwoViewGeometry( const image_t image_id1, const image_t image_id2, const TwoViewGeometry& two_view_geometry) { std::lock_guard lock(database_mutex_); database_->WriteTwoViewGeometry(image_id1, image_id2, two_view_geometry); } void FeatureMatcherCache::DeleteMatches(const image_t image_id1, const image_t image_id2) { std::lock_guard lock(database_mutex_); database_->DeleteMatches(image_id1, image_id2); } void FeatureMatcherCache::DeleteTwoViewGeometry(const image_t image_id1, const image_t image_id2) { std::lock_guard lock(database_mutex_); database_->DeleteTwoViewGeometry(image_id1, image_id2); } void FeatureMatcherCache::DeleteInlierMatches(const image_t image_id1, const image_t image_id2) { std::lock_guard lock(database_mutex_); database_->DeleteInlierMatches(image_id1, image_id2); } size_t FeatureMatcherCache::MaxNumKeypoints() { std::lock_guard lock(database_mutex_); if (!max_num_keypoints_) { max_num_keypoints_ = database_->MaxNumKeypoints(); } return *max_num_keypoints_; } void FeatureMatcherCache::MaybeLoadCameras() { std::lock_guard lock(database_mutex_); if (cameras_cache_) { return; } std::vector cameras = database_->ReadAllCameras(); cameras_cache_ = std::make_unique>(); cameras_cache_->reserve(cameras.size()); for (Camera& camera : cameras) { cameras_cache_->emplace(camera.camera_id, std::move(camera)); } } void FeatureMatcherCache::MaybeLoadFrames() { std::lock_guard lock(database_mutex_); if (frames_cache_) { return; } std::vector frames = database_->ReadAllFrames(); frames_cache_ = std::make_unique>(); frames_cache_->reserve(frames.size()); for (Frame& frame : frames) { frames_cache_->emplace(frame.FrameId(), std::move(frame)); } } void FeatureMatcherCache::MaybeLoadImages() { MaybeLoadFrames(); std::lock_guard lock(database_mutex_); if (images_cache_) { return; } std::vector images = database_->ReadAllImages(); images_cache_ = std::make_unique>(); images_cache_->reserve(images.size()); for (Image& image : images) { images_cache_->emplace(image.ImageId(), std::move(image)); } } void FeatureMatcherCache::MaybeLoadPosePriors() { MaybeLoadImages(); std::lock_guard lock(database_mutex_); if (pose_priors_cache_) { return; } std::vector pose_priors = database_->ReadAllPosePriors(); pose_priors_cache_ = std::make_unique>(); pose_priors_cache_->reserve(pose_priors.size()); for (PosePrior& pose_prior : pose_priors) { if (pose_prior.corr_data_id.sensor_id.type == SensorType::CAMERA) { const image_t image_id = pose_prior.corr_data_id.id; THROW_CHECK( pose_priors_cache_->emplace(image_id, std::move(pose_prior)).second) << "Duplicate pose prior for image " << image_id; } } } } // namespace colmap colmap-4.2.0/src/colmap/controllers/matcher_cache.h000066400000000000000000000117261524536416500223410ustar00rootroot00000000000000// Copyright (c), ETH Zurich and UNC Chapel Hill. // All rights reserved. // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are met: // // * Redistributions of source code must retain the above copyright // notice, this list of conditions and the following disclaimer. // // * Redistributions in binary form must reproduce the above copyright // notice, this list of conditions and the following disclaimer in the // documentation and/or other materials provided with the distribution. // // * Neither the name of ETH Zurich and UNC Chapel Hill nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE // ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE // LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR // CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF // SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS // INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN // CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) // ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE // POSSIBILITY OF SUCH DAMAGE. #pragma once #include "colmap/feature/index.h" #include "colmap/feature/types.h" #include "colmap/scene/camera.h" #include "colmap/scene/database.h" #include "colmap/scene/image.h" #include "colmap/scene/two_view_geometry.h" #include "colmap/util/cache.h" #include "colmap/util/hash_containers.h" #include "colmap/util/types.h" #include #include #include namespace colmap { // Cache for feature matching to minimize database access during matching. class FeatureMatcherCache { public: FeatureMatcherCache(size_t cache_size, const std::shared_ptr& database); // Executes a function that accesses the database. This function is thread // safe and ensures that only one function can access the database at a time. void AccessDatabase(const std::function& func); const Camera& GetCamera(camera_t camera_id); const Frame& GetFrame(frame_t frame_id); const Image& GetImage(image_t image_id); const PosePrior* FindImagePosePriorOrNull(image_t image_id); std::shared_ptr GetKeypoints(image_t image_id); std::shared_ptr GetDescriptors(image_t image_id); FeatureMatches GetMatches(image_t image_id1, image_t image_id2); TwoViewGeometry GetTwoViewGeometry(image_t image_id1, image_t image_id2); std::vector GetFrameIds(); std::vector GetImageIds(); ThreadSafeLRUCache& GetFeatureDescriptorIndexCache(); bool ExistsKeypoints(image_t image_id); bool ExistsDescriptors(image_t image_id); bool ExistsMatches(image_t image_id1, image_t image_id2); bool ExistsTwoViewGeometry(image_t image_id1, image_t image_id2); bool ExistsInlierMatches(image_t image_id1, image_t image_id2); void UpdateTwoViewGeometry(image_t image_id1, image_t image_id2, const TwoViewGeometry& two_view_geometry); void WriteMatches(image_t image_id1, image_t image_id2, const FeatureMatches& matches); void WriteTwoViewGeometry(image_t image_id1, image_t image_id2, const TwoViewGeometry& two_view_geometry); void DeleteMatches(image_t image_id1, image_t image_id2); void DeleteTwoViewGeometry(image_t image_id1, image_t image_id2); void DeleteInlierMatches(image_t image_id1, image_t image_id2); size_t MaxNumKeypoints(); private: void MaybeLoadCameras(); void MaybeLoadFrames(); void MaybeLoadImages(); void MaybeLoadPosePriors(); const size_t cache_size_; const std::shared_ptr database_; std::mutex database_mutex_; std::unique_ptr> cameras_cache_; std::unique_ptr> frames_cache_; std::unique_ptr> images_cache_; std::unique_ptr> pose_priors_cache_; std::unique_ptr> keypoints_cache_; std::unique_ptr> descriptors_cache_; std::unique_ptr> keypoints_exists_cache_; std::unique_ptr> descriptors_exists_cache_; ThreadSafeLRUCache descriptor_index_cache_; std::optional max_num_keypoints_; }; } // namespace colmap colmap-4.2.0/src/colmap/controllers/matcher_cache_test.cc000066400000000000000000000304061524536416500235320ustar00rootroot00000000000000// Copyright (c), ETH Zurich and UNC Chapel Hill. // All rights reserved. // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are met: // // * Redistributions of source code must retain the above copyright // notice, this list of conditions and the following disclaimer. // // * Redistributions in binary form must reproduce the above copyright // notice, this list of conditions and the following disclaimer in the // documentation and/or other materials provided with the distribution. // // * Neither the name of ETH Zurich and UNC Chapel Hill nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE // ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE // LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR // CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF // SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS // INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN // CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) // ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE // POSSIBILITY OF SUCH DAMAGE. #include "colmap/controllers/matcher_cache.h" #include "colmap/scene/synthetic.h" #include "colmap/util/testing.h" #include #include namespace colmap { namespace { struct TestData { std::shared_ptr database; Reconstruction reconstruction; }; TestData CreateTestData(int num_images, bool with_priors = false, int num_cameras_per_rig = 1) { TestData data; const auto test_dir = CreateTestDir(); const auto database_path = test_dir / "database.db"; data.database = Database::Open(database_path); SyntheticDatasetOptions options; options.num_rigs = num_images / num_cameras_per_rig; options.num_cameras_per_rig = num_cameras_per_rig; options.num_frames_per_rig = 1; options.num_points3D = 20; options.num_points2D_without_point3D = 3; options.prior_position = with_priors; SynthesizeDataset(options, &data.reconstruction, data.database.get()); return data; } TEST(FeatureMatcherCache, GetCamera) { auto data = CreateTestData(4); FeatureMatcherCache cache(5, data.database); const std::vector cameras = data.database->ReadAllCameras(); ASSERT_FALSE(cameras.empty()); for (const Camera& camera : cameras) { EXPECT_EQ(cache.GetCamera(camera.camera_id), camera); } } TEST(FeatureMatcherCache, GetFrame) { auto data = CreateTestData(4); FeatureMatcherCache cache(5, data.database); const std::vector frames = data.database->ReadAllFrames(); ASSERT_FALSE(frames.empty()); for (const Frame& frame : frames) { EXPECT_EQ(cache.GetFrame(frame.FrameId()), frame); } } TEST(FeatureMatcherCache, GetImage) { auto data = CreateTestData(4); FeatureMatcherCache cache(5, data.database); const std::vector images = data.database->ReadAllImages(); ASSERT_FALSE(images.empty()); for (const Image& image : images) { EXPECT_EQ(cache.GetImage(image.ImageId()), image); } } TEST(FeatureMatcherCache, GetImageIds) { auto data = CreateTestData(4); FeatureMatcherCache cache(5, data.database); const std::vector images = data.database->ReadAllImages(); std::vector expected_ids; expected_ids.reserve(images.size()); for (const Image& image : images) { expected_ids.push_back(image.ImageId()); } EXPECT_THAT(cache.GetImageIds(), ::testing::UnorderedElementsAreArray(expected_ids)); } TEST(FeatureMatcherCache, GetFrameIds) { auto data = CreateTestData(4); FeatureMatcherCache cache(5, data.database); const std::vector frames = data.database->ReadAllFrames(); std::vector expected_ids; expected_ids.reserve(frames.size()); for (const Frame& frame : frames) { expected_ids.push_back(frame.FrameId()); } EXPECT_THAT(cache.GetFrameIds(), ::testing::UnorderedElementsAreArray(expected_ids)); } TEST(FeatureMatcherCache, FindImagePosePriorOrNullWithPriors) { auto data = CreateTestData(4, /*with_priors=*/true); FeatureMatcherCache cache(5, data.database); const std::vector images = data.database->ReadAllImages(); ASSERT_FALSE(images.empty()); for (const Image& image : images) { const PosePrior* prior = cache.FindImagePosePriorOrNull(image.ImageId()); ASSERT_NE(prior, nullptr); EXPECT_TRUE(prior->HasPosition()); } } TEST(FeatureMatcherCache, FindImagePosePriorOrNullWithoutPriors) { auto data = CreateTestData(4, /*with_priors=*/false); FeatureMatcherCache cache(5, data.database); const std::vector images = data.database->ReadAllImages(); ASSERT_FALSE(images.empty()); for (const Image& image : images) { const PosePrior* prior = cache.FindImagePosePriorOrNull(image.ImageId()); EXPECT_EQ(prior, nullptr); } } TEST(FeatureMatcherCache, Features) { auto data = CreateTestData(4); FeatureMatcherCache cache(5, data.database); const std::vector images = data.database->ReadAllImages(); ASSERT_FALSE(images.empty()); for (const Image& image : images) { EXPECT_TRUE(cache.ExistsKeypoints(image.ImageId())); EXPECT_TRUE(cache.ExistsDescriptors(image.ImageId())); EXPECT_EQ(*cache.GetKeypoints(image.ImageId()), data.database->ReadKeypoints(image.ImageId())); auto cached_descriptors = cache.GetDescriptors(image.ImageId()); FeatureDescriptors db_descriptors = data.database->ReadDescriptors(image.ImageId()); EXPECT_EQ(cached_descriptors->type, db_descriptors.type); EXPECT_EQ(cached_descriptors->data, db_descriptors.data); } } TEST(FeatureMatcherCache, Matches) { auto data = CreateTestData(4); FeatureMatcherCache cache(5, data.database); const auto all_matches = data.database->ReadAllMatches(); ASSERT_FALSE(all_matches.empty()); for (const auto& [pair_id, matches] : all_matches) { const auto [image_id1, image_id2] = PairIdToImagePair(pair_id); EXPECT_TRUE(cache.ExistsMatches(image_id1, image_id2)); EXPECT_EQ(cache.GetMatches(image_id1, image_id2), matches); } } TEST(FeatureMatcherCache, TwoViewGeometry) { auto data = CreateTestData(4); FeatureMatcherCache cache(5, data.database); const auto all_tvg = data.database->ReadTwoViewGeometries(); ASSERT_FALSE(all_tvg.empty()); for (const auto& [pair_id, tvg] : all_tvg) { const auto [image_id1, image_id2] = PairIdToImagePair(pair_id); EXPECT_TRUE(cache.ExistsTwoViewGeometry(image_id1, image_id2)); EXPECT_EQ(cache.ExistsInlierMatches(image_id1, image_id2), !tvg.inlier_matches.empty()); EXPECT_EQ(cache.GetTwoViewGeometry(image_id1, image_id2).inlier_matches, tvg.inlier_matches); } } TEST(FeatureMatcherCache, WriteAndGetMatches) { auto data = CreateTestData(4); FeatureMatcherCache cache(5, data.database); const std::vector images = data.database->ReadAllImages(); ASSERT_GE(images.size(), 2); const image_t id1 = images[0].ImageId(); const image_t id2 = images[1].ImageId(); // Delete existing matches first. cache.DeleteMatches(id1, id2); EXPECT_FALSE(cache.ExistsMatches(id1, id2)); // Write new matches. FeatureMatches matches(5); for (size_t i = 0; i < matches.size(); ++i) { matches[i].point2D_idx1 = i; matches[i].point2D_idx2 = i; } cache.WriteMatches(id1, id2, matches); EXPECT_TRUE(cache.ExistsMatches(id1, id2)); FeatureMatches read_matches = cache.GetMatches(id1, id2); EXPECT_EQ(read_matches.size(), 5); } TEST(FeatureMatcherCache, WriteAndGetTwoViewGeometry) { auto data = CreateTestData(4); FeatureMatcherCache cache(5, data.database); const std::vector images = data.database->ReadAllImages(); ASSERT_GE(images.size(), 2); const image_t id1 = images[0].ImageId(); const image_t id2 = images[1].ImageId(); // Delete existing two-view geometry first. cache.DeleteTwoViewGeometry(id1, id2); EXPECT_FALSE(cache.ExistsTwoViewGeometry(id1, id2)); // Write new two-view geometry. TwoViewGeometry tvg; tvg.config = TwoViewGeometry::CALIBRATED; tvg.inlier_matches.resize(10); cache.WriteTwoViewGeometry(id1, id2, tvg); EXPECT_TRUE(cache.ExistsTwoViewGeometry(id1, id2)); TwoViewGeometry read_tvg = cache.GetTwoViewGeometry(id1, id2); EXPECT_EQ(read_tvg.config, TwoViewGeometry::CALIBRATED); EXPECT_EQ(read_tvg.inlier_matches.size(), 10); } TEST(FeatureMatcherCache, UpdateTwoViewGeometry) { auto data = CreateTestData(4); FeatureMatcherCache cache(5, data.database); const auto all_tvg = data.database->ReadTwoViewGeometries(); ASSERT_FALSE(all_tvg.empty()); const auto& [pair_id, original_tvg] = all_tvg.front(); const auto [id1, id2] = PairIdToImagePair(pair_id); TwoViewGeometry updated_tvg; updated_tvg.config = TwoViewGeometry::UNCALIBRATED; updated_tvg.inlier_matches.resize(7); cache.UpdateTwoViewGeometry(id1, id2, updated_tvg); TwoViewGeometry read_tvg = cache.GetTwoViewGeometry(id1, id2); EXPECT_EQ(read_tvg.config, TwoViewGeometry::UNCALIBRATED); EXPECT_EQ(read_tvg.inlier_matches.size(), 7); } TEST(FeatureMatcherCache, DeleteMatches) { auto data = CreateTestData(4); FeatureMatcherCache cache(5, data.database); const auto all_matches = data.database->ReadAllMatches(); ASSERT_FALSE(all_matches.empty()); const auto& [pair_id, _] = all_matches.front(); const auto [id1, id2] = PairIdToImagePair(pair_id); EXPECT_TRUE(cache.ExistsMatches(id1, id2)); cache.DeleteMatches(id1, id2); EXPECT_FALSE(cache.ExistsMatches(id1, id2)); } TEST(FeatureMatcherCache, DeleteTwoViewGeometry) { auto data = CreateTestData(4); FeatureMatcherCache cache(5, data.database); const auto all_tvg = data.database->ReadTwoViewGeometries(); ASSERT_FALSE(all_tvg.empty()); const auto& [pair_id, _] = all_tvg.front(); const auto [id1, id2] = PairIdToImagePair(pair_id); EXPECT_TRUE(cache.ExistsTwoViewGeometry(id1, id2)); cache.DeleteTwoViewGeometry(id1, id2); EXPECT_FALSE(cache.ExistsTwoViewGeometry(id1, id2)); } TEST(FeatureMatcherCache, DeleteInlierMatches) { auto data = CreateTestData(4); FeatureMatcherCache cache(5, data.database); const auto all_tvg = data.database->ReadTwoViewGeometries(); ASSERT_FALSE(all_tvg.empty()); // Find a pair with inlier matches. image_t image_id1 = 0, image_id2 = 0; bool found = false; for (const auto& [pair_id, tvg] : all_tvg) { if (!tvg.inlier_matches.empty()) { std::tie(image_id1, image_id2) = PairIdToImagePair(pair_id); found = true; break; } } ASSERT_TRUE(found); EXPECT_TRUE(cache.ExistsInlierMatches(image_id1, image_id2)); cache.DeleteInlierMatches(image_id1, image_id2); EXPECT_FALSE(cache.ExistsInlierMatches(image_id1, image_id2)); // The two-view geometry entry should still exist. EXPECT_TRUE(cache.ExistsTwoViewGeometry(image_id1, image_id2)); } TEST(FeatureMatcherCache, MaxNumKeypoints) { auto data = CreateTestData(4); FeatureMatcherCache cache(5, data.database); const size_t max_num_keypoints = cache.MaxNumKeypoints(); EXPECT_GT(max_num_keypoints, 0); // Calling again should return the cached value. EXPECT_EQ(cache.MaxNumKeypoints(), max_num_keypoints); } TEST(FeatureMatcherCache, AccessDatabase) { auto data = CreateTestData(4); FeatureMatcherCache cache(5, data.database); size_t num_images = 0; cache.AccessDatabase([&num_images](Database& database) { num_images = database.ReadAllImages().size(); }); EXPECT_EQ(num_images, 4); } TEST(FeatureMatcherCache, GetFeatureDescriptorIndexCache) { auto data = CreateTestData(4); FeatureMatcherCache cache(5, data.database); auto& index_cache = cache.GetFeatureDescriptorIndexCache(); const std::vector images = data.database->ReadAllImages(); ASSERT_FALSE(images.empty()); // Access descriptor index for the first image to trigger build. auto index = index_cache.Get(images[0].ImageId()); ASSERT_NE(index, nullptr); } } // namespace } // namespace colmap colmap-4.2.0/src/colmap/controllers/option_manager.cc000066400000000000000000001711341524536416500227330ustar00rootroot00000000000000// Copyright (c), ETH Zurich and UNC Chapel Hill. // All rights reserved. // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are met: // // * Redistributions of source code must retain the above copyright // notice, this list of conditions and the following disclaimer. // // * Redistributions in binary form must reproduce the above copyright // notice, this list of conditions and the following disclaimer in the // documentation and/or other materials provided with the distribution. // // * Neither the name of ETH Zurich and UNC Chapel Hill nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE // ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE // LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR // CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF // SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS // INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN // CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) // ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE // POSSIBILITY OF SUCH DAMAGE. #include "colmap/controllers/option_manager.h" #include "colmap/controllers/global_pipeline.h" #include "colmap/controllers/hierarchical_pipeline.h" #include "colmap/controllers/image_reader.h" #include "colmap/controllers/incremental_pipeline.h" #include "colmap/controllers/pairing.h" #ifdef CASPAR_ENABLED #include "colmap/estimators/bundle_adjustment_caspar.h" #endif #include "colmap/estimators/bundle_adjustment_ceres.h" #include "colmap/estimators/global_positioning.h" #include "colmap/estimators/gravity_refinement.h" #include "colmap/estimators/two_view_geometry.h" #include "colmap/feature/aliked.h" #include "colmap/feature/loma.h" #include "colmap/feature/sift.h" #if defined(COLMAP_MVS_ENABLED) #include "colmap/mvs/advancing_front_meshing.h" #include "colmap/mvs/delaunay_meshing.h" #include "colmap/mvs/fusion.h" #include "colmap/mvs/mesh_simplification.h" #include "colmap/mvs/patch_match_options.h" #include "colmap/mvs/poisson_meshing.h" #include "colmap/mvs/texture_mapping.h" #endif #include "colmap/scene/reconstruction_clustering.h" #include "colmap/ui/render_options.h" #include "colmap/util/file.h" #include "colmap/util/version.h" namespace config = boost::program_options; namespace colmap { OptionManager::OptionManager(bool add_project_options) : BaseOptionManager(add_project_options) { image_reader = std::make_shared(); feature_extraction = std::make_shared(); feature_matching = std::make_shared(); two_view_geometry = std::make_shared(); exhaustive_pairing = std::make_shared(); sequential_pairing = std::make_shared(); vocab_tree_pairing = std::make_shared(); spatial_pairing = std::make_shared(); transitive_pairing = std::make_shared(); imported_pairing = std::make_shared(); bundle_adjustment = std::make_shared(); mapper = std::make_shared(); global_mapper = std::make_shared(); hierarchical_mapper = std::make_shared(); gravity_refiner = std::make_shared(); reconstruction_clusterer = std::make_shared(); #if defined(COLMAP_MVS_ENABLED) patch_match_stereo = std::make_shared(); stereo_fusion = std::make_shared(); poisson_meshing = std::make_shared(); delaunay_meshing = std::make_shared(); advancing_front_meshing = std::make_shared(); mesh_texture_mapping = std::make_shared(); mesh_simplification = std::make_shared(); #endif render = std::make_shared(); } void OptionManager::ModifyForIndividualData() { mapper->min_focal_length_ratio = 0.1; mapper->max_focal_length_ratio = 10; mapper->max_extra_param = std::numeric_limits::max(); } void OptionManager::ModifyForVideoData() { const bool kResetPaths = false; ResetOptions(kResetPaths); mapper->mapper.init_min_tri_angle /= 2; mapper->ba_global_frames_ratio = 1.4; mapper->ba_global_points_ratio = 1.4; mapper->min_focal_length_ratio = 0.1; mapper->max_focal_length_ratio = 10; mapper->max_extra_param = std::numeric_limits::max(); #if defined(COLMAP_MVS_ENABLED) stereo_fusion->min_num_pixels = 15; #endif } void OptionManager::ModifyForInternetData() { #if defined(COLMAP_MVS_ENABLED) stereo_fusion->min_num_pixels = 10; #endif } void OptionManager::ModifyForLowQuality() { feature_extraction->max_image_size = static_cast(0.3125 * feature_extraction->EffMaxImageSize()); feature_extraction->sift->max_num_features = 2048; sequential_pairing->loop_detection_num_images /= 2; vocab_tree_pairing->max_num_features = 256; vocab_tree_pairing->num_images /= 2; mapper->ba_local_max_num_iterations = mapper->EffBaLocalMaxNumIterations() / 2; mapper->ba_global_max_num_iterations = mapper->EffBaGlobalMaxNumIterations() / 2; mapper->ba_global_frames_ratio *= 1.2; mapper->ba_global_points_ratio *= 1.2; mapper->ba_global_max_refinements = 2; #if defined(COLMAP_MVS_ENABLED) patch_match_stereo->max_image_size = 1000; patch_match_stereo->window_radius = 4; patch_match_stereo->window_step = 2; patch_match_stereo->num_samples /= 2; patch_match_stereo->num_iterations = 3; patch_match_stereo->geom_consistency = false; stereo_fusion->check_num_images /= 2; stereo_fusion->max_image_size = 1000; #endif } void OptionManager::ModifyForMediumQuality() { feature_extraction->max_image_size = static_cast(0.5 * feature_extraction->EffMaxImageSize()); feature_extraction->sift->max_num_features = 4096; sequential_pairing->loop_detection_num_images /= 1.5; vocab_tree_pairing->max_num_features = 1024; vocab_tree_pairing->num_images /= 1.5; mapper->ba_local_max_num_iterations = static_cast(mapper->EffBaLocalMaxNumIterations() / 1.5); mapper->ba_global_max_num_iterations = static_cast(mapper->EffBaGlobalMaxNumIterations() / 1.5); mapper->ba_global_frames_ratio *= 1.1; mapper->ba_global_points_ratio *= 1.1; mapper->ba_global_max_refinements = 2; #if defined(COLMAP_MVS_ENABLED) patch_match_stereo->max_image_size = 1600; patch_match_stereo->window_radius = 4; patch_match_stereo->window_step = 2; patch_match_stereo->num_samples /= 1.5; patch_match_stereo->num_iterations = 5; patch_match_stereo->geom_consistency = false; stereo_fusion->check_num_images /= 1.5; stereo_fusion->max_image_size = 1600; #endif } void OptionManager::ModifyForHighQuality() { feature_extraction->sift->estimate_affine_shape = true; feature_extraction->max_image_size = static_cast(0.75 * feature_extraction->EffMaxImageSize()); feature_extraction->sift->max_num_features = 8192; feature_matching->guided_matching = true; vocab_tree_pairing->max_num_features = 4096; mapper->ba_local_max_num_iterations = 30; mapper->ba_local_max_refinements = 3; mapper->ba_global_max_num_iterations = 75; #if defined(COLMAP_MVS_ENABLED) patch_match_stereo->max_image_size = 2400; stereo_fusion->max_image_size = 2400; #endif } void OptionManager::ModifyForExtremeQuality() { // Most of the options are set to extreme quality by default. feature_extraction->sift->estimate_affine_shape = true; feature_extraction->sift->domain_size_pooling = true; feature_matching->guided_matching = true; mapper->ba_local_max_num_iterations = 40; mapper->ba_local_max_refinements = 3; mapper->ba_global_max_num_iterations = 100; } void OptionManager::AddAllOptions() { BaseOptionManager::AddAllOptions(); AddFeatureExtractionOptions(); AddFeatureMatchingOptions(); AddTwoViewGeometryOptions(); AddExhaustivePairingOptions(); AddSequentialPairingOptions(); AddVocabTreePairingOptions(); AddSpatialPairingOptions(); AddTransitivePairingOptions(); AddImportedPairingOptions(); AddBundleAdjustmentOptions(); AddMapperOptions(); #if defined(COLMAP_MVS_ENABLED) AddPatchMatchStereoOptions(); AddStereoFusionOptions(); AddPoissonMeshingOptions(); AddDelaunayMeshingOptions(); AddAdvancingFrontMeshingOptions(); AddMeshTextureMappingOptions(); AddMeshSimplificationOptions(); #endif AddRenderOptions(); } void OptionManager::AddFeatureExtractionOptions() { if (added_feature_extraction_options_) { return; } added_feature_extraction_options_ = true; AddDefaultOption("ImageReader.mask_path", &image_reader->mask_path); AddDefaultOption("ImageReader.camera_model", &image_reader->camera_model); AddDefaultOption("ImageReader.single_camera", &image_reader->single_camera); AddDefaultOption("ImageReader.single_camera_per_folder", &image_reader->single_camera_per_folder); AddDefaultOption("ImageReader.single_camera_per_image", &image_reader->single_camera_per_image); AddDefaultOption("ImageReader.existing_camera_id", &image_reader->existing_camera_id); AddDefaultOption("ImageReader.camera_params", &image_reader->camera_params); AddDefaultOption("ImageReader.default_focal_length_factor", &image_reader->default_focal_length_factor); AddDefaultOption("ImageReader.camera_mask_path", &image_reader->camera_mask_path); AddDefaultEnumOption("FeatureExtraction.type", &feature_extraction->type, FeatureExtractorTypeToString, FeatureExtractorTypeFromString); AddDefaultOption("FeatureExtraction.num_threads", &feature_extraction->num_threads); AddDefaultOption("FeatureExtraction.use_gpu", &feature_extraction->use_gpu); AddDefaultOption("FeatureExtraction.gpu_index", &feature_extraction->gpu_index); AddDefaultOption("FeatureExtraction.max_image_size", &feature_extraction->max_image_size); AddDefaultOption("SiftExtraction.max_num_features", &feature_extraction->sift->max_num_features); AddDefaultOption("SiftExtraction.first_octave", &feature_extraction->sift->first_octave); AddDefaultOption("SiftExtraction.num_octaves", &feature_extraction->sift->num_octaves); AddDefaultOption("SiftExtraction.octave_resolution", &feature_extraction->sift->octave_resolution); AddDefaultOption("SiftExtraction.peak_threshold", &feature_extraction->sift->peak_threshold); AddDefaultOption("SiftExtraction.edge_threshold", &feature_extraction->sift->edge_threshold); AddDefaultOption("SiftExtraction.estimate_affine_shape", &feature_extraction->sift->estimate_affine_shape); AddDefaultOption("SiftExtraction.max_num_orientations", &feature_extraction->sift->max_num_orientations); AddDefaultOption("SiftExtraction.upright", &feature_extraction->sift->upright); AddDefaultOption("SiftExtraction.domain_size_pooling", &feature_extraction->sift->domain_size_pooling); AddDefaultOption("SiftExtraction.dsp_min_scale", &feature_extraction->sift->dsp_min_scale); AddDefaultOption("SiftExtraction.dsp_max_scale", &feature_extraction->sift->dsp_max_scale); AddDefaultOption("SiftExtraction.dsp_num_scales", &feature_extraction->sift->dsp_num_scales); AddDefaultOption("AlikedExtraction.max_num_features", &feature_extraction->aliked->max_num_features); AddDefaultOption("AlikedExtraction.min_score", &feature_extraction->aliked->min_score); AddDefaultOption("AlikedExtraction.n16rot_model_path", &feature_extraction->aliked->n16rot_model_path); AddDefaultOption("AlikedExtraction.n32_model_path", &feature_extraction->aliked->n32_model_path); AddDefaultOption("LomaExtraction.max_num_features", &feature_extraction->loma->max_num_features); AddDefaultOption("LomaExtraction.min_score", &feature_extraction->loma->min_score); AddDefaultOption("LomaExtraction.use_bf16", &feature_extraction->loma->use_bf16); AddDefaultOption("LomaExtraction.use_fast_resize", &feature_extraction->loma->use_fast_resize); AddDefaultOption("LomaExtraction.detector_model_path", &feature_extraction->loma->detector_model_path); AddDefaultOption("LomaExtraction.descriptor_model_path", &feature_extraction->loma->descriptor_model_path); AddDefaultOption("LomaExtraction.descriptor_model_path_bf16", &feature_extraction->loma->descriptor_model_path_bf16); AddDefaultOption("LomaExtraction.descriptor_b128_model_path", &feature_extraction->loma->descriptor_b128_model_path); } void OptionManager::AddFeatureMatchingOptions() { if (added_feature_matching_options_) { return; } added_feature_matching_options_ = true; AddDefaultEnumOption("FeatureMatching.type", &feature_matching->type, FeatureMatcherTypeToString, FeatureMatcherTypeFromString); AddDefaultOption("FeatureMatching.num_threads", &feature_matching->num_threads); AddDefaultOption("FeatureMatching.use_gpu", &feature_matching->use_gpu); AddDefaultOption("FeatureMatching.gpu_index", &feature_matching->gpu_index); AddDefaultOption("FeatureMatching.guided_matching", &feature_matching->guided_matching); AddDefaultOption("FeatureMatching.skip_geometric_verification", &feature_matching->skip_geometric_verification); AddDefaultOption("FeatureMatching.rig_verification", &feature_matching->rig_verification); AddDefaultOption("FeatureMatching.skip_image_pairs_in_same_frame", &feature_matching->skip_image_pairs_in_same_frame); AddDefaultOption("FeatureMatching.max_num_matches", &feature_matching->max_num_matches); AddDefaultOption("SiftMatching.max_ratio", &feature_matching->sift->max_ratio); AddDefaultOption("SiftMatching.max_distance", &feature_matching->sift->max_distance); AddDefaultOption("SiftMatching.cross_check", &feature_matching->sift->cross_check); AddDefaultOption("SiftMatching.cpu_brute_force_matcher", &feature_matching->sift->cpu_brute_force_matcher); AddDefaultOption("SiftMatching.lightglue_min_score", &feature_matching->sift->lightglue.min_score); AddDefaultOption("SiftMatching.lightglue_model_path", &feature_matching->sift->lightglue.model_path); AddDefaultOption("AlikedMatching.brute_force_min_cossim", &feature_matching->aliked->brute_force.min_cossim); AddDefaultOption("AlikedMatching.brute_force_max_ratio", &feature_matching->aliked->brute_force.max_ratio); AddDefaultOption("AlikedMatching.brute_force_cross_check", &feature_matching->aliked->brute_force.cross_check); AddDefaultOption("AlikedMatching.bruteforce_model_path", &feature_matching->aliked->brute_force.model_path); AddDefaultOption("AlikedMatching.lightglue_min_score", &feature_matching->aliked->lightglue.min_score); AddDefaultOption("AlikedMatching.lightglue_model_path", &feature_matching->aliked->lightglue.model_path); AddDefaultOption("LomaMatching.min_score", &feature_matching->loma->min_score); AddDefaultOption("LomaMatching.use_bf16", &feature_matching->loma->use_bf16); AddDefaultOption("LomaMatching.b_model_path", &feature_matching->loma->b.model_path); AddDefaultOption("LomaMatching.b_model_path_bf16", &feature_matching->loma->b.model_path_bf16); AddDefaultOption("LomaMatching.b128_model_path", &feature_matching->loma->b128.model_path); AddDefaultOption("LomaMatching.b128_model_path_bf16", &feature_matching->loma->b128.model_path_bf16); AddDefaultOption("LomaMatching.r_model_path", &feature_matching->loma->r.model_path); AddDefaultOption("LomaMatching.r_model_path_bf16", &feature_matching->loma->r.model_path_bf16); AddDefaultOption("LomaMatching.l_model_path", &feature_matching->loma->l.model_path); AddDefaultOption("LomaMatching.l_model_path_bf16", &feature_matching->loma->l.model_path_bf16); AddDefaultOption("LomaMatching.g_model_path", &feature_matching->loma->g.model_path); AddDefaultOption("LomaMatching.g_model_path_bf16", &feature_matching->loma->g.model_path_bf16); AddDefaultOption("LomaMatching.brute_force_min_cossim", &feature_matching->loma->brute_force.min_cossim); AddDefaultOption("LomaMatching.brute_force_max_ratio", &feature_matching->loma->brute_force.max_ratio); AddDefaultOption("LomaMatching.brute_force_cross_check", &feature_matching->loma->brute_force.cross_check); AddDefaultOption("LomaMatching.brute_force_model_path", &feature_matching->loma->brute_force.model_path); } void OptionManager::AddTwoViewGeometryOptions() { if (added_two_view_geometry_options_) { return; } added_two_view_geometry_options_ = true; AddDefaultOption("TwoViewGeometry.min_num_inliers", &two_view_geometry->min_num_inliers); AddDefaultOption("TwoViewGeometry.multiple_models", &two_view_geometry->multiple_models); AddDefaultOption("TwoViewGeometry.compute_relative_pose", &two_view_geometry->compute_relative_pose); AddDefaultOption("TwoViewGeometry.detect_watermark", &two_view_geometry->detect_watermark); AddDefaultOption("TwoViewGeometry.multiple_ignore_watermark", &two_view_geometry->multiple_ignore_watermark); AddDefaultOption("TwoViewGeometry.watermark_detection_max_error", &two_view_geometry->watermark_detection_max_error); AddDefaultOption("TwoViewGeometry.filter_stationary_matches", &two_view_geometry->filter_stationary_matches); AddDefaultOption("TwoViewGeometry.stationary_matches_max_error", &two_view_geometry->stationary_matches_max_error); AddDefaultOption("TwoViewGeometry.use_degensac", &two_view_geometry->use_degensac); AddDefaultOption("TwoViewGeometry.use_sampson_refinement", &two_view_geometry->use_sampson_refinement); AddDefaultOption("TwoViewGeometry.max_error", &two_view_geometry->ransac_options.max_error); AddDefaultOption("TwoViewGeometry.confidence", &two_view_geometry->ransac_options.confidence); AddDefaultOption("TwoViewGeometry.max_num_trials", &two_view_geometry->ransac_options.max_num_trials); AddDefaultOption("TwoViewGeometry.min_inlier_ratio", &two_view_geometry->ransac_options.min_inlier_ratio); AddDefaultOption("TwoViewGeometry.random_seed", &two_view_geometry->ransac_options.random_seed); } void OptionManager::AddExhaustivePairingOptions() { if (added_exhaustive_pairing_options_) { return; } added_exhaustive_pairing_options_ = true; AddFeatureMatchingOptions(); AddTwoViewGeometryOptions(); AddDefaultOption("ExhaustiveMatching.block_size", &exhaustive_pairing->block_size); } void OptionManager::AddSequentialPairingOptions() { if (added_sequential_pairing_options_) { return; } added_sequential_pairing_options_ = true; AddFeatureMatchingOptions(); AddTwoViewGeometryOptions(); AddDefaultOption("SequentialMatching.overlap", &sequential_pairing->overlap); AddDefaultOption("SequentialMatching.quadratic_overlap", &sequential_pairing->quadratic_overlap); AddDefaultOption("SequentialMatching.expand_rig_images", &sequential_pairing->expand_rig_images); AddDefaultOption("SequentialMatching.loop_detection", &sequential_pairing->loop_detection); AddDefaultOption("SequentialMatching.loop_detection_period", &sequential_pairing->loop_detection_period); AddDefaultOption("SequentialMatching.loop_detection_num_images", &sequential_pairing->loop_detection_num_images); AddDefaultOption("SequentialMatching.loop_detection_min_index_distance", &sequential_pairing->loop_detection_min_index_distance); AddDefaultOption("SequentialMatching.loop_detection_num_nearest_neighbors", &sequential_pairing->loop_detection_num_nearest_neighbors); AddDefaultOption("SequentialMatching.loop_detection_num_checks", &sequential_pairing->loop_detection_num_checks); AddDefaultOption( "SequentialMatching.loop_detection_num_images_after_verification", &sequential_pairing->loop_detection_num_images_after_verification); AddDefaultOption("SequentialMatching.loop_detection_max_num_features", &sequential_pairing->loop_detection_max_num_features); AddDefaultOption("SequentialMatching.vocab_tree_path", &sequential_pairing->vocab_tree_path); AddDefaultOption("SequentialMatching.num_threads", &sequential_pairing->num_threads); } void OptionManager::AddVocabTreePairingOptions() { if (added_vocab_tree_pairing_options_) { return; } added_vocab_tree_pairing_options_ = true; AddFeatureMatchingOptions(); AddTwoViewGeometryOptions(); AddDefaultOption("VocabTreeMatching.num_images", &vocab_tree_pairing->num_images); AddDefaultOption("VocabTreeMatching.num_nearest_neighbors", &vocab_tree_pairing->num_nearest_neighbors); AddDefaultOption("VocabTreeMatching.num_checks", &vocab_tree_pairing->num_checks); AddDefaultOption("VocabTreeMatching.num_images_after_verification", &vocab_tree_pairing->num_images_after_verification); AddDefaultOption("VocabTreeMatching.max_num_features", &vocab_tree_pairing->max_num_features); AddDefaultOption("VocabTreeMatching.vocab_tree_path", &vocab_tree_pairing->vocab_tree_path); AddDefaultOption("VocabTreeMatching.match_list_path", &vocab_tree_pairing->match_list_path); AddDefaultOption("VocabTreeMatching.num_threads", &vocab_tree_pairing->num_threads); } void OptionManager::AddSpatialPairingOptions() { if (added_spatial_pairing_options_) { return; } added_spatial_pairing_options_ = true; AddFeatureMatchingOptions(); AddTwoViewGeometryOptions(); AddDefaultOption("SpatialMatching.ignore_z", &spatial_pairing->ignore_z); AddDefaultOption("SpatialMatching.max_num_neighbors", &spatial_pairing->max_num_neighbors); AddDefaultOption("SpatialMatching.min_num_neighbors", &spatial_pairing->min_num_neighbors); AddDefaultOption("SpatialMatching.max_distance", &spatial_pairing->max_distance); } void OptionManager::AddTransitivePairingOptions() { if (added_transitive_pairing_options_) { return; } added_transitive_pairing_options_ = true; AddFeatureMatchingOptions(); AddTwoViewGeometryOptions(); AddDefaultOption("TransitiveMatching.batch_size", &transitive_pairing->batch_size); AddDefaultOption("TransitiveMatching.num_iterations", &transitive_pairing->num_iterations); } void OptionManager::AddImportedPairingOptions() { if (added_image_pairs_pairing_options_) { return; } added_image_pairs_pairing_options_ = true; AddFeatureMatchingOptions(); AddTwoViewGeometryOptions(); AddDefaultOption("ImagePairsMatching.block_size", &imported_pairing->block_size); } void OptionManager::AddBundleAdjustmentOptions() { if (added_ba_options_) { return; } added_ba_options_ = true; // Solver-agnostic options AddDefaultOption("BundleAdjustment.refine_focal_length", &bundle_adjustment->refine_focal_length); AddDefaultOption("BundleAdjustment.refine_principal_point", &bundle_adjustment->refine_principal_point); AddDefaultOption("BundleAdjustment.refine_extra_params", &bundle_adjustment->refine_extra_params); AddDefaultOption("BundleAdjustment.refine_rig_from_world", &bundle_adjustment->refine_rig_from_world); AddDefaultOption("BundleAdjustment.refine_sensor_from_rig", &bundle_adjustment->refine_sensor_from_rig); AddDefaultOption("BundleAdjustment.refine_points3D", &bundle_adjustment->refine_points3D); AddDefaultOption("BundleAdjustment.constant_rig_from_world_rotation", &bundle_adjustment->constant_rig_from_world_rotation); AddDefaultOption("BundleAdjustment.min_track_length", &bundle_adjustment->min_track_length); AddDefaultEnumOption("BundleAdjustment.backend", &bundle_adjustment->backend, BundleAdjustmentBackendToString, BundleAdjustmentBackendFromString); // Ceres-specific options AddDefaultOption( "BundleAdjustmentCeres.max_num_iterations", &bundle_adjustment->ceres->solver_options.max_num_iterations); AddDefaultOption( "BundleAdjustmentCeres.max_linear_solver_iterations", &bundle_adjustment->ceres->solver_options.max_linear_solver_iterations); AddDefaultOption( "BundleAdjustmentCeres.function_tolerance", &bundle_adjustment->ceres->solver_options.function_tolerance); AddDefaultOption( "BundleAdjustmentCeres.gradient_tolerance", &bundle_adjustment->ceres->solver_options.gradient_tolerance); AddDefaultOption( "BundleAdjustmentCeres.parameter_tolerance", &bundle_adjustment->ceres->solver_options.parameter_tolerance); AddDefaultOption("BundleAdjustmentCeres.use_gpu", &bundle_adjustment->ceres->use_gpu); AddDefaultOption("BundleAdjustmentCeres.gpu_index", &bundle_adjustment->ceres->gpu_index); AddDefaultOption("BundleAdjustmentCeres.min_num_images_gpu_solver", &bundle_adjustment->ceres->min_num_images_gpu_solver); AddDefaultOption( "BundleAdjustmentCeres.min_num_residuals_for_cpu_multi_threading", &bundle_adjustment->ceres->min_num_residuals_for_cpu_multi_threading); AddDefaultOption( "BundleAdjustmentCeres.max_num_images_direct_dense_cpu_solver", &bundle_adjustment->ceres->max_num_images_direct_dense_cpu_solver); AddDefaultOption( "BundleAdjustmentCeres.max_num_images_direct_sparse_cpu_solver", &bundle_adjustment->ceres->max_num_images_direct_sparse_cpu_solver); AddDefaultOption( "BundleAdjustmentCeres.max_num_images_direct_dense_gpu_solver", &bundle_adjustment->ceres->max_num_images_direct_dense_gpu_solver); AddDefaultOption( "BundleAdjustmentCeres.max_num_images_direct_sparse_gpu_solver", &bundle_adjustment->ceres->max_num_images_direct_sparse_gpu_solver); #ifdef CASPAR_ENABLED // Caspar-specific options AddDefaultOption("BundleAdjustmentCaspar.solver_iter_max", &bundle_adjustment->caspar->solver_iter_max); AddDefaultOption("BundleAdjustmentCaspar.pcg_iter_max", &bundle_adjustment->caspar->pcg_iter_max); AddDefaultOption("BundleAdjustmentCaspar.diag_init", &bundle_adjustment->caspar->diag_init); AddDefaultOption("BundleAdjustmentCaspar.diag_min", &bundle_adjustment->caspar->diag_min); AddDefaultOption("BundleAdjustmentCaspar.diag_scaling_up", &bundle_adjustment->caspar->diag_scaling_up); AddDefaultOption("BundleAdjustmentCaspar.diag_scaling_down", &bundle_adjustment->caspar->diag_scaling_down); AddDefaultOption("BundleAdjustmentCaspar.diag_exit_value", &bundle_adjustment->caspar->diag_exit_value); AddDefaultOption("BundleAdjustmentCaspar.score_exit_value", &bundle_adjustment->caspar->score_exit_value); AddDefaultOption("BundleAdjustmentCaspar.pcg_rel_error_exit", &bundle_adjustment->caspar->pcg_rel_error_exit); AddDefaultOption("BundleAdjustmentCaspar.pcg_rel_score_exit", &bundle_adjustment->caspar->pcg_rel_score_exit); AddDefaultOption("BundleAdjustmentCaspar.pcg_rel_decrease_min", &bundle_adjustment->caspar->pcg_rel_decrease_min); AddDefaultOption("BundleAdjustmentCaspar.solver_rel_decrease_min", &bundle_adjustment->caspar->solver_rel_decrease_min); AddDefaultOption("BundleAdjustmentCaspar.gpu_index", &bundle_adjustment->caspar->gpu_index); #endif // CASPAR_ENABLED } void OptionManager::AddMapperOptions() { if (added_mapper_options_) { return; } added_mapper_options_ = true; AddDefaultOption("Mapper.min_num_matches", &mapper->min_num_matches); AddDefaultOption("Mapper.ignore_watermarks", &mapper->ignore_watermarks); AddDefaultOption("Mapper.multiple_models", &mapper->multiple_models); AddDefaultOption("Mapper.max_num_models", &mapper->max_num_models); AddDefaultOption("Mapper.max_model_overlap", &mapper->max_model_overlap); AddDefaultOption("Mapper.min_model_size", &mapper->min_model_size); AddDefaultOption("Mapper.init_image_id1", &mapper->init_image_id1); AddDefaultOption("Mapper.init_image_id2", &mapper->init_image_id2); AddDefaultOption("Mapper.init_num_trials", &mapper->init_num_trials); AddDefaultOption("Mapper.structure_less_registration_fallback", &mapper->structure_less_registration_fallback); AddDefaultOption("Mapper.structure_less_registration_only", &mapper->structure_less_registration_only); AddDefaultOption("Mapper.extract_colors", &mapper->extract_colors); AddDefaultOption("Mapper.num_threads", &mapper->num_threads); AddDefaultOption("Mapper.random_seed", &mapper->random_seed); AddDefaultOption("Mapper.min_focal_length_ratio", &mapper->min_focal_length_ratio); AddDefaultOption("Mapper.max_focal_length_ratio", &mapper->max_focal_length_ratio); AddDefaultOption("Mapper.max_extra_param", &mapper->max_extra_param); AddDefaultOption("Mapper.ba_refine_focal_length", &mapper->ba_refine_focal_length); AddDefaultOption("Mapper.ba_refine_principal_point", &mapper->ba_refine_principal_point); AddDefaultOption("Mapper.ba_refine_extra_params", &mapper->ba_refine_extra_params); AddDefaultOption("Mapper.ba_refine_sensor_from_rig", &mapper->ba_refine_sensor_from_rig); AddDefaultOption("Mapper.ba_local_function_tolerance", &mapper->ba_local_function_tolerance); AddDefaultOption("Mapper.ba_local_max_num_iterations", &mapper->ba_local_max_num_iterations); AddDefaultOption("Mapper.ba_global_frames_ratio", &mapper->ba_global_frames_ratio); AddDefaultOption("Mapper.ba_global_points_ratio", &mapper->ba_global_points_ratio); AddDefaultOption("Mapper.ba_global_frames_freq", &mapper->ba_global_frames_freq); AddDefaultOption("Mapper.ba_global_points_freq", &mapper->ba_global_points_freq); AddDefaultOption("Mapper.ba_global_function_tolerance", &mapper->ba_global_function_tolerance); AddDefaultOption("Mapper.ba_global_max_num_iterations", &mapper->ba_global_max_num_iterations); AddDefaultOption("Mapper.ba_global_max_refinements", &mapper->ba_global_max_refinements); AddDefaultOption("Mapper.ba_global_max_refinement_change", &mapper->ba_global_max_refinement_change); AddDefaultOption("Mapper.ba_local_max_refinements", &mapper->ba_local_max_refinements); AddDefaultOption("Mapper.ba_local_max_refinement_change", &mapper->ba_local_max_refinement_change); AddDefaultOption("Mapper.ba_use_gpu", &mapper->ba_use_gpu); AddDefaultOption("Mapper.ba_gpu_index", &mapper->ba_gpu_index); AddDefaultEnumOption("Mapper.ba_local_backend", &mapper->ba_local_backend, BundleAdjustmentBackendToString, BundleAdjustmentBackendFromString); AddDefaultEnumOption("Mapper.ba_global_backend", &mapper->ba_global_backend, BundleAdjustmentBackendToString, BundleAdjustmentBackendFromString); AddDefaultOption("Mapper.ba_min_num_residuals_for_cpu_multi_threading", &mapper->ba_min_num_residuals_for_cpu_multi_threading); AddDefaultOption("Mapper.snapshot_path", &mapper->snapshot_path); AddDefaultOption("Mapper.snapshot_frames_freq", &mapper->snapshot_frames_freq); AddDefaultOption("Mapper.fix_existing_frames", &mapper->fix_existing_frames); // IncrementalMapper. AddDefaultOption("Mapper.init_min_num_inliers", &mapper->mapper.init_min_num_inliers); AddDefaultOption("Mapper.init_max_error", &mapper->mapper.init_max_error); AddDefaultOption("Mapper.init_max_forward_motion", &mapper->mapper.init_max_forward_motion); AddDefaultOption("Mapper.init_min_tri_angle", &mapper->mapper.init_min_tri_angle); AddDefaultOption("Mapper.init_max_reg_trials", &mapper->mapper.init_max_reg_trials); AddDefaultOption("Mapper.abs_pose_max_error", &mapper->mapper.abs_pose_max_error); AddDefaultOption("Mapper.abs_pose_min_num_inliers", &mapper->mapper.abs_pose_min_num_inliers); AddDefaultOption("Mapper.abs_pose_min_inlier_ratio", &mapper->mapper.abs_pose_min_inlier_ratio); AddDefaultOption("Mapper.filter_max_reproj_error", &mapper->mapper.filter_max_reproj_error); AddDefaultOption("Mapper.filter_min_tri_angle", &mapper->mapper.filter_min_tri_angle); AddDefaultOption("Mapper.max_reg_trials", &mapper->mapper.max_reg_trials); AddDefaultOption("Mapper.ba_local_num_images", &mapper->mapper.ba_local_num_images); AddDefaultOption("Mapper.ba_local_min_tri_angle", &mapper->mapper.ba_local_min_tri_angle); AddDefaultOption("Mapper.ba_global_ignore_redundant_points3D", &mapper->mapper.ba_global_ignore_redundant_points3D); AddDefaultOption( "Mapper.ba_global_ignore_redundant_points3D_min_coverage_gain", &mapper->mapper.ba_global_ignore_redundant_points3D_min_coverage_gain); AddDefaultOption("Mapper.image_list_path", &mapper_image_list_path_); AddDefaultOption("Mapper.constant_rig_list_path", &mapper_constant_rig_list_path_); AddDefaultOption("Mapper.constant_camera_list_path", &mapper_constant_camera_list_path_); AddDefaultOption("Mapper.max_runtime_seconds", &mapper->max_runtime_seconds); // IncrementalTriangulator. AddDefaultOption("Mapper.tri_max_transitivity", &mapper->triangulation.max_transitivity); AddDefaultOption("Mapper.tri_create_max_angle_error", &mapper->triangulation.create_max_angle_error); AddDefaultOption("Mapper.tri_continue_max_angle_error", &mapper->triangulation.continue_max_angle_error); AddDefaultOption("Mapper.tri_merge_max_reproj_error", &mapper->triangulation.merge_max_reproj_error); AddDefaultOption("Mapper.tri_complete_max_reproj_error", &mapper->triangulation.complete_max_reproj_error); AddDefaultOption("Mapper.tri_complete_max_transitivity", &mapper->triangulation.complete_max_transitivity); AddDefaultOption("Mapper.tri_re_max_angle_error", &mapper->triangulation.re_max_angle_error); AddDefaultOption("Mapper.tri_re_min_ratio", &mapper->triangulation.re_min_ratio); AddDefaultOption("Mapper.tri_re_max_trials", &mapper->triangulation.re_max_trials); AddDefaultOption("Mapper.tri_min_angle", &mapper->triangulation.min_angle); AddDefaultOption("Mapper.tri_ignore_two_view_tracks", &mapper->triangulation.ignore_two_view_tracks); } void OptionManager::AddGlobalMapperOptions() { if (added_global_mapper_options_) { return; } added_global_mapper_options_ = true; // Global mapper options. AddDefaultOption("GlobalMapper.image_list_path", &global_mapper_image_list_path_); AddDefaultOption("GlobalMapper.min_num_matches", &global_mapper->min_num_matches); AddDefaultOption("GlobalMapper.ignore_watermarks", &global_mapper->ignore_watermarks); AddDefaultOption("GlobalMapper.num_threads", &global_mapper->num_threads); AddDefaultOption("GlobalMapper.random_seed", &global_mapper->random_seed); AddDefaultOption("GlobalMapper.decompose_relative_pose", &global_mapper->decompose_relative_pose); AddDefaultOption("GlobalMapper.multiple_models", &global_mapper->multiple_models); AddDefaultOption("GlobalMapper.min_model_size", &global_mapper->min_model_size); AddDefaultOption("GlobalMapper.ba_num_iterations", &global_mapper->mapper.ba_num_iterations); AddDefaultOption("GlobalMapper.skip_rotation_averaging", &global_mapper->mapper.skip_rotation_averaging); AddDefaultOption("GlobalMapper.skip_track_establishment", &global_mapper->mapper.skip_track_establishment); AddDefaultOption("GlobalMapper.skip_global_positioning", &global_mapper->mapper.skip_global_positioning); AddDefaultOption("GlobalMapper.skip_bundle_adjustment", &global_mapper->mapper.skip_bundle_adjustment); AddDefaultOption("GlobalMapper.skip_retriangulation", &global_mapper->mapper.skip_retriangulation); // Track establishment options. AddDefaultOption( "GlobalMapper.track_intra_image_consistency_threshold", &global_mapper->mapper.track_intra_image_consistency_threshold); AddDefaultOption("GlobalMapper.track_required_tracks_per_view", &global_mapper->mapper.track_required_tracks_per_view); AddDefaultOption("GlobalMapper.track_min_num_views_per_track", &global_mapper->mapper.track_min_num_views_per_track); AddDefaultOption("GlobalMapper.keep_max_num_tracks", &global_mapper->mapper.keep_max_num_tracks); // Global positioning options. AddDefaultOption("GlobalMapper.gp_use_gpu", &global_mapper->mapper.global_positioning.use_gpu); AddDefaultOption("GlobalMapper.gp_gpu_index", &global_mapper->mapper.global_positioning.gpu_index); AddDefaultOption( "GlobalMapper.gp_optimize_positions", &global_mapper->mapper.global_positioning.optimize_positions); AddDefaultOption("GlobalMapper.gp_optimize_points", &global_mapper->mapper.global_positioning.optimize_points); AddDefaultOption("GlobalMapper.gp_optimize_scales", &global_mapper->mapper.global_positioning.optimize_scales); AddDefaultOption( "GlobalMapper.gp_loss_function_scale", &global_mapper->mapper.global_positioning.loss_function_scale); AddDefaultOption("GlobalMapper.gp_max_num_iterations", &global_mapper->mapper.global_positioning.solver_options .max_num_iterations); // Bundle adjustment options (solver-agnostic). AddDefaultOption( "GlobalMapper.ba_refine_focal_length", &global_mapper->mapper.bundle_adjustment.refine_focal_length); AddDefaultOption( "GlobalMapper.ba_refine_principal_point", &global_mapper->mapper.bundle_adjustment.refine_principal_point); AddDefaultOption( "GlobalMapper.ba_refine_extra_params", &global_mapper->mapper.bundle_adjustment.refine_extra_params); AddDefaultOption("GlobalMapper.refine_sensor_from_rig", &global_mapper->mapper.refine_sensor_from_rig); AddDefaultOption( "GlobalMapper.ba_refine_rig_from_world", &global_mapper->mapper.bundle_adjustment.refine_rig_from_world); AddDefaultOption("GlobalMapper.ba_refine_points3D", &global_mapper->mapper.bundle_adjustment.refine_points3D); AddDefaultOption("GlobalMapper.ba_min_track_length", &global_mapper->mapper.bundle_adjustment.min_track_length); AddDefaultEnumOption("GlobalMapper.ba_backend", &global_mapper->mapper.bundle_adjustment.backend, BundleAdjustmentBackendToString, BundleAdjustmentBackendFromString); AddDefaultOption("GlobalMapper.ba_gpu_index", &global_mapper->mapper.ba_gpu_index); // Bundle adjustment options (Ceres-specific). AddDefaultOption("GlobalMapper.ba_ceres_use_gpu", &global_mapper->mapper.bundle_adjustment.ceres->use_gpu); AddDefaultOption( "GlobalMapper.ba_ceres_loss_function_scale", &global_mapper->mapper.bundle_adjustment.ceres->loss_function_scale); AddDefaultOption("GlobalMapper.ba_ceres_max_num_iterations", &global_mapper->mapper.bundle_adjustment.ceres ->solver_options.max_num_iterations); AddDefaultOption("GlobalMapper.ba_skip_fixed_rotation_stage", &global_mapper->mapper.ba_skip_fixed_rotation_stage); AddDefaultOption("GlobalMapper.ba_skip_joint_optimization_stage", &global_mapper->mapper.ba_skip_joint_optimization_stage); // Retriangulation options. AddDefaultOption( "GlobalMapper.tri_complete_max_reproj_error", &global_mapper->mapper.retriangulation.complete_max_reproj_error); AddDefaultOption( "GlobalMapper.tri_merge_max_reproj_error", &global_mapper->mapper.retriangulation.merge_max_reproj_error); AddDefaultOption("GlobalMapper.tri_min_angle", &global_mapper->mapper.retriangulation.min_angle); // Rotation averaging options. AddDefaultOption("GlobalMapper.ra_use_gravity", &global_mapper->mapper.rotation_averaging.use_gravity); AddDefaultOption("GlobalMapper.ra_use_stratified", &global_mapper->mapper.rotation_averaging.use_stratified); AddDefaultOption( "GlobalMapper.ra_max_rotation_error_deg", &global_mapper->mapper.rotation_averaging.max_rotation_error_deg); AddDefaultEnumOption("GlobalMapper.ra_reweighting", &global_mapper->mapper.rotation_averaging.reweighting, RotationAveragingReweightingToString, RotationAveragingReweightingFromString); // Threshold options. AddDefaultOption("GlobalMapper.max_angular_reproj_error_deg", &global_mapper->mapper.max_angular_reproj_error_deg); AddDefaultOption("GlobalMapper.max_normalized_reproj_error", &global_mapper->mapper.max_normalized_reproj_error); AddDefaultOption("GlobalMapper.min_tri_angle_deg", &global_mapper->mapper.min_tri_angle_deg); } void OptionManager::AddHierarchicalMapperOptions() { if (added_hierarchical_mapper_options_) { return; } added_hierarchical_mapper_options_ = true; // The per-cluster reconstruction is configured through the incremental mapper // options (Mapper.*), so only the hierarchical-specific options are added // here. The incremental_options member is populated from `mapper` by callers. AddDefaultOption("HierarchicalMapper.init_num_trials", &hierarchical_mapper->init_num_trials); AddDefaultOption("HierarchicalMapper.num_threads", &hierarchical_mapper->num_threads); AddDefaultOption("HierarchicalMapper.num_workers", &hierarchical_mapper->num_workers); AddDefaultOption("HierarchicalMapper.is_hierarchical", &hierarchical_mapper->clustering_options.is_hierarchical); AddDefaultOption("HierarchicalMapper.branching", &hierarchical_mapper->clustering_options.branching); AddDefaultOption("HierarchicalMapper.image_overlap", &hierarchical_mapper->clustering_options.image_overlap); AddDefaultOption("HierarchicalMapper.num_image_matches", &hierarchical_mapper->clustering_options.num_image_matches); AddDefaultOption( "HierarchicalMapper.leaf_max_num_images", &hierarchical_mapper->clustering_options.leaf_max_num_images); } void OptionManager::AddGravityRefinerOptions() { if (added_gravity_refiner_options_) { return; } added_gravity_refiner_options_ = true; AddDefaultOption("GravityRefiner.max_outlier_ratio", &gravity_refiner->max_outlier_ratio); AddDefaultOption("GravityRefiner.max_gravity_error", &gravity_refiner->max_gravity_error); AddDefaultOption("GravityRefiner.min_num_neighbors", &gravity_refiner->min_num_neighbors); } void OptionManager::AddReconstructionClustererOptions() { if (added_reconstruction_clusterer_options_) { return; } added_reconstruction_clusterer_options_ = true; AddDefaultOption("ReconstructionClusterer.min_covisibility_count", &reconstruction_clusterer->min_covisibility_count); AddDefaultOption("ReconstructionClusterer.min_edge_weight_threshold", &reconstruction_clusterer->min_edge_weight_threshold); AddDefaultOption("ReconstructionClusterer.min_num_reg_frames", &reconstruction_clusterer->min_num_reg_frames); } #if defined(COLMAP_MVS_ENABLED) void OptionManager::AddPatchMatchStereoOptions() { if (added_patch_match_stereo_options_) { return; } added_patch_match_stereo_options_ = true; AddDefaultOption("PatchMatchStereo.max_image_size", &patch_match_stereo->max_image_size); AddDefaultOption("PatchMatchStereo.gpu_index", &patch_match_stereo->gpu_index); AddDefaultOption("PatchMatchStereo.depth_min", &patch_match_stereo->depth_min); AddDefaultOption("PatchMatchStereo.depth_max", &patch_match_stereo->depth_max); AddDefaultOption("PatchMatchStereo.window_radius", &patch_match_stereo->window_radius); AddDefaultOption("PatchMatchStereo.window_step", &patch_match_stereo->window_step); AddDefaultOption("PatchMatchStereo.sigma_spatial", &patch_match_stereo->sigma_spatial); AddDefaultOption("PatchMatchStereo.sigma_color", &patch_match_stereo->sigma_color); AddDefaultOption("PatchMatchStereo.num_samples", &patch_match_stereo->num_samples); AddDefaultOption("PatchMatchStereo.ncc_sigma", &patch_match_stereo->ncc_sigma); AddDefaultOption("PatchMatchStereo.min_triangulation_angle", &patch_match_stereo->min_triangulation_angle); AddDefaultOption("PatchMatchStereo.incident_angle_sigma", &patch_match_stereo->incident_angle_sigma); AddDefaultOption("PatchMatchStereo.num_iterations", &patch_match_stereo->num_iterations); AddDefaultOption("PatchMatchStereo.geom_consistency", &patch_match_stereo->geom_consistency); AddDefaultOption("PatchMatchStereo.geom_consistency_regularizer", &patch_match_stereo->geom_consistency_regularizer); AddDefaultOption("PatchMatchStereo.geom_consistency_max_cost", &patch_match_stereo->geom_consistency_max_cost); AddDefaultOption("PatchMatchStereo.filter", &patch_match_stereo->filter); AddDefaultOption("PatchMatchStereo.filter_min_ncc", &patch_match_stereo->filter_min_ncc); AddDefaultOption("PatchMatchStereo.filter_min_triangulation_angle", &patch_match_stereo->filter_min_triangulation_angle); AddDefaultOption("PatchMatchStereo.filter_min_num_consistent", &patch_match_stereo->filter_min_num_consistent); AddDefaultOption("PatchMatchStereo.filter_geom_consistency_max_cost", &patch_match_stereo->filter_geom_consistency_max_cost); AddDefaultOption("PatchMatchStereo.cache_size", &patch_match_stereo->cache_size); AddDefaultOption("PatchMatchStereo.allow_missing_files", &patch_match_stereo->allow_missing_files); AddDefaultOption("PatchMatchStereo.write_consistency_graph", &patch_match_stereo->write_consistency_graph); AddDefaultOption("PatchMatchStereo.num_threads", &patch_match_stereo->num_threads); } void OptionManager::AddStereoFusionOptions() { if (added_stereo_fusion_options_) { return; } added_stereo_fusion_options_ = true; AddDefaultOption("StereoFusion.mask_path", &stereo_fusion->mask_path); AddDefaultOption("StereoFusion.num_threads", &stereo_fusion->num_threads); AddDefaultOption("StereoFusion.max_image_size", &stereo_fusion->max_image_size); AddDefaultOption("StereoFusion.min_num_pixels", &stereo_fusion->min_num_pixels); AddDefaultOption("StereoFusion.max_num_pixels", &stereo_fusion->max_num_pixels); AddDefaultOption("StereoFusion.max_traversal_depth", &stereo_fusion->max_traversal_depth); AddDefaultOption("StereoFusion.max_reproj_error", &stereo_fusion->max_reproj_error); AddDefaultOption("StereoFusion.max_depth_error", &stereo_fusion->max_depth_error); AddDefaultOption("StereoFusion.max_normal_error", &stereo_fusion->max_normal_error); AddDefaultOption("StereoFusion.check_num_images", &stereo_fusion->check_num_images); AddDefaultOption("StereoFusion.cache_size", &stereo_fusion->cache_size); AddDefaultOption("StereoFusion.use_cache", &stereo_fusion->use_cache); } void OptionManager::AddPoissonMeshingOptions() { if (added_poisson_meshing_options_) { return; } added_poisson_meshing_options_ = true; AddDefaultOption("PoissonMeshing.point_weight", &poisson_meshing->point_weight); AddDefaultOption("PoissonMeshing.depth", &poisson_meshing->depth); AddDefaultOption("PoissonMeshing.color", &poisson_meshing->color); AddDefaultOption("PoissonMeshing.trim", &poisson_meshing->trim); AddDefaultOption("PoissonMeshing.num_threads", &poisson_meshing->num_threads); } void OptionManager::AddDelaunayMeshingOptions() { if (added_delaunay_meshing_options_) { return; } added_delaunay_meshing_options_ = true; AddDefaultOption("DelaunayMeshing.max_proj_dist", &delaunay_meshing->max_proj_dist); AddDefaultOption("DelaunayMeshing.max_depth_dist", &delaunay_meshing->max_depth_dist); AddDefaultOption("DelaunayMeshing.visibility_sigma", &delaunay_meshing->visibility_sigma); AddDefaultOption("DelaunayMeshing.distance_sigma_factor", &delaunay_meshing->distance_sigma_factor); AddDefaultOption("DelaunayMeshing.quality_regularization", &delaunay_meshing->quality_regularization); AddDefaultOption("DelaunayMeshing.max_side_length_factor", &delaunay_meshing->max_side_length_factor); AddDefaultOption("DelaunayMeshing.max_side_length_percentile", &delaunay_meshing->max_side_length_percentile); AddDefaultOption("DelaunayMeshing.num_threads", &delaunay_meshing->num_threads); } void OptionManager::AddAdvancingFrontMeshingOptions() { if (added_advancing_front_meshing_options_) { return; } added_advancing_front_meshing_options_ = true; AddDefaultOption("AdvancingFrontMeshing.max_edge_length", &advancing_front_meshing->max_edge_length); AddDefaultOption("AdvancingFrontMeshing.visibility_filtering", &advancing_front_meshing->visibility_filtering); AddDefaultOption( "AdvancingFrontMeshing.visibility_filtering_max_intersections", &advancing_front_meshing->visibility_filtering_max_intersections); AddDefaultOption("AdvancingFrontMeshing.visibility_post_filtering", &advancing_front_meshing->visibility_post_filtering); AddDefaultOption("AdvancingFrontMeshing.visibility_ray_trim_offset", &advancing_front_meshing->visibility_ray_trim_offset); AddDefaultOption("AdvancingFrontMeshing.block_size", &advancing_front_meshing->block_size); AddDefaultOption("AdvancingFrontMeshing.block_overlap", &advancing_front_meshing->block_overlap); AddDefaultOption("AdvancingFrontMeshing.num_threads", &advancing_front_meshing->num_threads); } void OptionManager::AddMeshTextureMappingOptions() { if (added_mesh_texture_mapping_options_) { return; } added_mesh_texture_mapping_options_ = true; AddDefaultOption("MeshTextureMapping.min_cos_normal_angle", &mesh_texture_mapping->min_cos_normal_angle); AddDefaultOption("MeshTextureMapping.min_visible_vertices", &mesh_texture_mapping->min_visible_vertices); AddDefaultOption("MeshTextureMapping.view_selection_smoothing_iterations", &mesh_texture_mapping->view_selection_smoothing_iterations); AddDefaultOption("MeshTextureMapping.atlas_patch_padding", &mesh_texture_mapping->atlas_patch_padding); AddDefaultOption("MeshTextureMapping.inpaint_radius", &mesh_texture_mapping->inpaint_radius); AddDefaultOption("MeshTextureMapping.apply_color_correction", &mesh_texture_mapping->apply_color_correction); AddDefaultOption("MeshTextureMapping.color_correction_regularization", &mesh_texture_mapping->color_correction_regularization); AddDefaultOption("MeshTextureMapping.num_threads", &mesh_texture_mapping->num_threads); AddDefaultOption("MeshTextureMapping.texture_scale_factor", &mesh_texture_mapping->texture_scale_factor); } void OptionManager::AddMeshSimplificationOptions() { if (added_mesh_simplification_options_) { return; } added_mesh_simplification_options_ = true; AddDefaultOption("MeshSimplification.target_face_ratio", &mesh_simplification->target_face_ratio); AddDefaultOption("MeshSimplification.max_error", &mesh_simplification->max_error); AddDefaultOption("MeshSimplification.boundary_weight", &mesh_simplification->boundary_weight); AddDefaultOption("MeshSimplification.interpolate_colors", &mesh_simplification->interpolate_colors); AddDefaultOption("MeshSimplification.num_threads", &mesh_simplification->num_threads); } #endif // COLMAP_MVS_ENABLED void OptionManager::AddRenderOptions() { if (added_render_options_) { return; } added_render_options_ = true; AddDefaultOption("Render.min_track_len", &render->min_track_len); AddDefaultOption("Render.max_error", &render->max_error); AddDefaultOption("Render.refresh_rate", &render->refresh_rate); AddDefaultOption("Render.adapt_refresh_rate", &render->adapt_refresh_rate); AddDefaultOption("Render.image_connections", &render->image_connections); AddDefaultOption("Render.projection_type", &render->projection_type); } void OptionManager::Reset(bool reset_logging) { BaseOptionManager::Reset(reset_logging); added_feature_extraction_options_ = false; added_feature_matching_options_ = false; added_two_view_geometry_options_ = false; added_exhaustive_pairing_options_ = false; added_sequential_pairing_options_ = false; added_vocab_tree_pairing_options_ = false; added_spatial_pairing_options_ = false; added_transitive_pairing_options_ = false; added_image_pairs_pairing_options_ = false; added_ba_options_ = false; added_mapper_options_ = false; added_global_mapper_options_ = false; added_gravity_refiner_options_ = false; added_reconstruction_clusterer_options_ = false; #if defined(COLMAP_MVS_ENABLED) added_patch_match_stereo_options_ = false; added_stereo_fusion_options_ = false; added_poisson_meshing_options_ = false; added_delaunay_meshing_options_ = false; added_advancing_front_meshing_options_ = false; added_mesh_texture_mapping_options_ = false; added_mesh_simplification_options_ = false; #endif added_render_options_ = false; } void OptionManager::ResetOptions(const bool reset_paths) { *image_reader = ImageReaderOptions(); *feature_extraction = FeatureExtractionOptions(); *feature_matching = FeatureMatchingOptions(); *exhaustive_pairing = ExhaustivePairingOptions(); *sequential_pairing = SequentialPairingOptions(); *vocab_tree_pairing = VocabTreePairingOptions(); *spatial_pairing = SpatialPairingOptions(); *transitive_pairing = TransitivePairingOptions(); *imported_pairing = ImportedPairingOptions(); *bundle_adjustment = BundleAdjustmentOptions(); *mapper = IncrementalPipelineOptions(); *global_mapper = GlobalPipelineOptions(); *hierarchical_mapper = HierarchicalPipelineOptions(); *gravity_refiner = GravityRefinerOptions(); *reconstruction_clusterer = ReconstructionClusteringOptions(); #if defined(COLMAP_MVS_ENABLED) *patch_match_stereo = mvs::PatchMatchOptions(); *stereo_fusion = mvs::StereoFusionOptions(); *poisson_meshing = mvs::PoissonMeshingOptions(); *delaunay_meshing = mvs::DelaunayMeshingOptions(); *mesh_texture_mapping = mvs::MeshTextureMappingOptions(); *mesh_simplification = mvs::MeshSimplificationOptions(); #endif *render = RenderOptions(); BaseOptionManager::ResetOptions(reset_paths); } bool OptionManager::Check() { if (!BaseOptionManager::Check()) { return false; } bool success = true; if (image_reader) success = success && image_reader->Check(); if (feature_extraction) success = success && feature_extraction->Check(); if (feature_matching) success = success && feature_matching->Check(); if (two_view_geometry) success = success && two_view_geometry->Check(); if (exhaustive_pairing) success = success && exhaustive_pairing->Check(); if (sequential_pairing) success = success && sequential_pairing->Check(); if (vocab_tree_pairing) success = success && vocab_tree_pairing->Check(); if (spatial_pairing) success = success && spatial_pairing->Check(); if (transitive_pairing) success = success && transitive_pairing->Check(); if (imported_pairing) success = success && imported_pairing->Check(); if (bundle_adjustment) success = success && bundle_adjustment->Check(); if (mapper) success = success && mapper->Check(); #if defined(COLMAP_MVS_ENABLED) if (patch_match_stereo) success = success && patch_match_stereo->Check(); if (stereo_fusion) success = success && stereo_fusion->Check(); if (poisson_meshing) success = success && poisson_meshing->Check(); if (delaunay_meshing) success = success && delaunay_meshing->Check(); if (mesh_texture_mapping) success = success && mesh_texture_mapping->Check(); #endif #if defined(COLMAP_GUI_ENABLED) if (render) success = success && render->Check(); #endif return success; } bool OptionManager::Read(const std::filesystem::path& path, bool allow_unregistered) { if (!BaseOptionManager::Read(path, allow_unregistered)) { return false; } return Check(); } void OptionManager::PostParse() { if (!mapper_image_list_path_.empty()) { mapper->image_names = ReadTextFileLines(mapper_image_list_path_); } if (!global_mapper_image_list_path_.empty()) { global_mapper->image_names = ReadTextFileLines(global_mapper_image_list_path_); } if (!mapper_constant_rig_list_path_.empty()) { for (const std::string& line : ReadTextFileLines(mapper_constant_rig_list_path_)) { mapper->constant_rigs.insert(std::stoi(line)); } } if (!mapper_constant_camera_list_path_.empty()) { for (const std::string& line : ReadTextFileLines(mapper_constant_camera_list_path_)) { mapper->constant_cameras.insert(std::stoi(line)); } } } void OptionManager::PrintHelp() const { LOG(INFO) << StringPrintf( "%s (%s)", GetVersionInfo().c_str(), GetBuildInfo().c_str()); LOG(INFO) << "Options can either be specified via command-line or by " "defining them in a .ini project file passed to " "`--project_path`.\n" << *desc_; } } // namespace colmap colmap-4.2.0/src/colmap/controllers/option_manager.h000066400000000000000000000164201524536416500225710ustar00rootroot00000000000000// Copyright (c), ETH Zurich and UNC Chapel Hill. // All rights reserved. // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are met: // // * Redistributions of source code must retain the above copyright // notice, this list of conditions and the following disclaimer. // // * Redistributions in binary form must reproduce the above copyright // notice, this list of conditions and the following disclaimer in the // documentation and/or other materials provided with the distribution. // // * Neither the name of ETH Zurich and UNC Chapel Hill nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE // ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE // LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR // CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF // SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS // INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN // CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) // ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE // POSSIBILITY OF SUCH DAMAGE. #pragma once #include "colmap/controllers/base_option_manager.h" #include namespace colmap { struct ImageReaderOptions; struct FeatureExtractionOptions; struct FeatureMatchingOptions; struct SiftMatchingOptions; struct TwoViewGeometryOptions; struct ExhaustivePairingOptions; struct SequentialPairingOptions; struct VocabTreePairingOptions; struct SpatialPairingOptions; struct TransitivePairingOptions; struct ImportedPairingOptions; struct ExistingMatchedPairingOptions; struct BundleAdjustmentOptions; struct IncrementalPipelineOptions; struct GlobalPipelineOptions; struct HierarchicalPipelineOptions; struct RenderOptions; struct ReconstructionClusteringOptions; #if defined(COLMAP_MVS_ENABLED) namespace mvs { struct PatchMatchOptions; struct StereoFusionOptions; struct PoissonMeshingOptions; struct DelaunayMeshingOptions; struct AdvancingFrontMeshingOptions; struct MeshTextureMappingOptions; struct MeshSimplificationOptions; } // namespace mvs #endif struct GravityRefinerOptions; } // namespace colmap namespace colmap { class OptionManager : public BaseOptionManager { public: explicit OptionManager(bool add_project_options = true); // Create "optimal" set of options for different reconstruction scenarios. void ModifyForIndividualData(); void ModifyForVideoData(); void ModifyForInternetData(); // Create "optimal" set of options for different quality settings. // Note that the existing options are modified, so if your parameters are // already low quality, they will be further degraded. void ModifyForLowQuality(); void ModifyForMediumQuality(); void ModifyForHighQuality(); void ModifyForExtremeQuality(); void AddAllOptions() override; void AddFeatureExtractionOptions(); void AddFeatureMatchingOptions(); void AddTwoViewGeometryOptions(); void AddExhaustivePairingOptions(); void AddSequentialPairingOptions(); void AddVocabTreePairingOptions(); void AddSpatialPairingOptions(); void AddTransitivePairingOptions(); void AddImportedPairingOptions(); void AddBundleAdjustmentOptions(); void AddMapperOptions(); void AddGlobalMapperOptions(); void AddHierarchicalMapperOptions(); void AddGravityRefinerOptions(); void AddReconstructionClustererOptions(); #if defined(COLMAP_MVS_ENABLED) void AddPatchMatchStereoOptions(); void AddStereoFusionOptions(); void AddPoissonMeshingOptions(); void AddDelaunayMeshingOptions(); void AddAdvancingFrontMeshingOptions(); void AddMeshTextureMappingOptions(); void AddMeshSimplificationOptions(); #endif void AddRenderOptions(); void Reset(bool reset_logging = true) override; void ResetOptions(bool reset_paths) override; bool Check() override; bool Read(const std::filesystem::path& path, bool allow_unregistered = true) override; std::shared_ptr image_reader; std::shared_ptr feature_extraction; std::shared_ptr feature_matching; std::shared_ptr two_view_geometry; std::shared_ptr exhaustive_pairing; std::shared_ptr sequential_pairing; std::shared_ptr vocab_tree_pairing; std::shared_ptr spatial_pairing; std::shared_ptr transitive_pairing; std::shared_ptr imported_pairing; std::shared_ptr bundle_adjustment; std::shared_ptr mapper; std::shared_ptr global_mapper; std::shared_ptr hierarchical_mapper; std::shared_ptr reconstruction_clusterer; std::shared_ptr gravity_refiner; #if defined(COLMAP_MVS_ENABLED) std::shared_ptr patch_match_stereo; std::shared_ptr stereo_fusion; std::shared_ptr poisson_meshing; std::shared_ptr delaunay_meshing; std::shared_ptr advancing_front_meshing; std::shared_ptr mesh_texture_mapping; std::shared_ptr mesh_simplification; #endif std::shared_ptr render; protected: void PostParse() override; void PrintHelp() const override; std::filesystem::path mapper_image_list_path_; std::filesystem::path mapper_constant_rig_list_path_; std::filesystem::path mapper_constant_camera_list_path_; std::filesystem::path global_mapper_image_list_path_; bool added_feature_extraction_options_ = false; bool added_feature_matching_options_ = false; bool added_two_view_geometry_options_ = false; bool added_exhaustive_pairing_options_ = false; bool added_sequential_pairing_options_ = false; bool added_vocab_tree_pairing_options_ = false; bool added_spatial_pairing_options_ = false; bool added_transitive_pairing_options_ = false; bool added_image_pairs_pairing_options_ = false; bool added_ba_options_ = false; bool added_mapper_options_ = false; bool added_global_mapper_options_ = false; bool added_hierarchical_mapper_options_ = false; bool added_gravity_refiner_options_ = false; bool added_reconstruction_clusterer_options_ = false; #if defined(COLMAP_MVS_ENABLED) bool added_patch_match_stereo_options_ = false; bool added_stereo_fusion_options_ = false; bool added_poisson_meshing_options_ = false; bool added_delaunay_meshing_options_ = false; bool added_advancing_front_meshing_options_ = false; bool added_mesh_texture_mapping_options_ = false; bool added_mesh_simplification_options_ = false; #endif bool added_render_options_ = false; }; } // namespace colmap colmap-4.2.0/src/colmap/controllers/option_manager_test.cc000066400000000000000000000276421524536416500237760ustar00rootroot00000000000000// Copyright (c), ETH Zurich and UNC Chapel Hill. // All rights reserved. // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are met: // // * Redistributions of source code must retain the above copyright // notice, this list of conditions and the following disclaimer. // // * Redistributions in binary form must reproduce the above copyright // notice, this list of conditions and the following disclaimer in the // documentation and/or other materials provided with the distribution. // // * Neither the name of ETH Zurich and UNC Chapel Hill nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE // ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE // LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR // CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF // SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS // INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN // CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) // ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE // POSSIBILITY OF SUCH DAMAGE. #include "colmap/controllers/option_manager.h" #include "colmap/controllers/image_reader.h" #include "colmap/controllers/incremental_pipeline.h" #include "colmap/feature/sift.h" #include "colmap/mvs/patch_match_options.h" #include "colmap/util/file.h" #include "colmap/util/testing.h" #include namespace colmap { namespace { TEST(OptionManager, Reset) { OptionManager options; *options.database_path = "/test/path"; *options.image_path = "/test/images"; options.AddDatabaseOptions(); options.AddImageOptions(); EXPECT_EQ(*options.database_path, "/test/path"); EXPECT_EQ(*options.image_path, "/test/images"); options.Reset(); EXPECT_EQ(*options.database_path, ""); EXPECT_EQ(*options.image_path, ""); } TEST(OptionManager, ResetOptions) { OptionManager options; *options.database_path = "/test/path"; *options.image_path = "/test/images"; const int original_num_threads = options.feature_extraction->num_threads; options.feature_extraction->num_threads = original_num_threads + 42; options.ResetOptions(/*reset_paths=*/true); EXPECT_EQ(*options.database_path, ""); EXPECT_EQ(*options.image_path, ""); EXPECT_EQ(options.feature_extraction->num_threads, original_num_threads); *options.database_path = "/test/path"; *options.image_path = "/test/images"; options.feature_extraction->num_threads = original_num_threads + 42; options.ResetOptions(/*reset_paths=*/false); EXPECT_EQ(*options.database_path, "/test/path"); EXPECT_EQ(*options.image_path, "/test/images"); EXPECT_EQ(options.feature_extraction->num_threads, original_num_threads); } TEST(OptionManager, AddOptionsIdempotent) { OptionManager options; // Adding options multiple times should not cause issues options.AddLogOptions(); options.AddLogOptions(); options.AddRandomOptions(); options.AddRandomOptions(); options.AddFeatureExtractionOptions(); options.AddFeatureExtractionOptions(); options.AddFeatureMatchingOptions(); options.AddFeatureMatchingOptions(); options.AddMapperOptions(); options.AddMapperOptions(); // If idempotency is not maintained, the above would cause errors SUCCEED(); } TEST(OptionManager, AddAllOptions) { OptionManager options; options.AddAllOptions(); // Verify that at least some key options are initialized EXPECT_NE(options.image_reader, nullptr); EXPECT_NE(options.feature_extraction, nullptr); EXPECT_NE(options.feature_matching, nullptr); EXPECT_NE(options.bundle_adjustment, nullptr); EXPECT_NE(options.mapper, nullptr); #if defined(COLMAP_MVS_ENABLED) EXPECT_NE(options.patch_match_stereo, nullptr); #endif } TEST(OptionManager, WriteAndRead) { const auto test_dir = CreateTestDir(); const auto config_path = test_dir / "config.ini"; // Create necessary directories CreateDirIfNotExists(test_dir / "images"); // Create and configure an OptionManager OptionManager options_write; options_write.AddDatabaseOptions(); options_write.AddImageOptions(); options_write.AddFeatureExtractionOptions(); options_write.AddMapperOptions(); options_write.AddGlobalMapperOptions(); *options_write.database_path = test_dir / "database.db"; *options_write.image_path = test_dir / "images"; options_write.feature_extraction->max_image_size = 2048; options_write.feature_extraction->sift->max_num_features = 4096; options_write.mapper->min_num_matches = 20; // Write to file options_write.Write(config_path); EXPECT_TRUE(ExistsFile(config_path)); // Read from file OptionManager options_read; options_read.AddDatabaseOptions(); options_read.AddImageOptions(); options_read.AddFeatureExtractionOptions(); options_read.AddMapperOptions(); options_read.AddGlobalMapperOptions(); EXPECT_TRUE(options_read.Read(config_path)); // Verify that values were read correctly EXPECT_EQ(*options_read.database_path, *options_write.database_path); EXPECT_EQ(*options_read.image_path, *options_write.image_path); EXPECT_EQ(options_read.feature_extraction->max_image_size, options_write.feature_extraction->max_image_size); EXPECT_EQ(options_read.feature_extraction->sift->max_num_features, options_write.feature_extraction->sift->max_num_features); EXPECT_EQ(options_read.mapper->min_num_matches, options_write.mapper->min_num_matches); } TEST(OptionManager, ReRead) { const auto test_dir = CreateTestDir(); const auto config_path = test_dir / "config.ini"; // Create necessary directories CreateDirIfNotExists(test_dir / "images"); // Create and write initial config OptionManager options_write; options_write.AddAllOptions(); *options_write.database_path = test_dir / "database.db"; *options_write.image_path = test_dir / "images"; options_write.feature_extraction->max_image_size = 2048; options_write.Write(config_path); // Read with ReRead OptionManager options_read; EXPECT_TRUE(options_read.ReRead(config_path)); // Verify values EXPECT_EQ(*options_read.database_path, *options_write.database_path); EXPECT_EQ(*options_read.image_path, *options_write.image_path); EXPECT_EQ(options_read.feature_extraction->max_image_size, 2048); } TEST(OptionManager, ReadNonExistentFile) { OptionManager options; options.AddAllOptions(); EXPECT_FALSE(options.Read("/path/that/does/not/exist.ini")); } TEST(OptionManager, Check) { const auto test_dir = CreateTestDir(); OptionManager options; options.AddDatabaseOptions(); options.AddImageOptions(); // Should fail with non-existent paths *options.database_path = test_dir / "database.db"; *options.image_path = "/path/that/does/not/exist"; EXPECT_FALSE(options.Check()); // Should succeed with valid paths CreateDirIfNotExists(test_dir / "images"); *options.image_path = test_dir / "images"; EXPECT_TRUE(options.Check()); } TEST(OptionManager, CheckDatabaseParentDir) { const auto test_dir = CreateTestDir(); OptionManager options; options.AddDatabaseOptions(); // Should succeed when database parent dir exists *options.database_path = test_dir / "database.db"; EXPECT_TRUE(options.Check()); // Should fail when database path is a directory CreateDirIfNotExists(test_dir / "bad_database"); *options.database_path = test_dir / "bad_database"; EXPECT_FALSE(options.Check()); } TEST(OptionManager, ParseWithOptions) { const auto test_dir = CreateTestDir(); CreateDirIfNotExists(test_dir / "images"); OptionManager options; options.AddDatabaseOptions(); options.AddImageOptions(); options.AddFeatureExtractionOptions(); const auto database_path = test_dir / "database.db"; const auto image_path = test_dir / "images"; // Create argv with additional options const std::vector args = { "colmap", "--database_path", database_path.string(), "--image_path", image_path.string(), "--FeatureExtraction.max_image_size", "1024", "--SiftExtraction.max_num_features", "2048", }; std::vector argv; argv.reserve(args.size()); for (auto& arg : args) { argv.push_back(const_cast(arg.c_str())); } EXPECT_TRUE(options.Parse(argv.size(), argv.data())); // Verify parsed values EXPECT_EQ(*options.database_path, database_path); EXPECT_EQ(*options.image_path, image_path); EXPECT_EQ(options.feature_extraction->max_image_size, 1024); EXPECT_EQ(options.feature_extraction->sift->max_num_features, 2048); } TEST(OptionManager, ParseWithProjectPath) { const auto test_dir = CreateTestDir(); const auto config_path = test_dir / "config.ini"; CreateDirIfNotExists(test_dir / "images"); // Create and write a config file OptionManager options_write; options_write.AddDatabaseOptions(); options_write.AddImageOptions(); options_write.AddFeatureExtractionOptions(); *options_write.database_path = test_dir / "database.db"; *options_write.image_path = test_dir / "images"; options_write.feature_extraction->max_image_size = 3000; options_write.Write(config_path); // Parse using project_path OptionManager options; options.AddDatabaseOptions(); options.AddImageOptions(); options.AddFeatureExtractionOptions(); const std::vector args = { "colmap", "--project_path", config_path.string(), }; std::vector argv; argv.reserve(args.size()); for (auto& arg : args) { argv.push_back(const_cast(arg.c_str())); } EXPECT_TRUE(options.Parse(argv.size(), argv.data())); // Verify values were loaded from config file EXPECT_EQ(*options.database_path, *options_write.database_path); EXPECT_EQ(*options.image_path, *options_write.image_path); EXPECT_EQ(options.feature_extraction->max_image_size, 3000); } TEST(OptionManager, ParseEmptyArguments) { OptionManager options; const std::vector args = {"colmap"}; std::vector argv; argv.reserve(args.size()); for (auto& arg : args) { argv.push_back(const_cast(arg.c_str())); } // Should succeed with no required options EXPECT_TRUE(options.Parse(argv.size(), argv.data())); } TEST(OptionManager, ParseUnknownArgumentsFails) { const auto test_dir = CreateTestDir(); OptionManager options; options.AddDatabaseOptions(); const auto database_path = test_dir / "database.db"; // Create argv with an unknown option const std::vector args = { "colmap", "--database_path", database_path.string(), "--unknown_option", "value", }; std::vector argv; argv.reserve(args.size()); for (auto& arg : args) { argv.push_back(const_cast(arg.c_str())); } // Should return false when encountering unknown option EXPECT_FALSE(options.Parse(argv.size(), argv.data())); } TEST(OptionManager, WriteAfterResetOptions) { const auto test_dir = CreateTestDir(); const auto config_path = test_dir / "config.ini"; OptionManager options; options.AddAllOptions(); *options.database_path = test_dir / "database.db"; CreateDirIfNotExists(test_dir / "images"); *options.image_path = test_dir / "images"; // ResetOptions reassigns option structs, which reallocates sub-objects // (e.g., feature_matching->sift, bundle_adjustment->ceres). This must not // invalidate the raw pointers registered by AddAllOptions, otherwise // Write() will dereference dangling pointers. options.ResetOptions(/*reset_paths=*/false); EXPECT_NO_THROW(options.Write(config_path)); EXPECT_TRUE(ExistsFile(config_path)); } } // namespace } // namespace colmap colmap-4.2.0/src/colmap/controllers/pairing.cc000066400000000000000000001047341524536416500213640ustar00rootroot00000000000000// Copyright (c), ETH Zurich and UNC Chapel Hill. // All rights reserved. // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are met: // // * Redistributions of source code must retain the above copyright // notice, this list of conditions and the following disclaimer. // // * Redistributions in binary form must reproduce the above copyright // notice, this list of conditions and the following disclaimer in the // documentation and/or other materials provided with the distribution. // // * Neither the name of ETH Zurich and UNC Chapel Hill nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE // ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE // LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR // CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF // SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS // INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN // CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) // ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE // POSSIBILITY OF SUCH DAMAGE. #include "colmap/controllers/pairing.h" #include "colmap/feature/utils.h" #include "colmap/geometry/gps.h" #include "colmap/retrieval/resources.h" #include "colmap/util/file.h" #include "colmap/util/hash_containers.h" #include "colmap/util/logging.h" #include "colmap/util/timer.h" #include #include #include #include namespace colmap { namespace { std::vector> ReadImagePairsText( const std::filesystem::path& path, const NodeHashMap& image_name_to_image_id) { std::ifstream file(path); THROW_CHECK_FILE_OPEN(file, path); std::string line; std::vector> image_pairs; FlatHashSet image_pairs_set; while (std::getline(file, line)) { StringTrim(&line); if (line.empty() || line[0] == '#') { continue; } std::stringstream line_stream(line); std::string image_name1; std::string image_name2; std::getline(line_stream, image_name1, ' '); StringTrim(&image_name1); std::getline(line_stream, image_name2, ' '); StringTrim(&image_name2); if (image_name_to_image_id.count(image_name1) == 0) { LOG(ERROR) << "Image " << image_name1 << " does not exist."; continue; } if (image_name_to_image_id.count(image_name2) == 0) { LOG(ERROR) << "Image " << image_name2 << " does not exist."; continue; } const image_t image_id1 = image_name_to_image_id.at(image_name1); const image_t image_id2 = image_name_to_image_id.at(image_name2); const image_pair_t image_pair = ImagePairToPairId(image_id1, image_id2); const bool image_pair_exists = image_pairs_set.insert(image_pair).second; if (image_pair_exists) { image_pairs.emplace_back(image_id1, image_id2); } } return image_pairs; } } // namespace bool ExistingMatchedPairingOptions::Check() const { CHECK_OPTION_GT(batch_size, 1); return true; } bool ExhaustivePairingOptions::Check() const { CHECK_OPTION_GT(block_size, 1); return true; } bool VocabTreePairingOptions::Check() const { CHECK_OPTION_GT(num_images, 0); CHECK_OPTION_GT(num_nearest_neighbors, 0); CHECK_OPTION_GT(num_checks, 0); return true; } bool SequentialPairingOptions::Check() const { CHECK_OPTION_GT(overlap, 0); CHECK_OPTION_GT(loop_detection_period, 0); CHECK_OPTION_GT(loop_detection_num_images, 0); CHECK_OPTION_GE(loop_detection_min_index_distance, 0); CHECK_OPTION_GT(loop_detection_num_nearest_neighbors, 0); CHECK_OPTION_GT(loop_detection_num_checks, 0); return true; } VocabTreePairingOptions SequentialPairingOptions::VocabTreeOptions() const { VocabTreePairingOptions options; options.num_images = loop_detection_num_images; options.num_nearest_neighbors = loop_detection_num_nearest_neighbors; options.num_checks = loop_detection_num_checks; options.num_images_after_verification = loop_detection_num_images_after_verification; options.max_num_features = loop_detection_max_num_features; options.vocab_tree_path = vocab_tree_path; options.num_threads = num_threads; return options; } bool SpatialPairingOptions::Check() const { CHECK_OPTION_GE(max_distance, 0.0); CHECK_OPTION_GT(max_num_neighbors, 0); CHECK_OPTION_LE(min_num_neighbors, max_num_neighbors); CHECK_OPTION_GE(min_num_neighbors, 0); CHECK_OPTION(max_distance > 0.0 || min_num_neighbors > 0); return true; } bool TransitivePairingOptions::Check() const { CHECK_OPTION_GT(batch_size, 0); CHECK_OPTION_GT(num_iterations, 0); return true; } bool ImportedPairingOptions::Check() const { CHECK_OPTION_GT(block_size, 0); return true; } bool FeaturePairsMatchingOptions::Check() const { return true; } std::vector> PairGenerator::AllPairs() { std::vector> image_pairs; while (!this->HasFinished()) { std::vector> image_pairs_block = this->Next(); image_pairs.insert(image_pairs.end(), std::make_move_iterator(image_pairs_block.begin()), std::make_move_iterator(image_pairs_block.end())); } return image_pairs; } ExhaustivePairGenerator::ExhaustivePairGenerator( const ExhaustivePairingOptions& options, const std::shared_ptr& cache) : options_(options), image_ids_(THROW_CHECK_NOTNULL(cache)->GetImageIds()), block_size_(static_cast(options_.block_size)), num_blocks_(static_cast( std::ceil(static_cast(image_ids_.size()) / block_size_))) { THROW_CHECK(options.Check()); LOG(INFO) << "Generating exhaustive image pairs..."; const size_t num_pairs_per_block = block_size_ * (block_size_ - 1) / 2; image_pairs_.reserve(num_pairs_per_block); } ExhaustivePairGenerator::ExhaustivePairGenerator( const ExhaustivePairingOptions& options, const std::shared_ptr& database) : ExhaustivePairGenerator( options, std::make_shared( options.CacheSize(), THROW_CHECK_NOTNULL(database))) {} void ExhaustivePairGenerator::Reset() { start_idx1_ = 0; start_idx2_ = 0; } bool ExhaustivePairGenerator::HasFinished() const { return start_idx1_ >= image_ids_.size(); } std::vector> ExhaustivePairGenerator::Next() { image_pairs_.clear(); if (HasFinished()) { return image_pairs_; } const size_t end_idx1 = std::min(image_ids_.size(), start_idx1_ + block_size_) - 1; const size_t end_idx2 = std::min(image_ids_.size(), start_idx2_ + block_size_) - 1; LOG(INFO) << StringPrintf("Processing block [%d/%d, %d/%d]", start_idx1_ / block_size_ + 1, num_blocks_, start_idx2_ / block_size_ + 1, num_blocks_); for (size_t idx1 = start_idx1_; idx1 <= end_idx1; ++idx1) { for (size_t idx2 = start_idx2_; idx2 <= end_idx2; ++idx2) { const size_t block_id1 = idx1 % block_size_; const size_t block_id2 = idx2 % block_size_; if ((idx1 > idx2 && block_id1 <= block_id2) || (idx1 < idx2 && block_id1 < block_id2)) { // Avoid duplicate pairs image_pairs_.emplace_back(image_ids_[idx1], image_ids_[idx2]); } } } start_idx2_ += block_size_; if (start_idx2_ >= image_ids_.size()) { start_idx2_ = 0; start_idx1_ += block_size_; } return image_pairs_; } VocabTreePairGenerator::VocabTreePairGenerator( const VocabTreePairingOptions& options, const std::shared_ptr& cache, const std::vector& query_image_ids, std::function image_pair_filter) : options_(options), cache_(THROW_CHECK_NOTNULL(cache)), thread_pool_(options_.num_threads), queue_(options_.num_threads), image_pair_filter_(std::move(image_pair_filter)) { THROW_CHECK(options.Check()); LOG(INFO) << "Generating image pairs with vocabulary tree..."; const std::vector all_image_ids = cache_->GetImageIds(); if (query_image_ids.size() > 0) { query_image_ids_ = query_image_ids; } else if (options_.match_list_path == "") { query_image_ids_ = cache_->GetImageIds(); } else { // Map image names to image identifiers. NodeHashMap image_name_to_image_id; image_name_to_image_id.reserve(all_image_ids.size()); for (const auto image_id : all_image_ids) { const auto& image = cache_->GetImage(image_id); image_name_to_image_id.emplace(image.Name(), image_id); } // Read the match list path. std::ifstream file(options_.match_list_path); THROW_CHECK_FILE_OPEN(file, options_.match_list_path); std::string line; while (std::getline(file, line)) { StringTrim(&line); if (line.empty() || line[0] == '#') { continue; } if (image_name_to_image_id.count(line) == 0) { LOG(ERROR) << "Image " << line << " does not exist."; } else { query_image_ids_.push_back(image_name_to_image_id.at(line)); } } } IndexImages(all_image_ids); // Since we parallelize over the query images, there is no need to parallelize // the nearest neighbor search over the query descriptors. query_options_.num_threads = 1; query_options_.max_num_images = options_.num_images; query_options_.num_neighbors = options_.num_nearest_neighbors; query_options_.num_checks = options_.num_checks; query_options_.num_images_after_verification = options_.num_images_after_verification; } VocabTreePairGenerator::VocabTreePairGenerator( const VocabTreePairingOptions& options, const std::shared_ptr& database, const std::vector& query_image_ids, std::function image_pair_filter) : VocabTreePairGenerator( options, std::make_shared(options.CacheSize(), THROW_CHECK_NOTNULL(database)), query_image_ids, std::move(image_pair_filter)) {} void VocabTreePairGenerator::Reset() { query_idx_ = 0; result_idx_ = 0; } bool VocabTreePairGenerator::HasFinished() const { return result_idx_ >= query_image_ids_.size(); } std::vector> VocabTreePairGenerator::Next() { image_pairs_.clear(); if (HasFinished()) { return {}; } if (query_idx_ == 0) { // Initially, make all retrieval threads busy and continue with the // matching. const size_t init_num_tasks = std::min(query_image_ids_.size(), 2 * thread_pool_.NumThreads()); for (; query_idx_ < init_num_tasks; ++query_idx_) { thread_pool_.AddTask( &VocabTreePairGenerator::Query, this, query_image_ids_[query_idx_]); } } LOG(INFO) << StringPrintf( "Processing image [%d/%d]", result_idx_ + 1, query_image_ids_.size()); // Push the next image to the retrieval queue. if (query_idx_ < query_image_ids_.size()) { thread_pool_.AddTask( &VocabTreePairGenerator::Query, this, query_image_ids_[query_idx_++]); } // Pop the next results from the retrieval queue. auto retrieval = queue_.Pop(); THROW_CHECK(retrieval.IsValid()); const auto& image_id = retrieval.Data().image_id; const auto& image_scores = retrieval.Data().image_scores; // Compose the image pairs from the scores. image_pairs_.reserve(image_scores.size()); for (const auto& image_score : image_scores) { image_pairs_.emplace_back(image_id, image_score.image_id); } ++result_idx_; return image_pairs_; } void VocabTreePairGenerator::IndexImages( const std::vector& image_ids) { retrieval::VisualIndex::IndexOptions index_options; // We only assign each feature to a single visual word in the indexing phase. // During the query phase, we check for overlap in possibly multiple nearest // neighbor visual words. We could do it symmetrically but experiments showed // only marginal improvements that do not justify the memory/compute increase. index_options.num_neighbors = 1; index_options.num_checks = options_.num_checks; index_options.num_threads = options_.num_threads; for (size_t i = 0; i < image_ids.size(); ++i) { Timer timer; timer.Start(); LOG(INFO) << StringPrintf( "Indexing image [%d/%d]", i + 1, image_ids.size()); auto keypoints = *cache_->GetKeypoints(image_ids[i]); auto descriptors = *cache_->GetDescriptors(image_ids[i]); if (visual_index_ == nullptr) { visual_index_ = retrieval::VisualIndex::Read( options_.vocab_tree_path.empty() ? GetVocabTreeUriForFeatureType(descriptors.type) : options_.vocab_tree_path); } if (options_.max_num_features > 0 && descriptors.data.rows() > options_.max_num_features) { ExtractTopScaleFeatures( &keypoints, &descriptors, options_.max_num_features); } visual_index_->Add( index_options, image_ids[i], keypoints, descriptors.ToFloat()); LOG(INFO) << StringPrintf(" in %.3fs", timer.ElapsedSeconds()); } // Compute the TF-IDF weights, etc. visual_index_->Prepare(); } void VocabTreePairGenerator::Query(const image_t image_id) { Retrieval retrieval; retrieval.image_id = image_id; // Each query must push exactly one result, because the consuming Next() pops // exactly one result per query. If a query fails (e.g., due to corrupt // features or an out-of-memory error during spatial verification), we still // push an empty result and skip retrieval for this image. Otherwise, the // consumer would block indefinitely waiting for a result that never arrives. try { auto keypoints = *cache_->GetKeypoints(image_id); auto descriptors = *cache_->GetDescriptors(image_id); if (options_.max_num_features > 0 && descriptors.data.rows() > options_.max_num_features) { ExtractTopScaleFeatures( &keypoints, &descriptors, options_.max_num_features); } auto query_options = query_options_; if (image_pair_filter_) { query_options.image_id_filter = [this, image_id](const int candidate_id) { return image_pair_filter_(image_id, candidate_id); }; } visual_index_->Query(query_options, keypoints, descriptors.ToFloat(), &retrieval.image_scores); } catch (const std::exception& error) { LOG(ERROR) << "Failed to query image " << image_id << " against vocabulary tree, skipping: " << error.what(); retrieval.image_scores.clear(); } THROW_CHECK(queue_.Push(std::move(retrieval))); } SequentialPairGenerator::SequentialPairGenerator( const SequentialPairingOptions& options, const std::shared_ptr& cache) : options_(options), cache_(THROW_CHECK_NOTNULL(cache)) { THROW_CHECK(options.Check()); LOG(INFO) << "Generating sequential image pairs..."; image_ids_ = GetOrderedImageIds(); image_pairs_.reserve(options_.overlap); if (options_.loop_detection) { image_id_to_idx_.reserve(image_ids_.size()); for (size_t i = 0; i < image_ids_.size(); ++i) { image_id_to_idx_.emplace(image_ids_[i], i); } std::vector query_image_ids; for (size_t i = 0; i < image_ids_.size(); i += options_.loop_detection_period) { query_image_ids.push_back(image_ids_[i]); } vocab_tree_pair_generator_ = std::make_unique( options_.VocabTreeOptions(), cache_, query_image_ids, [this](const image_t image_id1, const image_t image_id2) { return IsValidLoopDetectionPair(image_id1, image_id2); }); } if (options_.expand_rig_images) { const std::vector frame_ids = cache_->GetFrameIds(); frame_to_image_ids_.reserve(frame_ids.size()); image_to_frame_id_.reserve(image_ids_.size()); for (const frame_t frame_id : frame_ids) { const Frame& frame = cache_->GetFrame(frame_id); auto& frame_image_ids = frame_to_image_ids_[frame_id]; for (const data_t& data_id : frame.ImageIds()) { frame_image_ids.push_back(data_id.id); image_to_frame_id_[data_id.id] = frame_id; } } } } SequentialPairGenerator::SequentialPairGenerator( const SequentialPairingOptions& options, const std::shared_ptr& database) : SequentialPairGenerator( options, std::make_shared( options.CacheSize(), THROW_CHECK_NOTNULL(database))) {} void SequentialPairGenerator::Reset() { image_idx_ = 0; if (vocab_tree_pair_generator_) { vocab_tree_pair_generator_->Reset(); } } bool SequentialPairGenerator::HasFinished() const { return image_idx_ >= image_ids_.size() && (vocab_tree_pair_generator_ ? vocab_tree_pair_generator_->HasFinished() : true); } void SequentialPairGenerator::MaybeExpandRigImages(image_t image_id1, image_t image_id2) { if (!options_.expand_rig_images) { return; } const auto frame_id2_it = image_to_frame_id_.find(image_id2); if (frame_id2_it != image_to_frame_id_.end()) { // Pair with all images in second frame. for (const image_t frame_image_id2 : frame_to_image_ids_.at(frame_id2_it->second)) { if (image_id1 != frame_image_id2 && image_id2 != frame_image_id2) { image_pairs_.emplace_back(image_id1, frame_image_id2); } } } } bool SequentialPairGenerator::IsValidSequentialNeighbor( image_t image_id1, image_t image_id2) const { if (!options_.expand_rig_images) { return true; } const auto frame_id1_it = image_to_frame_id_.find(image_id1); const auto frame_id2_it = image_to_frame_id_.find(image_id2); if (frame_id1_it == image_to_frame_id_.end() || frame_id2_it == image_to_frame_id_.end()) { return true; } const frame_t frame_id1 = frame_id1_it->second; const frame_t frame_id2 = frame_id2_it->second; if (frame_to_image_ids_.at(frame_id1).size() == 1 && frame_to_image_ids_.at(frame_id2).size() == 1) { return true; } // Rig images are sorted by their sensor-prefixed names. Crossing from the // end of one sensor's sequence to the beginning of the next would create // a false temporal neighbor. Same-frame sensor pairs are added in Next(), // and temporal pairs are expanded to the other sensors by // MaybeExpandRigImages(). return cache_->GetImage(image_id1).CameraId() == cache_->GetImage(image_id2).CameraId(); } bool SequentialPairGenerator::IsValidLoopDetectionPair( const image_t image_id1, const image_t image_id2) const { const size_t image_idx1 = image_id_to_idx_.at(image_id1); const size_t image_idx2 = image_id_to_idx_.at(image_id2); const size_t image_idx_distance = image_idx1 > image_idx2 ? image_idx1 - image_idx2 : image_idx2 - image_idx1; return image_idx_distance >= static_cast(options_.loop_detection_min_index_distance); } std::vector> SequentialPairGenerator::Next() { image_pairs_.clear(); if (image_idx_ >= image_ids_.size()) { if (vocab_tree_pair_generator_) { return vocab_tree_pair_generator_->Next(); } return image_pairs_; } LOG(INFO) << StringPrintf( "Processing image [%d/%d]", image_idx_ + 1, image_ids_.size()); const auto image_id1 = image_ids_.at(image_idx_); // If image is part of a rig, then pair the other images in the same frame. if (options_.expand_rig_images) { if (const auto frame_id1_it = image_to_frame_id_.find(image_id1); frame_id1_it != image_to_frame_id_.end()) { for (const image_t frame_image_id2 : frame_to_image_ids_.at(frame_id1_it->second)) { if (image_id1 != frame_image_id2) { image_pairs_.emplace_back(image_id1, frame_image_id2); } } } } for (int i = 0; i < options_.overlap; ++i) { if (options_.quadratic_overlap) { const size_t image_idx_2_quadratic = image_idx_ + (1ull << i); if (image_idx_2_quadratic < image_ids_.size()) { const image_t image_id2 = image_ids_.at(image_idx_2_quadratic); if (!IsValidSequentialNeighbor(image_id1, image_id2)) { continue; } image_pairs_.emplace_back(image_id1, image_id2); MaybeExpandRigImages(image_id1, image_id2); } else { break; } } else { const size_t image_idx_2 = image_idx_ + i + 1; if (image_idx_2 < image_ids_.size()) { const image_t image_id2 = image_ids_.at(image_idx_2); if (!IsValidSequentialNeighbor(image_id1, image_id2)) { continue; } image_pairs_.emplace_back(image_id1, image_id2); MaybeExpandRigImages(image_id1, image_id2); } else { break; } } } ++image_idx_; return image_pairs_; } std::vector SequentialPairGenerator::GetOrderedImageIds() const { const std::vector image_ids = cache_->GetImageIds(); std::vector ordered_images; ordered_images.reserve(image_ids.size()); for (const auto image_id : image_ids) { ordered_images.push_back(cache_->GetImage(image_id)); } std::sort(ordered_images.begin(), ordered_images.end(), [](const Image& image1, const Image& image2) { return image1.Name() < image2.Name(); }); std::vector ordered_image_ids; ordered_image_ids.reserve(image_ids.size()); for (const auto& image : ordered_images) { ordered_image_ids.push_back(image.ImageId()); } return ordered_image_ids; } SpatialPairGenerator::SpatialPairGenerator( const SpatialPairingOptions& options, const std::shared_ptr& cache) : options_(options), image_ids_(THROW_CHECK_NOTNULL(cache)->GetImageIds()) { LOG(INFO) << "Generating spatial image pairs..."; THROW_CHECK(options.Check()); Timer timer; timer.Start(); LOG(INFO) << "Indexing images..."; Eigen::RowMajorMatrixXf position_matrix = ReadPositionPriorData(*cache); const int num_positions = position_idxs_.size(); LOG(INFO) << StringPrintf(" in %.3fs", timer.ElapsedSeconds()); if (num_positions == 0) { LOG(INFO) << "=> No images with location data."; return; } if (num_positions <= options_.min_num_neighbors) { LOG(WARNING) << StringPrintf( "min_num_neighbors (%d) exceeds number of images with location data " "(%zu), this may limit the number of matched pairs.", options_.min_num_neighbors, num_positions); } timer.Restart(); LOG(INFO) << "Building search index..."; faiss::IndexFlatL2 search_index(/*d=*/3); search_index.add(position_matrix.rows(), position_matrix.data()); LOG(INFO) << StringPrintf(" in %.3fs", timer.ElapsedSeconds()); timer.Restart(); LOG(INFO) << "Searching for nearest neighbors..."; knn_ = std::min(options_.max_num_neighbors + 1, num_positions); image_pairs_.reserve(knn_); index_matrix_.resize(num_positions, knn_); distance_squared_matrix_.resize(num_positions, knn_); omp_set_num_threads(GetEffectiveNumThreads(options_.num_threads)); search_index.search(position_matrix.rows(), position_matrix.data(), knn_, distance_squared_matrix_.data(), index_matrix_.data()); LOG(INFO) << StringPrintf(" in %.3fs", timer.ElapsedSeconds()); } SpatialPairGenerator::SpatialPairGenerator( const SpatialPairingOptions& options, const std::shared_ptr& database) : SpatialPairGenerator( options, std::make_shared( options.CacheSize(), THROW_CHECK_NOTNULL(database))) {} void SpatialPairGenerator::Reset() { current_idx_ = 0; } bool SpatialPairGenerator::HasFinished() const { return current_idx_ >= position_idxs_.size(); } std::vector> SpatialPairGenerator::Next() { image_pairs_.clear(); if (HasFinished()) { return image_pairs_; } LOG(INFO) << StringPrintf( "Processing image [%d/%d]", current_idx_ + 1, position_idxs_.size()); const float max_distance_squared = static_cast(options_.max_distance * options_.max_distance); for (int j = 0; j < knn_; ++j) { // Check if query equals result. if (index_matrix_(current_idx_, j) == static_cast(current_idx_)) { continue; } // Since the nearest neighbors are sorted by distance, we can break // once the distance is too large and enough neighbors are collected. if (distance_squared_matrix_(current_idx_, j) > max_distance_squared && j > options_.min_num_neighbors) { break; } const image_t image_id = image_ids_.at(position_idxs_[current_idx_]); const size_t nn_idx = position_idxs_.at(index_matrix_(current_idx_, j)); const image_t nn_image_id = image_ids_.at(nn_idx); image_pairs_.emplace_back(image_id, nn_image_id); } ++current_idx_; return image_pairs_; } Eigen::RowMajorMatrixXf SpatialPairGenerator::ReadPositionPriorData( FeatureMatcherCache& cache) { GPSTransform gps_transform; std::vector ells(1); Eigen::RowMajorMatrixXd position_matrix(image_ids_.size(), 3); position_idxs_.clear(); position_idxs_.reserve(image_ids_.size()); for (size_t i = 0; i < image_ids_.size(); ++i) { const PosePrior* pose_prior = cache.FindImagePosePriorOrNull(image_ids_[i]); if (pose_prior == nullptr) { continue; } if ((!options_.ignore_z && !pose_prior->HasPosition()) || (options_.ignore_z && !pose_prior->position.head<2>().allFinite())) { continue; } const size_t position_idx = position_idxs_.size(); position_idxs_.push_back(i); switch (pose_prior->coordinate_system) { case PosePrior::CoordinateSystem::WGS84: { ells[0](0) = pose_prior->position(0); ells[0](1) = pose_prior->position(1); ells[0](2) = options_.ignore_z ? 0 : pose_prior->position(2); const std::vector xyzs = gps_transform.EllipsoidToECEF(ells); position_matrix(position_idx, 0) = xyzs[0](0); position_matrix(position_idx, 1) = xyzs[0](1); position_matrix(position_idx, 2) = xyzs[0](2); } break; case PosePrior::CoordinateSystem::UNDEFINED: default: LOG(WARNING) << "Unknown coordinate system for image " << image_ids_[i] << ", assuming cartesian."; case PosePrior::CoordinateSystem::CARTESIAN: position_matrix(position_idx, 0) = pose_prior->position(0); position_matrix(position_idx, 1) = pose_prior->position(1); position_matrix(position_idx, 2) = options_.ignore_z ? 0 : pose_prior->position(2); } } // Trim unused rows for images without a valid position before calculating // the mean coordinate below. const size_t num_populated_rows = position_idxs_.size(); position_matrix.conservativeResize(num_populated_rows, Eigen::NoChange); // Subtract the mean coordinate before casting to float for better numerical // precision when dealing with large coordinates (e.g. GPS). This is // particularly important for projected Cartesian coordinate systems, which // can contain very large values in metres. For even better precision, we // could also rescale the coordinates. const Eigen::RowVector3d mean_position = position_matrix.colwise().mean(); Eigen::IOFormat vec_fmt(Eigen::FullPrecision, Eigen::DontAlignCols, ", "); VLOG(1) << "Internally offsetting image pose priors by mean coordinate " << mean_position.format(vec_fmt) << " prior to spatial matching."; position_matrix.rowwise() -= mean_position; return position_matrix.cast(); } TransitivePairGenerator::TransitivePairGenerator( const TransitivePairingOptions& options, const std::shared_ptr& cache) : options_(options), cache_(cache) { THROW_CHECK(options.Check()); } TransitivePairGenerator::TransitivePairGenerator( const TransitivePairingOptions& options, const std::shared_ptr& database) : TransitivePairGenerator( options, std::make_shared( options.CacheSize(), THROW_CHECK_NOTNULL(database))) {} void TransitivePairGenerator::Reset() { current_iteration_ = 0; current_batch_idx_ = 0; image_pairs_.clear(); image_pair_ids_.clear(); } bool TransitivePairGenerator::HasFinished() const { return current_iteration_ >= options_.num_iterations && image_pairs_.empty(); } std::vector> TransitivePairGenerator::Next() { if (!image_pairs_.empty()) { current_batch_idx_++; std::vector> batch; while (!image_pairs_.empty() && static_cast(batch.size()) < options_.batch_size) { batch.push_back(image_pairs_.back()); image_pairs_.pop_back(); } LOG(INFO) << StringPrintf( "Processing batch [%d/%d]", current_batch_idx_, current_num_batches_); return batch; } if (current_iteration_ >= options_.num_iterations) { return {}; } current_batch_idx_ = 0; current_num_batches_ = 0; current_iteration_++; LOG(INFO) << StringPrintf( "Iteration [%d/%d]", current_iteration_, options_.num_iterations); std::vector> existing_pair_ids_and_num_inliers; cache_->AccessDatabase( [&existing_pair_ids_and_num_inliers](Database& database) { existing_pair_ids_and_num_inliers = database.ReadTwoViewGeometryNumInliers(); }); std::map> adjacency; for (const auto& [pair_id, _] : existing_pair_ids_and_num_inliers) { const auto [image_id1, image_id2] = PairIdToImagePair(pair_id); adjacency[image_id1].push_back(image_id2); adjacency[image_id2].push_back(image_id1); image_pair_ids_.insert(pair_id); } for (const auto& image : adjacency) { const auto image_id1 = image.first; for (const auto& image_id2 : image.second) { const auto it = adjacency.find(image_id2); if (it == adjacency.end()) { continue; } for (const auto& image_id3 : it->second) { if (image_id1 == image_id3) { continue; } const auto image_pair_id = ImagePairToPairId(image_id1, image_id3); if (image_pair_ids_.count(image_pair_id) != 0) { continue; } image_pairs_.emplace_back(std::minmax(image_id1, image_id3)); image_pair_ids_.insert(image_pair_id); } } } current_num_batches_ = std::ceil(static_cast(image_pairs_.size()) / options_.batch_size); return Next(); } ImportedPairGenerator::ImportedPairGenerator( const ImportedPairingOptions& options, const std::shared_ptr& cache) : options_(options) { THROW_CHECK(options.Check()); LOG(INFO) << "Importing image pairs..."; const std::vector image_ids = cache->GetImageIds(); NodeHashMap image_name_to_image_id; image_name_to_image_id.reserve(image_ids.size()); for (const auto image_id : image_ids) { const auto& image = cache->GetImage(image_id); image_name_to_image_id.emplace(image.Name(), image_id); } image_pairs_ = ReadImagePairsText(options_.match_list_path, image_name_to_image_id); block_image_pairs_.reserve(options_.block_size); } ImportedPairGenerator::ImportedPairGenerator( const ImportedPairingOptions& options, const std::shared_ptr& database) : ImportedPairGenerator( options, std::make_shared( options.CacheSize(), THROW_CHECK_NOTNULL(database))) {} void ImportedPairGenerator::Reset() { pair_idx_ = 0; } bool ImportedPairGenerator::HasFinished() const { return pair_idx_ >= image_pairs_.size(); } std::vector> ImportedPairGenerator::Next() { block_image_pairs_.clear(); if (HasFinished()) { return block_image_pairs_; } LOG(INFO) << StringPrintf("Processing block [%d/%d]", pair_idx_ / options_.block_size + 1, image_pairs_.size() / options_.block_size + 1); const size_t block_end = std::min(pair_idx_ + options_.block_size, image_pairs_.size()); for (size_t j = pair_idx_; j < block_end; ++j) { block_image_pairs_.push_back(image_pairs_[j]); } pair_idx_ += options_.block_size; return block_image_pairs_; } ExistingMatchedPairGenerator::ExistingMatchedPairGenerator( const ExistingMatchedPairingOptions& options, const std::shared_ptr& cache) : options_(options) { THROW_CHECK(options.Check()); LOG(INFO) << "Generating existing image pairs..."; cache->AccessDatabase([this](Database& database) { auto num_matches = database.ReadNumMatches(); image_pairs_.reserve(num_matches.size()); for (const auto& [pair_id, _] : num_matches) { image_pairs_.emplace_back(PairIdToImagePair(pair_id)); } }); num_batches_ = std::ceil(static_cast(image_pairs_.size()) / options_.batch_size); } ExistingMatchedPairGenerator::ExistingMatchedPairGenerator( const ExistingMatchedPairingOptions& options, const std::shared_ptr& database) : ExistingMatchedPairGenerator( options, std::make_shared( options.CacheSize(), THROW_CHECK_NOTNULL(database))) {} void ExistingMatchedPairGenerator::Reset() { start_idx_ = 0; } bool ExistingMatchedPairGenerator::HasFinished() const { return start_idx_ >= image_pairs_.size(); } std::vector> ExistingMatchedPairGenerator::Next() { if (HasFinished()) { return {}; } const size_t end_idx = std::min(start_idx_ + options_.batch_size, image_pairs_.size()); std::vector> batch; batch.reserve(end_idx - start_idx_); for (size_t idx = start_idx_; idx < end_idx; ++idx) { batch.emplace_back(image_pairs_[idx]); } LOG(INFO) << StringPrintf("Processing batch [%d/%d]", start_idx_ / options_.batch_size + 1, num_batches_); start_idx_ = end_idx; return batch; } } // namespace colmap colmap-4.2.0/src/colmap/controllers/pairing.h000066400000000000000000000351101524536416500212150ustar00rootroot00000000000000// Copyright (c), ETH Zurich and UNC Chapel Hill. // All rights reserved. // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are met: // // * Redistributions of source code must retain the above copyright // notice, this list of conditions and the following disclaimer. // // * Redistributions in binary form must reproduce the above copyright // notice, this list of conditions and the following disclaimer in the // documentation and/or other materials provided with the distribution. // // * Neither the name of ETH Zurich and UNC Chapel Hill nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE // ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE // LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR // CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF // SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS // INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN // CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) // ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE // POSSIBILITY OF SUCH DAMAGE. #pragma once #include "colmap/controllers/matcher_cache.h" #include "colmap/retrieval/visual_index.h" #include "colmap/scene/database.h" #include "colmap/util/hash_containers.h" #include "colmap/util/threading.h" #include "colmap/util/types.h" #include #include namespace colmap { struct ExhaustivePairingOptions { // Block size, i.e. number of images to simultaneously load into memory. int block_size = 50; bool Check() const; // Each block matches two sets of images with size block_size. To hold all // images in the block, the cache thus needs to hold 2 * block_size. inline size_t CacheSize() const { return 2 * block_size; } }; struct VocabTreePairingOptions { // Number of images to retrieve for each query image. int num_images = 100; // Number of nearest neighbors to retrieve per query feature. int num_nearest_neighbors = 5; // Number of nearest-neighbor checks to use in retrieval. int num_checks = 64; // How many images to return after spatial verification. Set to 0 to turn off // spatial verification. int num_images_after_verification = 0; // The maximum number of features to use for indexing an image. If an // image has more features, only the largest-scale features will be indexed. int max_num_features = -1; // Path to the vocabulary tree. std::filesystem::path vocab_tree_path; // Optional path to file with specific image names to match. std::filesystem::path match_list_path = ""; // Number of threads for indexing and retrieval. int num_threads = -1; bool Check() const; inline size_t CacheSize() const { return 5 * num_images; } }; struct SequentialPairingOptions { // Number of overlapping image pairs. int overlap = 10; // Whether to match images against their quadratic neighbors. bool quadratic_overlap = true; // Whether to match an image against all images within the same rig frame // and all images in neighboring rig frames. Note that this assumes that // images are appropriate named according to the following scheme: // // rig1/ // camera1/ // image0001.jpg // image0002.jpg // image0003.jpg // ... // camera2/ // image0001.jpg // image0002.jpg // image0003.jpg // ... // camera3/ // image0001.jpg // image0002.jpg // image0003.jpg // ... // ... // // where, for overlap=1, rig1/camera1/image0001.jpg will be matched against: // // rig1/camera2/image0001.jpg # same frame // rig1/camera3/image0001.jpg # same frame // rig1/camera1/image0002.jpg # neighboring frame // rig1/camera2/image0002.jpg # neighboring frame // rig1/camera3/image0002.jpg # neighboring frame // // If no rigs/frames are configured in the database, this option is ignored. bool expand_rig_images = true; // Whether to enable vocabulary tree based loop detection. bool loop_detection = false; // The frequency at which loop detection is triggered, in number of images. int loop_detection_period = 10; // The number of images to retrieve in loop detection. This number should // be significantly larger than the sequential matching overlap. int loop_detection_num_images = 50; // The minimum image index distance between a loop detection query and a // retrieved image. The index is determined by the sequential image order. // Set to 0 to disable this restriction. int loop_detection_min_index_distance = 0; // Number of nearest neighbors to retrieve per query feature. int loop_detection_num_nearest_neighbors = 1; // Number of nearest-neighbor checks to use in retrieval. int loop_detection_num_checks = 64; // How many images to return after spatial verification. Set to 0 to turn off // spatial verification. int loop_detection_num_images_after_verification = 0; // The maximum number of features to use for indexing an image. If an // image has more features, only the largest-scale features will be indexed. int loop_detection_max_num_features = -1; // Number of threads for loop detection indexing and retrieval. int num_threads = -1; // Path to the vocabulary tree. std::filesystem::path vocab_tree_path; bool Check() const; VocabTreePairingOptions VocabTreeOptions() const; inline size_t CacheSize() const { return std::max(5 * loop_detection_num_images, 5 * overlap); } }; struct SpatialPairingOptions { // Whether to ignore the Z-component of the location prior. bool ignore_z = true; // The maximum number of nearest neighbors to match. int max_num_neighbors = 50; // The minimum number of nearest neighbors to match. Neighbors include those // within max_distance or to satisfy min_num_neighbors. int min_num_neighbors = 0; // The maximum distance between the query and nearest neighbor. For GPS // coordinates the unit is Euclidean distance in meters. double max_distance = 100; // Number of threads for indexing and retrieval. int num_threads = -1; bool Check() const; inline size_t CacheSize() const { return 5 * max_num_neighbors; } }; struct TransitivePairingOptions { // The maximum number of image pairs to process in one batch. int batch_size = 1000; // The number of transitive closure iterations. int num_iterations = 3; bool Check() const; inline size_t CacheSize() const { return 2 * batch_size; } }; struct ImportedPairingOptions { // Number of image pairs to match in one batch. int block_size = 1225; // Path to the file with the matches. std::filesystem::path match_list_path = ""; bool Check() const; inline size_t CacheSize() const { return block_size; } }; struct FeaturePairsMatchingOptions { // Whether to geometrically verify the given matches. bool verify_matches = true; // Path to the file with the matches. std::filesystem::path match_list_path = ""; bool Check() const; }; struct ExistingMatchedPairingOptions { // The number of image pairs to match in one batch. int batch_size = 1000; bool Check() const; inline size_t CacheSize() const { return std::max(10, static_cast(2 * std::sqrt(batch_size))); } }; class PairGenerator { public: virtual ~PairGenerator() = default; virtual void Reset() = 0; virtual bool HasFinished() const = 0; virtual std::vector> Next() = 0; std::vector> AllPairs(); }; class ExhaustivePairGenerator : public PairGenerator { public: using PairingOptions = ExhaustivePairingOptions; ExhaustivePairGenerator(const ExhaustivePairingOptions& options, const std::shared_ptr& cache); ExhaustivePairGenerator(const ExhaustivePairingOptions& options, const std::shared_ptr& database); void Reset() override; bool HasFinished() const override; std::vector> Next() override; private: const ExhaustivePairingOptions options_; const std::vector image_ids_; const size_t block_size_; const size_t num_blocks_; size_t start_idx1_ = 0; size_t start_idx2_ = 0; std::vector> image_pairs_; }; class VocabTreePairGenerator : public PairGenerator { public: using PairingOptions = VocabTreePairingOptions; VocabTreePairGenerator( const VocabTreePairingOptions& options, const std::shared_ptr& cache, const std::vector& query_image_ids = {}, std::function image_pair_filter = {}); VocabTreePairGenerator( const VocabTreePairingOptions& options, const std::shared_ptr& database, const std::vector& query_image_ids = {}, std::function image_pair_filter = {}); void Reset() override; bool HasFinished() const override; std::vector> Next() override; private: void IndexImages(const std::vector& image_ids); struct Retrieval { image_t image_id = kInvalidImageId; std::vector image_scores; }; void Query(image_t image_id); const VocabTreePairingOptions options_; const std::shared_ptr cache_; ThreadPool thread_pool_; JobQueue queue_; std::unique_ptr visual_index_; retrieval::VisualIndex::QueryOptions query_options_; std::function image_pair_filter_; std::vector query_image_ids_; std::vector> image_pairs_; size_t query_idx_ = 0; size_t result_idx_ = 0; }; class SequentialPairGenerator : public PairGenerator { public: using PairingOptions = SequentialPairingOptions; SequentialPairGenerator(const SequentialPairingOptions& options, const std::shared_ptr& cache); SequentialPairGenerator(const SequentialPairingOptions& options, const std::shared_ptr& database); void Reset() override; bool HasFinished() const override; std::vector> Next() override; private: void MaybeExpandRigImages(image_t image_id1, image_t image_id2); bool IsValidSequentialNeighbor(image_t image_id1, image_t image_id2) const; bool IsValidLoopDetectionPair(image_t image_id1, image_t image_id2) const; std::vector GetOrderedImageIds() const; const SequentialPairingOptions options_; const std::shared_ptr cache_; std::vector image_ids_; FlatHashMap image_id_to_idx_; // Optional mapping from frames to images and vice versa. NodeHashMap> frame_to_image_ids_; NodeHashMap image_to_frame_id_; std::unique_ptr vocab_tree_pair_generator_; std::vector> image_pairs_; size_t image_idx_ = 0; }; class SpatialPairGenerator : public PairGenerator { public: using PairingOptions = SpatialPairingOptions; SpatialPairGenerator(const SpatialPairingOptions& options, const std::shared_ptr& cache); SpatialPairGenerator(const SpatialPairingOptions& options, const std::shared_ptr& database); void Reset() override; bool HasFinished() const override; std::vector> Next() override; Eigen::RowMajorMatrixXf ReadPositionPriorData(FeatureMatcherCache& cache); private: const SpatialPairingOptions options_; std::vector> image_pairs_; Eigen::Matrix index_matrix_; Eigen::RowMajorMatrixXf distance_squared_matrix_; std::vector image_ids_; std::vector position_idxs_; size_t current_idx_ = 0; int knn_ = 0; }; class TransitivePairGenerator : public PairGenerator { public: using PairingOptions = TransitivePairingOptions; TransitivePairGenerator(const TransitivePairingOptions& options, const std::shared_ptr& cache); TransitivePairGenerator(const TransitivePairingOptions& options, const std::shared_ptr& database); void Reset() override; bool HasFinished() const override; std::vector> Next() override; private: const TransitivePairingOptions options_; const std::shared_ptr cache_; int current_iteration_ = 0; int current_batch_idx_ = 0; int current_num_batches_ = 0; std::vector> image_pairs_; FlatHashSet image_pair_ids_; }; class ImportedPairGenerator : public PairGenerator { public: using PairingOptions = ImportedPairingOptions; ImportedPairGenerator(const ImportedPairingOptions& options, const std::shared_ptr& cache); ImportedPairGenerator(const ImportedPairingOptions& options, const std::shared_ptr& database); void Reset() override; bool HasFinished() const override; std::vector> Next() override; private: const ImportedPairingOptions options_; std::vector> image_pairs_; std::vector> block_image_pairs_; size_t pair_idx_ = 0; }; class ExistingMatchedPairGenerator : public PairGenerator { public: using PairingOptions = ExistingMatchedPairingOptions; ExistingMatchedPairGenerator( const ExistingMatchedPairingOptions& options, const std::shared_ptr& cache); ExistingMatchedPairGenerator(const ExistingMatchedPairingOptions& options, const std::shared_ptr& database); void Reset() override; bool HasFinished() const override; std::vector> Next() override; private: const ExistingMatchedPairingOptions options_; std::vector> image_pairs_; size_t start_idx_ = 0; size_t num_batches_ = 0; }; } // namespace colmap colmap-4.2.0/src/colmap/controllers/pairing_test.cc000066400000000000000000001163121524536416500224160ustar00rootroot00000000000000// Copyright (c), ETH Zurich and UNC Chapel Hill. // All rights reserved. // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are met: // // * Redistributions of source code must retain the above copyright // notice, this list of conditions and the following disclaimer. // // * Redistributions in binary form must reproduce the above copyright // notice, this list of conditions and the following disclaimer in the // documentation and/or other materials provided with the distribution. // // * Neither the name of ETH Zurich and UNC Chapel Hill nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE // ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE // LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR // CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF // SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS // INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN // CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) // ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE // POSSIBILITY OF SUCH DAMAGE. #include "colmap/controllers/pairing.h" #include "colmap/feature/types.h" #include "colmap/retrieval/visual_index.h" #include "colmap/scene/database_sqlite.h" #include "colmap/scene/synthetic.h" #include "colmap/util/eigen_matchers.h" #include "colmap/util/testing.h" #include #include #include namespace colmap { namespace { void CreateSyntheticDatabase(int num_images, Database& database) { Reconstruction unused_reconstruction; SyntheticDatasetOptions synthetic_dataset_options; synthetic_dataset_options.num_rigs = num_images; synthetic_dataset_options.num_cameras_per_rig = 1; synthetic_dataset_options.num_frames_per_rig = 1; SynthesizeDataset( synthetic_dataset_options, &unused_reconstruction, &database); } TEST(ExhaustivePairGenerator, Nominal) { constexpr int kNumImages = 34; auto database = Database::Open(kInMemorySqliteDatabasePath); CreateSyntheticDatabase(kNumImages, *database); const std::vector images = database->ReadAllImages(); CHECK_EQ(images.size(), kNumImages); ExhaustivePairingOptions options; options.block_size = 10; ExhaustivePairGenerator generator(options, database); const int num_expected_blocks = std::ceil(static_cast(kNumImages) / options.block_size) * std::ceil(static_cast(kNumImages) / options.block_size); std::set> pairs; for (int i = 0; i < num_expected_blocks; ++i) { for (const auto& pair : generator.Next()) { pairs.insert(pair); } } EXPECT_EQ(pairs.size(), kNumImages * (kNumImages - 1) / 2); EXPECT_TRUE(generator.Next().empty()); EXPECT_TRUE(generator.HasFinished()); } std::unique_ptr CreateSyntheticVisualIndex() { auto visual_index = retrieval::VisualIndex::Create(); retrieval::VisualIndex::BuildOptions build_options; build_options.num_visual_words = 5; // NOLINTNEXTLINE(clang-analyzer-optin.cplusplus.VirtualCall) visual_index->Build( build_options, FeatureDescriptorsFloat(FeatureExtractorType::SIFT, FeatureDescriptorsFloatData::Random(50, 128))); return visual_index; } TEST(VocabTreePairGenerator, Nominal) { constexpr int kNumImages = 5; auto database = Database::Open(kInMemorySqliteDatabasePath); CreateSyntheticDatabase(kNumImages, *database); const std::vector images = database->ReadAllImages(); CHECK_EQ(images.size(), kNumImages); VocabTreePairingOptions options; options.vocab_tree_path = CreateTestDir() / "vocab_tree.txt"; // NOLINTNEXTLINE(clang-analyzer-optin.cplusplus.VirtualCall) CreateSyntheticVisualIndex()->Write(options.vocab_tree_path); { options.num_images = 3; VocabTreePairGenerator generator(options, database); for (int i = 0; i < kNumImages; ++i) { const auto pairs = generator.Next(); EXPECT_EQ(pairs.size(), options.num_images); EXPECT_EQ( (std::set>(pairs.begin(), pairs.end()) .size()), pairs.size()); } EXPECT_TRUE(generator.Next().empty()); EXPECT_TRUE(generator.HasFinished()); } { options.num_images = 100; VocabTreePairGenerator generator(options, database); for (int i = 0; i < kNumImages; ++i) { const auto pairs = generator.Next(); EXPECT_EQ(pairs.size(), kNumImages); } EXPECT_TRUE(generator.Next().empty()); EXPECT_TRUE(generator.HasFinished()); } } TEST(VocabTreePairGenerator, DoesNotDeadlockOnFailedQuery) { constexpr int kNumImages = 5; auto database = Database::Open(kInMemorySqliteDatabasePath); CreateSyntheticDatabase(kNumImages, *database); std::vector images = database->ReadAllImages(); CHECK_EQ(images.size(), kNumImages); VocabTreePairingOptions options; options.vocab_tree_path = CreateTestDir() / "vocab_tree.txt"; CreateSyntheticVisualIndex()->Write(options.vocab_tree_path); options.num_images = 3; // Query a set of images that includes an invalid image identifier. Querying // the invalid image fails inside the worker thread. Even so, the generator // must not deadlock and must produce one result per query image. Regression // test for a hang where a failing query never pushed a result and the // consumer blocked indefinitely on the result queue (GH #4456). std::vector query_image_ids; query_image_ids.reserve(images.size() + 1); for (const auto& image : images) { query_image_ids.push_back(image.ImageId()); } const image_t kInvalidQueryImageId = 1000; query_image_ids.push_back(kInvalidQueryImageId); VocabTreePairGenerator generator(options, database, query_image_ids); for (size_t i = 0; i < query_image_ids.size(); ++i) { generator.Next(); } EXPECT_TRUE(generator.Next().empty()); EXPECT_TRUE(generator.HasFinished()); } TEST(SequentialPairGenerator, Linear) { constexpr int kNumImages = 5; auto database = Database::Open(kInMemorySqliteDatabasePath); CreateSyntheticDatabase(kNumImages, *database); const std::vector images = database->ReadAllImages(); CHECK_EQ(images.size(), kNumImages); SequentialPairingOptions options; options.overlap = 3; options.quadratic_overlap = false; SequentialPairGenerator generator(options, database); EXPECT_THAT(generator.Next(), testing::ElementsAre( std::make_pair(images[0].ImageId(), images[1].ImageId()), std::make_pair(images[0].ImageId(), images[2].ImageId()), std::make_pair(images[0].ImageId(), images[3].ImageId()))); EXPECT_THAT(generator.Next(), testing::ElementsAre( std::make_pair(images[1].ImageId(), images[2].ImageId()), std::make_pair(images[1].ImageId(), images[3].ImageId()), std::make_pair(images[1].ImageId(), images[4].ImageId()))); EXPECT_THAT(generator.Next(), testing::ElementsAre( std::make_pair(images[2].ImageId(), images[3].ImageId()), std::make_pair(images[2].ImageId(), images[4].ImageId()))); EXPECT_THAT(generator.Next(), testing::ElementsAre( std::make_pair(images[3].ImageId(), images[4].ImageId()))); EXPECT_TRUE(generator.Next().empty()); EXPECT_TRUE(generator.HasFinished()); } TEST(SequentialPairGenerator, LinearRig) { auto database = Database::Open(kInMemorySqliteDatabasePath); Reconstruction unused_reconstruction; SyntheticDatasetOptions synthetic_dataset_options; synthetic_dataset_options.num_rigs = 1; synthetic_dataset_options.num_cameras_per_rig = 2; synthetic_dataset_options.num_frames_per_rig = 3; SynthesizeDataset( synthetic_dataset_options, &unused_reconstruction, database.get()); const std::vector images = database->ReadAllImages(); CHECK_EQ(images.size(), synthetic_dataset_options.num_cameras_per_rig * synthetic_dataset_options.num_frames_per_rig); SequentialPairingOptions options; options.overlap = 1; options.quadratic_overlap = false; SequentialPairGenerator generator(options, database); EXPECT_THAT(generator.Next(), testing::ElementsAre( std::make_pair(images[0].ImageId(), images[1].ImageId()), std::make_pair(images[0].ImageId(), images[2].ImageId()), std::make_pair(images[0].ImageId(), images[3].ImageId()))); EXPECT_THAT(generator.Next(), testing::ElementsAre( std::make_pair(images[2].ImageId(), images[3].ImageId()), std::make_pair(images[2].ImageId(), images[4].ImageId()), std::make_pair(images[2].ImageId(), images[5].ImageId()))); EXPECT_THAT(generator.Next(), testing::ElementsAre( std::make_pair(images[4].ImageId(), images[5].ImageId()))); EXPECT_THAT(generator.Next(), testing::ElementsAre( std::make_pair(images[1].ImageId(), images[0].ImageId()), std::make_pair(images[1].ImageId(), images[3].ImageId()), std::make_pair(images[1].ImageId(), images[2].ImageId()))); EXPECT_THAT(generator.Next(), testing::ElementsAre( std::make_pair(images[3].ImageId(), images[2].ImageId()), std::make_pair(images[3].ImageId(), images[5].ImageId()), std::make_pair(images[3].ImageId(), images[4].ImageId()))); EXPECT_THAT(generator.Next(), testing::ElementsAre( std::make_pair(images[5].ImageId(), images[4].ImageId()))); EXPECT_TRUE(generator.Next().empty()); EXPECT_TRUE(generator.HasFinished()); } TEST(SequentialPairGenerator, QuadraticRig) { auto database = Database::Open(kInMemorySqliteDatabasePath); Reconstruction unused_reconstruction; SyntheticDatasetOptions synthetic_dataset_options; synthetic_dataset_options.num_rigs = 1; synthetic_dataset_options.num_cameras_per_rig = 2; synthetic_dataset_options.num_frames_per_rig = 3; SynthesizeDataset( synthetic_dataset_options, &unused_reconstruction, database.get()); const std::vector images = database->ReadAllImages(); SequentialPairingOptions options; options.overlap = 3; options.quadratic_overlap = true; SequentialPairGenerator generator(options, database); EXPECT_THAT(generator.Next(), testing::ElementsAre( std::make_pair(images[0].ImageId(), images[1].ImageId()), std::make_pair(images[0].ImageId(), images[2].ImageId()), std::make_pair(images[0].ImageId(), images[3].ImageId()), std::make_pair(images[0].ImageId(), images[4].ImageId()), std::make_pair(images[0].ImageId(), images[5].ImageId()))); EXPECT_THAT(generator.Next(), testing::ElementsAre( std::make_pair(images[2].ImageId(), images[3].ImageId()), std::make_pair(images[2].ImageId(), images[4].ImageId()), std::make_pair(images[2].ImageId(), images[5].ImageId()))); EXPECT_THAT(generator.Next(), testing::ElementsAre( std::make_pair(images[4].ImageId(), images[5].ImageId()))); EXPECT_THAT(generator.Next(), testing::ElementsAre( std::make_pair(images[1].ImageId(), images[0].ImageId()), std::make_pair(images[1].ImageId(), images[3].ImageId()), std::make_pair(images[1].ImageId(), images[2].ImageId()), std::make_pair(images[1].ImageId(), images[5].ImageId()), std::make_pair(images[1].ImageId(), images[4].ImageId()))); EXPECT_THAT(generator.Next(), testing::ElementsAre( std::make_pair(images[3].ImageId(), images[2].ImageId()), std::make_pair(images[3].ImageId(), images[5].ImageId()), std::make_pair(images[3].ImageId(), images[4].ImageId()))); EXPECT_THAT(generator.Next(), testing::ElementsAre( std::make_pair(images[5].ImageId(), images[4].ImageId()))); EXPECT_TRUE(generator.Next().empty()); EXPECT_TRUE(generator.HasFinished()); } TEST(SequentialPairGenerator, Quadratic) { constexpr int kNumImages = 5; auto database = Database::Open(kInMemorySqliteDatabasePath); CreateSyntheticDatabase(kNumImages, *database); const std::vector images = database->ReadAllImages(); CHECK_EQ(images.size(), kNumImages); SequentialPairingOptions options; options.overlap = 3; options.quadratic_overlap = true; SequentialPairGenerator generator(options, database); EXPECT_THAT(generator.Next(), testing::ElementsAre( std::make_pair(images[0].ImageId(), images[1].ImageId()), std::make_pair(images[0].ImageId(), images[2].ImageId()), std::make_pair(images[0].ImageId(), images[4].ImageId()))); EXPECT_THAT(generator.Next(), testing::ElementsAre( std::make_pair(images[1].ImageId(), images[2].ImageId()), std::make_pair(images[1].ImageId(), images[3].ImageId()))); EXPECT_THAT(generator.Next(), testing::ElementsAre( std::make_pair(images[2].ImageId(), images[3].ImageId()), std::make_pair(images[2].ImageId(), images[4].ImageId()))); EXPECT_THAT(generator.Next(), testing::ElementsAre( std::make_pair(images[3].ImageId(), images[4].ImageId()))); EXPECT_TRUE(generator.Next().empty()); EXPECT_TRUE(generator.HasFinished()); } TEST(SequentialPairGenerator, LoopDetectionMinIndexDistance) { constexpr int kNumImages = 6; auto database = Database::Open(kInMemorySqliteDatabasePath); CreateSyntheticDatabase(kNumImages, *database); const std::vector images = database->ReadAllImages(); CHECK_EQ(images.size(), kNumImages); SequentialPairingOptions options; options.overlap = 1; options.quadratic_overlap = false; options.expand_rig_images = false; options.loop_detection = true; options.loop_detection_period = 2; options.loop_detection_num_images = 2; options.loop_detection_min_index_distance = 2; options.num_threads = 1; options.vocab_tree_path = CreateTestDir() / "vocab_tree.txt"; CreateSyntheticVisualIndex()->Write(options.vocab_tree_path); SequentialPairGenerator generator(options, database); for (int i = 0; i < kNumImages; ++i) { generator.Next(); } FlatHashMap image_id_to_idx; for (size_t i = 0; i < images.size(); ++i) { image_id_to_idx.emplace(images[i].ImageId(), i); } const int num_loop_detection_queries = (kNumImages + options.loop_detection_period - 1) / options.loop_detection_period; for (int i = 0; i < num_loop_detection_queries; ++i) { const auto pairs = generator.Next(); ASSERT_EQ(pairs.size(), options.loop_detection_num_images); for (const auto& [image_id1, image_id2] : pairs) { const size_t image_idx1 = image_id_to_idx.at(image_id1); const size_t image_idx2 = image_id_to_idx.at(image_id2); const size_t image_idx_distance = image_idx1 > image_idx2 ? image_idx1 - image_idx2 : image_idx2 - image_idx1; EXPECT_GE(image_idx_distance, options.loop_detection_min_index_distance); } } EXPECT_TRUE(generator.Next().empty()); EXPECT_TRUE(generator.HasFinished()); } TEST(SpatialPairGenerator, Nominal) { constexpr int kNumImages = 3; auto database = Database::Open(kInMemorySqliteDatabasePath); CreateSyntheticDatabase(kNumImages, *database); const std::vector images = database->ReadAllImages(); CHECK_EQ(images.size(), kNumImages); PosePrior pose_prior1; pose_prior1.corr_data_id = images[0].DataId(); pose_prior1.position = Eigen::Vector3d(1, 2, 3); database->WritePosePrior(pose_prior1); PosePrior pose_prior2; pose_prior2.corr_data_id = images[1].DataId(); pose_prior2.position = Eigen::Vector3d(2, 3, 4); database->WritePosePrior(pose_prior2); PosePrior pose_prior3; pose_prior3.corr_data_id = images[2].DataId(); pose_prior3.position = Eigen::Vector3d(2, 4, 12); database->WritePosePrior(pose_prior3); SpatialPairingOptions options; options.max_num_neighbors = 1; options.max_distance = 1000; options.ignore_z = false; { SpatialPairGenerator generator(options, database); EXPECT_THAT(generator.Next(), testing::ElementsAre( std::make_pair(images[0].ImageId(), images[1].ImageId()))); EXPECT_THAT(generator.Next(), testing::ElementsAre( std::make_pair(images[1].ImageId(), images[0].ImageId()))); EXPECT_THAT(generator.Next(), testing::ElementsAre( std::make_pair(images[2].ImageId(), images[1].ImageId()))); EXPECT_TRUE(generator.Next().empty()); EXPECT_TRUE(generator.HasFinished()); } { options.ignore_z = true; SpatialPairGenerator generator(options, database); EXPECT_THAT(generator.Next(), testing::ElementsAre( std::make_pair(images[0].ImageId(), images[1].ImageId()))); EXPECT_THAT(generator.Next(), testing::ElementsAre( std::make_pair(images[1].ImageId(), images[2].ImageId()))); EXPECT_THAT(generator.Next(), testing::ElementsAre( std::make_pair(images[2].ImageId(), images[1].ImageId()))); EXPECT_TRUE(generator.Next().empty()); EXPECT_TRUE(generator.HasFinished()); } { options.ignore_z = false; options.max_distance = 5; SpatialPairGenerator generator(options, database); EXPECT_THAT(generator.Next(), testing::ElementsAre( std::make_pair(images[0].ImageId(), images[1].ImageId()))); EXPECT_THAT(generator.Next(), testing::ElementsAre( std::make_pair(images[1].ImageId(), images[0].ImageId()))); EXPECT_TRUE(generator.Next().empty()); EXPECT_TRUE(generator.Next().empty()); EXPECT_TRUE(generator.HasFinished()); } { options.max_num_neighbors = 2; options.max_distance = 1000; SpatialPairGenerator generator(options, database); EXPECT_THAT(generator.Next(), testing::ElementsAre( std::make_pair(images[0].ImageId(), images[1].ImageId()), std::make_pair(images[0].ImageId(), images[2].ImageId()))); EXPECT_THAT(generator.Next(), testing::ElementsAre( std::make_pair(images[1].ImageId(), images[0].ImageId()), std::make_pair(images[1].ImageId(), images[2].ImageId()))); EXPECT_THAT(generator.Next(), testing::ElementsAre( std::make_pair(images[2].ImageId(), images[1].ImageId()), std::make_pair(images[2].ImageId(), images[0].ImageId()))); EXPECT_TRUE(generator.Next().empty()); EXPECT_TRUE(generator.HasFinished()); } } TEST(SpatialPairGenerator, LargeCoordinates) { constexpr int kNumImages = 3; auto database = Database::Open(kInMemorySqliteDatabasePath); CreateSyntheticDatabase(kNumImages, *database); const std::vector images = database->ReadAllImages(); CHECK_EQ(images.size(), kNumImages); PosePrior pose_prior1; pose_prior1.corr_data_id = images[0].DataId(); pose_prior1.position = Eigen::Vector3d(1, 2, 3) + Eigen::Vector3d::Constant(1e16); database->WritePosePrior(pose_prior1); PosePrior pose_prior2; pose_prior2.corr_data_id = images[1].DataId(); pose_prior2.position = Eigen::Vector3d(2, 3, 4) + Eigen::Vector3d::Constant(1e16); database->WritePosePrior(pose_prior2); PosePrior pose_prior3; pose_prior3.corr_data_id = images[2].DataId(); pose_prior3.position = Eigen::Vector3d(2, 4, 12) + Eigen::Vector3d::Constant(1e16); database->WritePosePrior(pose_prior3); SpatialPairingOptions options; options.max_num_neighbors = 1; options.max_distance = 1000; options.ignore_z = false; SpatialPairGenerator generator(options, database); EXPECT_THAT(generator.Next(), testing::ElementsAre( std::make_pair(images[0].ImageId(), images[1].ImageId()))); EXPECT_THAT(generator.Next(), testing::ElementsAre( std::make_pair(images[1].ImageId(), images[0].ImageId()))); EXPECT_THAT(generator.Next(), testing::ElementsAre( std::make_pair(images[2].ImageId(), images[1].ImageId()))); EXPECT_TRUE(generator.Next().empty()); EXPECT_TRUE(generator.HasFinished()); } TEST(SpatialPairGenerator, CentersLargeCoordinatesWithMissingPosePrior) { // Verifies that images with missing pose priors do not bias the internal // offset applied to position priors during spatial matching, i.e. by // including rows of zeros in the average position calculation. constexpr int kNumImages = 4; auto database = Database::Open(kInMemorySqliteDatabasePath); CreateSyntheticDatabase(kNumImages, *database); const std::vector images = database->ReadAllImages(); CHECK_EQ(images.size(), kNumImages); // Add pose priors for 3 of the 4 images, with large coordinate values. database->ClearPosePriors(); const Eigen::Vector3d offset(1'600'000, 5'400'000, 100); PosePrior pose_prior1; pose_prior1.corr_data_id = images[0].DataId(); pose_prior1.position = offset + Eigen::Vector3d(-1, -2, -3); pose_prior1.coordinate_system = PosePrior::CoordinateSystem::CARTESIAN; database->WritePosePrior(pose_prior1); PosePrior pose_prior2; pose_prior2.corr_data_id = images[1].DataId(); pose_prior2.position = offset + Eigen::Vector3d(0, 0, 0); pose_prior2.coordinate_system = PosePrior::CoordinateSystem::CARTESIAN; database->WritePosePrior(pose_prior2); PosePrior pose_prior4; pose_prior4.corr_data_id = images[3].DataId(); pose_prior4.position = offset + Eigen::Vector3d(1, 2, 3); pose_prior4.coordinate_system = PosePrior::CoordinateSystem::CARTESIAN; database->WritePosePrior(pose_prior4); // Read the position prior data, with the expectation that positions will be // centered automatically around a local origin. SpatialPairingOptions options; options.ignore_z = false; auto cache = std::make_shared( options.CacheSize(), THROW_CHECK_NOTNULL(database)); SpatialPairGenerator generator(options, cache); const Eigen::RowMajorMatrixXf position_matrix = generator.ReadPositionPriorData(*cache); // Verify that the missing pose prior did not bias the calculated offset. Eigen::RowMajorMatrixXf expected_position_matrix(3, 3); expected_position_matrix << -1, -2, -3, 0, 0, 0, 1, 2, 3; EXPECT_THAT(position_matrix, EigenMatrixNear(expected_position_matrix)); } TEST(SpatialPairGenerator, MinNumNeighborsControlsMatchingDistance) { constexpr int kNumImages = 4; auto database = Database::Open(kInMemorySqliteDatabasePath); CreateSyntheticDatabase(kNumImages, *database); const auto images = database->ReadAllImages(); PosePrior pose_prior1; pose_prior1.corr_data_id = images[0].DataId(); pose_prior1.position = Eigen::Vector3d(1, 1, 2); database->WritePosePrior(pose_prior1); PosePrior pose_prior2; pose_prior2.corr_data_id = images[1].DataId(); pose_prior2.position = Eigen::Vector3d(1, 2, 3); database->WritePosePrior(pose_prior2); PosePrior pose_prior3; pose_prior3.corr_data_id = images[2].DataId(); pose_prior3.position = Eigen::Vector3d(2, 3, 4); database->WritePosePrior(pose_prior3); PosePrior pose_prior4; pose_prior4.corr_data_id = images[3].DataId(); pose_prior4.position = Eigen::Vector3d(2, 4, 12); database->WritePosePrior(pose_prior4); SpatialPairingOptions options; options.ignore_z = false; options.max_num_neighbors = kNumImages; options.max_distance = 0.0; { options.min_num_neighbors = 0; EXPECT_FALSE(options.Check()); } { options.min_num_neighbors = 1; SpatialPairGenerator generator(options, database); EXPECT_THAT(generator.Next(), testing::ElementsAre( std::make_pair(images[0].ImageId(), images[1].ImageId()))); EXPECT_THAT(generator.Next(), testing::ElementsAre( std::make_pair(images[1].ImageId(), images[0].ImageId()))); EXPECT_THAT(generator.Next(), testing::ElementsAre( std::make_pair(images[2].ImageId(), images[1].ImageId()))); EXPECT_THAT(generator.Next(), testing::ElementsAre( std::make_pair(images[3].ImageId(), images[2].ImageId()))); EXPECT_TRUE(generator.Next().empty()); } { options.min_num_neighbors = 2; SpatialPairGenerator generator(options, database); EXPECT_THAT(generator.Next(), testing::ElementsAre( std::make_pair(images[0].ImageId(), images[1].ImageId()), std::make_pair(images[0].ImageId(), images[2].ImageId()))); EXPECT_THAT(generator.Next(), testing::ElementsAre( std::make_pair(images[1].ImageId(), images[0].ImageId()), std::make_pair(images[1].ImageId(), images[2].ImageId()))); EXPECT_THAT(generator.Next(), testing::ElementsAre( std::make_pair(images[2].ImageId(), images[1].ImageId()), std::make_pair(images[2].ImageId(), images[0].ImageId()))); EXPECT_THAT(generator.Next(), testing::ElementsAre( std::make_pair(images[3].ImageId(), images[2].ImageId()), std::make_pair(images[3].ImageId(), images[1].ImageId()))); EXPECT_TRUE(generator.Next().empty()); } { options.min_num_neighbors = 3; SpatialPairGenerator generator(options, database); EXPECT_THAT(generator.Next(), testing::ElementsAre( std::make_pair(images[0].ImageId(), images[1].ImageId()), std::make_pair(images[0].ImageId(), images[2].ImageId()), std::make_pair(images[0].ImageId(), images[3].ImageId()))); EXPECT_THAT(generator.Next(), testing::ElementsAre( std::make_pair(images[1].ImageId(), images[0].ImageId()), std::make_pair(images[1].ImageId(), images[2].ImageId()), std::make_pair(images[1].ImageId(), images[3].ImageId()))); EXPECT_THAT(generator.Next(), testing::ElementsAre( std::make_pair(images[2].ImageId(), images[1].ImageId()), std::make_pair(images[2].ImageId(), images[0].ImageId()), std::make_pair(images[2].ImageId(), images[3].ImageId()))); EXPECT_THAT(generator.Next(), testing::ElementsAre( std::make_pair(images[3].ImageId(), images[2].ImageId()), std::make_pair(images[3].ImageId(), images[1].ImageId()), std::make_pair(images[3].ImageId(), images[0].ImageId()))); EXPECT_TRUE(generator.Next().empty()); } } TEST(SpatialPairGenerator, ReadPositionPriorData) { { constexpr int kNumImages = 3; auto database = Database::Open(kInMemorySqliteDatabasePath); CreateSyntheticDatabase(kNumImages, *database); const std::vector images = database->ReadAllImages(); CHECK_EQ(images.size(), kNumImages); PosePrior pose_prior1; pose_prior1.corr_data_id = images[0].DataId(); pose_prior1.position = Eigen::Vector3d(1, 2, 3); database->WritePosePrior(pose_prior1); PosePrior pose_prior2; pose_prior2.corr_data_id = images[1].DataId(); pose_prior2.position = Eigen::Vector3d(2, 3, 4); database->WritePosePrior(pose_prior2); PosePrior pose_prior3; pose_prior3.corr_data_id = images[2].DataId(); pose_prior3.position = Eigen::Vector3d(2, 4, 12); database->WritePosePrior(pose_prior3); SpatialPairingOptions options; options.max_num_neighbors = 1; options.max_distance = 1000; options.ignore_z = false; auto cache = std::make_shared( options.CacheSize(), THROW_CHECK_NOTNULL(database)); SpatialPairGenerator generator(options, cache); Eigen::RowMajorMatrixXf position_matrix = generator.ReadPositionPriorData(*cache); EXPECT_EQ(position_matrix.rows(), 3); } { // Test that the position prior data is read correctly when some images // don't have a pose prior. constexpr int kNumImages = 4; auto database = Database::Open(kInMemorySqliteDatabasePath); CreateSyntheticDatabase(kNumImages, *database); const std::vector images = database->ReadAllImages(); CHECK_EQ(images.size(), kNumImages); database->ClearPosePriors(); PosePrior pose_prior1; pose_prior1.corr_data_id = images[0].DataId(); pose_prior1.position = Eigen::Vector3d(1, 2, 3); database->WritePosePrior(pose_prior1); PosePrior pose_prior2; pose_prior2.corr_data_id = images[1].DataId(); pose_prior2.position = Eigen::Vector3d(2, 3, 4); database->WritePosePrior(pose_prior2); PosePrior pose_prior4; pose_prior4.corr_data_id = images[3].DataId(); pose_prior4.position = Eigen::Vector3d(2, 4, 12); database->WritePosePrior(pose_prior4); SpatialPairingOptions options; options.max_num_neighbors = 1; options.max_distance = 1000; options.ignore_z = false; auto cache = std::make_shared( options.CacheSize(), THROW_CHECK_NOTNULL(database)); SpatialPairGenerator generator(options, cache); Eigen::RowMajorMatrixXf position_matrix = generator.ReadPositionPriorData(*cache); EXPECT_EQ(position_matrix.rows(), 3); } { constexpr int kNumImages = 3; auto database = Database::Open(kInMemorySqliteDatabasePath); CreateSyntheticDatabase(kNumImages, *database); const std::vector images = database->ReadAllImages(); CHECK_EQ(images.size(), kNumImages); PosePrior pose_prior1; pose_prior1.corr_data_id = images[0].DataId(); pose_prior1.position = Eigen::Vector3d(0, 0, std::numeric_limits::quiet_NaN()); database->WritePosePrior(pose_prior1); PosePrior pose_prior2; pose_prior2.corr_data_id = images[1].DataId(); pose_prior2.position = Eigen::Vector3d(2, 3, 4); database->WritePosePrior(pose_prior2); PosePrior pose_prior3; pose_prior3.corr_data_id = images[2].DataId(); pose_prior3.position = Eigen::Vector3d(2, 4, 12); database->WritePosePrior(pose_prior3); SpatialPairingOptions options; options.max_num_neighbors = 1; options.max_distance = 1000; options.ignore_z = false; auto cache = std::make_shared( options.CacheSize(), THROW_CHECK_NOTNULL(database)); SpatialPairGenerator generator(options, cache); Eigen::RowMajorMatrixXf position_matrix = generator.ReadPositionPriorData(*cache); EXPECT_EQ(position_matrix.rows(), 2); } { constexpr int kNumImages = 3; auto database = Database::Open(kInMemorySqliteDatabasePath); CreateSyntheticDatabase(kNumImages, *database); const std::vector images = database->ReadAllImages(); CHECK_EQ(images.size(), kNumImages); PosePrior pose_prior1; pose_prior1.corr_data_id = images[0].DataId(); pose_prior1.position = Eigen::Vector3d(0, 0, std::numeric_limits::quiet_NaN()); database->WritePosePrior(pose_prior1); PosePrior pose_prior2; pose_prior2.corr_data_id = images[1].DataId(); pose_prior2.position = Eigen::Vector3d(2, 3, 4); database->WritePosePrior(pose_prior2); PosePrior pose_prior3; pose_prior3.corr_data_id = images[2].DataId(); pose_prior3.position = Eigen::Vector3d(2, 4, 12); database->WritePosePrior(pose_prior3); SpatialPairingOptions options; options.max_num_neighbors = 1; options.max_distance = 1000; options.ignore_z = true; auto cache = std::make_shared( options.CacheSize(), THROW_CHECK_NOTNULL(database)); SpatialPairGenerator generator(options, cache); Eigen::RowMajorMatrixXf position_matrix = generator.ReadPositionPriorData(*cache); EXPECT_EQ(position_matrix.rows(), 3); } { constexpr int kNumImages = 3; auto database = Database::Open(kInMemorySqliteDatabasePath); CreateSyntheticDatabase(kNumImages, *database); const std::vector images = database->ReadAllImages(); CHECK_EQ(images.size(), kNumImages); PosePrior pose_prior1; pose_prior1.corr_data_id = images[0].DataId(); pose_prior1.position = Eigen::Vector3d(std::numeric_limits::quiet_NaN(), std::numeric_limits::quiet_NaN(), std::numeric_limits::quiet_NaN()); database->WritePosePrior(pose_prior1); PosePrior pose_prior2; pose_prior2.corr_data_id = images[1].DataId(); pose_prior2.position = Eigen::Vector3d(2, 3, 4); database->WritePosePrior(pose_prior2); PosePrior pose_prior3; pose_prior3.corr_data_id = images[2].DataId(); pose_prior3.position = Eigen::Vector3d(2, 4, 12); database->WritePosePrior(pose_prior3); SpatialPairingOptions options; options.max_num_neighbors = 1; options.max_distance = 1000; options.ignore_z = false; auto cache = std::make_shared( options.CacheSize(), THROW_CHECK_NOTNULL(database)); SpatialPairGenerator generator(options, cache); Eigen::RowMajorMatrixXf position_matrix = generator.ReadPositionPriorData(*cache); EXPECT_EQ(position_matrix.rows(), 2); } { constexpr int kNumImages = 3; auto database = Database::Open(kInMemorySqliteDatabasePath); CreateSyntheticDatabase(kNumImages, *database); const std::vector images = database->ReadAllImages(); CHECK_EQ(images.size(), kNumImages); PosePrior pose_prior1; pose_prior1.corr_data_id = images[0].DataId(); pose_prior1.position = Eigen::Vector3d(std::numeric_limits::quiet_NaN(), std::numeric_limits::quiet_NaN(), std::numeric_limits::quiet_NaN()); database->WritePosePrior(pose_prior1); PosePrior pose_prior2; pose_prior2.corr_data_id = images[1].DataId(); pose_prior2.position = Eigen::Vector3d(2, 3, 4); database->WritePosePrior(pose_prior2); PosePrior pose_prior3; pose_prior3.corr_data_id = images[2].DataId(); pose_prior3.position = Eigen::Vector3d(2, 4, 12); database->WritePosePrior(pose_prior3); SpatialPairingOptions options; options.max_num_neighbors = 1; options.max_distance = 1000; options.ignore_z = true; auto cache = std::make_shared( options.CacheSize(), THROW_CHECK_NOTNULL(database)); SpatialPairGenerator generator(options, cache); Eigen::RowMajorMatrixXf position_matrix = generator.ReadPositionPriorData(*cache); EXPECT_EQ(position_matrix.rows(), 2); } } TEST(TransitivePairGenerator, Nominal) { constexpr int kNumImages = 5; auto database = Database::Open(kInMemorySqliteDatabasePath); CreateSyntheticDatabase(kNumImages, *database); const std::vector images = database->ReadAllImages(); CHECK_EQ(images.size(), kNumImages); TwoViewGeometry two_view_geometry; two_view_geometry.inlier_matches.resize(10); database->ClearTwoViewGeometries(); database->WriteTwoViewGeometry( images[0].ImageId(), images[1].ImageId(), two_view_geometry); database->WriteTwoViewGeometry( images[0].ImageId(), images[2].ImageId(), two_view_geometry); database->WriteTwoViewGeometry( images[1].ImageId(), images[3].ImageId(), two_view_geometry); TransitivePairingOptions options; TransitivePairGenerator generator(options, database); const auto pairs1 = generator.Next(); EXPECT_THAT(pairs1, testing::UnorderedElementsAre( std::make_pair(images[1].ImageId(), images[2].ImageId()), std::make_pair(images[0].ImageId(), images[3].ImageId()))); for (const auto& pair : pairs1) { database->WriteTwoViewGeometry(pair.first, pair.second, two_view_geometry); } EXPECT_THAT(generator.Next(), testing::ElementsAre( std::make_pair(images[2].ImageId(), images[3].ImageId()))); EXPECT_TRUE(generator.Next().empty()); EXPECT_TRUE(generator.HasFinished()); } TEST(ImportedPairGenerator, Nominal) { constexpr int kNumImages = 10; auto database = Database::Open(kInMemorySqliteDatabasePath); CreateSyntheticDatabase(kNumImages, *database); const std::vector images = database->ReadAllImages(); CHECK_EQ(images.size(), kNumImages); ImportedPairingOptions options; options.match_list_path = CreateTestDir() / "pairs.txt"; { std::ofstream match_list_file(options.match_list_path); match_list_file.close(); ImportedPairGenerator generator(options, database); EXPECT_TRUE(generator.Next().empty()); EXPECT_TRUE(generator.HasFinished()); } { std::ofstream match_list_file(options.match_list_path); match_list_file << images[2].Name() << " " << images[4].Name() << '\n'; match_list_file << images[1].Name() << " " << images[3].Name() << '\n'; match_list_file << images[2].Name() << " " << images[9].Name() << '\n'; match_list_file.close(); ImportedPairGenerator generator(options, database); EXPECT_THAT(generator.Next(), testing::ElementsAre( std::make_pair(images[2].ImageId(), images[4].ImageId()), std::make_pair(images[1].ImageId(), images[3].ImageId()), std::make_pair(images[2].ImageId(), images[9].ImageId()))); EXPECT_TRUE(generator.Next().empty()); EXPECT_TRUE(generator.HasFinished()); } } TEST(ExistingMatchedPairGenerator, Nominal) { constexpr int kNumImages = 5; auto database = Database::Open(kInMemorySqliteDatabasePath); CreateSyntheticDatabase(kNumImages, *database); const std::vector images = database->ReadAllImages(); CHECK_EQ(images.size(), kNumImages); database->ClearMatches(); database->WriteMatches( images[0].ImageId(), images[1].ImageId(), FeatureMatches(1)); database->WriteMatches( images[0].ImageId(), images[2].ImageId(), FeatureMatches(2)); database->WriteMatches( images[1].ImageId(), images[3].ImageId(), FeatureMatches(3)); database->WriteMatches( images[2].ImageId(), images[3].ImageId(), FeatureMatches(0)); ExistingMatchedPairingOptions options; options.batch_size = 2; ExistingMatchedPairGenerator generator(options, database); EXPECT_THAT(generator.Next(), testing::UnorderedElementsAre( std::make_pair(images[0].ImageId(), images[1].ImageId()), std::make_pair(images[0].ImageId(), images[2].ImageId()))); EXPECT_THAT(generator.Next(), testing::UnorderedElementsAre( std::make_pair(images[1].ImageId(), images[3].ImageId()))); EXPECT_TRUE(generator.Next().empty()); EXPECT_TRUE(generator.HasFinished()); } } // namespace } // namespace colmap colmap-4.2.0/src/colmap/controllers/reconstruction_clustering.cc000066400000000000000000000135361524536416500252520ustar00rootroot00000000000000// Copyright (c), ETH Zurich and UNC Chapel Hill. // All rights reserved. // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are met: // // * Redistributions of source code must retain the above copyright // notice, this list of conditions and the following disclaimer. // // * Redistributions in binary form must reproduce the above copyright // notice, this list of conditions and the following disclaimer in the // documentation and/or other materials provided with the distribution. // // * Neither the name of ETH Zurich and UNC Chapel Hill nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE // ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE // LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR // CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF // SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS // INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN // CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) // ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE // POSSIBILITY OF SUCH DAMAGE. #include "colmap/controllers/reconstruction_clustering.h" #include "colmap/scene/reconstruction_clustering.h" #include "colmap/util/hash_containers.h" #include "colmap/util/logging.h" #include "colmap/util/misc.h" #include "colmap/util/timer.h" namespace colmap { namespace { // Extract a subset of the reconstruction for a specific cluster. // Returns a new Reconstruction containing only frames/images/points from the // specified cluster. std::shared_ptr SubReconstructionByClusterId( const Reconstruction& reconstruction, const NodeHashMap& cluster_ids, int cluster_id) { // Helper to get cluster id for a frame auto get_cluster_id = [&cluster_ids](frame_t frame_id) -> int { auto it = cluster_ids.find(frame_id); return it != cluster_ids.end() ? it->second : -1; }; // Make a copy of the reconstruction auto filtered = std::make_shared(reconstruction); // Collect frames to deregister (those not in this cluster) std::vector frames_to_deregister; for (const auto& [frame_id, frame] : filtered->Frames()) { if (!frame.HasPose() || get_cluster_id(frame_id) != cluster_id) { frames_to_deregister.push_back(frame_id); } } // Deregister frames not in this cluster // This also removes point observations from those frames' images for (frame_t frame_id : frames_to_deregister) { if (filtered->Frame(frame_id).HasPose()) { filtered->DeRegisterFrame(frame_id); } } filtered->UpdatePoint3DErrors(); return filtered; } } // namespace ReconstructionClustererController::ReconstructionClustererController( const ReconstructionClusteringOptions& options, std::shared_ptr reconstruction, std::shared_ptr reconstruction_manager) : options_(options), reconstruction_(std::move(reconstruction)), reconstruction_manager_(std::move(reconstruction_manager)) {} void ReconstructionClustererController::Run() { THROW_CHECK_NOTNULL(reconstruction_); THROW_CHECK_NOTNULL(reconstruction_manager_); LOG_HEADING1("Pruning weakly connected frames"); Timer timer; timer.Start(); NodeHashMap cluster_ids = ClusterReconstructionFrames(options_, *reconstruction_); LOG(INFO) << "Pruning done in " << timer.ElapsedSeconds() << " seconds"; LOG(INFO) << "Number of frames after pruning: " << reconstruction_->NumRegFrames(); // Find max cluster id int max_cluster_id = -1; for (const auto& [frame_id, cluster_id] : cluster_ids) { if (cluster_id > max_cluster_id) { max_cluster_id = cluster_id; } } // Clear any existing reconstructions reconstruction_manager_->Clear(); // If no clusters (or single cluster), add the single reconstruction // Note that cluster_id start from 0, so max_cluster_id of -1 means no // clusters if (max_cluster_id < 0) { if (reconstruction_->NumRegFrames() >= static_cast(options_.min_num_reg_frames)) { size_t idx = reconstruction_manager_->Add(); *reconstruction_manager_->Get(idx) = *reconstruction_; } else { LOG(WARNING) << "Reconstruction has only " << reconstruction_->NumRegFrames() << " registered frames, below minimum threshold of " << options_.min_num_reg_frames; } } else { // For invalid frames, clusters ids are -1 and are skipped automatically // Split by cluster and add multiple reconstructions for (int comp = 0; comp <= max_cluster_id; comp++) { std::shared_ptr cluster_reconstruction = SubReconstructionByClusterId(*reconstruction_, cluster_ids, comp); THROW_CHECK_GE( cluster_reconstruction->NumRegFrames(), static_cast( options_.min_num_reg_frames)); // Should always be true const size_t num_reg_frames = cluster_reconstruction->NumRegFrames(); size_t idx = reconstruction_manager_->Add(); reconstruction_manager_->Get(idx) = std::move(cluster_reconstruction); LOG(INFO) << "Added cluster " << comp << " with " << num_reg_frames << " registered frames"; } LOG(INFO) << "Created " << reconstruction_manager_->Size() << " cluster reconstructions"; } } } // namespace colmap colmap-4.2.0/src/colmap/controllers/reconstruction_clustering.h000066400000000000000000000052721524536416500251120ustar00rootroot00000000000000// Copyright (c), ETH Zurich and UNC Chapel Hill. // All rights reserved. // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are met: // // * Redistributions of source code must retain the above copyright // notice, this list of conditions and the following disclaimer. // // * Redistributions in binary form must reproduce the above copyright // notice, this list of conditions and the following disclaimer in the // documentation and/or other materials provided with the distribution. // // * Neither the name of ETH Zurich and UNC Chapel Hill nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE // ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE // LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR // CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF // SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS // INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN // CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) // ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE // POSSIBILITY OF SUCH DAMAGE. #pragma once #include "colmap/scene/reconstruction.h" #include "colmap/scene/reconstruction_clustering.h" #include "colmap/scene/reconstruction_manager.h" #include "colmap/util/base_controller.h" #include namespace colmap { // Controller that clusters frames from a reconstruction // and splits it into multiple reconstructions based on clustering. // Note: this module is experimental and should be verified carefully // before use in production pipelines. class ReconstructionClustererController : public BaseController { public: ReconstructionClustererController( const ReconstructionClusteringOptions& options, std::shared_ptr reconstruction, std::shared_ptr reconstruction_manager); // Runs the pruning and clustering algorithm. // Results are stored in the reconstruction manager passed to the constructor. void Run() override; private: const ReconstructionClusteringOptions options_; std::shared_ptr reconstruction_; std::shared_ptr reconstruction_manager_; }; } // namespace colmap colmap-4.2.0/src/colmap/controllers/reconstruction_clustering_test.cc000066400000000000000000000266721524536416500263160ustar00rootroot00000000000000// Copyright (c), ETH Zurich and UNC Chapel Hill. // All rights reserved. // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are met: // // * Redistributions of source code must retain the above copyright // notice, this list of conditions and the following disclaimer. // // * Redistributions in binary form must reproduce the above copyright // notice, this list of conditions and the following disclaimer in the // documentation and/or other materials provided with the distribution. // // * Neither the name of ETH Zurich and UNC Chapel Hill nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE // ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE // LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR // CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF // SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS // INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN // CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) // ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE // POSSIBILITY OF SUCH DAMAGE. #include "colmap/controllers/reconstruction_clustering.h" #include "colmap/math/random.h" #include "colmap/scene/synthetic.h" #include "colmap/util/hash_containers.h" #include namespace colmap { namespace { // Creates a reconstruction with two weakly connected clusters. // The reconstruction is synthesized with `num_frames` frames, then split into // two clusters by removing cross-cluster observations, keeping only // `num_weak_links` 3D points that connect both clusters. void CreateTwoWeaklyConnectedClusters(Reconstruction* reconstruction, int num_frames, int num_points3D, int num_weak_links) { SyntheticDatasetOptions synthetic_options; synthetic_options.num_rigs = 1; synthetic_options.num_cameras_per_rig = 1; synthetic_options.num_frames_per_rig = num_frames; synthetic_options.num_points3D = num_points3D; synthetic_options.match_config = SyntheticDatasetOptions::MatchConfig::EXHAUSTIVE; SynthesizeDataset(synthetic_options, reconstruction); // Collect all frame IDs and split them into two clusters std::vector all_frames; for (const auto& [frame_id, frame] : reconstruction->Frames()) { if (frame.HasPose()) { all_frames.push_back(frame_id); } } std::sort(all_frames.begin(), all_frames.end()); const size_t half = all_frames.size() / 2; FlatHashSet cluster1_frames(all_frames.begin(), all_frames.begin() + half); FlatHashSet cluster2_frames(all_frames.begin() + half, all_frames.end()); // For each 3D point, randomly assign it to one cluster and remove all // observations from the other cluster. Keep a few points as weak links. std::vector points_to_delete; int weak_link_count = 0; for (auto& [point3D_id, point3D] : reconstruction->Points3D()) { std::vector> cluster1_obs; std::vector> cluster2_obs; for (const auto& elem : point3D.track.Elements()) { const frame_t frame_id = reconstruction->Image(elem.image_id).FrameId(); if (cluster1_frames.count(frame_id)) { cluster1_obs.emplace_back(elem.image_id, elem.point2D_idx); } else if (cluster2_frames.count(frame_id)) { cluster2_obs.emplace_back(elem.image_id, elem.point2D_idx); } } // If the point has observations in both clusters if (!cluster1_obs.empty() && !cluster2_obs.empty()) { // Keep a few points as weak links (with observations in both clusters) if (weak_link_count < num_weak_links) { weak_link_count++; continue; } // Randomly assign this point to one cluster bool assign_to_cluster1 = (RandomUniformInteger(0, 1) == 0); const auto& obs_to_remove = assign_to_cluster1 ? cluster2_obs : cluster1_obs; for (const auto& [image_id, point2D_idx] : obs_to_remove) { reconstruction->DeleteObservation(image_id, point2D_idx); } // If the track is now too short, mark for deletion if (point3D.track.Length() < 2) { points_to_delete.push_back(point3D_id); } } } // Delete points with insufficient observations for (point3D_t point3D_id : points_to_delete) { if (reconstruction->ExistsPoint3D(point3D_id)) { reconstruction->DeletePoint3D(point3D_id); } } } TEST(ReconstructionClustererController, EmptyReconstruction) { auto reconstruction = std::make_shared(); auto reconstruction_manager = std::make_shared(); ReconstructionClusteringOptions options; ReconstructionClustererController controller( options, reconstruction, reconstruction_manager); EXPECT_NO_THROW(controller.Run()); // Empty reconstruction should result in no output reconstructions EXPECT_EQ(reconstruction_manager->Size(), 0); } TEST(ReconstructionClustererController, SingleCluster) { auto reconstruction = std::make_shared(); // Create a synthetic dataset with well-connected frames SyntheticDatasetOptions synthetic_options; synthetic_options.num_rigs = 1; synthetic_options.num_cameras_per_rig = 1; synthetic_options.num_frames_per_rig = 5; synthetic_options.num_points3D = 200; // More points for better covisibility synthetic_options.match_config = SyntheticDatasetOptions::MatchConfig::EXHAUSTIVE; SynthesizeDataset(synthetic_options, reconstruction.get()); EXPECT_EQ(reconstruction->NumRegFrames(), 5); auto reconstruction_manager = std::make_shared(); // Use relaxed clustering options to ensure all frames stay connected ReconstructionClusteringOptions options; ReconstructionClustererController controller( options, reconstruction, reconstruction_manager); // Controller should run without crashing EXPECT_NO_THROW(controller.Run()); EXPECT_EQ(reconstruction_manager->Size(), 1); } TEST(ReconstructionClustererController, SingleClusterWithOutlierFrames) { auto reconstruction = std::make_shared(); // Create a well-connected reconstruction with more frames SyntheticDatasetOptions synthetic_options; synthetic_options.num_rigs = 1; synthetic_options.num_cameras_per_rig = 1; synthetic_options.num_frames_per_rig = 10; synthetic_options.num_points3D = 500; synthetic_options.match_config = SyntheticDatasetOptions::MatchConfig::EXHAUSTIVE; SynthesizeDataset(synthetic_options, reconstruction.get()); const size_t total_frames = reconstruction->NumRegFrames(); EXPECT_EQ(total_frames, 10); // Select the last 3 frames as outliers and remove all their 3D point // observations, making them isolated from the main cluster constexpr int kNumOutliers = 3; std::vector all_frame_ids(reconstruction->RegFrameIds().begin(), reconstruction->RegFrameIds().end()); std::sort(all_frame_ids.begin(), all_frame_ids.end()); FlatHashSet outlier_frame_ids; for (size_t i = total_frames - kNumOutliers; i < total_frames; i++) { outlier_frame_ids.insert(all_frame_ids[i]); } // Remove all 3D point observations from outlier frames for (const frame_t outlier_frame_id : outlier_frame_ids) { const Frame& frame = reconstruction->Frame(outlier_frame_id); for (const data_t& data_id : frame.ImageIds()) { const image_t image_id = data_id.id; Image& image = reconstruction->Image(image_id); const auto num_points2D = image.NumPoints2D(); for (point2D_t point2D_idx = 0; point2D_idx < num_points2D; ++point2D_idx) { if (image.Point2D(point2D_idx).HasPoint3D()) { reconstruction->DeleteObservation(image_id, point2D_idx); } } } } const size_t main_cluster_frames = total_frames - kNumOutliers; auto reconstruction_manager = std::make_shared(); ReconstructionClusteringOptions options; options.min_num_reg_frames = 3; ReconstructionClustererController controller( options, reconstruction, reconstruction_manager); controller.Run(); // Should produce exactly one cluster containing only the main frames // The outlier frames should be filtered out (cluster_id = -1) because // they have no covisibility edges with any other frames EXPECT_EQ(reconstruction_manager->Size(), 1) << "Expected single cluster after filtering outliers"; // The single cluster should contain only the main cluster frames EXPECT_EQ(reconstruction_manager->Get(0)->NumRegFrames(), main_cluster_frames) << "Outlier frames should not be included in the output reconstruction"; } TEST(ReconstructionClustererController, MinNumRegFramesFilter) { auto reconstruction = std::make_shared(); // Create a small synthetic dataset SyntheticDatasetOptions synthetic_options; synthetic_options.num_rigs = 1; synthetic_options.num_cameras_per_rig = 1; synthetic_options.num_frames_per_rig = 2; synthetic_options.num_points3D = 20; SynthesizeDataset(synthetic_options, reconstruction.get()); auto reconstruction_manager = std::make_shared(); // Set min_num_reg_frames higher than the number of frames ReconstructionClusteringOptions options; options.min_num_reg_frames = 5; ReconstructionClustererController controller( options, reconstruction, reconstruction_manager); controller.Run(); // Should be filtered out due to min_num_reg_frames threshold EXPECT_EQ(reconstruction_manager->Size(), 0); } TEST(ReconstructionClustererController, TwoWeaklyConnectedClusters) { auto reconstruction = std::make_shared(); // Create a reconstruction with two clusters connected by only 10 weak links constexpr int kNumFrames = 10; constexpr int kNumPoints3D = 500; constexpr int kNumWeakLinks = 10; CreateTwoWeaklyConnectedClusters( reconstruction.get(), kNumFrames, kNumPoints3D, kNumWeakLinks); EXPECT_EQ(reconstruction->NumRegFrames(), kNumFrames); auto reconstruction_manager = std::make_shared(); // Use default clustering options - the algorithm should detect the weak // connection and split into two clusters ReconstructionClusteringOptions options; options.min_num_reg_frames = 3; ReconstructionClustererController controller( options, reconstruction, reconstruction_manager); controller.Run(); // The algorithm should produce two separate reconstructions EXPECT_EQ(reconstruction_manager->Size(), 2) << "Expected two clusters from weakly connected reconstruction"; // Check that each cluster has exactly half of the frames const size_t expected_frames_per_cluster = kNumFrames / 2; for (size_t i = 0; i < reconstruction_manager->Size(); i++) { EXPECT_EQ(reconstruction_manager->Get(i)->NumRegFrames(), expected_frames_per_cluster); } } } // namespace } // namespace colmap colmap-4.2.0/src/colmap/controllers/rotation_averaging.cc000066400000000000000000000126631524536416500236140ustar00rootroot00000000000000// Copyright (c), ETH Zurich and UNC Chapel Hill. // All rights reserved. // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are met: // // * Redistributions of source code must retain the above copyright // notice, this list of conditions and the following disclaimer. // // * Redistributions in binary form must reproduce the above copyright // notice, this list of conditions and the following disclaimer in the // documentation and/or other materials provided with the distribution. // // * Neither the name of ETH Zurich and UNC Chapel Hill nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE // ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE // LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR // CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF // SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS // INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN // CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) // ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE // POSSIBILITY OF SUCH DAMAGE. #include "colmap/controllers/rotation_averaging.h" #include "colmap/estimators/gravity_refinement.h" #include "colmap/estimators/rotation_averaging.h" #include "colmap/estimators/two_view_geometry.h" #include "colmap/geometry/pose.h" #include "colmap/scene/pose_graph.h" #include "colmap/util/hash_containers.h" #include "colmap/util/logging.h" #include "colmap/util/misc.h" #include "colmap/util/timer.h" #include namespace colmap { RotationAveragingPipeline::RotationAveragingPipeline( const RotationAveragingPipelineOptions& options, std::shared_ptr database, std::shared_ptr reconstruction) : options_(options), reconstruction_(std::move(THROW_CHECK_NOTNULL(reconstruction))) { THROW_CHECK_NOTNULL(database); LOG(INFO) << "Loading database"; DatabaseCache::Options database_cache_options; database_cache_options.min_num_matches = options_.min_num_matches; database_cache_options.ignore_watermarks = options_.ignore_watermarks; database_cache_options.image_names = {options_.image_names.begin(), options_.image_names.end()}; database_cache_ = DatabaseCache::Create(*database, database_cache_options); if (options_.decompose_relative_pose) { MaybeDecomposeRelativePoses(database_cache_.get()); } } void RotationAveragingPipeline::Run() { // Propagate options to component options. RotationAveragingPipelineOptions options = options_; options.rotation_estimation.random_seed = options.random_seed; options.gravity_refiner.solver_options.num_threads = options.num_threads; Timer run_timer; run_timer.Start(); // Load reconstruction and pose graph from database cache. reconstruction_->Load(*database_cache_); PoseGraph pose_graph; pose_graph.Load(*database_cache_->CorrespondenceGraph()); if (pose_graph.Empty()) { LOG(ERROR) << "Cannot continue without image pairs"; return; } // Get a mutable copy of pose priors. std::vector pose_priors = database_cache_->PosePriors(); // Initialize frame rotations from gravity priors. const Eigen::Vector3d kUnknownTranslation = Eigen::Vector3d::Constant(std::numeric_limits::quiet_NaN()); for (const auto& pose_prior : pose_priors) { if (!pose_prior.HasGravity()) { continue; } const auto& image = reconstruction_->Image(pose_prior.pose_prior_id); if (!image.IsRefInFrame()) { continue; } reconstruction_->Frame(image.FrameId()) .SetRigFromWorld(Rigid3d( Eigen::Quaterniond(GravityAlignedRotation(pose_prior.gravity)), kUnknownTranslation)); } // Optionally refine gravity priors (only if gravity priors exist). if (options.refine_gravity && !pose_priors.empty()) { // Compute largest connected component and invalidate pairs before gravity // refinement. const FlatHashSet active_frame_ids = pose_graph.LargestConnectedFrameComponent( *reconstruction_, /*filter_unregistered=*/false); FlatHashSet active_image_ids; for (const auto& [image_id, image] : reconstruction_->Images()) { if (active_frame_ids.count(image.FrameId())) { active_image_ids.insert(image_id); } } pose_graph.InvalidatePairsOutsideActiveImageIds(active_image_ids); LOG_HEADING1("Running gravity refinement"); RunGravityRefinement( options.gravity_refiner, pose_graph, *reconstruction_, pose_priors); } LOG_HEADING1("Running rotation averaging"); if (!RunRotationAveraging(options.rotation_estimation, pose_graph, *reconstruction_, pose_priors)) { LOG(ERROR) << "Failed to solve rotation averaging"; return; } LOG(INFO) << "Rotation averaging done in " << run_timer.ElapsedSeconds() << "s"; } } // namespace colmap colmap-4.2.0/src/colmap/controllers/rotation_averaging.h000066400000000000000000000065611524536416500234560ustar00rootroot00000000000000// Copyright (c), ETH Zurich and UNC Chapel Hill. // All rights reserved. // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are met: // // * Redistributions of source code must retain the above copyright // notice, this list of conditions and the following disclaimer. // // * Redistributions in binary form must reproduce the above copyright // notice, this list of conditions and the following disclaimer in the // documentation and/or other materials provided with the distribution. // // * Neither the name of ETH Zurich and UNC Chapel Hill nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE // ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE // LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR // CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF // SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS // INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN // CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) // ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE // POSSIBILITY OF SUCH DAMAGE. #pragma once #include "colmap/estimators/gravity_refinement.h" #include "colmap/estimators/rotation_averaging.h" #include "colmap/scene/database_cache.h" #include "colmap/scene/reconstruction.h" #include "colmap/util/base_controller.h" #include #include namespace colmap { struct RotationAveragingPipelineOptions { // The minimum number of matches for inlier matches to be considered. int min_num_matches = 0; // Whether to ignore the inlier matches of watermark image pairs. bool ignore_watermarks = false; // Names of images to reconstruct. If empty, all images are used. std::vector image_names; // Number of threads. int num_threads = -1; // PRNG seed for all stochastic methods during reconstruction. // If -1 (default), the seed is derived from the current time // (non-deterministic). If >= 0, the pipeline is deterministic with the given // seed. int random_seed = -1; // Whether to decompose relative poses from two-view geometries. bool decompose_relative_pose = true; // Whether to refine gravity priors before rotation averaging. bool refine_gravity = false; // Options for gravity refinement. GravityRefinerOptions gravity_refiner; // Options for rotation averaging. RotationEstimatorOptions rotation_estimation; }; class RotationAveragingPipeline : public BaseController { public: RotationAveragingPipeline(const RotationAveragingPipelineOptions& options, std::shared_ptr database, std::shared_ptr reconstruction); void Run() override; private: const RotationAveragingPipelineOptions options_; std::shared_ptr database_cache_; std::shared_ptr reconstruction_; }; } // namespace colmap colmap-4.2.0/src/colmap/controllers/rotation_averaging_test.cc000066400000000000000000000161511524536416500246470ustar00rootroot00000000000000// Copyright (c), ETH Zurich and UNC Chapel Hill. // All rights reserved. // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are met: // // * Redistributions of source code must retain the above copyright // notice, this list of conditions and the following disclaimer. // // * Redistributions in binary form must reproduce the above copyright // notice, this list of conditions and the following disclaimer in the // documentation and/or other materials provided with the distribution. // // * Neither the name of ETH Zurich and UNC Chapel Hill nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE // ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE // LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR // CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF // SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS // INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN // CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) // ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE // POSSIBILITY OF SUCH DAMAGE. #include "colmap/controllers/rotation_averaging.h" #include "colmap/scene/synthetic.h" #include "colmap/util/testing.h" #include namespace colmap { namespace { void ExpectEqualRotations(const Reconstruction& gt, const Reconstruction& computed, const double max_rotation_error_deg) { const double max_rotation_error_rad = DegToRad(max_rotation_error_deg); const std::vector reg_image_ids = gt.RegImageIds(); for (size_t i = 0; i < reg_image_ids.size(); i++) { const image_t image_id1 = reg_image_ids[i]; for (size_t j = 0; j < i; j++) { const image_t image_id2 = reg_image_ids[j]; const Eigen::Quaterniond cam2_from_cam1 = computed.Image(image_id2).CamFromWorld().rotation() * computed.Image(image_id1).CamFromWorld().rotation().inverse(); const Eigen::Quaterniond cam2_from_cam1_gt = gt.Image(image_id2).CamFromWorld().rotation() * gt.Image(image_id1).CamFromWorld().rotation().inverse(); EXPECT_LT(cam2_from_cam1.angularDistance(cam2_from_cam1_gt), max_rotation_error_rad); } } } TEST(RotationAveragingPipeline, WithoutNoise) { const auto database_path = CreateTestDir() / "database.db"; auto database = Database::Open(database_path); Reconstruction gt_reconstruction; SyntheticDatasetOptions synthetic_dataset_options; synthetic_dataset_options.num_rigs = 1; synthetic_dataset_options.num_cameras_per_rig = 1; synthetic_dataset_options.num_frames_per_rig = 5; synthetic_dataset_options.num_points3D = 50; SynthesizeDataset( synthetic_dataset_options, >_reconstruction, database.get()); auto reconstruction = std::make_shared(); RotationAveragingPipelineOptions options; RotationAveragingPipeline controller(options, database, reconstruction); controller.Run(); ExpectEqualRotations(gt_reconstruction, *reconstruction, /*max_rotation_error_deg=*/1e-2); } TEST(RotationAveragingPipeline, WithNoiseAndOutliers) { const auto database_path = CreateTestDir() / "database.db"; auto database = Database::Open(database_path); Reconstruction gt_reconstruction; SyntheticDatasetOptions synthetic_dataset_options; synthetic_dataset_options.num_rigs = 2; synthetic_dataset_options.num_cameras_per_rig = 1; synthetic_dataset_options.num_frames_per_rig = 7; synthetic_dataset_options.num_points3D = 100; synthetic_dataset_options.inlier_match_ratio = 0.6; SynthesizeDataset( synthetic_dataset_options, >_reconstruction, database.get()); SyntheticNoiseOptions synthetic_noise_options; synthetic_noise_options.point2D_stddev = 1; SynthesizeNoise(synthetic_noise_options, >_reconstruction, database.get()); auto reconstruction = std::make_shared(); RotationAveragingPipelineOptions options; RotationAveragingPipeline controller(options, database, reconstruction); controller.Run(); ExpectEqualRotations(gt_reconstruction, *reconstruction, /*max_rotation_error_deg=*/3); } void ExpectExactEqualRotations(const Reconstruction& reconstruction1, const Reconstruction& reconstruction2) { const std::vector reg_image_ids = reconstruction1.RegImageIds(); ASSERT_EQ(reg_image_ids.size(), reconstruction2.RegImageIds().size()); for (const image_t image_id : reg_image_ids) { EXPECT_EQ( reconstruction1.Image(image_id).CamFromWorld().rotation().coeffs(), reconstruction2.Image(image_id).CamFromWorld().rotation().coeffs()); } } TEST(RotationAveragingPipeline, WithRandomSeedStability) { const auto database_path = CreateTestDir() / "database.db"; auto database = Database::Open(database_path); Reconstruction gt_reconstruction; SyntheticDatasetOptions synthetic_dataset_options; synthetic_dataset_options.num_rigs = 2; synthetic_dataset_options.num_cameras_per_rig = 1; synthetic_dataset_options.num_frames_per_rig = 5; synthetic_dataset_options.num_points3D = 100; SynthesizeDataset( synthetic_dataset_options, >_reconstruction, database.get()); SyntheticNoiseOptions synthetic_noise_options; synthetic_noise_options.point2D_stddev = 0.5; SynthesizeNoise(synthetic_noise_options, >_reconstruction, database.get()); auto run_controller = [&](int num_threads, int random_seed) { auto reconstruction = std::make_shared(); RotationAveragingPipelineOptions options; options.num_threads = num_threads; options.random_seed = random_seed; RotationAveragingPipeline controller(options, database, reconstruction); controller.Run(); return reconstruction; }; constexpr int kRandomSeed = 42; // Single-threaded execution. { auto reconstruction0 = run_controller(/*num_threads=*/1, /*random_seed=*/kRandomSeed); auto reconstruction1 = run_controller(/*num_threads=*/1, /*random_seed=*/kRandomSeed); ExpectExactEqualRotations(*reconstruction0, *reconstruction1); } // Multi-threaded execution. { auto reconstruction0 = run_controller(/*num_threads=*/3, /*random_seed=*/kRandomSeed); auto reconstruction1 = run_controller(/*num_threads=*/3, /*random_seed=*/kRandomSeed); // Same seed should produce similar results, up to floating-point variations // in optimization. ExpectEqualRotations(*reconstruction0, *reconstruction1, /*max_rotation_error_deg=*/1e-10); } } } // namespace } // namespace colmap colmap-4.2.0/src/colmap/controllers/undistorters.cc000066400000000000000000000727261524536416500225050ustar00rootroot00000000000000// Copyright (c), ETH Zurich and UNC Chapel Hill. // All rights reserved. // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are met: // // * Redistributions of source code must retain the above copyright // notice, this list of conditions and the following disclaimer. // // * Redistributions in binary form must reproduce the above copyright // notice, this list of conditions and the following disclaimer in the // documentation and/or other materials provided with the distribution. // // * Neither the name of ETH Zurich and UNC Chapel Hill nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE // ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE // LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR // CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF // SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS // INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN // CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) // ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE // POSSIBILITY OF SUCH DAMAGE. #include "colmap/controllers/undistorters.h" #include "colmap/scene/reconstruction_io.h" #include "colmap/sensor/models.h" #include "colmap/util/hash_containers.h" #include "colmap/util/misc.h" #include "colmap/util/threading.h" #include #include #include namespace colmap { namespace { void MaybeSetJpegQuality(const std::filesystem::path& path, Bitmap& bitmap, int jpeg_quality) { if ((HasFileExtension(path, ".jpg") || HasFileExtension(path, ".jpeg")) && jpeg_quality > 0) { bitmap.SetMetaData("Compression", "jpeg:" + std::to_string(jpeg_quality)); } } template void WriteMatrix(const Eigen::MatrixBase& matrix, std::ofstream* file) { using index_t = typename Eigen::MatrixBase::Index; for (index_t r = 0; r < matrix.rows(); ++r) { for (index_t c = 0; c < matrix.cols() - 1; ++c) { *file << matrix(r, c) << " "; } *file << matrix(r, matrix.cols() - 1) << '\n'; } } // Write projection matrix P = K * [R t] to file and prepend given header. void WriteProjectionMatrix(const std::filesystem::path& path, const Camera& camera, const Image& image, const std::string& header) { THROW_CHECK(camera.model_id == PinholeCameraModel::model_id); std::ofstream file(path, std::ios::trunc); THROW_CHECK_FILE_OPEN(file, path); file.imbue(std::locale::classic()); Eigen::Matrix3d calib_matrix = Eigen::Matrix3d::Identity(); calib_matrix(0, 0) = camera.FocalLengthX(); calib_matrix(1, 1) = camera.FocalLengthY(); calib_matrix(0, 2) = camera.PrincipalPointX(); calib_matrix(1, 2) = camera.PrincipalPointY(); const Eigen::Matrix3x4d img_from_world = calib_matrix * image.CamFromWorld().ToMatrix(); if (!header.empty()) { file << header << '\n'; } WriteMatrix(img_from_world, &file); } void WriteCOLMAPCommands(const bool geometric, const std::filesystem::path& workspace_path, const std::string& workspace_format, const std::string& pmvs_option_name, const std::string& output_prefix, const std::string& indent, std::ofstream* file) { if (geometric) { *file << indent << "$COLMAP_EXE_PATH/colmap patch_match_stereo \\\n"; *file << indent << " --workspace_path " << workspace_path << " \\\n"; *file << indent << " --workspace_format " << workspace_format << " \\\n"; if (workspace_format == "PMVS") { *file << indent << " --pmvs_option_name " << pmvs_option_name << " \\\n"; } *file << indent << " --PatchMatchStereo.max_image_size 2000 \\\n"; *file << indent << " --PatchMatchStereo.geom_consistency true\n"; } else { *file << indent << "$COLMAP_EXE_PATH/colmap patch_match_stereo \\\n"; *file << indent << " --workspace_path " << workspace_path << " \\\n"; *file << indent << " --workspace_format " << workspace_format << " \\\n"; if (workspace_format == "PMVS") { *file << indent << " --pmvs_option_name " << pmvs_option_name << " \\\n"; } *file << indent << " --PatchMatchStereo.max_image_size 2000 \\\n"; *file << indent << " --PatchMatchStereo.geom_consistency false\n"; } *file << indent << "$COLMAP_EXE_PATH/colmap stereo_fusion \\\n"; *file << indent << " --workspace_path " << workspace_path << " \\\n"; *file << indent << " --workspace_format " << workspace_format << " \\\n"; if (workspace_format == "PMVS") { *file << indent << " --pmvs_option_name " << pmvs_option_name << " \\\n"; } if (geometric) { *file << indent << " --input_type geometric \\\n"; } else { *file << indent << " --input_type photometric \\\n"; } *file << indent << " --output_path " << workspace_path / (output_prefix + "fused.ply") << " \\\n"; *file << indent << "$COLMAP_EXE_PATH/colmap poisson_mesher \\\n"; *file << indent << " --input_path " << workspace_path / (output_prefix + "fused.ply") << " \\\n"; *file << indent << " --output_path " << workspace_path / (output_prefix + "meshed-poisson.ply") << " \\\n"; *file << indent << "$COLMAP_EXE_PATH/colmap delaunay_mesher \\\n"; *file << indent << " --input_path " << workspace_path / output_prefix << " \\\n"; *file << indent << " --input_type dense \\\n"; *file << indent << " --output_path " << workspace_path / (output_prefix + "meshed-delaunay.ply") << " \\\n"; } } // namespace COLMAPUndistorter::COLMAPUndistorter( Options options, const UndistortCameraOptions& camera_options, const Reconstruction& reconstruction, const std::filesystem::path& image_path, const std::filesystem::path& output_path) : options_(std::move(options)), camera_options_(camera_options), reconstruction_(reconstruction), image_path_(image_path), output_path_(output_path) { THROW_CHECK_GE(options_.num_patch_match_src_images, 1); THROW_CHECK_GE(options_.jpeg_quality, -1); THROW_CHECK_LE(options_.jpeg_quality, 100); } void COLMAPUndistorter::Run() { LOG_HEADING1("Image undistortion"); Timer run_timer; run_timer.Start(); CreateDirIfNotExists(output_path_ / "images"); CreateDirIfNotExists(output_path_ / "sparse"); CreateDirIfNotExists(output_path_ / "stereo"); CreateDirIfNotExists(output_path_ / "stereo" / "depth_maps"); CreateDirIfNotExists(output_path_ / "stereo" / "normal_maps"); CreateDirIfNotExists(output_path_ / "stereo" / "consistency_graphs"); reconstruction_.CreateImageDirs(output_path_ / "images"); reconstruction_.CreateImageDirs(output_path_ / "stereo" / "depth_maps"); reconstruction_.CreateImageDirs(output_path_ / "stereo" / "normal_maps"); reconstruction_.CreateImageDirs(output_path_ / "stereo" / "consistency_graphs"); const std::vector image_ids = options_.image_ids.empty() ? reconstruction_.RegImageIds() : options_.image_ids; const size_t num_images = image_ids.size(); ThreadPool thread_pool(options_.num_threads); std::vector> futures; futures.reserve(num_images); for (const image_t image_id : image_ids) { futures.push_back( thread_pool.AddTask(&COLMAPUndistorter::Undistort, this, image_id)); } // Only use the image names for the successfully undistorted images // when writing the MVS config files std::vector image_names; image_names.reserve(num_images); bool stopped = false; for (size_t i = 0; i < futures.size(); ++i) { if (CheckIfStopped()) { thread_pool.Stop(); stopped = true; break; } LOG(INFO) << StringPrintf( "Undistorting image [%d/%d]", i + 1, futures.size()); if (futures[i].get()) { image_names.push_back(reconstruction_.Image(image_ids[i]).Name()); } } if (stopped) { LOG(WARNING) << "Stopped image undistortion before writing the sparse " "model and stereo configuration files."; run_timer.PrintMinutes(); return; } LOG(INFO) << "Writing reconstruction..."; Reconstruction undistorted_reconstruction = reconstruction_; UndistortReconstruction(camera_options_, &undistorted_reconstruction); undistorted_reconstruction.Write(output_path_ / "sparse"); LOG(INFO) << "Writing configuration..."; WritePatchMatchConfig(image_names); WriteFusionConfig(image_names); LOG(INFO) << "Writing scripts..."; WriteScript(/*geometric=*/false); WriteScript(/*geometric=*/true); run_timer.PrintMinutes(); } bool COLMAPUndistorter::Undistort(const image_t image_id) const { const Image& image = reconstruction_.Image(image_id); const Camera& camera = *image.CameraPtr(); const auto input_image_path = image_path_ / image.Name(); const auto output_image_path = output_path_ / "images" / image.Name(); // Non-perspective cameras (e.g. EQUIRECTANGULAR) have no pinhole image plane // to undistort to. Without a size limit they are copied through unchanged // (they cannot be rescaled to a pinhole image for MVS); with a max_image_size // they still go through UndistortImage below, which resizes them to a smaller // image of the same model. if (!camera.IsPerspective() && camera_options_.max_image_size < 0 && ExistsFile(input_image_path)) { LOG(WARNING) << "Cannot undistort image " << image.Name() << " with non-perspective camera model " << camera.ModelName() << "; copying the original image."; FileCopy(input_image_path, output_image_path, options_.copy_type); return true; } // Already-undistorted perspective images are copied through only when no // rescaling is requested; with a max_image_size they still go through // UndistortImage below so the size limit is applied. if (camera.IsUndistorted() && camera_options_.max_image_size < 0 && ExistsFile(input_image_path)) { LOG(INFO) << "Copying already undistorted image to location: " << output_image_path; FileCopy(input_image_path, output_image_path, options_.copy_type); return true; } Bitmap distorted_bitmap; if (!distorted_bitmap.Read(input_image_path)) { LOG(ERROR) << "Cannot read image at path: " << input_image_path; return false; } Bitmap undistorted_bitmap; Camera undistorted_camera; UndistortImage(camera_options_, distorted_bitmap, camera, &undistorted_bitmap, &undistorted_camera); MaybeSetJpegQuality( output_image_path, undistorted_bitmap, options_.jpeg_quality); return undistorted_bitmap.Write(output_image_path); } void COLMAPUndistorter::WritePatchMatchConfig( const std::vector& image_names) const { const auto path = output_path_ / "stereo" / "patch-match.cfg"; std::ofstream file(path, std::ios::trunc); THROW_CHECK_FILE_OPEN(file, path); for (const auto& image_name : image_names) { file << image_name << '\n'; file << "__auto__, " << options_.num_patch_match_src_images << '\n'; } } void COLMAPUndistorter::WriteFusionConfig( const std::vector& image_names) const { const auto path = output_path_ / "stereo" / "fusion.cfg"; std::ofstream file(path, std::ios::trunc); THROW_CHECK_FILE_OPEN(file, path); for (const auto& image_name : image_names) { file << image_name << '\n'; } } void COLMAPUndistorter::WriteScript(const bool geometric) const { const auto path = output_path_ / (geometric ? "run-colmap-geometric.sh" : "run-colmap-photometric.sh"); std::ofstream file(path, std::ios::trunc); THROW_CHECK_FILE_OPEN(file, path); file << "# You must set $COLMAP_EXE_PATH to \n" << "# the directory containing the COLMAP executables.\n"; WriteCOLMAPCommands(geometric, ".", "COLMAP", "option-all", "", "", &file); } PMVSUndistorter::PMVSUndistorter(const Options& options, const UndistortCameraOptions& camera_options, const Reconstruction& reconstruction, const std::filesystem::path& image_path, const std::filesystem::path& output_path) : options_(options), camera_options_(camera_options), reconstruction_(reconstruction), image_path_(image_path), output_path_(output_path) { THROW_CHECK_GE(options_.jpeg_quality, -1); THROW_CHECK_LE(options_.jpeg_quality, 100); } void PMVSUndistorter::Run() { LOG_HEADING1("Image undistortion (CMVS/PMVS)"); Timer run_timer; run_timer.Start(); CreateDirIfNotExists(output_path_ / "pmvs"); CreateDirIfNotExists(output_path_ / "pmvs" / "txt"); CreateDirIfNotExists(output_path_ / "pmvs" / "visualize"); CreateDirIfNotExists(output_path_ / "pmvs" / "models"); ThreadPool thread_pool(options_.num_threads); std::vector> futures; futures.reserve(reconstruction_.NumRegImages()); for (size_t i = 0; i < reconstruction_.NumRegImages(); ++i) { futures.push_back( thread_pool.AddTask(&PMVSUndistorter::Undistort, this, i)); } bool stopped = false; for (size_t i = 0; i < futures.size(); ++i) { if (CheckIfStopped()) { thread_pool.Stop(); stopped = true; break; } LOG(INFO) << StringPrintf( "Undistorting image [%d/%d]", i + 1, futures.size()); futures[i].get(); } if (stopped) { LOG(WARNING) << "Stopped image undistortion before writing the bundle and " "configuration files."; run_timer.PrintMinutes(); return; } LOG(INFO) << "Writing bundle file..."; Reconstruction undistorted_reconstruction = reconstruction_; UndistortReconstruction(camera_options_, &undistorted_reconstruction); const auto bundle_path = output_path_ / "pmvs" / "bundle.rd.out"; ExportBundler(undistorted_reconstruction, bundle_path, AddFileExtension(bundle_path, ".list.txt")); LOG(INFO) << "Writing visibility file..."; WriteVisibilityData(); LOG(INFO) << "Writing option file..."; WriteOptionFile(); LOG(INFO) << "Writing scripts..."; WritePMVSScript(); WriteCMVSPMVSScript(); WriteCOLMAPScript(false); WriteCOLMAPScript(true); WriteCMVSCOLMAPScript(false); WriteCMVSCOLMAPScript(true); run_timer.PrintMinutes(); } bool PMVSUndistorter::Undistort(const size_t reg_image_idx) const { const auto output_image_path = output_path_ / StringPrintf("pmvs/visualize/%08d.jpg", reg_image_idx); const auto proj_matrix_path = output_path_ / StringPrintf("pmvs/txt/%08d.txt", reg_image_idx); const image_t image_id = *std::next(reconstruction_.RegImageIds().begin(), reg_image_idx); const Image& image = reconstruction_.Image(image_id); const Camera& camera = *image.CameraPtr(); Bitmap distorted_bitmap; const auto input_image_path = image_path_ / image.Name(); if (!distorted_bitmap.Read(input_image_path)) { LOG(ERROR) << "Cannot read image at path " << input_image_path; return false; } Bitmap undistorted_bitmap; Camera undistorted_camera; UndistortImage(camera_options_, distorted_bitmap, camera, &undistorted_bitmap, &undistorted_camera); WriteProjectionMatrix(proj_matrix_path, undistorted_camera, image, "CONTOUR"); MaybeSetJpegQuality( output_image_path, undistorted_bitmap, options_.jpeg_quality); return undistorted_bitmap.Write(output_image_path); } void PMVSUndistorter::WriteVisibilityData() const { const auto path = output_path_ / "pmvs" / "vis.dat"; std::ofstream file(path, std::ios::trunc); THROW_CHECK_FILE_OPEN(file, path); file << "VISDATA\n"; file << reconstruction_.NumRegImages() << '\n'; size_t image_idx = 0; for (const image_t image_id : reconstruction_.RegImageIds()) { const Image& image = reconstruction_.Image(image_id); FlatHashSet visible_image_ids; for (point2D_t point2D_idx = 0; point2D_idx < image.NumPoints2D(); ++point2D_idx) { const Point2D& point2D = image.Point2D(point2D_idx); if (point2D.HasPoint3D()) { const Point3D& point3D = reconstruction_.Point3D(point2D.point3D_id); for (const TrackElement& track_el : point3D.track.Elements()) { if (track_el.image_id != image_id) { visible_image_ids.insert(track_el.image_id); } } } } std::vector sorted_visible_image_ids(visible_image_ids.begin(), visible_image_ids.end()); std::sort(sorted_visible_image_ids.begin(), sorted_visible_image_ids.end()); file << image_idx++ << " " << visible_image_ids.size(); for (const image_t visible_image_id : sorted_visible_image_ids) { file << " " << visible_image_id; } file << '\n'; } } void PMVSUndistorter::WritePMVSScript() const { const auto path = output_path_ / "run-pmvs.sh"; std::ofstream file(path, std::ios::trunc); THROW_CHECK_FILE_OPEN(file, path); file << "# You must set $PMVS_EXE_PATH to \n" << "# the directory containing the CMVS-PMVS executables.\n"; file << "$PMVS_EXE_PATH/pmvs2 pmvs/ option-all\n"; } void PMVSUndistorter::WriteCMVSPMVSScript() const { const auto path = output_path_ / "run-cmvs-pmvs.sh"; std::ofstream file(path, std::ios::trunc); THROW_CHECK_FILE_OPEN(file, path); file << "# You must set $PMVS_EXE_PATH to \n" << "# the directory containing the CMVS-PMVS executables.\n"; file << "$PMVS_EXE_PATH/cmvs pmvs/\n"; file << "$PMVS_EXE_PATH/genOption pmvs/\n"; file << "find pmvs/ -iname \"option-*\" | sort | while read file_name\n"; file << "do\n"; file << " option_name=$(basename \"$file_name\")\n"; file << " if [ \"$option_name\" = \"option-all\" ]; then\n"; file << " continue\n"; file << " fi\n"; file << " $PMVS_EXE_PATH/pmvs2 pmvs/ $option_name\n"; file << "done\n"; } void PMVSUndistorter::WriteCOLMAPScript(const bool geometric) const { const auto path = output_path_ / (geometric ? "run-colmap-geometric.sh" : "run-colmap-photometric.sh"); std::ofstream file(path, std::ios::trunc); THROW_CHECK_FILE_OPEN(file, path); file << "# You must set $COLMAP_EXE_PATH to \n" << "# the directory containing the COLMAP executables.\n"; WriteCOLMAPCommands( geometric, "pmvs", "PMVS", "option-all", "option-all-", "", &file); } void PMVSUndistorter::WriteCMVSCOLMAPScript(const bool geometric) const { const auto path = output_path_ / (geometric ? "run-cmvs-colmap-geometric.sh" : "run-cmvs-colmap-photometric.sh"); std::ofstream file(path, std::ios::trunc); THROW_CHECK_FILE_OPEN(file, path); file << "# You must set $PMVS_EXE_PATH to \n" << "# the directory containing the CMVS-PMVS executables\n"; file << "# and you must set $COLMAP_EXE_PATH to \n" << "# the directory containing the COLMAP executables.\n"; file << "$PMVS_EXE_PATH/cmvs pmvs/\n"; file << "$PMVS_EXE_PATH/genOption pmvs/\n"; file << "find pmvs/ -iname \"option-*\" | sort | while read file_name\n"; file << "do\n"; file << " workspace_path=$(dirname \"$file_name\")\n"; file << " option_name=$(basename \"$file_name\")\n"; file << " if [ \"$option_name\" = \"option-all\" ]; then\n"; file << " continue\n"; file << " fi\n"; file << " rm -rf \"$workspace_path/stereo\"\n"; WriteCOLMAPCommands(geometric, "pmvs", "PMVS", "$option_name", "$option_name-", " ", &file); file << "done\n"; } void PMVSUndistorter::WriteOptionFile() const { const auto path = output_path_ / "pmvs" / "option-all"; std::ofstream file(path, std::ios::trunc); THROW_CHECK_FILE_OPEN(file, path); file << "# Generated by COLMAP - all images, no clustering.\n"; file << "level 1\n"; file << "csize 2\n"; file << "threshold 0.7\n"; file << "wsize 7\n"; file << "minImageNum 3\n"; file << "CPU " << std::thread::hardware_concurrency() << '\n'; file << "setEdge 0\n"; file << "useBound 0\n"; file << "useVisData 1\n"; file << "sequence -1\n"; file << "maxAngle 10\n"; file << "quad 2.0\n"; file << "timages " << reconstruction_.NumRegImages(); for (size_t i = 0; i < reconstruction_.NumRegImages(); ++i) { file << " " << i; } file << '\n'; file << "oimages 0\n"; } CMPMVSUndistorter::CMPMVSUndistorter( const Options& options, const UndistortCameraOptions& camera_options, const Reconstruction& reconstruction, const std::filesystem::path& image_path, const std::filesystem::path& output_path) : options_(options), camera_options_(camera_options), image_path_(image_path), output_path_(output_path), reconstruction_(reconstruction) { THROW_CHECK_GE(options_.jpeg_quality, -1); THROW_CHECK_LE(options_.jpeg_quality, 100); } void CMPMVSUndistorter::Run() { LOG_HEADING1("Image undistortion (CMP-MVS)"); Timer run_timer; run_timer.Start(); ThreadPool thread_pool(options_.num_threads); std::vector> futures; futures.reserve(reconstruction_.NumRegImages()); for (size_t i = 0; i < reconstruction_.NumRegImages(); ++i) { futures.push_back( thread_pool.AddTask(&CMPMVSUndistorter::Undistort, this, i)); } for (size_t i = 0; i < futures.size(); ++i) { if (CheckIfStopped()) { thread_pool.Stop(); break; } LOG(INFO) << StringPrintf( "Undistorting image [%d/%d]", i + 1, futures.size()); futures[i].get(); } run_timer.PrintMinutes(); } bool CMPMVSUndistorter::Undistort(const size_t reg_image_idx) const { const auto output_image_path = output_path_ / StringPrintf("%05d.jpg", reg_image_idx + 1); const auto proj_matrix_path = output_path_ / StringPrintf("%05d_P.txt", reg_image_idx + 1); const image_t image_id = *std::next(reconstruction_.RegImageIds().begin(), reg_image_idx); const Image& image = reconstruction_.Image(image_id); const Camera& camera = *image.CameraPtr(); Bitmap distorted_bitmap; const auto input_image_path = image_path_ / image.Name(); if (!distorted_bitmap.Read(input_image_path)) { LOG(ERROR) << "Cannot read image at path " << input_image_path; return false; } Bitmap undistorted_bitmap; Camera undistorted_camera; UndistortImage(camera_options_, distorted_bitmap, camera, &undistorted_bitmap, &undistorted_camera); WriteProjectionMatrix(proj_matrix_path, undistorted_camera, image, "CONTOUR"); MaybeSetJpegQuality( output_image_path, undistorted_bitmap, options_.jpeg_quality); return undistorted_bitmap.Write(output_image_path); } StandaloneImageUndistorter::StandaloneImageUndistorter( Options options, const UndistortCameraOptions& camera_options, const std::filesystem::path& image_path, const std::filesystem::path& output_path) : options_(std::move(options)), camera_options_(camera_options), image_path_(image_path), output_path_(output_path) { THROW_CHECK_GE(options_.jpeg_quality, -1); THROW_CHECK_LE(options_.jpeg_quality, 100); } void StandaloneImageUndistorter::Run() { LOG_HEADING1("Image undistortion"); Timer run_timer; run_timer.Start(); CreateDirIfNotExists(output_path_); ThreadPool thread_pool(options_.num_threads); std::vector> futures; const size_t num_images = options_.image_names_and_cameras.size(); futures.reserve(num_images); for (size_t i = 0; i < num_images; ++i) { futures.push_back( thread_pool.AddTask(&StandaloneImageUndistorter::Undistort, this, i)); } for (size_t i = 0; i < futures.size(); ++i) { if (CheckIfStopped()) { thread_pool.Stop(); break; } LOG(INFO) << StringPrintf( "Undistorting image [%d/%d]", i + 1, futures.size()); futures[i].get(); } run_timer.PrintMinutes(); } bool StandaloneImageUndistorter::Undistort(const size_t image_idx) const { const auto& [image_name, camera] = options_.image_names_and_cameras[image_idx]; const auto output_image_path = output_path_ / image_name; const auto input_image_path = image_path_ / image_name; // Check if the image is already undistorted and copy from source if no // scaling is needed if (camera.IsUndistorted() && camera_options_.max_image_size < 0 && ExistsFile(input_image_path)) { FileCopy(input_image_path, output_image_path, options_.copy_type); return true; } Bitmap distorted_bitmap; if (!distorted_bitmap.Read(input_image_path)) { LOG(ERROR) << "Cannot read image at path " << input_image_path; return false; } Bitmap undistorted_bitmap; Camera undistorted_camera; UndistortImage(camera_options_, distorted_bitmap, camera, &undistorted_bitmap, &undistorted_camera); MaybeSetJpegQuality( output_image_path, undistorted_bitmap, options_.jpeg_quality); return undistorted_bitmap.Write(output_image_path); } StereoImageRectifier::StereoImageRectifier( Options options, const UndistortCameraOptions& camera_options, const Reconstruction& reconstruction, const std::filesystem::path& image_path, const std::filesystem::path& output_path) : options_(std::move(options)), camera_options_(camera_options), reconstruction_(reconstruction), image_path_(image_path), output_path_(output_path) { THROW_CHECK_GE(options_.jpeg_quality, -1); THROW_CHECK_LE(options_.jpeg_quality, 100); } void StereoImageRectifier::Run() { LOG_HEADING1("Stereo rectification"); Timer run_timer; run_timer.Start(); ThreadPool thread_pool(options_.num_threads); std::vector> futures; futures.reserve(options_.stereo_pairs.size()); for (const auto& stereo_pair : options_.stereo_pairs) { futures.push_back(thread_pool.AddTask(&StereoImageRectifier::Rectify, this, stereo_pair.first, stereo_pair.second)); } for (size_t i = 0; i < futures.size(); ++i) { if (CheckIfStopped()) { thread_pool.Stop(); break; } LOG(INFO) << StringPrintf( "Rectifying image pair [%d/%d]", i + 1, futures.size()); futures[i].get(); } run_timer.PrintMinutes(); } void StereoImageRectifier::Rectify(const image_t image_id1, const image_t image_id2) const { const Image& image1 = reconstruction_.Image(image_id1); const Image& image2 = reconstruction_.Image(image_id2); const Camera& camera1 = reconstruction_.Camera(image1.CameraId()); const Camera& camera2 = reconstruction_.Camera(image2.CameraId()); const std::string image_name1 = StringReplace(image1.Name(), "/", "-"); const std::string image_name2 = StringReplace(image2.Name(), "/", "-"); const std::string stereo_pair_name = StringPrintf("%s-%s", image_name1.c_str(), image_name2.c_str()); CreateDirIfNotExists(output_path_ / stereo_pair_name); const auto output_image_path1 = output_path_ / stereo_pair_name / image_name1; const auto output_image_path2 = output_path_ / stereo_pair_name / image_name2; Bitmap distorted_bitmap1; const auto input_image1_path = image_path_ / image1.Name(); if (!distorted_bitmap1.Read(input_image1_path)) { LOG(ERROR) << "Cannot read image at path " << input_image1_path; return; } Bitmap distorted_bitmap2; const auto input_image2_path = image_path_ / image2.Name(); if (!distorted_bitmap2.Read(input_image2_path)) { LOG(ERROR) << "Cannot read image at path " << input_image2_path; return; } const Rigid3d cam2_from_cam1 = image2.CamFromWorld() * Inverse(image1.CamFromWorld()); Bitmap undistorted_bitmap1; Bitmap undistorted_bitmap2; Camera undistorted_camera; Eigen::Matrix4d Q; RectifyAndUndistortStereoImages(camera_options_, distorted_bitmap1, distorted_bitmap2, camera1, camera2, cam2_from_cam1, &undistorted_bitmap1, &undistorted_bitmap2, &undistorted_camera, &Q); MaybeSetJpegQuality( output_image_path1, undistorted_bitmap1, options_.jpeg_quality); MaybeSetJpegQuality( output_image_path2, undistorted_bitmap2, options_.jpeg_quality); undistorted_bitmap1.Write(output_image_path1); undistorted_bitmap2.Write(output_image_path2); const auto Q_path = output_path_ / stereo_pair_name / "Q.txt"; std::ofstream Q_file(Q_path, std::ios::trunc); THROW_CHECK_FILE_OPEN(Q_file, Q_path); Q_file.imbue(std::locale::classic()); WriteMatrix(Q, &Q_file); } } // namespace colmap colmap-4.2.0/src/colmap/controllers/undistorters.h000066400000000000000000000203001524536416500223240ustar00rootroot00000000000000// Copyright (c), ETH Zurich and UNC Chapel Hill. // All rights reserved. // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are met: // // * Redistributions of source code must retain the above copyright // notice, this list of conditions and the following disclaimer. // // * Redistributions in binary form must reproduce the above copyright // notice, this list of conditions and the following disclaimer in the // documentation and/or other materials provided with the distribution. // // * Neither the name of ETH Zurich and UNC Chapel Hill nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE // ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE // LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR // CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF // SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS // INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN // CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) // ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE // POSSIBILITY OF SUCH DAMAGE. #pragma once #include "colmap/image/undistortion.h" #include "colmap/scene/reconstruction.h" #include "colmap/util/base_controller.h" #include "colmap/util/file.h" namespace colmap { // Undistort images and export undistorted cameras, as required by the // mvs::PatchMatchController class. class COLMAPUndistorter : public BaseController { public: struct Options { // The copy type to use when copying already undistorted images to the // output directory. This can be used to speed up the undistortion process // when a majority of the images are already undistorted and choosing // COPY_HARD_LINK or COPY_SYMLINK to avoid duplicating the images. FileCopyType copy_type = FileCopyType::COPY; // How many images to use as patch match source images when generating the // patch match config file. int num_patch_match_src_images = 20; // List of images to undistort. If empty, all images are undistorted. std::vector image_ids; // JPEG quality setting in the range [0, 100]. A value of -1 uses the // default (quality 100). Lower values produce smaller file sizes. int jpeg_quality = -1; // Number of threads to use for undistortion. A value of -1 uses all // available CPU cores. int num_threads = -1; }; COLMAPUndistorter(Options options, const UndistortCameraOptions& camera_options, const Reconstruction& reconstruction, const std::filesystem::path& image_path, const std::filesystem::path& output_path); void Run(); private: bool Undistort(image_t image_id) const; void WritePatchMatchConfig(const std::vector& image_names) const; void WriteFusionConfig(const std::vector& image_names) const; void WriteScript(bool geometric) const; const Options options_; const UndistortCameraOptions camera_options_; const Reconstruction& reconstruction_; const std::filesystem::path image_path_; const std::filesystem::path output_path_; }; // Undistort images and prepare data for CMVS/PMVS. class PMVSUndistorter : public BaseController { public: struct Options { // JPEG quality setting in the range [0, 100]. A value of -1 uses the // default (quality 100). Lower values produce smaller file sizes. int jpeg_quality = -1; // Number of threads to use for undistortion. A value of -1 uses all // available CPU cores. int num_threads = -1; }; PMVSUndistorter(const Options& options, const UndistortCameraOptions& camera_options, const Reconstruction& reconstruction, const std::filesystem::path& image_path, const std::filesystem::path& output_path); void Run(); private: bool Undistort(size_t reg_image_idx) const; void WriteVisibilityData() const; void WriteOptionFile() const; void WritePMVSScript() const; void WriteCMVSPMVSScript() const; void WriteCOLMAPScript(bool geometric) const; void WriteCMVSCOLMAPScript(bool geometric) const; const Options options_; const UndistortCameraOptions camera_options_; const Reconstruction& reconstruction_; const std::filesystem::path image_path_; const std::filesystem::path output_path_; }; // Undistort images and prepare data for CMP-MVS. class CMPMVSUndistorter : public BaseController { public: struct Options { // JPEG quality setting in the range [0, 100]. A value of -1 uses the // default (quality 100). Lower values produce smaller file sizes. int jpeg_quality = -1; // Number of threads to use for undistortion. A value of -1 uses all // available CPU cores. int num_threads = -1; }; CMPMVSUndistorter(const Options& options, const UndistortCameraOptions& camera_options, const Reconstruction& reconstruction, const std::filesystem::path& image_path, const std::filesystem::path& output_path); void Run(); private: bool Undistort(size_t reg_image_idx) const; const Options options_; const UndistortCameraOptions camera_options_; const std::filesystem::path image_path_; const std::filesystem::path output_path_; const Reconstruction& reconstruction_; }; // Undistort images and export undistorted cameras without the need for a // reconstruction. Instead, the image names and camera model information are // read from a text file. class StandaloneImageUndistorter : public BaseController { public: struct Options { // The images and cameras to undistort. std::vector> image_names_and_cameras; // The copy type to use when copying already undistorted images to the // output directory. FileCopyType copy_type = FileCopyType::COPY; // JPEG quality setting in the range [0, 100]. A value of -1 uses the // default (quality 100). Lower values produce smaller file sizes. int jpeg_quality = -1; // Number of threads to use for undistortion. A value of -1 uses all // available CPU cores. int num_threads = -1; }; StandaloneImageUndistorter(Options options, const UndistortCameraOptions& camera_options, const std::filesystem::path& image_path, const std::filesystem::path& output_path); void Run(); private: bool Undistort(size_t image_idx) const; const Options options_; const UndistortCameraOptions camera_options_; const std::filesystem::path image_path_; const std::filesystem::path output_path_; }; // Rectify stereo image pairs. class StereoImageRectifier : public BaseController { public: struct Options { // The stereo image pairs to rectify. std::vector> stereo_pairs; // JPEG quality setting in the range [0, 100]. A value of -1 uses the // default (quality 100). Lower values produce smaller file sizes. int jpeg_quality = -1; // Number of threads to use for undistortion. A value of -1 uses all // available CPU cores. int num_threads = -1; }; StereoImageRectifier(Options options, const UndistortCameraOptions& camera_options, const Reconstruction& reconstruction, const std::filesystem::path& image_path, const std::filesystem::path& output_path); void Run(); private: void Rectify(image_t image_id1, image_t image_id2) const; const Options options_; const UndistortCameraOptions camera_options_; const Reconstruction& reconstruction_; const std::filesystem::path image_path_; const std::filesystem::path output_path_; }; } // namespace colmap colmap-4.2.0/src/colmap/controllers/undistorters_test.cc000066400000000000000000000314071524536416500235330ustar00rootroot00000000000000// Copyright (c), ETH Zurich and UNC Chapel Hill. // All rights reserved. // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are met: // // * Redistributions of source code must retain the above copyright // notice, this list of conditions and the following disclaimer. // // * Redistributions in binary form must reproduce the above copyright // notice, this list of conditions and the following disclaimer in the // documentation and/or other materials provided with the distribution. // // * Neither the name of ETH Zurich and UNC Chapel Hill nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE // ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE // LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR // CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF // SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS // INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN // CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) // ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE // POSSIBILITY OF SUCH DAMAGE. #include "colmap/controllers/undistorters.h" #include "colmap/scene/synthetic.h" #include "colmap/sensor/bitmap.h" #include "colmap/util/file.h" #include "colmap/util/string.h" #include "colmap/util/testing.h" #include #include #include namespace colmap { namespace { Reconstruction CreateSyntheticReconstructionWithBitmaps( const std::filesystem::path& image_path, int num_images = 2, int image_width = 100, int image_height = 100, const std::string& image_extension = ".png") { SyntheticDatasetOptions synthetic_dataset_options; synthetic_dataset_options.num_rigs = 1; synthetic_dataset_options.num_cameras_per_rig = 1; synthetic_dataset_options.num_frames_per_rig = num_images; synthetic_dataset_options.camera_width = image_width; synthetic_dataset_options.camera_height = image_height; synthetic_dataset_options.image_extension = image_extension; Reconstruction reconstruction; SynthesizeDataset(synthetic_dataset_options, &reconstruction); // Create dummy images. for (const auto& [image_id, image] : reconstruction.Images()) { Bitmap bitmap(image_width, image_height, true); bitmap.Fill(BitmapColor(128, 128, 128)); bitmap.Write(image_path / image.Name()); } return reconstruction; } TEST(COLMAPUndistorter, Integration) { const auto temp_dir = CreateTestDir(); const auto image_path = temp_dir / "input_images"; const auto output_path = temp_dir / "output"; CreateDirIfNotExists(image_path); CreateDirIfNotExists(output_path); // Create synthetic reconstruction with dummy images. const Reconstruction reconstruction = CreateSyntheticReconstructionWithBitmaps(image_path); // Run COLMAP undistorter. COLMAPUndistorter undistorter(COLMAPUndistorter::Options(), UndistortCameraOptions(), reconstruction, image_path, output_path); undistorter.Run(); // Verify output directories were created. EXPECT_TRUE(ExistsDir(output_path / "images")); EXPECT_TRUE(ExistsDir(output_path / "sparse")); EXPECT_TRUE(ExistsDir(output_path / "stereo")); // Verify undistorted images were written. for (const auto& [image_id, image] : reconstruction.Images()) { EXPECT_TRUE(ExistsFile(output_path / "images" / image.Name())); } // Expect dense reconstruction files to be written. EXPECT_TRUE(ExistsFile(output_path / "stereo/patch-match.cfg")); EXPECT_TRUE(ExistsFile(output_path / "stereo/fusion.cfg")); } TEST(COLMAPUndistorter, StopsPendingWork) { const auto temp_dir = CreateTestDir(); const auto image_path = temp_dir / "input_images"; const auto output_path = temp_dir / "output"; CreateDirIfNotExists(image_path); CreateDirIfNotExists(output_path); const Reconstruction reconstruction = CreateSyntheticReconstructionWithBitmaps(image_path, /*num_images=*/10); COLMAPUndistorter undistorter(COLMAPUndistorter::Options(), UndistortCameraOptions(), reconstruction, image_path, output_path); bool stop_checked = false; undistorter.SetCheckIfStoppedFunc([&stop_checked]() { stop_checked = true; return true; }); undistorter.Run(); EXPECT_TRUE(stop_checked); EXPECT_FALSE(ExistsFile(output_path / "stereo/patch-match.cfg")); EXPECT_FALSE(ExistsFile(output_path / "stereo/fusion.cfg")); } TEST(COLMAPUndistorter, SpecificImages) { const auto temp_dir = CreateTestDir(); const auto image_path = temp_dir / "input_images"; const auto output_path = temp_dir / "output"; CreateDirIfNotExists(image_path); CreateDirIfNotExists(output_path); // Create synthetic reconstruction with dummy images. Reconstruction reconstruction = CreateSyntheticReconstructionWithBitmaps(image_path, /*num_images=*/2, /*image_width=*/100, /*image_height=*/100, /*image_extension=*/".jpg"); const Image& image = reconstruction.Image(reconstruction.RegImageIds()[0]); // Run COLMAP undistorter. COLMAPUndistorter::Options options; options.image_ids = {image.ImageId()}; COLMAPUndistorter undistorter(options, UndistortCameraOptions(), reconstruction, image_path, output_path); undistorter.Run(); // Verify that only the specified image was written. EXPECT_THAT( GetRecursiveFileList(output_path / "images"), testing::UnorderedElementsAre(output_path / "images" / image.Name())); } TEST(COLMAPUndistorter, JpegQuality) { const auto temp_dir = CreateTestDir(); const auto image_path = temp_dir / "input_images"; const auto output_path = temp_dir / "output"; CreateDirIfNotExists(image_path); CreateDirIfNotExists(output_path); // Create synthetic reconstruction with dummy images. Reconstruction reconstruction = CreateSyntheticReconstructionWithBitmaps(image_path, /*num_images=*/1, /*image_width=*/100, /*image_height=*/100, /*image_extension=*/".jpg"); // Run COLMAP undistorter. COLMAPUndistorter::Options options; options.jpeg_quality = 50; COLMAPUndistorter undistorter(options, UndistortCameraOptions(), reconstruction, image_path, output_path); undistorter.Run(); // Verify undistorted images were written. for (const auto& [image_id, image] : reconstruction.Images()) { EXPECT_TRUE(ExistsFile(output_path / "images" / image.Name())); } } TEST(PMVSUndistorter, Integration) { const auto temp_dir = CreateTestDir(); const auto image_path = temp_dir / "input_images"; const auto output_path = temp_dir / "pmvs_output"; CreateDirIfNotExists(image_path); CreateDirIfNotExists(output_path); // Create synthetic reconstruction with dummy images. const Reconstruction reconstruction = CreateSyntheticReconstructionWithBitmaps(image_path); // Run PMVS undistorter. PMVSUndistorter undistorter(PMVSUndistorter::Options(), UndistortCameraOptions(), reconstruction, image_path, output_path); undistorter.Run(); // Verify PMVS output structure was created (under pmvs/ subdirectory). EXPECT_TRUE(ExistsDir(output_path / "pmvs")); EXPECT_TRUE(ExistsDir(output_path / "pmvs" / "models")); EXPECT_TRUE(ExistsDir(output_path / "pmvs" / "txt")); EXPECT_TRUE(ExistsDir(output_path / "pmvs" / "visualize")); // Verify undistorted images were written with numbered names. // PMVS writes images as 00000000.jpg, 00000001.jpg, etc. const size_t num_images = reconstruction.NumRegImages(); for (size_t i = 0; i < num_images; ++i) { const std::string image_name = StringPrintf("%08zu.jpg", i); EXPECT_TRUE(ExistsFile(output_path / "pmvs" / "visualize" / image_name)); } } TEST(CMPMVSUndistorter, Integration) { const auto temp_dir = CreateTestDir(); const auto image_path = temp_dir / "input_images"; const auto output_path = temp_dir / "cmpmvs_output"; CreateDirIfNotExists(image_path); CreateDirIfNotExists(output_path); // Create synthetic reconstruction with dummy images. const Reconstruction reconstruction = CreateSyntheticReconstructionWithBitmaps(image_path); // Run CMP-MVS undistorter. CMPMVSUndistorter undistorter(CMPMVSUndistorter::Options(), UndistortCameraOptions(), reconstruction, image_path, output_path); undistorter.Run(); // Verify CMP-MVS output structure was created. EXPECT_TRUE(ExistsDir(output_path)); // Verify undistorted images were written with sequential numbering. // CMP-MVS writes images as 00001.jpg, 00002.jpg, etc. const size_t num_images = reconstruction.NumRegImages(); for (size_t i = 1; i <= num_images; ++i) { const std::string image_name = StringPrintf("%05zu.jpg", i); EXPECT_TRUE(ExistsFile(output_path / image_name)); } } TEST(StandaloneImageUndistorter, Integration) { const auto temp_dir = CreateTestDir(); const auto image_path = temp_dir / "input_images"; const auto output_path = temp_dir / "pure_output"; CreateDirIfNotExists(image_path); // Create synthetic reconstruction with dummy images. const Reconstruction reconstruction = CreateSyntheticReconstructionWithBitmaps(image_path); StandaloneImageUndistorter::Options options; for (const auto& [_, image] : reconstruction.Images()) { options.image_names_and_cameras.emplace_back(image.Name(), *image.CameraPtr()); } // Run standalone image undistorter. StandaloneImageUndistorter undistorter( options, UndistortCameraOptions(), image_path, output_path); undistorter.Run(); // Verify output directory was created. EXPECT_TRUE(ExistsDir(output_path)); // Verify undistorted images were written. for (const auto& [image_name, camera] : options.image_names_and_cameras) { EXPECT_TRUE(ExistsFile(output_path / image_name)); } } TEST(StereoImageRectifier, Integration) { const auto temp_dir = CreateTestDir(); const auto image_path = temp_dir / "input_images"; const auto output_path = temp_dir / "stereo_output"; CreateDirIfNotExists(image_path); CreateDirIfNotExists(output_path); // Create synthetic reconstruction with dummy images. const Reconstruction reconstruction = CreateSyntheticReconstructionWithBitmaps(image_path); // Create stereo pair from first two images. StereoImageRectifier::Options options; const std::vector image_ids = reconstruction.RegImageIds(); ASSERT_GE(image_ids.size(), 2); options.stereo_pairs.emplace_back(image_ids[0], image_ids[1]); // Run stereo image rectifier. StereoImageRectifier rectifier(options, UndistortCameraOptions(), reconstruction, image_path, output_path); rectifier.Run(); // Verify output directory was created. EXPECT_TRUE(ExistsDir(output_path)); // Verify rectified images were written. // StereoImageRectifier creates a subdirectory for each stereo pair. const auto& image1 = reconstruction.Image(options.stereo_pairs[0].first); const auto& image2 = reconstruction.Image(options.stereo_pairs[0].second); const std::string stereo_pair_name = StringPrintf("%s-%s", image1.Name().c_str(), image2.Name().c_str()); EXPECT_TRUE(ExistsDir(output_path / stereo_pair_name)); EXPECT_TRUE(ExistsFile(output_path / stereo_pair_name / image1.Name())); EXPECT_TRUE(ExistsFile(output_path / stereo_pair_name / image2.Name())); } } // namespace } // namespace colmap colmap-4.2.0/src/colmap/estimators/000077500000000000000000000000001524536416500172375ustar00rootroot00000000000000colmap-4.2.0/src/colmap/estimators/CMakeLists.txt000066400000000000000000000113401524536416500217760ustar00rootroot00000000000000# Copyright (c), ETH Zurich and UNC Chapel Hill. # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are met: # # * Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer. # # * Redistributions in binary form must reproduce the above copyright # notice, this list of conditions and the following disclaimer in the # documentation and/or other materials provided with the distribution. # # * Neither the name of ETH Zurich and UNC Chapel Hill nor the names of # its contributors may be used to endorse or promote products derived # from this software without specific prior written permission. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. set(FOLDER_NAME "estimators") add_subdirectory(cost_functions) add_subdirectory(solvers) set(ESTIMATORS_SRCS alignment.h alignment.cc bundle_adjustment.h bundle_adjustment.cc bundle_adjustment_ceres.h bundle_adjustment_ceres.cc coordinate_frame.h coordinate_frame.cc covariance.h covariance.cc fundamental_matrix_degensac.h fundamental_matrix_degensac.cc generalized_pose.h generalized_pose.cc global_positioning.h global_positioning.cc gravity_refinement.h gravity_refinement.cc pose.h pose.cc rotation_averaging.h rotation_averaging.cc rotation_averaging_impl.h rotation_averaging_impl.cc triangulation.h triangulation.cc two_view_geometry.h two_view_geometry.cc view_graph_calibration.h view_graph_calibration.cc ) if(CASPAR_ENABLED) list(APPEND ESTIMATORS_SRCS bundle_adjustment_caspar.h bundle_adjustment_caspar.cc ) endif() COLMAP_ADD_LIBRARY( NAME colmap_estimators SRCS ${ESTIMATORS_SRCS} PUBLIC_LINK_LIBS colmap_util colmap_math colmap_feature_types colmap_geometry colmap_sensor colmap_image colmap_scene colmap_optim colmap_estimators_cost_functions colmap_estimators_solvers Eigen3::Eigen Ceres::ceres PoseLib::PoseLib ) if(CASPAR_ENABLED) target_link_libraries(colmap_estimators PUBLIC caspar_lib_core) endif() if(CUDA_ENABLED) target_link_libraries(colmap_estimators PUBLIC colmap_util_cuda) endif() COLMAP_ADD_TEST( NAME alignment_test SRCS alignment_test.cc LINK_LIBS colmap_estimators ) COLMAP_ADD_TEST( NAME bundle_adjustment_test SRCS bundle_adjustment_test.cc LINK_LIBS colmap_estimators ) COLMAP_ADD_TEST( NAME bundle_adjustment_ceres_test SRCS bundle_adjustment_ceres_test.cc LINK_LIBS colmap_estimators ) if(CASPAR_ENABLED) COLMAP_ADD_TEST( NAME bundle_adjustment_caspar_test SRCS bundle_adjustment_caspar_test.cc LINK_LIBS colmap_estimators ) endif() COLMAP_ADD_TEST( NAME coordinate_frame_test SRCS coordinate_frame_test.cc LINK_LIBS colmap_estimators ) COLMAP_ADD_TEST( NAME covariance_test SRCS covariance_test.cc LINK_LIBS colmap_estimators ) COLMAP_ADD_TEST( NAME fundamental_matrix_degensac_test SRCS fundamental_matrix_degensac_test.cc LINK_LIBS colmap_estimators ) COLMAP_ADD_TEST( NAME generalized_pose_test SRCS generalized_pose_test.cc LINK_LIBS colmap_estimators ) COLMAP_ADD_TEST( NAME pose_test SRCS pose_test.cc LINK_LIBS colmap_estimators ) COLMAP_ADD_TEST( NAME two_view_geometry_test SRCS two_view_geometry_test.cc LINK_LIBS colmap_estimators ) COLMAP_ADD_TEST( NAME view_graph_calibration_test SRCS view_graph_calibration_test.cc LINK_LIBS colmap_estimators ) COLMAP_ADD_TEST( NAME rotation_averaging_test SRCS rotation_averaging_test.cc LINK_LIBS colmap_estimators ) COLMAP_ADD_TEST( NAME gravity_refinement_test SRCS gravity_refinement_test.cc LINK_LIBS colmap_estimators ) COLMAP_ADD_TEST( NAME global_positioning_test SRCS global_positioning_test.cc LINK_LIBS colmap_estimators ) colmap-4.2.0/src/colmap/estimators/alignment.cc000066400000000000000000000610421524536416500215270ustar00rootroot00000000000000// Copyright (c), ETH Zurich and UNC Chapel Hill. // All rights reserved. // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are met: // // * Redistributions of source code must retain the above copyright // notice, this list of conditions and the following disclaimer. // // * Redistributions in binary form must reproduce the above copyright // notice, this list of conditions and the following disclaimer in the // documentation and/or other materials provided with the distribution. // // * Neither the name of ETH Zurich and UNC Chapel Hill nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE // ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE // LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR // CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF // SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS // INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN // CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) // ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE // POSSIBILITY OF SUCH DAMAGE. #include "colmap/estimators/alignment.h" #include "colmap/estimators/solvers/similarity_transform.h" #include "colmap/geometry/pose.h" #include "colmap/math/math.h" #include "colmap/optim/loransac.h" #include "colmap/scene/projection.h" #include "colmap/util/hash_containers.h" #include "colmap/util/logging.h" namespace colmap { namespace { struct ReconstructionAlignmentEstimator { static const int kMinNumSamples = 3; using X_t = const Image*; using Y_t = const Image*; using M_t = Sim3d; ReconstructionAlignmentEstimator(double max_reproj_error, const Reconstruction* src_reconstruction, const Reconstruction* tgt_reconstruction) : max_squared_reproj_error_(max_reproj_error * max_reproj_error), src_reconstruction_(src_reconstruction), tgt_reconstruction_(tgt_reconstruction) { THROW_CHECK_GE(max_reproj_error, 0); THROW_CHECK_NOTNULL(src_reconstruction_); THROW_CHECK_NOTNULL(tgt_reconstruction_); } // Estimate 3D similarity transform from corresponding projection centers. void Estimate(const std::vector& src_images, const std::vector& tgt_images, std::vector* models) const { THROW_CHECK_GE(src_images.size(), 3); THROW_CHECK_GE(tgt_images.size(), 3); THROW_CHECK_EQ(src_images.size(), tgt_images.size()); THROW_CHECK(models != nullptr); models->clear(); std::vector proj_centers1(src_images.size()); std::vector proj_centers2(tgt_images.size()); for (size_t i = 0; i < src_images.size(); ++i) { THROW_CHECK_EQ(src_images[i]->ImageId(), tgt_images[i]->ImageId()); proj_centers1[i] = src_images[i]->ProjectionCenter(); proj_centers2[i] = tgt_images[i]->ProjectionCenter(); } Sim3d tgt_from_src; if (!EstimateSim3d(proj_centers1, proj_centers2, tgt_from_src)) { return; } models->resize(1); (*models)[0] = tgt_from_src; } // For each image, determine the ratio of 3D points that correctly project // from one image to the other image and vice versa for the given // tgt_from_src. The residual is then defined as 1 minus this ratio, i.e., an // error threshold of 0.3 means that 70% of the points for that image must // reproject within the given maximum reprojection error threshold. void Residuals(const std::vector& src_images, const std::vector& tgt_images, const M_t& tgt_from_src, std::vector* residuals) const { THROW_CHECK_EQ(src_images.size(), tgt_images.size()); THROW_CHECK_NOTNULL(src_reconstruction_); THROW_CHECK_NOTNULL(tgt_reconstruction_); const Sim3d src_from_tgt = Inverse(tgt_from_src); residuals->resize(src_images.size()); for (size_t i = 0; i < src_images.size(); ++i) { const Image& src_image = *src_images[i]; const Image& tgt_image = *tgt_images[i]; THROW_CHECK_EQ(src_image.ImageId(), tgt_image.ImageId()); const Camera& src_camera = *src_image.CameraPtr(); const Camera& tgt_camera = *tgt_image.CameraPtr(); const Eigen::Matrix3x4d src_cam_from_world = src_image.CamFromWorld().ToMatrix(); const Eigen::Matrix3x4d tgt_cam_from_world = tgt_image.CamFromWorld().ToMatrix(); THROW_CHECK_EQ(src_image.NumPoints2D(), tgt_image.NumPoints2D()); size_t num_inliers = 0; size_t num_common_points = 0; for (point2D_t point2D_idx = 0; point2D_idx < src_image.NumPoints2D(); ++point2D_idx) { // Check if both images have a 3D point. const auto& src_point2D = src_image.Point2D(point2D_idx); if (!src_point2D.HasPoint3D()) { continue; } const auto& tgt_point2D = tgt_image.Point2D(point2D_idx); if (!tgt_point2D.HasPoint3D()) { continue; } num_common_points += 1; const Eigen::Vector3d src_point_in_tgt = tgt_from_src * src_reconstruction_->Point3D(src_point2D.point3D_id).xyz; if (CalculateSquaredReprojectionError(tgt_point2D.xy, src_point_in_tgt, tgt_cam_from_world, tgt_camera) > max_squared_reproj_error_) { continue; } const Eigen::Vector3d tgt_point_in_src = src_from_tgt * tgt_reconstruction_->Point3D(tgt_point2D.point3D_id).xyz; if (CalculateSquaredReprojectionError(src_point2D.xy, tgt_point_in_src, src_cam_from_world, src_camera) > max_squared_reproj_error_) { continue; } num_inliers += 1; } if (num_common_points == 0) { (*residuals)[i] = 1.0; } else { const double negative_inlier_ratio = 1.0 - static_cast(num_inliers) / static_cast(num_common_points); (*residuals)[i] = negative_inlier_ratio * negative_inlier_ratio; } } } private: double max_squared_reproj_error_; const Reconstruction* src_reconstruction_; const Reconstruction* tgt_reconstruction_; }; } // namespace bool AlignReconstructionToLocations( const Reconstruction& src_reconstruction, const std::vector& tgt_image_names, const std::vector& tgt_image_locations, const int min_common_images, const RANSACOptions& ransac_options, Sim3d* tgt_from_src) { THROW_CHECK_GE(min_common_images, 3); THROW_CHECK_EQ(tgt_image_names.size(), tgt_image_locations.size()); // Find out which images are contained in the reconstruction and get the // positions of their camera centers. FlatHashSet common_image_ids; std::vector src; std::vector dst; for (size_t i = 0; i < tgt_image_names.size(); ++i) { const class Image* src_image = src_reconstruction.FindImageWithName(tgt_image_names[i]); if (src_image == nullptr) { continue; } if (!src_image->HasPose()) { continue; } // Ignore duplicate images. if (!common_image_ids.insert(src_image->ImageId()).second) { continue; } src.push_back(src_image->ProjectionCenter()); dst.push_back(tgt_image_locations[i]); } // Only compute the alignment if there are enough correspondences. if (common_image_ids.size() < static_cast(min_common_images)) { return false; } Sim3d tgt_from_src_; const auto report = EstimateSim3dRobust(src, dst, ransac_options, tgt_from_src_); if (report.support.num_inliers < static_cast(min_common_images)) { return false; } if (tgt_from_src != nullptr) { *tgt_from_src = tgt_from_src_; } return true; } bool AlignReconstructionToPosePriors( const Reconstruction& src_reconstruction, const std::vector& tgt_pose_priors, RANSACOptions ransac_options, const double prior_position_fallback_stddev, Sim3d* tgt_from_src) { THROW_CHECK_GT(prior_position_fallback_stddev, 0.0); std::vector src; std::vector tgt; std::vector rms_vars; src.reserve(tgt_pose_priors.size()); tgt.reserve(tgt_pose_priors.size()); rms_vars.reserve(tgt_pose_priors.size()); NodeHashMap tgt_image_to_pose_prior; for (const auto& pose_prior : tgt_pose_priors) { if (pose_prior.corr_data_id.sensor_id.type == SensorType::CAMERA && pose_prior.HasPosition()) { THROW_CHECK(tgt_image_to_pose_prior .emplace(pose_prior.corr_data_id.id, pose_prior) .second) << "Duplicate pose prior for image " << pose_prior.corr_data_id.id; } } for (const image_t image_id : src_reconstruction.RegImageIds()) { const auto pose_prior_it = tgt_image_to_pose_prior.find(image_id); if (pose_prior_it != tgt_image_to_pose_prior.end()) { const auto& image = src_reconstruction.Image(image_id); src.push_back(image.ProjectionCenter()); tgt.push_back(pose_prior_it->second.position); const double trace = pose_prior_it->second.position_covariance.trace(); if (trace > 0.0) { rms_vars.push_back(trace / 3.0); } } } if (src.size() < 3) { LOG(WARNING) << "Not enough valid pose priors for alignment"; return false; } if (ransac_options.max_error <= 0) { if (rms_vars.empty()) { LOG(WARNING) << "No pose priors with valid covariance found."; rms_vars.push_back(prior_position_fallback_stddev * prior_position_fallback_stddev); } // Scale the median RMS variance by the 95% chi-square quantile for 3 DOF. ransac_options.max_error = std::sqrt(kChiSquare95ThreeDof * Median(rms_vars)); } VLOG(2) << "Robustly aligning reconstruction with max_error=" << ransac_options.max_error; return EstimateSim3dRobust(src, tgt, ransac_options, *tgt_from_src).success; } bool AlignReconstructionsViaReprojections( const Reconstruction& src_reconstruction, const Reconstruction& tgt_reconstruction, const double min_inlier_observations, const double max_reproj_error, Sim3d* tgt_from_src) { THROW_CHECK_GE(min_inlier_observations, 0.0); THROW_CHECK_LE(min_inlier_observations, 1.0); RANSACOptions ransac_options; ransac_options.max_error = 1.0 - min_inlier_observations; ransac_options.min_inlier_ratio = 0.2; LORANSAC ransac(ransac_options, ReconstructionAlignmentEstimator( max_reproj_error, &src_reconstruction, &tgt_reconstruction), ReconstructionAlignmentEstimator( max_reproj_error, &src_reconstruction, &tgt_reconstruction)); const std::vector> common_image_ids = src_reconstruction.FindCommonRegImageIds(tgt_reconstruction); if (common_image_ids.size() < 3) { return false; } std::vector src_images(common_image_ids.size()); std::vector tgt_images(common_image_ids.size()); for (size_t i = 0; i < common_image_ids.size(); ++i) { src_images[i] = &src_reconstruction.Image(common_image_ids[i].first); tgt_images[i] = &tgt_reconstruction.Image(common_image_ids[i].second); } const auto report = ransac.Estimate(src_images, tgt_images); if (report.success) { *tgt_from_src = report.model; } return report.success; } bool AlignReconstructionsViaProjCenters( const Reconstruction& src_reconstruction, const Reconstruction& tgt_reconstruction, const double max_proj_center_error, Sim3d* tgt_from_src) { THROW_CHECK_GT(max_proj_center_error, 0); std::vector ref_image_names; std::vector ref_proj_centers; for (const auto& image : tgt_reconstruction.Images()) { if (image.second.HasPose()) { ref_image_names.push_back(image.second.Name()); ref_proj_centers.push_back(image.second.ProjectionCenter()); } } Sim3d tform; RANSACOptions ransac_options; ransac_options.max_error = max_proj_center_error; return AlignReconstructionToLocations(src_reconstruction, ref_image_names, ref_proj_centers, /*min_common_images=*/3, ransac_options, tgt_from_src); } std::vector ComputeImageAlignmentError( const Reconstruction& src_reconstruction, const Reconstruction& tgt_reconstruction, const Sim3d& tgt_from_src) { const std::vector> common_image_ids = src_reconstruction.FindCommonRegImageIds(tgt_reconstruction); const int num_common_images = common_image_ids.size(); std::vector errors; errors.reserve(num_common_images); for (const auto& image_ids : common_image_ids) { const auto& src_image = src_reconstruction.Image(image_ids.first); const Rigid3d tgt_world_from_src_cam = Inverse(TransformCameraWorld(tgt_from_src, src_image.CamFromWorld())); const Rigid3d tgt_world_from_tgt_cam = Inverse(tgt_reconstruction.Image(image_ids.second).CamFromWorld()); ImageAlignmentError error; error.image_name = src_image.Name(); error.rotation_error_deg = RadToDeg(tgt_world_from_src_cam.rotation().angularDistance( tgt_world_from_tgt_cam.rotation())); error.proj_center_error = (tgt_world_from_src_cam.translation() - tgt_world_from_tgt_cam.translation()) .norm(); errors.push_back(error); } return errors; } bool AlignReconstructionsViaPoints(const Reconstruction& src_reconstruction, const Reconstruction& tgt_reconstruction, const size_t min_common_observations, const double max_error, const double min_inlier_ratio, Sim3d* tgt_from_src) { THROW_CHECK_GT(min_common_observations, 0); THROW_CHECK_GT(max_error, 0.0); THROW_CHECK_GE(min_inlier_ratio, 0.0); THROW_CHECK_LE(min_inlier_ratio, 1.0); std::vector src_xyz; std::vector tgt_xyz; FlatHashMap counts; // Associate 3D points using point2D_idx for (const auto& src_point3D : src_reconstruction.Points3D()) { counts.clear(); // Count how often a 3D point in tgt is associated to this 3D point. for (const auto& track_el : src_point3D.second.track.Elements()) { const Image& tgt_image = tgt_reconstruction.Image(track_el.image_id); if (!tgt_image.HasPose()) { continue; } const Point2D& tgt_point2D = tgt_image.Point2D(track_el.point2D_idx); if (tgt_point2D.HasPoint3D()) { if (counts.find(tgt_point2D.point3D_id) != counts.end()) { counts[tgt_point2D.point3D_id]++; } else { counts[tgt_point2D.point3D_id] = 0; } } } if (counts.empty()) { continue; } // The 3D point in tgt who is associated the most is selected auto best_point3D = std::max_element(counts.begin(), counts.end(), [](const std::pair& p1, const std::pair& p2) { return p1.second < p2.second; }); if (best_point3D->second >= min_common_observations) { src_xyz.push_back(src_point3D.second.xyz); tgt_xyz.push_back(tgt_reconstruction.Point3D(best_point3D->first).xyz); } } THROW_CHECK_EQ(src_xyz.size(), tgt_xyz.size()); LOG(INFO) << "Found " << src_xyz.size() << " / " << src_reconstruction.NumPoints3D() << " valid correspondences."; RANSACOptions ransac_options; ransac_options.max_error = max_error; ransac_options.min_inlier_ratio = min_inlier_ratio; const auto report = EstimateSim3dRobust(src_xyz, tgt_xyz, ransac_options, *tgt_from_src); return report.success; } namespace { void CopyRegisteredImage(image_t image_id, const Sim3d& tgt_from_src, const Reconstruction& src_reconstruction, Reconstruction& tgt_reconstruction) { const Image& src_image = src_reconstruction.Image(image_id); if (!tgt_reconstruction.ExistsCamera(src_image.CameraId())) { tgt_reconstruction.AddCamera( src_reconstruction.Camera(src_image.CameraId())); } if (!tgt_reconstruction.ExistsRig(src_image.FramePtr()->RigId())) { tgt_reconstruction.AddRig( src_reconstruction.Rig(src_image.FramePtr()->RigId())); } if (!tgt_reconstruction.ExistsFrame(src_image.FrameId())) { Frame tgt_frame = src_reconstruction.Frame(src_image.FrameId()); tgt_frame.ResetRigPtr(); tgt_reconstruction.AddFrame(std::move(tgt_frame)); const Rigid3d cam_from_tgt_world = TransformCameraWorld(tgt_from_src, src_image.CamFromWorld()); tgt_reconstruction.Frame(src_image.FrameId()) .SetCamFromWorld(src_image.CameraId(), cam_from_tgt_world); } Image tgt_image = src_image; tgt_image.ResetCameraPtr(); tgt_image.ResetFramePtr(); tgt_reconstruction.AddImage(std::move(tgt_image)); } } // namespace bool MergeReconstructions(const double max_reproj_error, const Reconstruction& src_reconstruction, Reconstruction& tgt_reconstruction) { Sim3d tgt_from_src; if (!AlignReconstructionsViaReprojections(src_reconstruction, tgt_reconstruction, /*min_inlier_observations=*/0.3, max_reproj_error, &tgt_from_src)) { return false; } // Find common and missing images in the two reconstructions. Images are // matched by image id, which assumes that both reconstructions share a // consistent image_id<->name mapping (i.e. were derived from the same // database). If this assumption is violated -- e.g. the reconstructions were // built from independent databases that both number their images 1..N -- then // distinct physical images end up with colliding ids. Detect the // inconsistency via the image name and fail loudly instead. FlatHashSet common_image_ids; common_image_ids.reserve(src_reconstruction.NumRegImages()); FlatHashSet missing_image_ids; missing_image_ids.reserve(src_reconstruction.NumRegImages()); for (const image_t image_id : src_reconstruction.RegImageIds()) { if (tgt_reconstruction.ExistsImage(image_id)) { const std::string& src_name = src_reconstruction.Image(image_id).Name(); const std::string& tgt_name = tgt_reconstruction.Image(image_id).Name(); if (src_name != tgt_name) { LOG(ERROR) << "Cannot merge reconstructions: image_id=" << image_id << " refers to \"" << src_name << "\" in the source reconstruction but \"" << tgt_name << "\" in the target. MergeReconstructions requires both " << "reconstructions to share a consistent image_id<->name mapping " << "(i.e., be derived from the same database)."; return false; } common_image_ids.insert(image_id); } else { missing_image_ids.insert(image_id); } } // Register the missing images in this src_reconstruction. for (const auto image_id : missing_image_ids) { CopyRegisteredImage( image_id, tgt_from_src, src_reconstruction, tgt_reconstruction); } // Merge the two point clouds using the following two rules: // - copy points to this src_reconstruction with non-conflicting tracks, // i.e. points that do not have an already triangulated observation // in this src_reconstruction. // - merge tracks that are unambiguous, i.e. only merge points in the two // reconstructions if they have a one-to-one mapping. // Note that in both cases no cheirality or reprojection test is performed. for (const auto& [_, point3D] : src_reconstruction.Points3D()) { Track new_track; Track old_track; FlatHashSet old_point3D_ids; for (const auto& track_el : point3D.track.Elements()) { if (common_image_ids.count(track_el.image_id) > 0) { const auto& point2D = tgt_reconstruction.Image(track_el.image_id) .Point2D(track_el.point2D_idx); if (point2D.HasPoint3D()) { old_track.AddElement(track_el); old_point3D_ids.insert(point2D.point3D_id); } else { new_track.AddElement(track_el); } } else if (missing_image_ids.count(track_el.image_id) > 0) { tgt_reconstruction.Image(track_el.image_id) .ResetPoint3DForPoint2D(track_el.point2D_idx); new_track.AddElement(track_el); } } const bool create_new_point = new_track.Length() >= 2; const bool merge_new_and_old_point = (new_track.Length() + old_track.Length()) >= 2 && old_point3D_ids.size() == 1; if (create_new_point || merge_new_and_old_point) { const Eigen::Vector3d xyz = tgt_from_src * point3D.xyz; const auto point3D_id = tgt_reconstruction.AddPoint3D(xyz, new_track, point3D.color); if (old_point3D_ids.size() == 1) { tgt_reconstruction.MergePoints3D(point3D_id, *old_point3D_ids.begin()); } } } return true; } bool AlignReconstructionToOrigRigScales( const NodeHashMap& orig_rigs, Reconstruction* reconstruction) { double scale_sum = 0; int scale_count = 0; for (const auto& [rig_id, orig_rig] : orig_rigs) { double scale_sum_rig = 0; int scale_count_rig = 0; for (auto& [sensor_id, sensor_from_orig_rig] : orig_rig.NonRefSensors()) { if (!sensor_from_orig_rig.has_value()) { continue; } // Here we do not include rigs that are panoramic. double sensor_from_orig_rig_norm = sensor_from_orig_rig->translation().norm(); if (sensor_from_orig_rig_norm < 1e-6) { continue; } THROW_CHECK(reconstruction->Rig(rig_id).HasSensorFromRig(sensor_id)); double scale = reconstruction->Rig(rig_id) .SensorFromRig(sensor_id) .translation() .norm() / sensor_from_orig_rig_norm; scale_sum_rig += scale; ++scale_count_rig; } if (scale_count_rig > 0) { scale_sum += scale_sum_rig / scale_count_rig; ++scale_count; } } if (scale_count == 0) { return false; } Sim3d new_from_old_world; new_from_old_world.scale() = scale_count / scale_sum; reconstruction->Transform(new_from_old_world); return true; } AlignmentErrorSummary AlignmentErrorSummary::Compute( const std::vector& errors) { AlignmentErrorSummary summary; if (errors.empty()) { return summary; } std::vector rotation_errors_deg; rotation_errors_deg.reserve(errors.size()); std::vector proj_center_errors; proj_center_errors.reserve(errors.size()); for (const auto& error : errors) { rotation_errors_deg.push_back(error.rotation_error_deg); proj_center_errors.push_back(error.proj_center_error); } auto ComputeStatistics = [](std::vector& values) { Statistics stats; if (values.empty()) { return stats; } stats.min = Percentile(values, 0); stats.max = Percentile(values, 100); stats.mean = Mean(values); stats.median = Median(values); stats.p90 = Percentile(values, 90); stats.p99 = Percentile(values, 99); return stats; }; summary.rotation_errors_deg = ComputeStatistics(rotation_errors_deg); summary.proj_center_errors = ComputeStatistics(proj_center_errors); return summary; } } // namespace colmap colmap-4.2.0/src/colmap/estimators/alignment.h000066400000000000000000000131201524536416500213630ustar00rootroot00000000000000// Copyright (c), ETH Zurich and UNC Chapel Hill. // All rights reserved. // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are met: // // * Redistributions of source code must retain the above copyright // notice, this list of conditions and the following disclaimer. // // * Redistributions in binary form must reproduce the above copyright // notice, this list of conditions and the following disclaimer in the // documentation and/or other materials provided with the distribution. // // * Neither the name of ETH Zurich and UNC Chapel Hill nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE // ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE // LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR // CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF // SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS // INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN // CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) // ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE // POSSIBILITY OF SUCH DAMAGE. #pragma once #include "colmap/geometry/sim3.h" #include "colmap/optim/ransac.h" #include "colmap/scene/reconstruction.h" #include "colmap/util/hash_containers.h" #include namespace colmap { // Robustly align reconstruction to given image locations (projection centers). bool AlignReconstructionToLocations( const Reconstruction& src_reconstruction, const std::vector& tgt_image_names, const std::vector& tgt_image_locations, int min_common_images, const RANSACOptions& ransac_options, Sim3d* tgt_from_src); // Robustly align reconstruction to given pose priors. If max_error is not set // in the RANSAC options, derive it from the median position covariance. bool AlignReconstructionToPosePriors( const Reconstruction& src_reconstruction, const std::vector& tgt_pose_priors, RANSACOptions ransac_options, double prior_position_fallback_stddev, Sim3d* tgt_from_src); // Robustly compute alignment between reconstructions by finding images that // are registered in both reconstructions. The alignment is then estimated // robustly inside RANSAC from corresponding projection centers. An alignment // is verified by reprojecting common 3D point observations. // The min_inlier_observations threshold determines how many observations // in a common image must reproject within the given threshold. bool AlignReconstructionsViaReprojections( const Reconstruction& src_reconstruction, const Reconstruction& tgt_reconstruction, double min_inlier_observations, double max_reproj_error, Sim3d* tgt_from_src); // Robustly compute alignment between reconstructions by finding images that // are registered in both reconstructions. The alignment is then estimated // robustly inside RANSAC from corresponding projection centers and by // minimizing the Euclidean distance between them in world space. bool AlignReconstructionsViaProjCenters( const Reconstruction& src_reconstruction, const Reconstruction& tgt_reconstruction, double max_proj_center_error, Sim3d* tgt_from_src); // Robustly compute the alignment between reconstructions that share the // same 2D points. It is estimated by minimizing the 3D distance between // corresponding 3D points. bool AlignReconstructionsViaPoints(const Reconstruction& src_reconstruction, const Reconstruction& tgt_reconstruction, size_t min_common_observations, double max_error, double min_inlier_ratio, Sim3d* tgt_from_src); // Compute image alignment errors in the target coordinate frame. struct ImageAlignmentError { std::string image_name; double rotation_error_deg = -1; double proj_center_error = -1; }; std::vector ComputeImageAlignmentError( const Reconstruction& src_reconstruction, const Reconstruction& tgt_reconstruction, const Sim3d& tgt_from_src); // Summary of alignment errors for image poses. struct AlignmentErrorSummary { struct Statistics { double min = 0; double max = 0; double mean = 0; double median = 0; double p90 = 0; double p99 = 0; }; Statistics rotation_errors_deg; Statistics proj_center_errors; static AlignmentErrorSummary Compute( const std::vector& errors); }; // Aligns the source to the target reconstruction and merges cameras, images, // points3D into the target using the alignment. Returns false on failure. bool MergeReconstructions(double max_reproj_error, const Reconstruction& src_reconstruction, Reconstruction& tgt_reconstruction); // Align reconstruction to the original metric scales in rig extrinsics. Returns // false if there is no available non-panoramic rig in the alignment process. bool AlignReconstructionToOrigRigScales( const NodeHashMap& orig_rigs, Reconstruction* reconstruction); } // namespace colmap colmap-4.2.0/src/colmap/estimators/alignment_test.cc000066400000000000000000000361701524536416500225720ustar00rootroot00000000000000// Copyright (c), ETH Zurich and UNC Chapel Hill. // All rights reserved. // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are met: // // * Redistributions of source code must retain the above copyright // notice, this list of conditions and the following disclaimer. // // * Redistributions in binary form must reproduce the above copyright // notice, this list of conditions and the following disclaimer in the // documentation and/or other materials provided with the distribution. // // * Neither the name of ETH Zurich and UNC Chapel Hill nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE // ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE // LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR // CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF // SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS // INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN // CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) // ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE // POSSIBILITY OF SUCH DAMAGE. #include "colmap/estimators/alignment.h" #include "colmap/geometry/rigid3_matchers.h" #include "colmap/geometry/sim3.h" #include "colmap/math/random.h" #include "colmap/math/random_eigen.h" #include "colmap/scene/reconstruction.h" #include "colmap/scene/synthetic.h" #include "colmap/util/hash_containers.h" #include namespace colmap { namespace { Sim3d TestSim3d() { return Sim3d(RandomUniformReal(0.5, 2), RandomEigenQuaterniond(), RandomEigenVectord<3>()); } void ExpectEqualSim3d(const Sim3d& gt_tgt_from_src, const Sim3d& tgt_from_src) { EXPECT_NEAR(gt_tgt_from_src.scale(), tgt_from_src.scale(), 1e-6); EXPECT_LT(gt_tgt_from_src.rotation().angularDistance(tgt_from_src.rotation()), 1e-6); EXPECT_LT((gt_tgt_from_src.translation() - tgt_from_src.translation()).norm(), 1e-6); } Reconstruction GenerateReconstructionForAlignment() { Reconstruction reconstruction; SyntheticDatasetOptions synthetic_dataset_options; synthetic_dataset_options.num_rigs = 2; synthetic_dataset_options.num_cameras_per_rig = 1; synthetic_dataset_options.num_frames_per_rig = 10; synthetic_dataset_options.num_points3D = 50; SynthesizeDataset(synthetic_dataset_options, &reconstruction); return reconstruction; } TEST(Alignment, AlignReconstructionToLocations) { Reconstruction src_reconstruction = GenerateReconstructionForAlignment(); Reconstruction tgt_reconstruction = src_reconstruction; Sim3d gt_tgt_from_src = TestSim3d(); tgt_reconstruction.Transform(gt_tgt_from_src); std::vector tgt_image_names; std::vector tgt_image_locations; for (const auto& [_, image] : tgt_reconstruction.Images()) { tgt_image_names.push_back(image.Name()); tgt_image_locations.push_back(image.ProjectionCenter()); } RANSACOptions ransac_options; ransac_options.max_error = 1e-2; Sim3d tgt_from_src; ASSERT_FALSE(AlignReconstructionToLocations( src_reconstruction, tgt_image_names, tgt_image_locations, /*min_common_images=*/tgt_image_names.size() + 1, ransac_options, &tgt_from_src)); ASSERT_TRUE(AlignReconstructionToLocations(src_reconstruction, tgt_image_names, tgt_image_locations, /*min_common_images=*/3, ransac_options, &tgt_from_src)); ExpectEqualSim3d(gt_tgt_from_src, tgt_from_src); } TEST(Alignment, AlignReconstructionToPosePriors) { Reconstruction src_reconstruction = GenerateReconstructionForAlignment(); Reconstruction tgt_reconstruction = src_reconstruction; Sim3d gt_tgt_from_src = TestSim3d(); tgt_reconstruction.Transform(gt_tgt_from_src); std::vector tgt_pose_priors; for (const auto& [image_id, image] : tgt_reconstruction.Images()) { PosePrior& pose_prior = tgt_pose_priors.emplace_back(); pose_prior.pose_prior_id = tgt_pose_priors.size(); pose_prior.corr_data_id = image.DataId(); pose_prior.coordinate_system = PosePrior::CoordinateSystem::CARTESIAN; pose_prior.position = image.ProjectionCenter(); pose_prior.position_covariance = 1e-2 * Eigen::Matrix3d::Identity(); } RANSACOptions ransac_options; ransac_options.max_error = 1e-2; Sim3d tgt_from_src; ASSERT_TRUE( AlignReconstructionToPosePriors(src_reconstruction, tgt_pose_priors, ransac_options, /*prior_position_fallback_stddev=*/1.0, &tgt_from_src)); ExpectEqualSim3d(gt_tgt_from_src, tgt_from_src); } TEST(Alignment, AlignReconstructionToPosePriorsWithAutomaticMaxError) { Reconstruction src_reconstruction = GenerateReconstructionForAlignment(); Reconstruction tgt_reconstruction = src_reconstruction; const Sim3d gt_tgt_from_src = TestSim3d(); tgt_reconstruction.Transform(gt_tgt_from_src); std::vector tgt_pose_priors; for (const auto& [image_id, image] : tgt_reconstruction.Images()) { PosePrior& pose_prior = tgt_pose_priors.emplace_back(); pose_prior.pose_prior_id = tgt_pose_priors.size(); pose_prior.corr_data_id = image.DataId(); pose_prior.coordinate_system = PosePrior::CoordinateSystem::CARTESIAN; pose_prior.position = image.ProjectionCenter(); pose_prior.position_covariance = 1e-4 * Eigen::Matrix3d::Identity(); } RANSACOptions ransac_options; ransac_options.max_error = 0.0; Sim3d tgt_from_src; ASSERT_TRUE( AlignReconstructionToPosePriors(src_reconstruction, tgt_pose_priors, ransac_options, /*prior_position_fallback_stddev=*/1.0, &tgt_from_src)); ExpectEqualSim3d(gt_tgt_from_src, tgt_from_src); } TEST(Alignment, AlignReconstructionsViaReprojections) { Reconstruction src_reconstruction = GenerateReconstructionForAlignment(); Reconstruction tgt_reconstruction = src_reconstruction; Sim3d gt_tgt_from_src = TestSim3d(); tgt_reconstruction.Transform(gt_tgt_from_src); Sim3d tgt_from_src; ASSERT_TRUE( AlignReconstructionsViaReprojections(src_reconstruction, tgt_reconstruction, /*min_inlier_observations=*/0.9, /*max_reproj_error=*/2, &tgt_from_src)); ExpectEqualSim3d(gt_tgt_from_src, tgt_from_src); } TEST(Alignment, AlignReconstructionsViaProjCenters) { Reconstruction src_reconstruction = GenerateReconstructionForAlignment(); Reconstruction tgt_reconstruction = src_reconstruction; Sim3d gt_tgt_from_src = TestSim3d(); tgt_reconstruction.Transform(gt_tgt_from_src); Sim3d tgt_from_src; ASSERT_TRUE(AlignReconstructionsViaProjCenters(src_reconstruction, tgt_reconstruction, /*max_proj_center_error=*/0.1, &tgt_from_src)); ExpectEqualSim3d(gt_tgt_from_src, tgt_from_src); } TEST(Alignment, AlignReconstructionsViaPoints) { Reconstruction src_reconstruction = GenerateReconstructionForAlignment(); Reconstruction tgt_reconstruction = src_reconstruction; Sim3d gt_tgt_from_src = TestSim3d(); tgt_reconstruction.Transform(gt_tgt_from_src); Sim3d tgt_from_src; ASSERT_TRUE(AlignReconstructionsViaPoints(src_reconstruction, tgt_reconstruction, /*min_common_observations=*/3, /*max_error=*/0.01, /*min_inlier_ratio=*/0.9, &tgt_from_src)); ExpectEqualSim3d(gt_tgt_from_src, tgt_from_src); } TEST(Alignment, MergeReconstructions) { // Synthesize a reconstruction which has at least two cameras Reconstruction src_reconstruction; SyntheticDatasetOptions synthetic_dataset_options; synthetic_dataset_options.num_rigs = 3; synthetic_dataset_options.num_cameras_per_rig = 1; synthetic_dataset_options.num_frames_per_rig = 10; synthetic_dataset_options.num_points3D = 50; SynthesizeDataset(synthetic_dataset_options, &src_reconstruction); Reconstruction orig_reconstruction = src_reconstruction; Reconstruction tgt_reconstruction = src_reconstruction; auto remove_rig_frames = [](Reconstruction& reconstruction, rig_t rig_id) { const std::vector frame_ids = reconstruction.RegFrameIds(); for (const auto& frame_id : frame_ids) { if (reconstruction.Frame(frame_id).RigId() == rig_id) { reconstruction.DeRegisterFrame(frame_id); } } }; remove_rig_frames(src_reconstruction, 1); remove_rig_frames(tgt_reconstruction, 2); // Remove all unregistered rigs/cameras/frames/images. src_reconstruction.TearDown(); tgt_reconstruction.TearDown(); EXPECT_EQ(src_reconstruction.NumRigs(), 2); EXPECT_EQ(src_reconstruction.NumCameras(), 2); EXPECT_EQ(src_reconstruction.NumFrames(), 20); EXPECT_EQ(src_reconstruction.NumRegFrames(), 20); EXPECT_EQ(src_reconstruction.NumImages(), 20); EXPECT_EQ(tgt_reconstruction.NumRigs(), 2); EXPECT_EQ(tgt_reconstruction.NumCameras(), 2); EXPECT_EQ(tgt_reconstruction.NumFrames(), 20); EXPECT_EQ(tgt_reconstruction.NumRegFrames(), 20); EXPECT_EQ(tgt_reconstruction.NumImages(), 20); // Merge reconstructions. ASSERT_TRUE(MergeReconstructions( /*max_reproj_error=*/1e-4, src_reconstruction, tgt_reconstruction)); EXPECT_EQ(tgt_reconstruction.NumRigs(), 3); EXPECT_EQ(tgt_reconstruction.NumCameras(), 3); EXPECT_EQ(tgt_reconstruction.NumFrames(), 30); EXPECT_EQ(tgt_reconstruction.NumRegFrames(), 30); EXPECT_EQ(tgt_reconstruction.NumImages(), 30); EXPECT_EQ(tgt_reconstruction.NumPoints3D(), 50); EXPECT_EQ(tgt_reconstruction.ComputeNumObservations(), orig_reconstruction.ComputeNumObservations()); } TEST(Alignment, MergeReconstructionsInconsistentImageNames) { // Reconstructions built from independent databases can assign the same image // id to distinct physical images. Merging by id would then silently drop // images and corrupt tracks, so such an inconsistent id<->name mapping must // be detected and rejected rather than merged (see issue #3405). Reconstruction src_reconstruction; SyntheticDatasetOptions synthetic_dataset_options; synthetic_dataset_options.num_rigs = 3; synthetic_dataset_options.num_cameras_per_rig = 1; synthetic_dataset_options.num_frames_per_rig = 10; synthetic_dataset_options.num_points3D = 50; SynthesizeDataset(synthetic_dataset_options, &src_reconstruction); Reconstruction tgt_reconstruction = src_reconstruction; auto remove_rig_frames = [](Reconstruction& reconstruction, rig_t rig_id) { const std::vector frame_ids = reconstruction.RegFrameIds(); for (const auto& frame_id : frame_ids) { if (reconstruction.Frame(frame_id).RigId() == rig_id) { reconstruction.DeRegisterFrame(frame_id); } } }; remove_rig_frames(src_reconstruction, 1); remove_rig_frames(tgt_reconstruction, 2); src_reconstruction.TearDown(); tgt_reconstruction.TearDown(); // Find an image id registered in both reconstructions and give it a different // name in the target, simulating an id collision between two distinct images. // Enough images still share a consistent name for the alignment step (which // matches by name) to succeed, so the merge reaches -- and must fail at -- // the id/name consistency check. bool found_shared_id = false; for (const image_t image_id : src_reconstruction.RegImageIds()) { if (tgt_reconstruction.ExistsImage(image_id)) { tgt_reconstruction.Image(image_id).SetName("colliding_name.jpg"); found_shared_id = true; break; } } ASSERT_TRUE(found_shared_id); const size_t num_tgt_images_before_merge = tgt_reconstruction.NumImages(); EXPECT_FALSE(MergeReconstructions( /*max_reproj_error=*/1e-4, src_reconstruction, tgt_reconstruction)); // The merge must abort before mutating the target reconstruction. EXPECT_EQ(tgt_reconstruction.NumImages(), num_tgt_images_before_merge); } TEST(Alignment, AlignReconstructionToOrigRigScales) { Reconstruction reconstruction; SyntheticDatasetOptions synthetic_dataset_options; synthetic_dataset_options.num_rigs = 2; synthetic_dataset_options.num_cameras_per_rig = 4; synthetic_dataset_options.num_frames_per_rig = 10; synthetic_dataset_options.num_points3D = 50; SynthesizeDataset(synthetic_dataset_options, &reconstruction); NodeHashMap orig_rigs = reconstruction.Rigs(); reconstruction.Transform(TestSim3d()); AlignReconstructionToOrigRigScales(orig_rigs, &reconstruction); for (const auto& [rig_id, orig_rig] : orig_rigs) { for (const auto& [sensor_id, sensor_from_orig_rig] : orig_rig.NonRefSensors()) { if (!sensor_from_orig_rig.has_value()) { continue; } EXPECT_THAT( reconstruction.Rig(rig_id).SensorFromRig(sensor_id), Rigid3dNear( sensor_from_orig_rig.value(), /*rtol=*/1e-6, /*ttol=*/1e-6)); } } } TEST(AlignmentErrorSummary, Empty) { std::vector errors; AlignmentErrorSummary summary = AlignmentErrorSummary::Compute(errors); EXPECT_EQ(summary.rotation_errors_deg.min, 0); EXPECT_EQ(summary.proj_center_errors.min, 0); } TEST(AlignmentErrorSummary, MultipleErrors) { std::vector errors(5); for (size_t i = 0; i < errors.size(); ++i) { errors[i].rotation_error_deg = static_cast(i + 1); errors[i].proj_center_error = static_cast(i + 1) * 0.1; } const AlignmentErrorSummary summary = AlignmentErrorSummary::Compute(errors); EXPECT_NEAR(summary.rotation_errors_deg.min, 1.0, 1e-10); EXPECT_NEAR(summary.rotation_errors_deg.max, 5.0, 1e-10); EXPECT_NEAR(summary.rotation_errors_deg.mean, 3.0, 1e-10); EXPECT_NEAR(summary.rotation_errors_deg.median, 3.0, 1e-10); EXPECT_NEAR(summary.rotation_errors_deg.p90, 4.6, 1e-10); EXPECT_NEAR(summary.rotation_errors_deg.p99, 4.96, 1e-10); EXPECT_NEAR(summary.proj_center_errors.min, 0.1, 1e-10); EXPECT_NEAR(summary.proj_center_errors.max, 0.5, 1e-10); EXPECT_NEAR(summary.proj_center_errors.mean, 0.3, 1e-10); EXPECT_NEAR(summary.proj_center_errors.p90, 0.46, 1e-10); EXPECT_NEAR(summary.proj_center_errors.p99, 0.496, 1e-10); } } // namespace } // namespace colmap colmap-4.2.0/src/colmap/estimators/bundle_adjustment.cc000066400000000000000000000326121524536416500232610ustar00rootroot00000000000000// Copyright (c), ETH Zurich and UNC Chapel Hill. // All rights reserved. // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are met: // // * Redistributions of source code must retain the above copyright // notice, this list of conditions and the following disclaimer. // // * Redistributions in binary form must reproduce the above copyright // notice, this list of conditions and the following disclaimer in the // documentation and/or other materials provided with the distribution. // // * Neither the name of ETH Zurich and UNC Chapel Hill nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE // ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE // LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR // CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF // SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS // INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN // CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) // ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE // POSSIBILITY OF SUCH DAMAGE. #include "colmap/estimators/bundle_adjustment.h" #include "colmap/estimators/bundle_adjustment_caspar.h" #include "colmap/estimators/bundle_adjustment_ceres.h" #include "colmap/util/hash_containers.h" namespace colmap { bool BundleAdjustmentSummary::IsSolutionUsable() const { return termination_type == BundleAdjustmentTerminationType::CONVERGENCE || termination_type == BundleAdjustmentTerminationType::NO_CONVERGENCE || termination_type == BundleAdjustmentTerminationType::USER_SUCCESS; } std::string BundleAdjustmentSummary::BriefReport() const { return "Bundle adjustment report: termination=" + std::string( BundleAdjustmentTerminationTypeToString(termination_type)) + ", num_residuals=" + std::to_string(num_residuals); } //////////////////////////////////////////////////////////////////////////////// // BundleAdjustmentConfig //////////////////////////////////////////////////////////////////////////////// void BundleAdjustmentConfig::FixGauge(BundleAdjustmentGauge gauge) { fixed_gauge_ = gauge; } BundleAdjustmentGauge BundleAdjustmentConfig::FixedGauge() const { return fixed_gauge_; } size_t BundleAdjustmentConfig::NumImages() const { return image_ids_.size(); } size_t BundleAdjustmentConfig::NumPoints() const { return variable_point3D_ids_.size() + constant_point3D_ids_.size(); } size_t BundleAdjustmentConfig::NumConstantCamIntrinsics() const { return constant_cam_intrinsics_.size(); } size_t BundleAdjustmentConfig::NumConstantSensorFromRigPoses() const { return constant_sensor_from_rig_poses_.size(); } size_t BundleAdjustmentConfig::NumConstantRigFromWorldPoses() const { return constant_rig_from_world_poses_.size(); } size_t BundleAdjustmentConfig::NumVariablePoints() const { return variable_point3D_ids_.size(); } size_t BundleAdjustmentConfig::NumConstantPoints() const { return constant_point3D_ids_.size(); } size_t BundleAdjustmentConfig::NumResiduals( const Reconstruction& reconstruction) const { // Count the number of observations for all added images. size_t num_observations = 0; for (const image_t image_id : image_ids_) { const auto& image = reconstruction.Image(image_id); for (const auto& point2D : image.Points2D()) { if (point2D.HasPoint3D() && !IsIgnoredPoint(point2D.point3D_id)) { ++num_observations; } } } // Count the number of observations for all added 3D points that are not // already added as part of the images above. auto NumObservationsForPoint = [this, &reconstruction](const point3D_t point3D_id) { size_t num_observations_for_point = 0; const auto& point3D = reconstruction.Point3D(point3D_id); for (const auto& track_el : point3D.track.Elements()) { if (image_ids_.count(track_el.image_id) == 0) { ++num_observations_for_point; } } return num_observations_for_point; }; for (const auto point3D_id : variable_point3D_ids_) { num_observations += NumObservationsForPoint(point3D_id); } for (const auto point3D_id : constant_point3D_ids_) { num_observations += NumObservationsForPoint(point3D_id); } CHECK_GE(num_observations, 0); return 2 * num_observations; } void BundleAdjustmentConfig::AddImage(const image_t image_id) { image_ids_.insert(image_id); } bool BundleAdjustmentConfig::HasImage(const image_t image_id) const { return image_ids_.find(image_id) != image_ids_.end(); } void BundleAdjustmentConfig::RemoveImage(const image_t image_id) { image_ids_.erase(image_id); } void BundleAdjustmentConfig::SetConstantCamIntrinsics( const camera_t camera_id) { constant_cam_intrinsics_.insert(camera_id); } void BundleAdjustmentConfig::SetVariableCamIntrinsics( const camera_t camera_id) { constant_cam_intrinsics_.erase(camera_id); } bool BundleAdjustmentConfig::HasConstantCamIntrinsics( const camera_t camera_id) const { return constant_cam_intrinsics_.find(camera_id) != constant_cam_intrinsics_.end(); } void BundleAdjustmentConfig::SetConstantSensorFromRigPose( const sensor_t sensor_id) { constant_sensor_from_rig_poses_.insert(sensor_id); } void BundleAdjustmentConfig::SetVariableSensorFromRigPose( const sensor_t sensor_id) { constant_sensor_from_rig_poses_.erase(sensor_id); } bool BundleAdjustmentConfig::HasConstantSensorFromRigPose( const sensor_t sensor_id) const { return constant_sensor_from_rig_poses_.find(sensor_id) != constant_sensor_from_rig_poses_.end(); } void BundleAdjustmentConfig::SetConstantRigFromWorldPose( const frame_t frame_id) { constant_rig_from_world_poses_.insert(frame_id); } void BundleAdjustmentConfig::SetVariableRigFromWorldPose( const frame_t frame_id) { constant_rig_from_world_poses_.erase(frame_id); } bool BundleAdjustmentConfig::HasConstantRigFromWorldPose( const frame_t frame_id) const { return constant_rig_from_world_poses_.find(frame_id) != constant_rig_from_world_poses_.end(); } const FlatHashSet& BundleAdjustmentConfig::Images() const { return image_ids_; } const FlatHashSet& BundleAdjustmentConfig::VariablePoints() const { return variable_point3D_ids_; } const FlatHashSet& BundleAdjustmentConfig::ConstantPoints() const { return constant_point3D_ids_; } const FlatHashSet& BundleAdjustmentConfig::ConstantCamIntrinsics() const { return constant_cam_intrinsics_; } const FlatHashSet& BundleAdjustmentConfig::ConstantSensorFromRigPoses() const { return constant_sensor_from_rig_poses_; } const FlatHashSet& BundleAdjustmentConfig::ConstantRigFromWorldPoses() const { return constant_rig_from_world_poses_; } void BundleAdjustmentConfig::AddVariablePoint(const point3D_t point3D_id) { THROW_CHECK(!HasConstantPoint(point3D_id)); variable_point3D_ids_.insert(point3D_id); } void BundleAdjustmentConfig::AddConstantPoint(const point3D_t point3D_id) { THROW_CHECK(!HasVariablePoint(point3D_id)); constant_point3D_ids_.insert(point3D_id); } void BundleAdjustmentConfig::IgnorePoint(const point3D_t point3D_id) { CHECK(!HasVariablePoint(point3D_id)); CHECK(!HasConstantPoint(point3D_id)); ignored_point3D_ids_.insert(point3D_id); } bool BundleAdjustmentConfig::HasPoint(const point3D_t point3D_id) const { return HasVariablePoint(point3D_id) || HasConstantPoint(point3D_id); } bool BundleAdjustmentConfig::HasVariablePoint( const point3D_t point3D_id) const { return variable_point3D_ids_.count(point3D_id); } bool BundleAdjustmentConfig::HasConstantPoint( const point3D_t point3D_id) const { return constant_point3D_ids_.count(point3D_id); } bool BundleAdjustmentConfig::IsIgnoredPoint(const point3D_t point3D_id) const { return ignored_point3D_ids_.count(point3D_id); } void BundleAdjustmentConfig::RemoveVariablePoint(const point3D_t point3D_id) { variable_point3D_ids_.erase(point3D_id); } void BundleAdjustmentConfig::RemoveConstantPoint(const point3D_t point3D_id) { constant_point3D_ids_.erase(point3D_id); } //////////////////////////////////////////////////////////////////////////////// // BundleAdjuster //////////////////////////////////////////////////////////////////////////////// BundleAdjuster::BundleAdjuster(const BundleAdjustmentOptions& options, const BundleAdjustmentConfig& config) : options_(options), config_(config) { THROW_CHECK(options_.Check()); } const BundleAdjustmentOptions& BundleAdjuster::Options() const { return options_; } const BundleAdjustmentConfig& BundleAdjuster::Config() const { return config_; } //////////////////////////////////////////////////////////////////////////////// // BundleAdjustmentOptions //////////////////////////////////////////////////////////////////////////////// BundleAdjustmentBackendOptions::BundleAdjustmentBackendOptions() : ceres(std::make_shared()), caspar(std::make_shared()) {} BundleAdjustmentBackendOptions::BundleAdjustmentBackendOptions( const BundleAdjustmentBackendOptions& other) { if (other.ceres) { ceres = std::make_shared(*other.ceres); } if (other.caspar) { caspar = std::make_shared(*other.caspar); } } BundleAdjustmentBackendOptions& BundleAdjustmentBackendOptions::operator=( const BundleAdjustmentBackendOptions& other) { if (this == &other) { return *this; } if (other.ceres) { ceres = std::make_shared(*other.ceres); } else { ceres.reset(); } if (other.caspar) { caspar = std::make_shared(*other.caspar); } else { caspar.reset(); } return *this; } bool BundleAdjustmentOptions::Check() const { return THROW_CHECK_NOTNULL(ceres)->Check(); } std::unique_ptr CreateDefaultBundleAdjuster( const BundleAdjustmentOptions& options, const BundleAdjustmentConfig& config, Reconstruction& reconstruction) { switch (options.backend) { case BundleAdjustmentBackend::CERES: return CreateDefaultCeresBundleAdjuster(options, config, reconstruction); case BundleAdjustmentBackend::CASPAR: #ifdef CASPAR_ENABLED return CreateDefaultCasparBundleAdjuster(options, config, reconstruction); #else LOG(FATAL_THROW) << "Caspar BA backend selected but COLMAP was built without " "CASPAR_ENABLED; rebuild with -DCASPAR_ENABLED=ON to use it"; return nullptr; #endif } LOG(FATAL_THROW) << "Unknown bundle adjustment backend: " << static_cast(options.backend); return nullptr; } //////////////////////////////////////////////////////////////////////////////// // PosePriorBundleAdjustmentOptions //////////////////////////////////////////////////////////////////////////////// PosePriorBundleAdjustmentBackendOptions:: PosePriorBundleAdjustmentBackendOptions() : ceres(std::make_shared()) {} PosePriorBundleAdjustmentBackendOptions:: PosePriorBundleAdjustmentBackendOptions( const PosePriorBundleAdjustmentBackendOptions& other) { if (other.ceres) { ceres = std::make_shared(*other.ceres); } } PosePriorBundleAdjustmentBackendOptions& PosePriorBundleAdjustmentBackendOptions::operator=( const PosePriorBundleAdjustmentBackendOptions& other) { if (this == &other) { return *this; } if (other.ceres) { ceres = std::make_shared(*other.ceres); } else { ceres.reset(); } return *this; } bool PosePriorBundleAdjustmentOptions::Check() const { CHECK_OPTION_GT(prior_position_fallback_stddev, 0); return THROW_CHECK_NOTNULL(ceres)->Check(); } std::unique_ptr CreatePosePriorBundleAdjuster( const BundleAdjustmentOptions& options, const PosePriorBundleAdjustmentOptions& prior_options, const BundleAdjustmentConfig& config, std::vector pose_priors, Reconstruction& reconstruction) { switch (options.backend) { case BundleAdjustmentBackend::CERES: return CreatePosePriorCeresBundleAdjuster(options, prior_options, config, std::move(pose_priors), reconstruction); case BundleAdjustmentBackend::CASPAR: #ifdef CASPAR_ENABLED LOG(FATAL_THROW) << "Caspar BA backend does not support pose priors"; #else LOG(FATAL_THROW) << "Caspar BA backend selected but COLMAP was built without " "CASPAR_ENABLED; rebuild with -DCASPAR_ENABLED=ON to use it"; #endif return nullptr; } LOG(FATAL_THROW) << "Unknown bundle adjustment backend: " << static_cast(options.backend); return nullptr; } } // namespace colmap colmap-4.2.0/src/colmap/estimators/bundle_adjustment.h000066400000000000000000000252171524536416500231260ustar00rootroot00000000000000// Copyright (c), ETH Zurich and UNC Chapel Hill. // All rights reserved. // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are met: // // * Redistributions of source code must retain the above copyright // notice, this list of conditions and the following disclaimer. // // * Redistributions in binary form must reproduce the above copyright // notice, this list of conditions and the following disclaimer in the // documentation and/or other materials provided with the distribution. // // * Neither the name of ETH Zurich and UNC Chapel Hill nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE // ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE // LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR // CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF // SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS // INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN // CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) // ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE // POSSIBILITY OF SUCH DAMAGE. #pragma once #include "colmap/optim/ransac.h" #include "colmap/scene/reconstruction.h" #include "colmap/util/eigen_alignment.h" #include "colmap/util/enum_utils.h" #include "colmap/util/hash_containers.h" #include #include #include namespace colmap { struct CeresBundleAdjustmentOptions; struct CeresPosePriorBundleAdjustmentOptions; MAKE_ENUM_CLASS_OVERLOAD_STREAM( BundleAdjustmentGauge, -1, UNSPECIFIED, TWO_CAMS_FROM_WORLD, THREE_POINTS); // Termination type for bundle adjustment, independent of solver backend. MAKE_ENUM_CLASS_OVERLOAD_STREAM(BundleAdjustmentTerminationType, 0, CONVERGENCE, NO_CONVERGENCE, FAILURE, USER_SUCCESS, USER_FAILURE); // Backend for bundle adjustment solver. MAKE_ENUM_CLASS_OVERLOAD_STREAM(BundleAdjustmentBackend, 0, CERES, CASPAR); // Summary of bundle adjustment results, independent of solver backend. struct BundleAdjustmentSummary { BundleAdjustmentTerminationType termination_type = BundleAdjustmentTerminationType::FAILURE; // Number of residuals connected to at least one variable parameter block. // Excludes residuals where all connected parameters are constant. int num_residuals = 0; bool IsSolutionUsable() const; virtual std::string BriefReport() const; virtual ~BundleAdjustmentSummary() = default; }; // Configuration container to setup bundle adjustment problems. class BundleAdjustmentConfig { public: BundleAdjustmentConfig() = default; void FixGauge(BundleAdjustmentGauge gauge); BundleAdjustmentGauge FixedGauge() const; size_t NumImages() const; size_t NumPoints() const; size_t NumVariablePoints() const; size_t NumConstantPoints() const; size_t NumConstantCamIntrinsics() const; size_t NumConstantSensorFromRigPoses() const; size_t NumConstantRigFromWorldPoses() const; // Determine the number of residuals for the given reconstruction. The number // of residuals equals the number of observations times two. size_t NumResiduals(const Reconstruction& reconstruction) const; // Add / remove images from the configuration. void AddImage(image_t image_id); bool HasImage(image_t image_id) const; void RemoveImage(image_t image_id); // Set cameras of added images as constant or variable. By default all // cameras of added images are variable. Note that the corresponding images // have to be added prior to calling these methods. void SetConstantCamIntrinsics(camera_t camera_id); void SetVariableCamIntrinsics(camera_t camera_id); bool HasConstantCamIntrinsics(camera_t camera_id) const; // Set the sensor-from-rig extrinsic pose as constant or variable. void SetConstantSensorFromRigPose(sensor_t sensor_id); void SetVariableSensorFromRigPose(sensor_t sensor_id); bool HasConstantSensorFromRigPose(sensor_t sensor_id) const; // Set the rig from world pose as constant. void SetConstantRigFromWorldPose(frame_t frame_id); void SetVariableRigFromWorldPose(frame_t frame_id); bool HasConstantRigFromWorldPose(frame_t frame_id) const; // Add / remove points from the configuration. Note that points can either // be variable or constant but not both at the same time. void AddVariablePoint(point3D_t point3D_id); void AddConstantPoint(point3D_t point3D_id); void IgnorePoint(point3D_t point3D_id); bool HasPoint(point3D_t point3D_id) const; bool HasVariablePoint(point3D_t point3D_id) const; bool HasConstantPoint(point3D_t point3D_id) const; bool IsIgnoredPoint(point3D_t point3D_id) const; void RemoveVariablePoint(point3D_t point3D_id); void RemoveConstantPoint(point3D_t point3D_id); // Access configuration data. const FlatHashSet& Images() const; const FlatHashSet& VariablePoints() const; const FlatHashSet& ConstantPoints() const; const FlatHashSet& ConstantCamIntrinsics() const; const FlatHashSet& ConstantSensorFromRigPoses() const; const FlatHashSet& ConstantRigFromWorldPoses() const; private: BundleAdjustmentGauge fixed_gauge_ = BundleAdjustmentGauge::UNSPECIFIED; FlatHashSet constant_cam_intrinsics_; FlatHashSet image_ids_; FlatHashSet variable_point3D_ids_; FlatHashSet constant_point3D_ids_; FlatHashSet ignored_point3D_ids_; FlatHashSet constant_sensor_from_rig_poses_; FlatHashSet constant_rig_from_world_poses_; }; struct CasparBundleAdjustmentOptions; struct BundleAdjustmentBackendOptions { // Ceres-specific options (only used when backend == CERES). std::shared_ptr ceres; // Caspar-specific options (only used when backend == CASPAR). // Type defined in bundle_adjustment_caspar.h. std::shared_ptr caspar; BundleAdjustmentBackendOptions(); BundleAdjustmentBackendOptions(const BundleAdjustmentBackendOptions& other); BundleAdjustmentBackendOptions& operator=( const BundleAdjustmentBackendOptions& other); BundleAdjustmentBackendOptions(BundleAdjustmentBackendOptions&& other) = default; BundleAdjustmentBackendOptions& operator=( BundleAdjustmentBackendOptions&& other) = default; }; // Solver-agnostic bundle adjustment options. struct BundleAdjustmentOptions : public BundleAdjustmentBackendOptions { // Whether to refine the focal length parameter group. bool refine_focal_length = true; // Whether to refine the principal point parameter group. bool refine_principal_point = false; // Whether to refine the extra parameter group. bool refine_extra_params = true; // Whether to refine the extrinsic parameter group. bool refine_sensor_from_rig = true; bool refine_rig_from_world = true; // Whether to refine the 3D point positions. When false, all 3D points are // treated as constant, enabling refinement of only camera intrinsics and // poses. This is useful when 3D points come from a reference model and // should not be modified. bool refine_points3D = true; // Minimum track length for a 3D point to be included in bundle adjustment. // Points with fewer observations are ignored. int min_track_length = 0; // Whether to keep the rotation component of rig_from_world constant. // Only takes effect when refine_rig_from_world is true. // When true, only translation is refined. bool constant_rig_from_world_rotation = false; // Whether to print a final summary. bool print_summary = true; // Solver backend to use for bundle adjustment. BundleAdjustmentBackend backend = BundleAdjustmentBackend::CERES; // Optional cooperative cancellation callback. Ceres evaluates this after // each iteration. Other backends may only evaluate it between solver runs. std::function check_if_stopped; bool Check() const; }; // Abstract base class for bundle adjustment, independent of solver backend. class BundleAdjuster { public: BundleAdjuster(const BundleAdjustmentOptions& options, const BundleAdjustmentConfig& config); virtual ~BundleAdjuster() = default; virtual std::shared_ptr Solve() = 0; const BundleAdjustmentOptions& Options() const; const BundleAdjustmentConfig& Config() const; protected: BundleAdjustmentOptions options_; BundleAdjustmentConfig config_; }; // Factory function to create bundle adjusters. // Currently uses Ceres as the backend, but can be extended to support // other backends (e.g., Caspar) in the future. std::unique_ptr CreateDefaultBundleAdjuster( const BundleAdjustmentOptions& options, const BundleAdjustmentConfig& config, Reconstruction& reconstruction); struct PosePriorBundleAdjustmentBackendOptions { // Ceres-specific options (only used when backend == CERES). std::shared_ptr ceres; PosePriorBundleAdjustmentBackendOptions(); PosePriorBundleAdjustmentBackendOptions( const PosePriorBundleAdjustmentBackendOptions& other); PosePriorBundleAdjustmentBackendOptions& operator=( const PosePriorBundleAdjustmentBackendOptions& other); PosePriorBundleAdjustmentBackendOptions( PosePriorBundleAdjustmentBackendOptions&& other) = default; PosePriorBundleAdjustmentBackendOptions& operator=( PosePriorBundleAdjustmentBackendOptions&& other) = default; }; // Solver-agnostic pose prior bundle adjustment options. struct PosePriorBundleAdjustmentOptions : public PosePriorBundleAdjustmentBackendOptions { // Fallback if no prior position covariance is provided. double prior_position_fallback_stddev = 1.0; // Sim3 alignment options. RANSACOptions alignment_ransac_options; bool Check() const; }; // Factory function to create pose prior bundle adjusters. std::unique_ptr CreatePosePriorBundleAdjuster( const BundleAdjustmentOptions& options, const PosePriorBundleAdjustmentOptions& prior_options, const BundleAdjustmentConfig& config, std::vector pose_priors, Reconstruction& reconstruction); } // namespace colmap colmap-4.2.0/src/colmap/estimators/bundle_adjustment_caspar.cc000066400000000000000000001170611524536416500246140ustar00rootroot00000000000000#include "colmap/estimators/bundle_adjustment_caspar.h" #include "colmap/estimators/bundle_adjustment.h" #include "colmap/geometry/rigid3.h" #include "colmap/scene/camera.h" #include "colmap/scene/image.h" #include "colmap/sensor/models.h" #include "colmap/util/cuda.h" #include "colmap/util/hash_containers.h" #include "colmap/util/misc.h" #ifdef CASPAR_ENABLED #include "colmap/estimators/caspar/caspar_model_adapter.h" #endif namespace colmap { namespace { class CasparBundleAdjuster : public BundleAdjuster { public: CasparBundleAdjuster(BundleAdjustmentOptions options, BundleAdjustmentConfig config, Reconstruction& reconstruction) : BundleAdjuster(options, config), reconstruction_(reconstruction) { VLOG(2) << "Creating Caspar bundle adjuster"; LogUnsupportedOptions(); BuildObservationCounts(); FixGauge(); BuildFactors(); } private: void LogUnsupportedOptions() const { if (options_.refine_rig_from_world && options_.constant_rig_from_world_rotation) { LOG(ERROR) << "Caspar does not support constant_rig_from_world_rotation=true. " "The option will be ignored and eligible rig rotations will be " "refined."; } } ICasparModelAdapter* GetAdapter(const CameraModelId model_id) { auto it = adapters_.find(model_id); if (it != adapters_.end()) { return it->second.get(); } auto adapter = CreateCasparAdapter(model_id); if (!adapter) { return nullptr; } auto* ptr = adapter.get(); adapters_[model_id] = std::move(adapter); model_data_per_model_.emplace(model_id, ModelData{}); calib_num_per_model_[model_id] = 0; return ptr; } void BuildObservationCounts() { for (const image_t image_id : config_.Images()) { const Image& image = reconstruction_.Image(image_id); const Camera& camera = *image.CameraPtr(); if (!GetAdapter(camera.model_id)) { LOG(WARNING) << "Skipping image " << image_id << " with unsupported camera model: " << camera.ModelName(); continue; } for (const Point2D& point2D : image.Points2D()) { if (!point2D.HasPoint3D() || config_.IsIgnoredPoint(point2D.point3D_id) || !HasSufficientTrackLength(point2D.point3D_id)) { continue; } point3D_num_observations_[point2D.point3D_id]++; } } for (const auto point3D_id : config_.VariablePoints()) { CountExternalObservations(point3D_id); } for (const auto point3D_id : config_.ConstantPoints()) { CountExternalObservations(point3D_id); } } void CountExternalObservations(const point3D_t point3D_id) { if (!HasSufficientTrackLength(point3D_id)) { return; } const Point3D& point3D = reconstruction_.Point3D(point3D_id); for (const auto& track_el : point3D.track.Elements()) { if (!config_.HasImage(track_el.image_id)) { Image& image = reconstruction_.Image(track_el.image_id); Camera& camera = *image.CameraPtr(); if (GetAdapter(camera.model_id)) { point3D_num_observations_[point3D_id]++; } } } } void BuildFactors() { CreateCalibrationNodes(); CreatePoseNodes(); CreatePointNodes(); AddFactors(); AddExternalFactors(); } void CreateCalibrationNodes() { std::vector sorted_camera_ids; sorted_camera_ids.reserve(config_.Images().size()); for (const image_t image_id : config_.Images()) { sorted_camera_ids.push_back(reconstruction_.Image(image_id).CameraId()); } std::sort(sorted_camera_ids.begin(), sorted_camera_ids.end()); sorted_camera_ids.erase( std::unique(sorted_camera_ids.begin(), sorted_camera_ids.end()), sorted_camera_ids.end()); for (const camera_t camera_id : sorted_camera_ids) { const Camera& camera = reconstruction_.Camera(camera_id); if (!GetAdapter(camera.model_id)) { continue; } GetOrCreateCalibration(camera_id, camera); } } void CreatePoseNodes() { std::map frame_to_model; for (const image_t image_id : config_.Images()) { const Image& image = reconstruction_.Image(image_id); const Camera& camera = reconstruction_.Camera(image.CameraId()); frame_to_model.emplace(image.FrameId(), camera.model_id); } for (const auto& [frame_id, model_id] : frame_to_model) { GetOrCreatePose(frame_id, model_id); } } void CreatePointNodes() { std::vector sorted_point3D_ids; sorted_point3D_ids.reserve(point3D_num_observations_.size()); for (const auto& [point_id, _] : point3D_num_observations_) { sorted_point3D_ids.push_back(point_id); } std::sort(sorted_point3D_ids.begin(), sorted_point3D_ids.end()); point3D_id_to_idx_.reserve(sorted_point3D_ids.size()); point3D_idx_to_id_.reserve(sorted_point3D_ids.size()); point3D_data_.reserve(sorted_point3D_ids.size() * 3); for (const point3D_t point_id : sorted_point3D_ids) { GetOrCreatePoint(point_id, reconstruction_.Point3D(point_id)); } } void AddFactors() { // Sort camera-first so all factors for the same calibration are contiguous. // This improves float32 numerical quality in the GPU gradient summation. std::vector> sorted_images; sorted_images.reserve(config_.Images().size()); for (const image_t image_id : config_.Images()) { sorted_images.emplace_back(reconstruction_.Image(image_id).CameraId(), image_id); } std::sort(sorted_images.begin(), sorted_images.end()); // Cache per-camera values across images sharing the same camera. These are // recomputed only when the camera changes (images are sorted camera-first). camera_t prev_camera_id = static_cast(-1); ICasparModelAdapter* adapter = nullptr; const Camera* camera_ptr = nullptr; bool focal_and_extra = false; bool principal_point_var = false; size_t calib_idx = 0; for (const auto& [camera_id, image_id] : sorted_images) { const Image& image = reconstruction_.Image(image_id); if (camera_id != prev_camera_id) { camera_ptr = &reconstruction_.Camera(camera_id); adapter = GetAdapter(camera_ptr->model_id); if (adapter) { if (options_.refine_focal_length != options_.refine_extra_params && !config_.HasConstantCamIntrinsics(camera_id) && !cameras_from_outside_config_.count(camera_id)) { LOG(FATAL_THROW) << "Camera " << camera_id << ": refine_focal_length != refine_extra_params is not " "supported by CASPAR's merged focal_and_extra block."; } focal_and_extra = IsFocalAndExtraVariable(camera_id); principal_point_var = IsPrincipalPointVariable(camera_id); calib_idx = GetOrCreateCalibration(camera_id, *camera_ptr); } prev_camera_id = camera_id; } if (!adapter) continue; if (options_.refine_sensor_from_rig) { const Frame& frame = *image.FramePtr(); if (frame.HasRigPtr() && !image.IsRefInFrame()) { LOG(FATAL_THROW) << "Camera " << camera_id << ": refine_sensor_from_rig=true is not supported by CASPAR. " "Set refine_sensor_from_rig=false or use the Ceres BA."; } } const bool pose_var = IsPoseVariable(image.FrameId()); for (const Point2D& point2D : image.Points2D()) { if (!point2D.HasPoint3D() || config_.IsIgnoredPoint(point2D.point3D_id) || !point3D_id_to_idx_.count(point2D.point3D_id)) { continue; } AddFactorCore(image, *camera_ptr, point2D, reconstruction_.Point3D(point2D.point3D_id), pose_var, focal_and_extra, principal_point_var, calib_idx, *adapter); } } } void AddExternalFactors() { for (const auto point3D_id : config_.VariablePoints()) { AddFactorsForExternalObservations(point3D_id); } for (const auto point3D_id : config_.ConstantPoints()) { AddFactorsForExternalObservations(point3D_id); } } void AddFactorsForExternalObservations(const point3D_t point3D_id) { THROW_CHECK(!config_.IsIgnoredPoint(point3D_id)); if (!HasSufficientTrackLength(point3D_id)) { return; } Point3D& point3D = reconstruction_.Point3D(point3D_id); GetOrCreatePoint(point3D_id, point3D); for (const auto& track_el : point3D.track.Elements()) { if (config_.HasImage(track_el.image_id)) { continue; } Image& image = reconstruction_.Image(track_el.image_id); Camera& camera = *image.CameraPtr(); ICasparModelAdapter* adapter = GetAdapter(camera.model_id); if (!adapter) { LOG(WARNING) << "Skipping external observation with unsupported " "camera model: " << camera.ModelName(); continue; } // Mark frame and camera as external so that IsPoseVariable and // IsFocalAndExtraVariable return false for all external // observations. frames_from_outside_config_.insert(image.FrameId()); cameras_from_outside_config_.insert(camera.camera_id); if (options_.refine_sensor_from_rig) { const Frame& frame = *image.FramePtr(); if (frame.HasRigPtr() && !image.IsRefInFrame()) { LOG(FATAL_THROW) << "Camera " << camera.camera_id << ": refine_sensor_from_rig=true is not supported by CASPAR. " "Set refine_sensor_from_rig=false or use the Ceres BA."; } } const Point2D& point2D = image.Point2D(track_el.point2D_idx); const size_t calib_idx = GetOrCreateCalibration(camera.camera_id, camera); AddFactorCore(image, camera, point2D, point3D, /*pose_var=*/false, /*focal_and_extra=*/false, /*principal_point_var=*/false, calib_idx, *adapter); } } void AddFactorCore(const Image& image, const Camera& camera, const Point2D& point2D, const Point3D& point3D, bool pose_var, bool focal_and_extra, bool principal_point_var, size_t calib_idx, ICasparModelAdapter& adapter) { const bool point_var = IsPointVariable(point2D.point3D_id); // Skip fully-constant observations, there's nothing to optimize if (!pose_var && !focal_and_extra && !principal_point_var && !point_var) { return; } ModelData& md = model_data_per_model_.at(camera.model_id); // 4-bit key: bit3=pose_var, bit2=fae_var, bit1=pp_var, bit0=pt_var. // Entry 0 (all fixed) is unreachable static constexpr FactorVariant kVariantTable[16] = { /* 0000 */ FactorVariant::BASE, // unreachable /* 0001 */ FactorVariant::FIXED_POSE_FIXED_FOCAL_AND_EXTRA_FIXED_PRINCIPAL_POINT, /* 0010 */ FactorVariant::FIXED_POSE_FIXED_FOCAL_AND_EXTRA_FIXED_POINT, /* 0011 */ FactorVariant::FIXED_POSE_FIXED_FOCAL_AND_EXTRA, /* 0100 */ FactorVariant::FIXED_POSE_FIXED_PRINCIPAL_POINT_FIXED_POINT, /* 0101 */ FactorVariant::FIXED_POSE_FIXED_PRINCIPAL_POINT, /* 0110 */ FactorVariant::FIXED_POSE_FIXED_POINT, /* 0111 */ FactorVariant::FIXED_POSE, /* 1000 */ FactorVariant::FIXED_FOCAL_AND_EXTRA_FIXED_PRINCIPAL_POINT_FIXED_POINT, /* 1001 */ FactorVariant::FIXED_FOCAL_AND_EXTRA_FIXED_PRINCIPAL_POINT, /* 1010 */ FactorVariant::FIXED_FOCAL_AND_EXTRA_FIXED_POINT, /* 1011 */ FactorVariant::FIXED_FOCAL_AND_EXTRA, /* 1100 */ FactorVariant::FIXED_PRINCIPAL_POINT_FIXED_POINT, /* 1101 */ FactorVariant::FIXED_PRINCIPAL_POINT, /* 1110 */ FactorVariant::FIXED_POINT, /* 1111 */ FactorVariant::BASE, }; const FactorVariant v = kVariantTable[(static_cast(pose_var) << 3) | (static_cast(focal_and_extra) << 2) | (static_cast(principal_point_var) << 1) | static_cast(point_var)]; VariantData& vd = md.variants[static_cast(v)]; AppendPose(vd.sensor_from_rig_data, GetSensorFromRig(image)); if (pose_var) { vd.pose_indices.push_back( GetOrCreatePose(image.FrameId(), camera.model_id)); } else { AppendPose(vd.const_poses, reconstruction_.Frame(image.FrameId()).RigFromWorld()); } if (focal_and_extra) { vd.focal_and_extra_indices.push_back(calib_idx); } else { const size_t fs = adapter.FocalAndExtraSize(); const auto* src = md.focal_and_extra_data.data() + calib_idx * fs; vd.const_focal_and_extra.insert( vd.const_focal_and_extra.end(), src, src + fs); } if (principal_point_var) { vd.principal_point_indices.push_back(calib_idx); } else { const size_t ps = adapter.PrincipalPointSize(); const auto* src = md.principal_point_data.data() + calib_idx * ps; vd.const_principal_point.insert( vd.const_principal_point.end(), src, src + ps); } if (point_var) { vd.point_indices.push_back(GetOrCreatePoint(point2D.point3D_id, point3D)); } else { AppendPoint(vd.const_points, point3D); } vd.pixels.push_back(point2D.xy.x()); vd.pixels.push_back(point2D.xy.y()); ++vd.num_factors; } static void AppendPose(std::vector& out, const Rigid3d& pose) { out.push_back(pose.rotation().x()); out.push_back(pose.rotation().y()); out.push_back(pose.rotation().z()); out.push_back(pose.rotation().w()); out.push_back(pose.translation().x()); out.push_back(pose.translation().y()); out.push_back(pose.translation().z()); } static void AppendPoint(std::vector& out, const Point3D& pt) { out.push_back(pt.xyz.x()); out.push_back(pt.xyz.y()); out.push_back(pt.xyz.z()); } size_t GetOrCreatePoint(const point3D_t point_id, const Point3D& point) { auto [it, inserted] = point3D_id_to_idx_.try_emplace(point_id, num_points_); if (inserted) { point3D_idx_to_id_.push_back(point_id); point3D_data_.push_back(point.xyz.x()); point3D_data_.push_back(point.xyz.y()); point3D_data_.push_back(point.xyz.z()); num_points_++; } return it->second; } size_t GetOrCreatePose(const frame_t frame_id, const CameraModelId model_id) { size_t& n = num_poses_per_model_[model_id]; auto [it, inserted] = frame_to_pose_index_per_model_[model_id].try_emplace(frame_id, n); if (inserted) { pose_index_to_frame_per_model_[model_id][n] = frame_id; const Rigid3d& pose = reconstruction_.Frame(frame_id).RigFromWorld(); auto& data = pose_data_per_model_[model_id]; data.push_back(pose.rotation().x()); data.push_back(pose.rotation().y()); data.push_back(pose.rotation().z()); data.push_back(pose.rotation().w()); data.push_back(pose.translation().x()); data.push_back(pose.translation().y()); data.push_back(pose.translation().z()); n++; } return it->second; } // Returns the sensor_from_rig transform for a camera. Identity for ref // sensors and single-camera datasets; actual transform for non-ref rigs. Rigid3d GetSensorFromRig(const Image& image) { const Frame& frame = *image.FramePtr(); if (frame.HasRigPtr() && !image.IsRefInFrame()) { return frame.RigPtr()->SensorFromRig(image.DataId().sensor_id); } return Rigid3d{}; } // Calib indices are per-model: index 0 for SimpleRadial is unrelated to // index 0 for Pinhole. size_t GetOrCreateCalibration(const camera_t camera_id, const Camera& camera) { auto [it, inserted] = camera_to_calib_index_.try_emplace(camera_id, 0); if (inserted) { ICasparModelAdapter* adapter = GetAdapter(camera.model_id); size_t& model_calib_count = calib_num_per_model_[camera.model_id]; it->second = model_calib_count; calib_index_to_camera_[{camera.model_id, model_calib_count}] = camera_id; ModelData& md = model_data_per_model_.at(camera.model_id); adapter->ExtractFocalAndExtra(camera, md.focal_and_extra_data); adapter->ExtractPrincipalPoint(camera, md.principal_point_data); ++model_calib_count; } return it->second; } bool IsPoseVariable(const frame_t frame_id) const { return options_.refine_rig_from_world && !config_.HasConstantRigFromWorldPose(frame_id) && !frames_from_outside_config_.count(frame_id) && !gauge_fixed_frames_.count(frame_id); } // Both focal and extra_params must be refined together (merged block). // If they disagree, observations are skipped. See AddFactorForObservation. bool IsFocalAndExtraVariable(const camera_t camera_id) const { return options_.refine_focal_length && options_.refine_extra_params && !config_.HasConstantCamIntrinsics(camera_id) && !cameras_from_outside_config_.count(camera_id); } bool IsPrincipalPointVariable(const camera_t camera_id) const { return options_.refine_principal_point && !config_.HasConstantCamIntrinsics(camera_id) && !cameras_from_outside_config_.count(camera_id); } bool AreIntrinsicsVariable(const camera_t camera_id) const { return IsFocalAndExtraVariable(camera_id) || IsPrincipalPointVariable(camera_id); } bool IsPointVariable(const point3D_t point3D_id) const { if (!options_.refine_points3D || config_.HasConstantPoint(point3D_id) || gauge_fixed_points_.count(point3D_id)) { return false; } const auto it = point3D_num_observations_.find(point3D_id); return it != point3D_num_observations_.end() && reconstruction_.Point3D(point3D_id).track.Length() <= it->second; } bool HasSufficientTrackLength(const point3D_t point3D_id) const { return options_.min_track_length <= 0 || static_cast( reconstruction_.Point3D(point3D_id).track.Length()) >= options_.min_track_length; } void FixGauge() { switch (config_.FixedGauge()) { case BundleAdjustmentGauge::UNSPECIFIED: break; case BundleAdjustmentGauge::TWO_CAMS_FROM_WORLD: FixGaugeWithOneFrameFromWorld(); break; case BundleAdjustmentGauge::THREE_POINTS: FixGaugeWithThreePoints(); break; default: LOG(FATAL_THROW) << "Unknown BundleAdjustmentGauge"; } } // Partial two-view gauge fix: fixes one pose only. Caspar can express the // second camera's 1-DOF translation manifold, but the gain is minimal and // the extra shared-memory cost is not worth it, so scale is left as the one // unfixed gauge DOF. void FixGaugeWithOneFrameFromWorld() { if (!options_.refine_rig_from_world) { return; } // Sort image IDs for deterministic selection (matches Ceres BA behavior). std::vector sorted_image_ids(config_.Images().begin(), config_.Images().end()); std::sort(sorted_image_ids.begin(), sorted_image_ids.end()); for (const image_t image_id : sorted_image_ids) { const Image& image = reconstruction_.Image(image_id); if (config_.HasConstantRigFromWorldPose(image.FrameId())) { VLOG(1) << "Gauge fix: frame " << image.FrameId() << " already constant, skipping TWO_CAMS_FROM_WORLD fix"; return; } } // Require a ref-sensor image so fixing the frame moves factors into // fixed-pose variants rather than being silently skipped. for (const image_t image_id : sorted_image_ids) { const Image& image = reconstruction_.Image(image_id); if (image.IsRefInFrame()) { gauge_fixed_frames_.insert(image.FrameId()); VLOG(1) << "Gauge fix: fixed frame " << image.FrameId() << " (image " << image_id << ") for TWO_CAMS_FROM_WORLD"; return; } } LOG(WARNING) << "Caspar TWO_CAMS_FROM_WORLD gauge fix: no ref-sensor " "frame found, gauge left unfixed."; } // Three-point gauge fix: mirrors the Ceres BA equivalent but promotes points // into gauge_fixed_points_ instead of calling SetParameterBlockConstant. void FixGaugeWithThreePoints() { Eigen::Index num_fixed = 0; Eigen::Matrix3d fixed_pts = Eigen::Matrix3d::Zero(); auto maybe_add = [&](const Eigen::Vector3d& xyz) -> bool { if (num_fixed >= 3) return false; fixed_pts.col(num_fixed) = xyz; if (fixed_pts.colPivHouseholderQr().rank() > num_fixed) { ++num_fixed; return true; } fixed_pts.col(num_fixed).setZero(); return false; }; // First pass: count already-constant points. for (const auto& [point3D_id, _] : point3D_num_observations_) { if (!config_.HasConstantPoint(point3D_id)) continue; const Point3D& pt = reconstruction_.Point3D(point3D_id); if (maybe_add(pt.xyz) && num_fixed >= 3) { VLOG(1) << "Gauge fix: 3 linearly independent constant points found, " "THREE_POINTS gauge fixed"; return; } } // Second pass: promote variable points to gauge-fixed. for (const auto& [point3D_id, _] : point3D_num_observations_) { if (!IsPointVariable(point3D_id)) continue; const Point3D& pt = reconstruction_.Point3D(point3D_id); if (maybe_add(pt.xyz)) { gauge_fixed_points_.insert(point3D_id); if (num_fixed >= 3) { VLOG(1) << "Gauge fix: fixed " << gauge_fixed_points_.size() << " points for THREE_POINTS gauge"; return; } } } LOG(WARNING) << "Caspar THREE_POINTS gauge fix: only " << num_fixed << " of 3 linearly independent points found."; } void SetupSolverData(caspar::GraphSolver& solver) { VLOG(2) << "=== CASPAR SOLVER SETUP ==="; VLOG(2) << " Points: " << num_points_; if (num_points_ > 0) { solver.SetPointNodesFromStackedHost(point3D_data_.data(), 0, num_points_); } for (const auto& [model_id, adapter_ptr] : adapters_) { const ModelData& md = model_data_per_model_.at(model_id); const size_t n_calib = calib_num_per_model_.at(model_id); const size_t n_poses = num_poses_per_model_.count(model_id) ? num_poses_per_model_.at(model_id) : 0; VLOG(2) << " Poses (" << static_cast(model_id) << "): " << n_poses; if (n_poses > 0) { adapter_ptr->SetPoseNodes( solver, pose_data_per_model_.at(model_id).data(), n_poses); } if (n_calib > 0) { adapter_ptr->SetFocalAndExtraNodes( solver, const_cast(md.focal_and_extra_data.data()), n_calib); adapter_ptr->SetPrincipalPointNodes( solver, const_cast(md.principal_point_data.data()), n_calib); // Set merged Calib nodes when both intrinsic groups are tunable. const bool has_merged = md.variants[static_cast(FactorVariant::BASE)].num_factors > 0 || md.variants[static_cast(FactorVariant::FIXED_POSE)] .num_factors > 0 || md.variants[static_cast(FactorVariant::FIXED_POINT)] .num_factors > 0 || md.variants[static_cast(FactorVariant::FIXED_POSE_FIXED_POINT)] .num_factors > 0; if (has_merged) { const size_t fae_size = adapter_ptr->FocalAndExtraSize(); const size_t pp_size = adapter_ptr->PrincipalPointSize(); const size_t cal_size = adapter_ptr->CalibSize(); std::vector calib_data(n_calib * cal_size); for (size_t i = 0; i < n_calib; ++i) { for (size_t j = 0; j < fae_size; ++j) { calib_data[i * cal_size + j] = md.focal_and_extra_data[i * fae_size + j]; } for (size_t j = 0; j < pp_size; ++j) { calib_data[i * cal_size + fae_size + j] = md.principal_point_data[i * pp_size + j]; } } if (n_calib > 0) { VLOG(2) << " SetCalibNodes [cam 0, model " << static_cast(model_id) << "]: [" << calib_data[0] << ", " << calib_data[1] << ", " << calib_data[2] << ", " << calib_data[3] << "]"; } adapter_ptr->SetCalibNodes(solver, calib_data.data(), n_calib); } } for (int v = 0; v < CASPAR_NUM_VARIANTS; ++v) { if (md.variants[v].num_factors > 0) { adapter_ptr->SetVariantFactors( solver, static_cast(v), md.variants[v]); } } } solver.finish_indices(); } void ReadSolverResults(caspar::GraphSolver& solver) { if (num_points_ > 0) { solver.GetPointNodesToStackedHost(point3D_data_.data(), 0, num_points_); } for (const auto& [model_id, adapter_ptr] : adapters_) { const size_t n_poses = num_poses_per_model_.count(model_id) ? num_poses_per_model_.at(model_id) : 0; if (n_poses > 0) { adapter_ptr->GetPoseNodes( solver, pose_data_per_model_.at(model_id).data(), n_poses); } } for (const auto& [model_id, adapter_ptr] : adapters_) { ModelData& md = model_data_per_model_.at(model_id); const size_t n_calib = calib_num_per_model_.at(model_id); if (n_calib > 0) { adapter_ptr->GetFocalAndExtraNodes( solver, md.focal_and_extra_data.data(), n_calib); adapter_ptr->GetPrincipalPointNodes( solver, md.principal_point_data.data(), n_calib); // Split the merged Calib node back into focal_and_extra_data and // principal_point_data, overwriting the stale split-pool values above. const bool has_merged = md.variants[static_cast(FactorVariant::BASE)].num_factors > 0 || md.variants[static_cast(FactorVariant::FIXED_POSE)] .num_factors > 0 || md.variants[static_cast(FactorVariant::FIXED_POINT)] .num_factors > 0 || md.variants[static_cast(FactorVariant::FIXED_POSE_FIXED_POINT)] .num_factors > 0; if (has_merged) { const size_t fae_size = adapter_ptr->FocalAndExtraSize(); const size_t pp_size = adapter_ptr->PrincipalPointSize(); const size_t cal_size = adapter_ptr->CalibSize(); std::vector calib_data(n_calib * cal_size); adapter_ptr->GetCalibNodes(solver, calib_data.data(), n_calib); if (n_calib > 0) { VLOG(2) << " GetCalibNodes [cam 0, model " << static_cast(model_id) << "]: [" << calib_data[0] << ", " << calib_data[1] << ", " << calib_data[2] << ", " << calib_data[3] << "]"; } for (size_t i = 0; i < n_calib; ++i) { for (size_t j = 0; j < fae_size; ++j) { md.focal_and_extra_data[i * fae_size + j] = calib_data[i * cal_size + j]; } for (size_t j = 0; j < pp_size; ++j) { md.principal_point_data[i * pp_size + j] = calib_data[i * cal_size + fae_size + j]; } } if (n_calib > 0) { VLOG(2) << " After split-back [cam 0]: fae=[" << md.focal_and_extra_data[0] << ", " << md.focal_and_extra_data[1] << "] pp=[" << md.principal_point_data[0] << ", " << md.principal_point_data[1] << "]"; } } } } } void WriteCalibsToReconstruction() { for (const auto& [camera_id, calib_idx] : camera_to_calib_index_) { if (!AreIntrinsicsVariable(camera_id)) { continue; } Camera& camera = reconstruction_.Camera(camera_id); ICasparModelAdapter* adapter = GetAdapter(camera.model_id); const ModelData& md = model_data_per_model_.at(camera.model_id); const std::string params_before = camera.ParamsToString(); if (IsFocalAndExtraVariable(camera_id)) { adapter->WriteFocalAndExtra( camera, md.focal_and_extra_data.data(), calib_idx); } if (IsPrincipalPointVariable(camera_id)) { adapter->WritePrincipalPoint( camera, md.principal_point_data.data(), calib_idx); } THROW_CHECK(camera.VerifyParams()); VLOG(1) << "Camera " << camera_id << " (" << camera.ModelName() << ")" << " params: [" << params_before << "] -> [" << camera.ParamsToString() << "]"; } } void WriteResultsToReconstruction() { for (size_t idx = 0; idx < point3D_idx_to_id_.size(); ++idx) { const point3D_t point_id = point3D_idx_to_id_[idx]; // Points with external observations are non-variable but have solver // nodes holding float copies; skip to avoid writing back stale values. if (!IsPointVariable(point_id)) { continue; } Point3D& point = reconstruction_.Point3D(point_id); point.xyz.x() = point3D_data_[idx * 3 + 0]; point.xyz.y() = point3D_data_[idx * 3 + 1]; point.xyz.z() = point3D_data_[idx * 3 + 2]; } for (const auto& [model_id, idx_to_frame] : pose_index_to_frame_per_model_) { const auto& data = pose_data_per_model_.at(model_id); for (const auto& [idx, frame_id] : idx_to_frame) { if (!IsPoseVariable(frame_id)) { continue; } Rigid3d& pose = reconstruction_.Frame(frame_id).RigFromWorld(); pose.rotation().x() = data[idx * 7 + 0]; pose.rotation().y() = data[idx * 7 + 1]; pose.rotation().z() = data[idx * 7 + 2]; pose.rotation().w() = data[idx * 7 + 3]; pose.translation().x() = data[idx * 7 + 4]; pose.translation().y() = data[idx * 7 + 5]; pose.translation().z() = data[idx * 7 + 6]; pose.rotation().normalize(); } } WriteCalibsToReconstruction(); } static const char* FactorVariantName(FactorVariant v) { switch (v) { case FactorVariant::BASE: return "BASE"; case FactorVariant::FIXED_POSE: return "FIXED_POSE"; case FactorVariant::FIXED_FOCAL_AND_EXTRA: return "FIXED_FAE"; case FactorVariant::FIXED_PRINCIPAL_POINT: return "FIXED_PP"; case FactorVariant::FIXED_POINT: return "FIXED_POINT"; case FactorVariant::FIXED_POSE_FIXED_FOCAL_AND_EXTRA: return "FIXED_POSE_FAE"; case FactorVariant::FIXED_POSE_FIXED_PRINCIPAL_POINT: return "FIXED_POSE_PP"; case FactorVariant::FIXED_POSE_FIXED_POINT: return "FIXED_POSE_POINT"; case FactorVariant::FIXED_FOCAL_AND_EXTRA_FIXED_PRINCIPAL_POINT: return "FIXED_FAE_PP"; case FactorVariant::FIXED_FOCAL_AND_EXTRA_FIXED_POINT: return "FIXED_FAE_POINT"; case FactorVariant::FIXED_PRINCIPAL_POINT_FIXED_POINT: return "FIXED_PP_POINT"; case FactorVariant:: FIXED_POSE_FIXED_FOCAL_AND_EXTRA_FIXED_PRINCIPAL_POINT: return "FIXED_POSE_FAE_PP"; case FactorVariant::FIXED_POSE_FIXED_FOCAL_AND_EXTRA_FIXED_POINT: return "FIXED_POSE_FAE_POINT"; case FactorVariant::FIXED_POSE_FIXED_PRINCIPAL_POINT_FIXED_POINT: return "FIXED_POSE_PP_POINT"; case FactorVariant:: FIXED_FOCAL_AND_EXTRA_FIXED_PRINCIPAL_POINT_FIXED_POINT: return "FIXED_FAE_PP_POINT"; default: return "UNKNOWN"; } } void LogFactorDistribution() const { VLOG(1) << "=== Caspar factor distribution ==="; VLOG(1) << " Points: " << num_points_ << " Frames: " << TotalPoses(); for (const auto& [model_id, md] : model_data_per_model_) { for (int v = 0; v < CASPAR_NUM_VARIANTS; ++v) { if (md.variants[v].num_factors == 0) { continue; } VLOG(1) << " model=" << static_cast(model_id) << " variant=" << FactorVariantName(static_cast(v)) << " factors=" << md.variants[v].num_factors; } } VLOG(1) << " Gauge-fixed frames: " << gauge_fixed_frames_.size() << " Gauge-fixed points: " << gauge_fixed_points_.size(); VLOG(1) << " refine_focal_length=" << options_.refine_focal_length << " refine_extra_params=" << options_.refine_extra_params << " refine_pp=" << options_.refine_principal_point << " refine_pose=" << options_.refine_rig_from_world; } size_t ComputeTotalResiduals() const { size_t total = 0; for (const auto& [model_id, md] : model_data_per_model_) { for (int v = 0; v < CASPAR_NUM_VARIANTS; ++v) { total += md.variants[v].num_factors; } } return 2 * total; } CasparSolverSizing BuildSizing() const { CasparSolverSizing sz; sz.num_points = num_points_; if (auto it = num_poses_per_model_.find(CameraModelId::kSimpleRadial); it != num_poses_per_model_.end()) { sz.num_simple_radial_poses = it->second; } if (auto it = num_poses_per_model_.find(CameraModelId::kPinhole); it != num_poses_per_model_.end()) { sz.num_pinhole_poses = it->second; } auto get_md = [&](CameraModelId id) -> const ModelData* { auto it = model_data_per_model_.find(id); return it != model_data_per_model_.end() ? &it->second : nullptr; }; auto get_n = [&](CameraModelId id) -> size_t { auto it = calib_num_per_model_.find(id); return it != calib_num_per_model_.end() ? it->second : 0; }; if (const ModelData* md = get_md(CameraModelId::kSimpleRadial)) { sz.num_simple_radial_calibs = get_n(CameraModelId::kSimpleRadial); adapters_.at(CameraModelId::kSimpleRadial) ->FillSizing(sz, *md, sz.num_simple_radial_calibs); } if (const ModelData* md = get_md(CameraModelId::kPinhole)) { sz.num_pinhole_calibs = get_n(CameraModelId::kPinhole); adapters_.at(CameraModelId::kPinhole) ->FillSizing(sz, *md, sz.num_pinhole_calibs); } return sz; } size_t TotalPoses() const { size_t n = 0; for (const auto& [_, count] : num_poses_per_model_) { n += count; } return n; } bool ValidateData() const { if (num_points_ == 0 && TotalPoses() == 0) { LOG(WARNING) << "No data to optimize"; return false; } if (ComputeTotalResiduals() == 0) { LOG(WARNING) << "No residuals to optimize"; return false; } return true; } std::shared_ptr Solve() override { if (!ValidateData()) { auto summary = std::make_shared(); summary->termination_type = BundleAdjustmentTerminationType::USER_FAILURE; return summary; } caspar::SolverParams params; int gpu_index = -1; if (options_.caspar) { const auto& co = *options_.caspar; const std::vector gpu_indices = CSVToVector(co.gpu_index); THROW_CHECK_GT(gpu_indices.size(), 0); gpu_index = gpu_indices[0]; params.solver_iter_max = co.solver_iter_max; params.pcg_iter_max = co.pcg_iter_max; params.diag_init = co.diag_init; params.diag_min = co.diag_min; params.diag_scaling_up = co.diag_scaling_up; params.diag_scaling_down = co.diag_scaling_down; params.diag_exit_value = co.diag_exit_value; params.score_exit_value = co.score_exit_value; params.pcg_rel_error_exit = co.pcg_rel_error_exit; params.pcg_rel_score_exit = co.pcg_rel_score_exit; params.pcg_rel_decrease_min = co.pcg_rel_decrease_min; params.solver_rel_decrease_min = co.solver_rel_decrease_min; } const size_t device_id = static_cast(gpu_index >= 0 ? gpu_index : FindBestCudaDevice()); LogFactorDistribution(); auto solver = CreateSolver(params, BuildSizing(), device_id); SetupSolverData(solver); const bool collect_iters = options_.caspar && options_.caspar->collect_iteration_data; caspar::SolveResult result = solver.solve( /*print_progress=*/VLOG_IS_ON(2), /*verbose_logging=*/collect_iters); ReadSolverResults(solver); WriteResultsToReconstruction(); auto summary = CasparBundleAdjustmentSummary::Create(result); summary->num_residuals = ComputeTotalResiduals(); return summary; } NodeHashMap> adapters_; NodeHashMap model_data_per_model_; NodeHashMap calib_num_per_model_; // camera_id -> calib index within its model's array NodeHashMap camera_to_calib_index_; // (model_id, calib_idx) -> camera_id (for write-back) std::map, camera_t> calib_index_to_camera_; Reconstruction& reconstruction_; size_t num_points_ = 0; std::vector point3D_data_; // Pose pools are per-model so that SimpleRadial and Pinhole factors are // never batched into the same Caspar block (reducing shared memory use). NodeHashMap num_poses_per_model_; NodeHashMap> pose_data_per_model_; NodeHashMap> frame_to_pose_index_per_model_; NodeHashMap> pose_index_to_frame_per_model_; FlatHashMap point3D_id_to_idx_; std::vector point3D_idx_to_id_; FlatHashSet frames_from_outside_config_; FlatHashSet cameras_from_outside_config_; FlatHashSet gauge_fixed_frames_; FlatHashSet gauge_fixed_points_; FlatHashMap point3D_num_observations_; }; } // namespace std::shared_ptr CasparBundleAdjustmentSummary::Create( const caspar::SolveResult& caspar_summary) { auto summary = std::make_shared(); summary->iteration_count = caspar_summary.iteration_count; summary->initial_score = caspar_summary.initial_score; summary->iterations = caspar_summary.iterations; switch (caspar_summary.exit_reason) { case caspar::ExitReason::CONVERGED_DIAG_EXIT: VLOG(1) << "Caspar: CONVERGED_DIAG_EXIT after " << caspar_summary.iteration_count << " iters" << " (diag limit hit -> likely premature termination)"; summary->termination_type = BundleAdjustmentTerminationType::CONVERGENCE; break; case caspar::ExitReason::CONVERGED_SCORE_THRESHOLD: VLOG(1) << "Caspar: CONVERGED_SCORE_THRESHOLD after " << caspar_summary.iteration_count << " iters"; summary->termination_type = BundleAdjustmentTerminationType::CONVERGENCE; break; case caspar::ExitReason::MAX_ITERATIONS: VLOG(1) << "Caspar: MAX_ITERATIONS (" << caspar_summary.iteration_count << ")"; summary->termination_type = BundleAdjustmentTerminationType::NO_CONVERGENCE; break; default: summary->termination_type = BundleAdjustmentTerminationType::FAILURE; } return summary; } std::unique_ptr CreateDefaultCasparBundleAdjuster( const BundleAdjustmentOptions& options, const BundleAdjustmentConfig& config, Reconstruction& reconstruction) { return std::make_unique( options, config, reconstruction); } } // namespace colmap colmap-4.2.0/src/colmap/estimators/bundle_adjustment_caspar.h000066400000000000000000000117061524536416500244550ustar00rootroot00000000000000// Copyright (c), ETH Zurich and UNC Chapel Hill. // All rights reserved. // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are met: // // * Redistributions of source code must retain the above copyright // notice, this list of conditions and the following disclaimer. // // * Redistributions in binary form must reproduce the above copyright // notice, this list of conditions and the following disclaimer in the // documentation and/or other materials provided with the distribution. // // * Neither the name of ETH Zurich and UNC Chapel Hill nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE // ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE // LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR // CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF // SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS // INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN // CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) // ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE // POSSIBILITY OF SUCH DAMAGE. #pragma once #include "colmap/estimators/bundle_adjustment.h" #include #include #include #ifdef CASPAR_ENABLED #ifdef CASPAR_USE_DOUBLE #include "thirdparty/Symforce-Caspar/generated/f64/solver.h" #else #include "thirdparty/Symforce-Caspar/generated/f32/solver.h" #endif #endif #ifdef CASPAR_USE_DOUBLE typedef double StorageType; #else typedef float StorageType; #endif // 2^4 - 1: all combinations with at least one variable param. #define CASPAR_NUM_VARIANTS 15 enum class FactorVariant { // r=0 BASE, // r=1 FIXED_POSE, FIXED_FOCAL_AND_EXTRA, FIXED_PRINCIPAL_POINT, FIXED_POINT, // r=2 FIXED_POSE_FIXED_FOCAL_AND_EXTRA, FIXED_POSE_FIXED_PRINCIPAL_POINT, FIXED_POSE_FIXED_POINT, FIXED_FOCAL_AND_EXTRA_FIXED_PRINCIPAL_POINT, // calibrated camera FIXED_FOCAL_AND_EXTRA_FIXED_POINT, FIXED_PRINCIPAL_POINT_FIXED_POINT, // r=3 FIXED_POSE_FIXED_FOCAL_AND_EXTRA_FIXED_PRINCIPAL_POINT, FIXED_POSE_FIXED_FOCAL_AND_EXTRA_FIXED_POINT, FIXED_POSE_FIXED_PRINCIPAL_POINT_FIXED_POINT, FIXED_FOCAL_AND_EXTRA_FIXED_PRINCIPAL_POINT_FIXED_POINT, }; struct VariantData { std::vector pose_indices; std::vector sensor_from_rig_data; // 7 floats per factor std::vector focal_and_extra_indices; std::vector principal_point_indices; std::vector point_indices; std::vector const_poses; // 7 floats per factor std::vector const_focal_and_extra; // FocalAndExtraSize() per factor std::vector const_principal_point; // PrincipalPointSize() per factor std::vector const_points; // 3 floats per factor std::vector pixels; // 2 floats per factor size_t num_factors = 0; }; struct ModelData { std::vector focal_and_extra_data; // FocalAndExtraSize() entries per camera std::vector principal_point_data; // PrincipalPointSize() per camera std::array variants{}; // Indexed by FactorVariant }; namespace colmap { // Solver parameters mirroring caspar::SolverParams, stored as double to // round-trip through OptionManager regardless of the float/double build. // Also includes GPU index selection option struct CasparBundleAdjustmentOptions { int solver_iter_max = 200; int pcg_iter_max = 20; double diag_init = 1.0; double diag_min = 1e-12; double diag_scaling_up = 2.0; double diag_scaling_down = 0.333333; double diag_exit_value = 1e3; double score_exit_value = 0.0; double pcg_rel_error_exit = 1e-4; // Negative value disables the corresponding early-exit criterion. double pcg_rel_score_exit = -1.0; double pcg_rel_decrease_min = -1.0; double solver_rel_decrease_min = 1.0; std::string gpu_index = "-1"; bool collect_iteration_data = false; }; std::unique_ptr CreateDefaultCasparBundleAdjuster( const BundleAdjustmentOptions& options, const BundleAdjustmentConfig& config, Reconstruction& reconstruction); #ifdef CASPAR_ENABLED struct CasparBundleAdjustmentSummary : public BundleAdjustmentSummary { static std::shared_ptr Create( const caspar::SolveResult& caspar_summary); int iteration_count = 0; double initial_score = 0.0; std::vector iterations; }; #endif } // namespace colmap colmap-4.2.0/src/colmap/estimators/bundle_adjustment_caspar_test.cc000066400000000000000000000753001524536416500256520ustar00rootroot00000000000000// Copyright (c), ETH Zurich and UNC Chapel Hill. // All rights reserved. // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are met: // // * Redistributions of source code must retain the above copyright // notice, this list of conditions and the following disclaimer. // // * Redistributions in binary form must reproduce the above copyright // notice, this list of conditions and the following disclaimer in the // documentation and/or other materials provided with the distribution. // // * Neither the name of ETH Zurich and UNC Chapel Hill nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE // ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE // LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR // CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF // SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS // INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN // CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) // ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE // POSSIBILITY OF SUCH DAMAGE. #include "colmap/estimators/bundle_adjustment_caspar.h" #include "colmap/estimators/bundle_adjustment_ceres.h" #include "colmap/geometry/rigid3_matchers.h" #include "colmap/scene/reconstruction_matchers.h" #include "colmap/scene/synthetic.h" #include "colmap/sensor/models.h" #include // Due to pose normalization operations, constant variables may not be perfectly // fixed during bundle adjustment. constexpr double kConstantPoseVarEps = 1e-9; #define CheckVariableCamera(camera, orig_camera) \ { \ const size_t focal_length_idx = \ SimpleRadialCameraModel::focal_length_idxs[0]; \ const size_t extra_param_idx = \ SimpleRadialCameraModel::extra_params_idxs[0]; \ EXPECT_NE((camera).params[focal_length_idx], \ (orig_camera).params[focal_length_idx]); \ EXPECT_NE((camera).params[extra_param_idx], \ (orig_camera).params[extra_param_idx]); \ } #define CheckConstantCamera(camera, orig_camera) \ { \ const size_t focal_length_idx = \ SimpleRadialCameraModel::focal_length_idxs[0]; \ const size_t extra_param_idx = \ SimpleRadialCameraModel::extra_params_idxs[0]; \ EXPECT_EQ((camera).params[focal_length_idx], \ (orig_camera).params[focal_length_idx]); \ EXPECT_EQ((camera).params[extra_param_idx], \ (orig_camera).params[extra_param_idx]); \ } #define CheckVariableCamFromWorld(image, orig_image) \ { \ EXPECT_THAT((image).CamFromWorld(), \ testing::Not(Rigid3dEq((orig_image).CamFromWorld()))); \ } #define CheckConstantCamFromWorld(image, orig_image) \ { \ EXPECT_THAT((image).CamFromWorld(), \ Rigid3dNear((orig_image).CamFromWorld(), \ kConstantPoseVarEps, \ kConstantPoseVarEps)); \ } #define CheckConstantPoint(point, orig_point) \ { \ EXPECT_EQ((point).xyz, (orig_point).xyz); \ } namespace colmap { namespace { TEST(DefaultBundleAdjuster, RigThrowsErrorOnVariableSensorFromRig) { Reconstruction reconstruction; SyntheticDatasetOptions synthetic_dataset_options; synthetic_dataset_options.num_rigs = 1; synthetic_dataset_options.num_cameras_per_rig = 2; synthetic_dataset_options.num_frames_per_rig = 10; synthetic_dataset_options.num_points3D = 200; SynthesizeDataset(synthetic_dataset_options, &reconstruction); BundleAdjustmentOptions options; BundleAdjustmentConfig config; options.refine_sensor_from_rig = true; // Not supported yet for (const image_t image_id : reconstruction.RegImageIds()) { config.AddImage(image_id); } EXPECT_THROW( CreateDefaultCasparBundleAdjuster(options, config, reconstruction), std::invalid_argument); } TEST(DefaultBundleAdjuster, MultiCameraRigLargeConstantSensorFromRig) { // Real-world multi-camera rigs (stereo, surround-view) have large // sensor_from_rig offsets — typically 20–90 degrees and 0.1–1 m baseline. // This test uses a 30-degree Z-rotation and 0.3 m translation to exercise // the non-identity sensor_from_rig path with realistic values. Reconstruction gt_reconstruction; SyntheticDatasetOptions synthetic_dataset_options; synthetic_dataset_options.num_rigs = 1; synthetic_dataset_options.num_cameras_per_rig = 2; synthetic_dataset_options.num_frames_per_rig = 10; synthetic_dataset_options.num_points3D = 200; synthetic_dataset_options.sensor_from_rig_rotation_stddev = 30.0; synthetic_dataset_options.sensor_from_rig_translation_stddev = 0.3; SynthesizeDataset(synthetic_dataset_options, >_reconstruction); Reconstruction reconstruction = gt_reconstruction; SyntheticNoiseOptions synthetic_noise_options; synthetic_noise_options.point2D_stddev = 0.5; synthetic_noise_options.point3D_stddev = 0.1; synthetic_noise_options.rig_from_world_rotation_stddev = 0.5; synthetic_noise_options.rig_from_world_translation_stddev = 0.1; SynthesizeNoise(synthetic_noise_options, &reconstruction); BundleAdjustmentConfig config; for (const image_t image_id : reconstruction.RegImageIds()) { config.AddImage(image_id); } BundleAdjustmentOptions options; options.refine_sensor_from_rig = false; std::unique_ptr bundle_adjuster = CreateDefaultCasparBundleAdjuster(options, config, reconstruction); const auto summary = bundle_adjuster->Solve(); ASSERT_NE(summary->termination_type, BundleAdjustmentTerminationType::FAILURE); EXPECT_THAT(gt_reconstruction, ReconstructionNear(reconstruction, /*max_rotation_error_deg=*/0.1, /*max_proj_center_error=*/0.1, /*max_scale_error=*/std::nullopt, /*num_obs_tolerance=*/0.0)); } TEST(DefaultBundleAdjuster, MergedCalibConvergence) { Reconstruction gt_reconstruction; SyntheticDatasetOptions synthetic_dataset_options; synthetic_dataset_options.num_rigs = 1; synthetic_dataset_options.num_cameras_per_rig = 1; synthetic_dataset_options.num_frames_per_rig = 10; synthetic_dataset_options.num_points3D = 200; SynthesizeDataset(synthetic_dataset_options, >_reconstruction); Reconstruction reconstruction = gt_reconstruction; SyntheticNoiseOptions synthetic_noise_options; synthetic_noise_options.point2D_stddev = 0.5; synthetic_noise_options.point3D_stddev = 0.1; synthetic_noise_options.rig_from_world_rotation_stddev = 0.5; synthetic_noise_options.rig_from_world_translation_stddev = 0.1; SynthesizeNoise(synthetic_noise_options, &reconstruction); BundleAdjustmentConfig config; for (const image_t image_id : reconstruction.RegImageIds()) { config.AddImage(image_id); } BundleAdjustmentOptions options; options.refine_principal_point = true; std::unique_ptr bundle_adjuster = CreateDefaultCasparBundleAdjuster(options, config, reconstruction); const auto summary = bundle_adjuster->Solve(); ASSERT_NE(summary->termination_type, BundleAdjustmentTerminationType::FAILURE); EXPECT_THAT(gt_reconstruction, ReconstructionNear(reconstruction, /*max_rotation_error_deg=*/0.2, /*max_proj_center_error=*/0.1, /*max_scale_error=*/std::nullopt, /*num_obs_tolerance=*/0.0)); } TEST(DefaultBundleAdjuster, MergedCalibFixedPose) { // Verifies that all four intrinsic parameters change. Reconstruction reconstruction; SyntheticDatasetOptions synthetic_dataset_options; synthetic_dataset_options.num_rigs = 2; synthetic_dataset_options.num_cameras_per_rig = 1; synthetic_dataset_options.num_frames_per_rig = 1; synthetic_dataset_options.num_points3D = 100; SynthesizeDataset(synthetic_dataset_options, &reconstruction); SyntheticNoiseOptions synthetic_noise_options; synthetic_noise_options.point2D_stddev = 1; SynthesizeNoise(synthetic_noise_options, &reconstruction); const Reconstruction orig_reconstruction = reconstruction; BundleAdjustmentConfig config; config.AddImage(1); config.AddImage(2); config.FixGauge(BundleAdjustmentGauge::TWO_CAMS_FROM_WORLD); BundleAdjustmentOptions options; options.refine_principal_point = true; std::unique_ptr bundle_adjuster = CreateDefaultCasparBundleAdjuster(options, config, reconstruction); const auto summary = bundle_adjuster->Solve(); ASSERT_NE(summary->termination_type, BundleAdjustmentTerminationType::FAILURE); const size_t focal_length_idx = SimpleRadialCameraModel::focal_length_idxs[0]; const size_t principal_point_idx_x = SimpleRadialCameraModel::principal_point_idxs[0]; const size_t principal_point_idx_y = SimpleRadialCameraModel::principal_point_idxs[1]; const size_t extra_param_idx = SimpleRadialCameraModel::extra_params_idxs[0]; for (const camera_t cam_id : {camera_t{1}, camera_t{2}}) { const auto& cam = reconstruction.Camera(cam_id); const auto& orig_cam = orig_reconstruction.Camera(cam_id); EXPECT_NE(cam.params[focal_length_idx], orig_cam.params[focal_length_idx]); EXPECT_NE(cam.params[extra_param_idx], orig_cam.params[extra_param_idx]); EXPECT_NE(cam.params[principal_point_idx_x], orig_cam.params[principal_point_idx_x]); EXPECT_NE(cam.params[principal_point_idx_y], orig_cam.params[principal_point_idx_y]); } CheckConstantCamFromWorld(reconstruction.Image(1), orig_reconstruction.Image(1)); } TEST(DefaultBundleAdjuster, MergedCalibFixedPoint) { Reconstruction reconstruction; SyntheticDatasetOptions synthetic_dataset_options; synthetic_dataset_options.num_rigs = 2; synthetic_dataset_options.num_cameras_per_rig = 1; synthetic_dataset_options.num_frames_per_rig = 1; synthetic_dataset_options.num_points3D = 100; SynthesizeDataset(synthetic_dataset_options, &reconstruction); SyntheticNoiseOptions synthetic_noise_options; synthetic_noise_options.point2D_stddev = 1; SynthesizeNoise(synthetic_noise_options, &reconstruction); const Reconstruction orig_reconstruction = reconstruction; BundleAdjustmentConfig config; config.AddImage(1); config.AddImage(2); // Fix all 3D points; only pose and calib are free. for (const auto& [point3D_id, _] : reconstruction.Points3D()) { config.AddConstantPoint(point3D_id); } BundleAdjustmentOptions options; options.refine_principal_point = true; std::unique_ptr bundle_adjuster = CreateDefaultCasparBundleAdjuster(options, config, reconstruction); const auto summary = bundle_adjuster->Solve(); ASSERT_NE(summary->termination_type, BundleAdjustmentTerminationType::FAILURE); for (const auto& [point3D_id, point3D] : reconstruction.Points3D()) { CheckConstantPoint(point3D, orig_reconstruction.Point3D(point3D_id)); } } TEST(DefaultBundleAdjuster, ExternalImagePoseIsInvariant) { Reconstruction reconstruction; SyntheticDatasetOptions opts; opts.num_rigs = 3; opts.num_cameras_per_rig = 1; opts.num_frames_per_rig = 1; opts.num_points3D = 100; opts.num_points2D_without_point3D = 0; SynthesizeDataset(opts, &reconstruction); SyntheticNoiseOptions noise_opts; noise_opts.point2D_stddev = 1; noise_opts.rig_from_world_rotation_stddev = 0.5; noise_opts.rig_from_world_translation_stddev = 0.1; SynthesizeNoise(noise_opts, &reconstruction); const Reconstruction orig_reconstruction = reconstruction; BundleAdjustmentConfig config; config.AddImage(1); config.AddImage(2); config.FixGauge(BundleAdjustmentGauge::TWO_CAMS_FROM_WORLD); for (const auto& [point3D_id, _] : reconstruction.Points3D()) { config.AddVariablePoint(point3D_id); } BundleAdjustmentOptions options; std::unique_ptr bundle_adjuster = CreateDefaultCasparBundleAdjuster(options, config, reconstruction); const auto summary = bundle_adjuster->Solve(); ASSERT_NE(summary->termination_type, BundleAdjustmentTerminationType::FAILURE); CheckConstantCamFromWorld(reconstruction.Image(3), orig_reconstruction.Image(3)); } TEST(DefaultBundleAdjuster, ExternalCameraIntrinsicsOrderingIsConsistent) { Reconstruction reconstruction; SyntheticDatasetOptions opts; opts.num_rigs = 3; opts.num_cameras_per_rig = 1; opts.num_frames_per_rig = 1; opts.num_points3D = 100; opts.num_points2D_without_point3D = 0; SynthesizeDataset(opts, &reconstruction); SyntheticNoiseOptions noise_opts; noise_opts.point2D_stddev = 1; SynthesizeNoise(noise_opts, &reconstruction); const Reconstruction orig_reconstruction = reconstruction; BundleAdjustmentConfig config; config.AddImage(1); config.AddImage(2); config.FixGauge(BundleAdjustmentGauge::TWO_CAMS_FROM_WORLD); for (const auto& [point3D_id, _] : reconstruction.Points3D()) { config.AddVariablePoint(point3D_id); } BundleAdjustmentOptions options; std::unique_ptr bundle_adjuster = CreateDefaultCasparBundleAdjuster(options, config, reconstruction); const auto summary = bundle_adjuster->Solve(); ASSERT_NE(summary->termination_type, BundleAdjustmentTerminationType::FAILURE); CheckConstantCamera(reconstruction.Camera(3), orig_reconstruction.Camera(3)); } TEST(DefaultBundleAdjuster, ExternalImageViaConstantPointsIsInvariant) { Reconstruction reconstruction; SyntheticDatasetOptions opts; opts.num_rigs = 3; opts.num_cameras_per_rig = 1; opts.num_frames_per_rig = 1; opts.num_points3D = 100; opts.num_points2D_without_point3D = 0; SynthesizeDataset(opts, &reconstruction); SyntheticNoiseOptions noise_opts; noise_opts.point2D_stddev = 1; noise_opts.rig_from_world_rotation_stddev = 0.5; noise_opts.rig_from_world_translation_stddev = 0.1; SynthesizeNoise(noise_opts, &reconstruction); const Reconstruction orig_reconstruction = reconstruction; BundleAdjustmentConfig config; config.AddImage(1); config.AddImage(2); config.FixGauge(BundleAdjustmentGauge::TWO_CAMS_FROM_WORLD); for (const auto& [point3D_id, _] : reconstruction.Points3D()) { config.AddConstantPoint(point3D_id); } BundleAdjustmentOptions options; std::unique_ptr bundle_adjuster = CreateDefaultCasparBundleAdjuster(options, config, reconstruction); const auto summary = bundle_adjuster->Solve(); ASSERT_NE(summary->termination_type, BundleAdjustmentTerminationType::FAILURE); CheckConstantCamFromWorld(reconstruction.Image(3), orig_reconstruction.Image(3)); } TEST(DefaultBundleAdjuster, MultipleExternalImagesAreInvariant) { Reconstruction reconstruction; SyntheticDatasetOptions opts; opts.num_rigs = 4; opts.num_cameras_per_rig = 1; opts.num_frames_per_rig = 1; opts.num_points3D = 100; opts.num_points2D_without_point3D = 0; SynthesizeDataset(opts, &reconstruction); SyntheticNoiseOptions noise_opts; noise_opts.point2D_stddev = 1; noise_opts.rig_from_world_rotation_stddev = 0.5; noise_opts.rig_from_world_translation_stddev = 0.1; SynthesizeNoise(noise_opts, &reconstruction); const Reconstruction orig_reconstruction = reconstruction; BundleAdjustmentConfig config; config.AddImage(1); config.AddImage(2); config.FixGauge(BundleAdjustmentGauge::TWO_CAMS_FROM_WORLD); for (const auto& [point3D_id, _] : reconstruction.Points3D()) { config.AddVariablePoint(point3D_id); } BundleAdjustmentOptions options; std::unique_ptr bundle_adjuster = CreateDefaultCasparBundleAdjuster(options, config, reconstruction); const auto summary = bundle_adjuster->Solve(); ASSERT_NE(summary->termination_type, BundleAdjustmentTerminationType::FAILURE); CheckConstantCamFromWorld(reconstruction.Image(3), orig_reconstruction.Image(3)); CheckConstantCamFromWorld(reconstruction.Image(4), orig_reconstruction.Image(4)); } TEST(DefaultBundleAdjuster, MergedCalibMatchesCeres) { Reconstruction reconstruction; SyntheticDatasetOptions synthetic_dataset_options; synthetic_dataset_options.num_rigs = 1; synthetic_dataset_options.num_cameras_per_rig = 1; synthetic_dataset_options.num_frames_per_rig = 4; synthetic_dataset_options.num_points3D = 100; SynthesizeDataset(synthetic_dataset_options, &reconstruction); SyntheticNoiseOptions synthetic_noise_options; synthetic_noise_options.point2D_stddev = 0.5; synthetic_noise_options.point3D_stddev = 0.1; synthetic_noise_options.rig_from_world_rotation_stddev = 0.3; synthetic_noise_options.rig_from_world_translation_stddev = 0.05; SynthesizeNoise(synthetic_noise_options, &reconstruction); BundleAdjustmentConfig config; for (const image_t image_id : reconstruction.RegImageIds()) { config.AddImage(image_id); } config.FixGauge(BundleAdjustmentGauge::TWO_CAMS_FROM_WORLD); BundleAdjustmentOptions options; options.refine_principal_point = true; Reconstruction reconstruction_ceres = reconstruction; Reconstruction reconstruction_caspar = reconstruction; std::unique_ptr ceres_adjuster = CreateDefaultCeresBundleAdjuster(options, config, reconstruction_ceres); ASSERT_NE(ceres_adjuster->Solve()->termination_type, BundleAdjustmentTerminationType::FAILURE); std::unique_ptr caspar_adjuster = CreateDefaultCasparBundleAdjuster(options, config, reconstruction_caspar); ASSERT_NE(caspar_adjuster->Solve()->termination_type, BundleAdjustmentTerminationType::FAILURE); #ifdef CASPAR_USE_DOUBLE constexpr double kFocalTol = 1.0; constexpr double kPPTol = 1.0; constexpr double kExtraTol = 1e-4; #else constexpr double kFocalTol = 20.0; constexpr double kPPTol = 10.0; constexpr double kExtraTol = 1.5e-2; #endif const size_t f_idx = SimpleRadialCameraModel::focal_length_idxs[0]; const size_t cx_idx = SimpleRadialCameraModel::principal_point_idxs[0]; const size_t cy_idx = SimpleRadialCameraModel::principal_point_idxs[1]; const size_t k_idx = SimpleRadialCameraModel::extra_params_idxs[0]; for (const auto& [cam_id, _] : reconstruction.Cameras()) { const auto& cam_ceres = reconstruction_ceres.Camera(cam_id); const auto& cam_caspar = reconstruction_caspar.Camera(cam_id); EXPECT_NEAR(cam_caspar.params[f_idx], cam_ceres.params[f_idx], kFocalTol) << "focal length mismatch for camera " << cam_id; EXPECT_NEAR(cam_caspar.params[cx_idx], cam_ceres.params[cx_idx], kPPTol) << "cx mismatch for camera " << cam_id; EXPECT_NEAR(cam_caspar.params[cy_idx], cam_ceres.params[cy_idx], kPPTol) << "cy mismatch for camera " << cam_id; EXPECT_NEAR(cam_caspar.params[k_idx], cam_ceres.params[k_idx], kExtraTol) << "radial distortion mismatch for camera " << cam_id; } } bool PoseExactlyUnchanged(const Image& a, const Image& b) { return a.CamFromWorld().rotation().coeffs() == b.CamFromWorld().rotation().coeffs() && a.CamFromWorld().translation() == b.CamFromWorld().translation(); } TEST(DefaultBundleAdjuster, GaugeFixingWithOneFrameFromWorld) { Reconstruction reconstruction; SyntheticDatasetOptions opts; opts.num_rigs = 2; opts.num_cameras_per_rig = 1; opts.num_frames_per_rig = 1; opts.num_points3D = 100; SynthesizeDataset(opts, &reconstruction); SyntheticNoiseOptions noise_opts; noise_opts.point2D_stddev = 1; noise_opts.point3D_stddev = 0.1; noise_opts.rig_from_world_rotation_stddev = 0.3; noise_opts.rig_from_world_translation_stddev = 0.05; SynthesizeNoise(noise_opts, &reconstruction); const Reconstruction orig_reconstruction = reconstruction; BundleAdjustmentConfig config; config.AddImage(1); config.AddImage(2); config.FixGauge(BundleAdjustmentGauge::TWO_CAMS_FROM_WORLD); BundleAdjustmentOptions options; auto adjuster = CreateDefaultCasparBundleAdjuster(options, config, reconstruction); ASSERT_NE(adjuster->Solve()->termination_type, BundleAdjustmentTerminationType::FAILURE); // Exactly one of the two frames must be pinned by gauge fixing. const int n_fixed = static_cast(PoseExactlyUnchanged(reconstruction.Image(1), orig_reconstruction.Image(1))) + static_cast(PoseExactlyUnchanged(reconstruction.Image(2), orig_reconstruction.Image(2))); EXPECT_EQ(n_fixed, 1); } TEST(DefaultBundleAdjuster, GaugeFixingWithOneFrameFromWorld_SkipsWhenAlreadyFixed) { Reconstruction reconstruction; SyntheticDatasetOptions opts; opts.num_rigs = 2; opts.num_cameras_per_rig = 1; opts.num_frames_per_rig = 1; opts.num_points3D = 100; SynthesizeDataset(opts, &reconstruction); SyntheticNoiseOptions noise_opts; noise_opts.point2D_stddev = 1; noise_opts.point3D_stddev = 0.1; noise_opts.rig_from_world_rotation_stddev = 0.3; noise_opts.rig_from_world_translation_stddev = 0.05; SynthesizeNoise(noise_opts, &reconstruction); const Reconstruction orig_reconstruction = reconstruction; BundleAdjustmentConfig config; config.AddImage(1); config.AddImage(2); config.SetConstantRigFromWorldPose(1); // frame 1 explicitly constant config.FixGauge(BundleAdjustmentGauge::TWO_CAMS_FROM_WORLD); BundleAdjustmentOptions options; auto adjuster = CreateDefaultCasparBundleAdjuster(options, config, reconstruction); ASSERT_NE(adjuster->Solve()->termination_type, BundleAdjustmentTerminationType::FAILURE); // Frame 1 is explicitly constant — must be unchanged. CheckConstantCamFromWorld(reconstruction.Image(1), orig_reconstruction.Image(1)); // Frame 2 is not gauge-fixed (gauge fixer saw frame 1 already fixed) — must // change. CheckVariableCamFromWorld(reconstruction.Image(2), orig_reconstruction.Image(2)); } TEST(DefaultBundleAdjuster, GaugeFixingWithThreePoints_PinsExactlyThreePoints) { Reconstruction reconstruction; SyntheticDatasetOptions opts; opts.num_rigs = 2; opts.num_cameras_per_rig = 1; opts.num_frames_per_rig = 1; opts.num_points3D = 100; SynthesizeDataset(opts, &reconstruction); SyntheticNoiseOptions noise_opts; noise_opts.point2D_stddev = 1; noise_opts.point3D_stddev = 0.1; noise_opts.rig_from_world_rotation_stddev = 0.3; noise_opts.rig_from_world_translation_stddev = 0.05; SynthesizeNoise(noise_opts, &reconstruction); const Reconstruction orig_reconstruction = reconstruction; BundleAdjustmentConfig config; config.AddImage(1); config.AddImage(2); config.FixGauge(BundleAdjustmentGauge::THREE_POINTS); BundleAdjustmentOptions options; auto adjuster = CreateDefaultCasparBundleAdjuster(options, config, reconstruction); ASSERT_NE(adjuster->Solve()->termination_type, BundleAdjustmentTerminationType::FAILURE); int n_unchanged = 0; for (const auto& [point3D_id, point3D] : reconstruction.Points3D()) { if (point3D.xyz == orig_reconstruction.Point3D(point3D_id).xyz) { ++n_unchanged; } } EXPECT_EQ(n_unchanged, 3); CheckVariableCamera(reconstruction.Camera(1), orig_reconstruction.Camera(1)); CheckVariableCamera(reconstruction.Camera(2), orig_reconstruction.Camera(2)); } TEST(DefaultBundleAdjuster, GaugeFixingWithThreePoints_CountsExistingConstantPoints) { Reconstruction reconstruction; SyntheticDatasetOptions opts; opts.num_rigs = 2; opts.num_cameras_per_rig = 1; opts.num_frames_per_rig = 1; opts.num_points3D = 100; SynthesizeDataset(opts, &reconstruction); SyntheticNoiseOptions noise_opts; noise_opts.point2D_stddev = 1; noise_opts.point3D_stddev = 0.1; noise_opts.rig_from_world_rotation_stddev = 0.3; noise_opts.rig_from_world_translation_stddev = 0.05; SynthesizeNoise(noise_opts, &reconstruction); const Reconstruction orig_reconstruction = reconstruction; BundleAdjustmentConfig config; config.AddImage(1); config.AddImage(2); config.AddConstantPoint(1); // 1 existing constant; gauge fixer adds 2 more config.FixGauge(BundleAdjustmentGauge::THREE_POINTS); BundleAdjustmentOptions options; auto adjuster = CreateDefaultCasparBundleAdjuster(options, config, reconstruction); ASSERT_NE(adjuster->Solve()->termination_type, BundleAdjustmentTerminationType::FAILURE); // Total unchanged = 1 config-constant + 2 gauge-fixed = 3. int n_unchanged = 0; for (const auto& [point3D_id, point3D] : reconstruction.Points3D()) { if (point3D.xyz == orig_reconstruction.Point3D(point3D_id).xyz) { ++n_unchanged; } } EXPECT_EQ(n_unchanged, 3); CheckConstantPoint(reconstruction.Point3D(1), orig_reconstruction.Point3D(1)); } TEST(DefaultBundleAdjuster, MultiCameraRigResidualCountConstantSensorFromRig) { // All sensor observations (ref and non-ref) must contribute residuals. // The old code skipped non-ref sensor observations when the pose was // variable, which would halve the residual count for a 2-camera rig. Reconstruction reconstruction; SyntheticDatasetOptions synthetic_dataset_options; synthetic_dataset_options.num_rigs = 1; synthetic_dataset_options.num_cameras_per_rig = 2; synthetic_dataset_options.num_frames_per_rig = 2; synthetic_dataset_options.num_points3D = 100; SynthesizeDataset(synthetic_dataset_options, &reconstruction); SyntheticNoiseOptions synthetic_noise_options; synthetic_noise_options.point2D_stddev = 1; SynthesizeNoise(synthetic_noise_options, &reconstruction); BundleAdjustmentConfig config; for (const image_t image_id : reconstruction.RegImageIds()) { config.AddImage(image_id); } config.FixGauge(BundleAdjustmentGauge::TWO_CAMS_FROM_WORLD); BundleAdjustmentOptions options; options.refine_sensor_from_rig = false; std::unique_ptr bundle_adjuster = CreateDefaultCasparBundleAdjuster(options, config, reconstruction); const auto summary = bundle_adjuster->Solve(); ASSERT_NE(summary->termination_type, BundleAdjustmentTerminationType::FAILURE); // 100 points × 4 images (2 sensors × 2 frames) × 2 residuals per obs EXPECT_EQ(summary->num_residuals, 800); } TEST(DefaultBundleAdjuster, MultiCameraRigConstantRigPoseHoldsAllSensors) { // When a frame's rig_from_world is held constant, and Caspar always holds // sensor_from_rig constant, ALL sensors in that frame (ref and non-ref) // must have invariant cam_from_world. Sensors in the variable frame must // change. This differs from the Ceres behaviour where non-ref sensors can // still move via a variable sensor_from_rig. Reconstruction reconstruction; SyntheticDatasetOptions synthetic_dataset_options; synthetic_dataset_options.num_rigs = 1; synthetic_dataset_options.num_cameras_per_rig = 2; synthetic_dataset_options.num_frames_per_rig = 2; synthetic_dataset_options.num_points3D = 100; SynthesizeDataset(synthetic_dataset_options, &reconstruction); SyntheticNoiseOptions synthetic_noise_options; synthetic_noise_options.point2D_stddev = 1; synthetic_noise_options.rig_from_world_rotation_stddev = 0.3; synthetic_noise_options.rig_from_world_translation_stddev = 0.05; SynthesizeNoise(synthetic_noise_options, &reconstruction); const Reconstruction orig_reconstruction = reconstruction; const frame_t constant_frame_id = 1; BundleAdjustmentConfig config; for (const image_t image_id : reconstruction.RegImageIds()) { config.AddImage(image_id); } config.SetConstantRigFromWorldPose(constant_frame_id); BundleAdjustmentOptions options; options.refine_sensor_from_rig = false; std::unique_ptr bundle_adjuster = CreateDefaultCasparBundleAdjuster(options, config, reconstruction); ASSERT_NE(bundle_adjuster->Solve()->termination_type, BundleAdjustmentTerminationType::FAILURE); for (const image_t image_id : reconstruction.RegImageIds()) { const auto& image = reconstruction.Image(image_id); if (image.FrameId() == constant_frame_id) { CheckConstantCamFromWorld(image, orig_reconstruction.Image(image_id)); } else { CheckVariableCamFromWorld(image, orig_reconstruction.Image(image_id)); } } } TEST(DefaultBundleAdjuster, MultiCameraRigLargeConvergenceConstantSensorFromRig) { // 2 rigs × 3 cameras × 5 frames = 30 images. Mirrors the Ceres // NominalMultiCameraRig test to verify Caspar converges to GT at the same // scale as the single-camera nominal test. Reconstruction gt_reconstruction; SyntheticDatasetOptions synthetic_dataset_options; synthetic_dataset_options.num_rigs = 2; synthetic_dataset_options.num_cameras_per_rig = 3; synthetic_dataset_options.num_frames_per_rig = 5; synthetic_dataset_options.num_points3D = 200; SynthesizeDataset(synthetic_dataset_options, >_reconstruction); Reconstruction reconstruction = gt_reconstruction; SyntheticNoiseOptions synthetic_noise_options; synthetic_noise_options.point2D_stddev = 0.5; synthetic_noise_options.point3D_stddev = 0.1; synthetic_noise_options.rig_from_world_rotation_stddev = 0.5; synthetic_noise_options.rig_from_world_translation_stddev = 0.1; SynthesizeNoise(synthetic_noise_options, &reconstruction); BundleAdjustmentConfig config; for (const image_t image_id : reconstruction.RegImageIds()) { config.AddImage(image_id); } config.FixGauge(BundleAdjustmentGauge::TWO_CAMS_FROM_WORLD); BundleAdjustmentOptions options; options.refine_sensor_from_rig = false; std::unique_ptr bundle_adjuster = CreateDefaultCasparBundleAdjuster(options, config, reconstruction); const auto summary = bundle_adjuster->Solve(); ASSERT_NE(summary->termination_type, BundleAdjustmentTerminationType::FAILURE); EXPECT_THAT(gt_reconstruction, ReconstructionNear(reconstruction, /*max_rotation_error_deg=*/0.1, /*max_proj_center_error=*/0.1, /*max_scale_error=*/std::nullopt, /*num_obs_tolerance=*/0.0)); } } // namespace } // namespace colmap colmap-4.2.0/src/colmap/estimators/bundle_adjustment_ceres.cc000066400000000000000000001316201524536416500244410ustar00rootroot00000000000000// Copyright (c), ETH Zurich and UNC Chapel Hill. // All rights reserved. // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are met: // // * Redistributions of source code must retain the above copyright // notice, this list of conditions and the following disclaimer. // // * Redistributions in binary form must reproduce the above copyright // notice, this list of conditions and the following disclaimer in the // documentation and/or other materials provided with the distribution. // // * Neither the name of ETH Zurich and UNC Chapel Hill nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE // ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE // LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR // CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF // SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS // INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN // CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) // ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE // POSSIBILITY OF SUCH DAMAGE. #include "colmap/estimators/bundle_adjustment_ceres.h" #include "colmap/estimators/alignment.h" #include "colmap/estimators/cost_functions/manifold.h" #include "colmap/estimators/cost_functions/pose_prior.h" #include "colmap/estimators/cost_functions/reprojection_error.h" #include "colmap/estimators/cost_functions/utils.h" #include "colmap/util/cuda.h" #include "colmap/util/hash_containers.h" #include "colmap/util/misc.h" #include "colmap/util/threading.h" #include namespace colmap { namespace { BundleAdjustmentTerminationType CeresTerminationTypeToTerminationType( ceres::TerminationType ceres_type) { switch (ceres_type) { case ceres::CONVERGENCE: return BundleAdjustmentTerminationType::CONVERGENCE; case ceres::NO_CONVERGENCE: return BundleAdjustmentTerminationType::NO_CONVERGENCE; case ceres::FAILURE: return BundleAdjustmentTerminationType::FAILURE; case ceres::USER_SUCCESS: return BundleAdjustmentTerminationType::USER_SUCCESS; case ceres::USER_FAILURE: return BundleAdjustmentTerminationType::USER_FAILURE; } LOG(FATAL_THROW) << "Unknown Ceres termination type: " << ceres_type; return BundleAdjustmentTerminationType::FAILURE; } std::unique_ptr CreateLossFunction( CeresBundleAdjustmentOptions::LossFunctionType loss_function_type, double loss_function_scale) { switch (loss_function_type) { case CeresBundleAdjustmentOptions::LossFunctionType::TRIVIAL: return std::make_unique(); case CeresBundleAdjustmentOptions::LossFunctionType::SOFT_L1: return std::make_unique(loss_function_scale); case CeresBundleAdjustmentOptions::LossFunctionType::CAUCHY: return std::make_unique(loss_function_scale); case CeresBundleAdjustmentOptions::LossFunctionType::HUBER: return std::make_unique(loss_function_scale); } return nullptr; } } // namespace std::shared_ptr CeresBundleAdjustmentSummary::Create(ceres::Solver::Summary ceres_summary) { auto summary = std::make_shared(); summary->termination_type = CeresTerminationTypeToTerminationType(ceres_summary.termination_type); summary->num_residuals = ceres_summary.num_residuals_reduced; summary->ceres_summary = std::move(ceres_summary); return summary; } std::string CeresBundleAdjustmentSummary::BriefReport() const { return ceres_summary.BriefReport(); } //////////////////////////////////////////////////////////////////////////////// // CeresBundleAdjustmentOptions //////////////////////////////////////////////////////////////////////////////// CeresBundleAdjustmentOptions::CeresBundleAdjustmentOptions() { solver_options.function_tolerance = 0.0; solver_options.gradient_tolerance = 1e-4; solver_options.parameter_tolerance = 0.0; solver_options.logging_type = ceres::LoggingType::SILENT; solver_options.max_num_iterations = 100; solver_options.max_linear_solver_iterations = 200; solver_options.max_num_consecutive_invalid_steps = 10; solver_options.max_consecutive_nonmonotonic_steps = 10; solver_options.num_threads = -1; #if CERES_VERSION_MAJOR < 2 solver_options.num_linear_solver_threads = -1; #endif // CERES_VERSION_MAJOR } std::unique_ptr CeresBundleAdjustmentOptions::CreateLossFunction() const { return colmap::CreateLossFunction(loss_function_type, loss_function_scale); } ceres::Solver::Options CeresBundleAdjustmentOptions::CreateSolverOptions( const BundleAdjustmentConfig& config, const ceres::Problem& problem) const { ceres::Solver::Options custom_solver_options = solver_options; if (VLOG_IS_ON(2)) { custom_solver_options.minimizer_progress_to_stdout = true; custom_solver_options.logging_type = ceres::LoggingType::PER_MINIMIZER_ITERATION; } const int num_images = config.NumImages(); const bool has_sparse = custom_solver_options.sparse_linear_algebra_library_type != ceres::NO_SPARSE; int max_num_images_direct_dense_solver = max_num_images_direct_dense_cpu_solver; int max_num_images_direct_sparse_solver = max_num_images_direct_sparse_cpu_solver; #ifdef COLMAP_CUDA_ENABLED bool cuda_solver_enabled = false; const bool cuda_solver_requested = use_gpu && num_images >= min_num_images_gpu_solver; const bool use_cuda_solver = cuda_solver_requested && GetNumCudaDevices() > 0; if (cuda_solver_requested && !use_cuda_solver) { LOG_FIRST_N(WARNING, 1) << "Requested to use GPU for bundle adjustment, but no CUDA GPU is " "available. Falling back to CPU-based solvers."; } #if (CERES_VERSION_MAJOR >= 3 || \ (CERES_VERSION_MAJOR == 2 && CERES_VERSION_MINOR >= 2)) && \ !defined(CERES_NO_CUDA) if (use_cuda_solver) { cuda_solver_enabled = true; custom_solver_options.dense_linear_algebra_library_type = ceres::CUDA; max_num_images_direct_dense_solver = max_num_images_direct_dense_gpu_solver; } #else if (use_gpu) { LOG_FIRST_N(WARNING, 1) << "Requested to use GPU for bundle adjustment, but Ceres was " "compiled without CUDA support. Falling back to CPU-based dense " "solvers."; } #endif #if (CERES_VERSION_MAJOR >= 3 || \ (CERES_VERSION_MAJOR == 2 && CERES_VERSION_MINOR >= 3)) && \ !defined(CERES_NO_CUDSS) if (use_cuda_solver) { cuda_solver_enabled = true; custom_solver_options.sparse_linear_algebra_library_type = ceres::CUDA_SPARSE; max_num_images_direct_sparse_solver = max_num_images_direct_sparse_gpu_solver; } #else if (use_gpu) { LOG_FIRST_N(WARNING, 1) << "Requested to use GPU for bundle adjustment, but Ceres was " "compiled without cuDSS support. Falling back to CPU-based sparse " "solvers."; } #endif if (cuda_solver_enabled) { const std::vector gpu_indices = CSVToVector(gpu_index); THROW_CHECK_GT(gpu_indices.size(), 0); SetBestCudaDevice(gpu_indices[0]); } #else if (use_gpu) { LOG_FIRST_N(WARNING, 1) << "Requested to use GPU for bundle adjustment, but COLMAP was " "compiled without CUDA support. Falling back to CPU-based " "solvers."; } #endif // COLMAP_CUDA_ENABLED // Auto-select solver type based on problem size, unless disabled. if (auto_select_solver_type) { if (num_images <= max_num_images_direct_dense_solver) { custom_solver_options.linear_solver_type = ceres::DENSE_SCHUR; } else if (has_sparse && num_images <= max_num_images_direct_sparse_solver) { custom_solver_options.linear_solver_type = ceres::SPARSE_SCHUR; } else { // Indirect sparse (preconditioned CG) solver. custom_solver_options.linear_solver_type = ceres::ITERATIVE_SCHUR; custom_solver_options.preconditioner_type = ceres::SCHUR_JACOBI; } } if (problem.NumResiduals() < min_num_residuals_for_cpu_multi_threading) { custom_solver_options.num_threads = 1; #if CERES_VERSION_MAJOR < 2 custom_solver_options.num_linear_solver_threads = 1; #endif // CERES_VERSION_MAJOR } else { custom_solver_options.num_threads = GetEffectiveNumThreads(custom_solver_options.num_threads); #if CERES_VERSION_MAJOR < 2 custom_solver_options.num_linear_solver_threads = GetEffectiveNumThreads(custom_solver_options.num_linear_solver_threads); #endif // CERES_VERSION_MAJOR } std::string solver_error; THROW_CHECK(custom_solver_options.IsValid(&solver_error)) << solver_error; return custom_solver_options; } bool CeresBundleAdjustmentOptions::Check() const { CHECK_OPTION_GE(loss_function_scale, 0); CHECK_OPTION_LT(max_num_images_direct_dense_cpu_solver, max_num_images_direct_sparse_cpu_solver); CHECK_OPTION_LT(max_num_images_direct_dense_gpu_solver, max_num_images_direct_sparse_gpu_solver); return true; } bool CeresPosePriorBundleAdjustmentOptions::Check() const { CHECK_OPTION_GT(prior_position_loss_scale, 0); return true; } namespace { struct FixedGaugeWithThreePoints { // The number of fixed points for the Gauge. Eigen::Index num_fixed_points = 0; // The coordinates of the fixed points as columns. Eigen::Matrix3d fixed_points = Eigen::Matrix3d::Zero(); bool MaybeAddFixedPoint(const Eigen::Vector3d& point) { if (num_fixed_points >= 3) { return false; } fixed_points.col(num_fixed_points) = point; if (fixed_points.colPivHouseholderQr().rank() > num_fixed_points) { ++num_fixed_points; return true; } else { fixed_points.col(num_fixed_points).setZero(); return false; } } }; void FixGaugeWithThreePoints( const FlatHashMap& point3D_num_observations, Reconstruction& reconstruction, ceres::Problem& problem) { FixedGaugeWithThreePoints fixed_gauge; // First check if we already fixed enough points in the problem. for (const auto& [point3D_id, num_observations] : point3D_num_observations) { const Point3D& point3D = reconstruction.Point3D(point3D_id); if (problem.IsParameterBlockConstant(point3D.xyz.data()) && fixed_gauge.MaybeAddFixedPoint(point3D.xyz) && fixed_gauge.num_fixed_points >= 3) { return; } } // Otherwise, fix sufficient points in the problem. for (const auto& [point3D_id, num_observations] : point3D_num_observations) { Point3D& point3D = reconstruction.Point3D(point3D_id); if (!problem.IsParameterBlockConstant(point3D.xyz.data()) && fixed_gauge.MaybeAddFixedPoint(point3D.xyz)) { problem.SetParameterBlockConstant(point3D.xyz.data()); if (fixed_gauge.num_fixed_points >= 3) { return; } } } LOG(WARNING) << "Failed to fix Gauge due to insufficient number of fixed points: " << fixed_gauge.num_fixed_points; } // Note that the following implementation does not handle all degenerate edge // cases well, e.g., where the selected two cameras are not well constrained // with respect to each other with shared observations. Furthermore, the // implementation could be more sophisticated for multi-camera rigs by selecting // camera pairs within a rig, etc. void FixGaugeWithTwoCamsFromWorld( const BundleAdjustmentOptions& options, const BundleAdjustmentConfig& config, const std::set& image_ids, const FlatHashMap& point3D_num_observations, Reconstruction& reconstruction, ceres::Problem& problem) { // No need to fix the Gauge if all frames are constant. if (!options.refine_rig_from_world) { return; } Image* image1 = nullptr; Image* image2 = nullptr; // Check if a sensor is either a reference sensor, or a non-reference sensor // with sensor_from_rig fixed. auto IsParameterizedConstSensor = [&problem, &config, &options](const Image& image) { const sensor_t sensor_id = image.CameraPtr()->SensorId(); if (image.FramePtr()->RigPtr()->IsRefSensor(sensor_id)) { return true; } const Rigid3d& sensor_from_rig = image.FramePtr()->RigPtr()->SensorFromRig(sensor_id); if (problem.HasParameterBlock(sensor_from_rig.params.data()) && problem.IsParameterBlockConstant(sensor_from_rig.params.data())) { return true; } // Cover corner case when ReprojErrorConstantPoseCostFunctor is used if (config.HasConstantSensorFromRigPose(sensor_id) || !options.refine_sensor_from_rig) { return true; } return false; }; // First, search through the already fixed cameras in the problem. for (const image_t image_id : image_ids) { Image& image = reconstruction.Image(image_id); if (config.HasConstantRigFromWorldPose(image.FrameId()) && IsParameterizedConstSensor(image)) { if (image1 == nullptr) { image1 = ℑ } else if (image1 != nullptr && image1->FrameId() != image.FrameId()) { // No need to fix the Gauge if two frames are already fixed. return; } } } // Otherwise, search through the variable cameras in the problem. int frame2_from_world_fixed_dim = 0; for (const image_t image_id : image_ids) { Image& image = reconstruction.Image(image_id); const Rigid3d& rig_from_world = image.FramePtr()->RigFromWorld(); if (image1 == nullptr && IsParameterizedConstSensor(image)) { image1 = ℑ } else if (image1 != nullptr && image1->FrameId() != image.FrameId() && IsParameterizedConstSensor(image) && problem.HasParameterBlock(rig_from_world.params.data())) { // Check if one of the baseline dimensions is large enough and // choose it as the fixed coordinate. If there is no such pair of // frames, then the scale is not constrained well. const Eigen::Vector3d baseline = (image1->FramePtr()->RigFromWorld() * Inverse(image.FramePtr()->RigFromWorld())) .translation(); Eigen::Index max_coeff_idx = 0; if (baseline.cwiseAbs().maxCoeff(&max_coeff_idx) > 1e-9) { image2 = ℑ frame2_from_world_fixed_dim = max_coeff_idx; break; } } } // TODO(jsch): Notice that we could alternatively fall back to fixing the // Gauge between two cameras in the same frame or in different frames. Since // there are many different combinations to iterate through, we instead fall // back to fixing the Gauge with three points for simplicity. Furthermore, // once we support IMUs or other sensors, we should fix the Gauge differently. if (image1 == nullptr || image2 == nullptr) { LOG(WARNING) << "Failed to fix Gauge with two cameras. " "Falling back to fixing Gauge with three points."; FixGaugeWithThreePoints(point3D_num_observations, reconstruction, problem); return; } if (!config.HasConstantRigFromWorldPose(image1->FrameId())) { const Rigid3d& frame1_from_world = image1->FramePtr()->RigFromWorld(); problem.SetParameterBlockConstant(frame1_from_world.params.data()); } if (!config.HasConstantRigFromWorldPose(image2->FrameId())) { Rigid3d& frame2_from_world = image2->FramePtr()->RigFromWorld(); if (options.constant_rig_from_world_rotation) { SetManifold(&problem, frame2_from_world.params.data(), CreateSubsetManifold( 7, {0, 1, 2, 3, 4 + frame2_from_world_fixed_dim})); } else { SetManifold(&problem, frame2_from_world.params.data(), CreateProductManifold( CreateEigenQuaternionManifold(), CreateSubsetManifold(3, {frame2_from_world_fixed_dim}))); } } } void ParameterizeCameras(const BundleAdjustmentOptions& options, const BundleAdjustmentConfig& config, const std::set& camera_ids, Reconstruction& reconstruction, ceres::Problem& problem) { const bool constant_camera = !options.refine_focal_length && !options.refine_principal_point && !options.refine_extra_params; for (const camera_t camera_id : camera_ids) { Camera& camera = reconstruction.Camera(camera_id); if (constant_camera || config.HasConstantCamIntrinsics(camera_id)) { problem.SetParameterBlockConstant(camera.params.data()); } else { std::vector const_camera_params; const_camera_params.reserve(camera.params.size()); { // Metadata parameters (e.g. the (w, h) image dimensions of spherical // models) are sensor properties and are never optimized. const span params_idxs = camera.MetaDataParamsIdxs(); const_camera_params.insert( const_camera_params.end(), params_idxs.begin(), params_idxs.end()); } if (!options.refine_focal_length) { const span params_idxs = camera.FocalLengthIdxs(); const_camera_params.insert( const_camera_params.end(), params_idxs.begin(), params_idxs.end()); } if (!options.refine_principal_point) { const span params_idxs = camera.PrincipalPointIdxs(); const_camera_params.insert( const_camera_params.end(), params_idxs.begin(), params_idxs.end()); } if (!options.refine_extra_params) { const span params_idxs = camera.ExtraParamsIdxs(); const_camera_params.insert( const_camera_params.end(), params_idxs.begin(), params_idxs.end()); } if (const_camera_params.size() == camera.params.size()) { problem.SetParameterBlockConstant(camera.params.data()); } else if (!const_camera_params.empty()) { SetManifold( &problem, camera.params.data(), CreateSubsetManifold(camera.params.size(), const_camera_params)); } } } } void ParameterizeRigsAndFrames(const BundleAdjustmentOptions& options, const BundleAdjustmentConfig& config, const std::set& image_ids, Reconstruction& reconstruction, ceres::Problem& problem) { FlatHashSet parameterized_rig_ids; FlatHashSet parameterized_sensor_ids; FlatHashSet parameterized_frame_ids; for (const image_t image_id : image_ids) { Image& image = reconstruction.Image(image_id); parameterized_rig_ids.insert(image.FramePtr()->RigId()); // Parameterize sensor_from_rig. const sensor_t sensor_id = image.CameraPtr()->SensorId(); const bool not_parameterized_before = parameterized_sensor_ids.insert(sensor_id).second; if (not_parameterized_before && !image.IsRefInFrame()) { Rigid3d& sensor_from_rig = image.FramePtr()->RigPtr()->SensorFromRig(sensor_id); // CostFunction assumes unit quaternions. sensor_from_rig.rotation().normalize(); if (problem.HasParameterBlock(sensor_from_rig.params.data())) { SetManifold(&problem, sensor_from_rig.params.data(), CreateProductManifold(CreateEigenQuaternionManifold(), CreateEuclideanManifold<3>())); if (!options.refine_sensor_from_rig || config.HasConstantSensorFromRigPose(sensor_id)) { problem.SetParameterBlockConstant(sensor_from_rig.params.data()); } } } // Parameterize rig_from_world. if (parameterized_frame_ids.insert(image.FrameId()).second) { Rigid3d& rig_from_world = image.FramePtr()->RigFromWorld(); // CostFunction assumes unit quaternions. rig_from_world.rotation().normalize(); if (problem.HasParameterBlock(rig_from_world.params.data())) { if (!options.refine_rig_from_world || config.HasConstantRigFromWorldPose(image.FrameId())) { problem.SetParameterBlockConstant(rig_from_world.params.data()); } else if (options.constant_rig_from_world_rotation) { SetManifold(&problem, rig_from_world.params.data(), CreateSubsetManifold(7, {0, 1, 2, 3})); } else { SetManifold(&problem, rig_from_world.params.data(), CreateProductManifold(CreateEigenQuaternionManifold(), CreateEuclideanManifold<3>())); } } } } // Set the rig poses as constant, if the reference sensor is not part of the // problem. Otherwise, the relative pose between the sensors is not well // constrained. Notice that this does not handle degenerate configurations and // assumes the observations in the problem constrain the relative poses // sufficiently. for (const rig_t rig_id : parameterized_rig_ids) { Rig& rig = reconstruction.Rig(rig_id); if (parameterized_sensor_ids.count(rig.RefSensorId()) != 0) { continue; } for (auto& [sensor_id, sensor_from_rig] : rig.NonRefSensors()) { THROW_CHECK(sensor_from_rig.has_value()); if (problem.HasParameterBlock(sensor_from_rig->params.data())) { problem.SetParameterBlockConstant(sensor_from_rig->params.data()); } } } } void ParameterizePoints( const BundleAdjustmentOptions& options, const BundleAdjustmentConfig& config, const FlatHashMap& point3D_num_observations, Reconstruction& reconstruction, ceres::Problem& problem) { for (const auto& [point3D_id, num_observations] : point3D_num_observations) { Point3D& point3D = reconstruction.Point3D(point3D_id); if (!options.refine_points3D || point3D.track.Length() > num_observations) { problem.SetParameterBlockConstant(point3D.xyz.data()); } } for (const point3D_t point3D_id : config.ConstantPoints()) { Point3D& point3D = reconstruction.Point3D(point3D_id); problem.SetParameterBlockConstant(point3D.xyz.data()); } } std::shared_ptr CreateSummaryAndLogFailure( ceres::Solver::Summary ceres_summary, const std::string& context) { auto summary = CeresBundleAdjustmentSummary::Create(std::move(ceres_summary)); if (!summary->IsSolutionUsable()) { LOG(ERROR) << context << " failed: " << summary->ceres_summary.message; } return summary; } class CancellationCallback : public ceres::IterationCallback { public: explicit CancellationCallback(std::function check_if_stopped) : check_if_stopped_(std::move(check_if_stopped)) {} ceres::CallbackReturnType operator()( const ceres::IterationSummary&) override { return check_if_stopped_ && check_if_stopped_() ? ceres::SOLVER_TERMINATE_SUCCESSFULLY : ceres::SOLVER_CONTINUE; } private: std::function check_if_stopped_; }; ceres::Solver::Summary SolveWithGpuFallback( const BundleAdjustmentOptions& options, const BundleAdjustmentConfig& config, ceres::Problem* problem) { CancellationCallback cancellation_callback(options.check_if_stopped); ceres::Solver::Options solver_options = options.ceres->CreateSolverOptions(config, *problem); if (options.check_if_stopped) { solver_options.callbacks.push_back(&cancellation_callback); } ceres::Solver::Summary ceres_summary; ceres::Solve(solver_options, problem, &ceres_summary); if (ceres_summary.termination_type == ceres::FAILURE && options.ceres->use_gpu) { const std::string& msg = ceres_summary.message; if (msg.find("CUDA initialization failed") != std::string::npos || msg.find("non-numeric") != std::string::npos || msg.find("Unable to create Jacobian") != std::string::npos) { LOG(WARNING) << "GPU bundle adjustment failed (" << msg << "), retrying with CPU."; auto cpu_options = std::make_shared(*options.ceres); cpu_options->use_gpu = false; ceres::Solver::Options cpu_solver_options = cpu_options->CreateSolverOptions(config, *problem); if (options.check_if_stopped) { cpu_solver_options.callbacks.push_back(&cancellation_callback); } ceres::Solve(cpu_solver_options, problem, &ceres_summary); } } return ceres_summary; } class DefaultBundleAdjuster : public CeresBundleAdjuster { public: DefaultBundleAdjuster(const BundleAdjustmentOptions& options, const BundleAdjustmentConfig& config, Reconstruction& reconstruction) : CeresBundleAdjuster(options, config), loss_function_(options_.ceres->CreateLossFunction()) { VLOG(2) << "Creating Ceres bundle adjuster"; ceres::Problem::Options problem_options; problem_options.loss_function_ownership = ceres::DO_NOT_TAKE_OWNERSHIP; problem_ = std::make_shared(problem_options); // Verify that reconstruction is internally consistent. THROW_CHECK(reconstruction.IsValid()); // Set up problem. // Warning: AddPointsToProblem assumes that AddImageToProblem is called // first. Do not change order of instructions! for (const image_t image_id : config_.Images()) { AddImageToProblem(image_id, reconstruction); } for (const auto point3D_id : config_.VariablePoints()) { AddPointToProblem(point3D_id, reconstruction); } for (const auto point3D_id : config_.ConstantPoints()) { AddPointToProblem(point3D_id, reconstruction); } ParameterizeCameras(options_, config_, parameterized_camera_ids_, reconstruction, *problem_); ParameterizeRigsAndFrames( options_, config_, parameterized_image_ids_, reconstruction, *problem_); ParameterizePoints(options_, config_, point3D_num_observations_, reconstruction, *problem_); switch (config_.FixedGauge()) { case BundleAdjustmentGauge::UNSPECIFIED: break; case BundleAdjustmentGauge::TWO_CAMS_FROM_WORLD: FixGaugeWithTwoCamsFromWorld(options_, config_, parameterized_image_ids_, point3D_num_observations_, reconstruction, *problem_); break; case BundleAdjustmentGauge::THREE_POINTS: FixGaugeWithThreePoints( point3D_num_observations_, reconstruction, *problem_); break; default: LOG(FATAL_THROW) << "Unknown BundleAdjustmentGauge"; } } std::shared_ptr Solve() override { if (problem_->NumResiduals() == 0) { return std::make_shared(); } ceres::Solver::Summary ceres_summary = SolveWithGpuFallback(options_, config_, problem_.get()); if (options_.print_summary || VLOG_IS_ON(1)) { PrintSolverSummary(ceres_summary, "Bundle adjustment report"); } return CreateSummaryAndLogFailure(std::move(ceres_summary), "Bundle adjustment"); } std::shared_ptr& Problem() override { return problem_; } const std::set& ParameterizedImageIds() const { return parameterized_image_ids_; } void AddImageToProblem(const image_t image_id, Reconstruction& reconstruction) { Image& image = reconstruction.Image(image_id); if (image.IsRefInFrame()) { AddImageWithTrivialFrame(image, reconstruction); } else { AddImageWithNonTrivialFrame(image, reconstruction); } } void AddImageWithTrivialFrame(Image& image, Reconstruction& reconstruction) { Camera& camera = *image.CameraPtr(); const bool constant_cam_from_world = !options_.refine_rig_from_world || config_.HasConstantRigFromWorldPose(image.FrameId()); THROW_CHECK(image.IsRefInFrame()); Rigid3d& rig_from_world = image.FramePtr()->RigFromWorld(); // Add residuals to bundle adjustment problem. size_t num_observations = 0; for (const Point2D& point2D : image.Points2D()) { if (!point2D.HasPoint3D() || config_.IsIgnoredPoint(point2D.point3D_id)) { continue; } Point3D& point3D = reconstruction.Point3D(point2D.point3D_id); THROW_CHECK_GT(point3D.track.Length(), 1); // Skip points with track length below minimum. if (options_.min_track_length > 0 && static_cast(point3D.track.Length()) < options_.min_track_length) { continue; } num_observations += 1; point3D_num_observations_[point2D.point3D_id] += 1; if (constant_cam_from_world) { problem_->AddResidualBlock( CreateCameraCostFunction( camera.model_id, point2D.xy, rig_from_world), loss_function_.get(), point3D.xyz.data(), camera.params.data()); } else { problem_->AddResidualBlock( CreateCameraCostFunction(camera.model_id, point2D.xy), loss_function_.get(), point3D.xyz.data(), rig_from_world.params.data(), camera.params.data()); } } if (num_observations > 0) { parameterized_camera_ids_.insert(image.CameraId()); parameterized_image_ids_.insert(image.ImageId()); } } void AddImageWithNonTrivialFrame(Image& image, Reconstruction& reconstruction) { Camera& camera = *image.CameraPtr(); const sensor_t sensor_id = camera.SensorId(); const bool constant_sensor_from_rig = !options_.refine_sensor_from_rig || config_.HasConstantSensorFromRigPose(sensor_id); const bool constant_rig_from_world = !options_.refine_rig_from_world || config_.HasConstantRigFromWorldPose(image.FrameId()); THROW_CHECK(!image.IsRefInFrame()); Rigid3d& sensor_from_rig = image.FramePtr()->RigPtr()->SensorFromRig(sensor_id); Rigid3d& rig_from_world = image.FramePtr()->RigFromWorld(); const std::optional cam_from_world = (constant_sensor_from_rig && constant_rig_from_world) ? std::make_optional(sensor_from_rig * rig_from_world) : std::nullopt; // Add residuals to bundle adjustment problem. size_t num_observations = 0; for (const Point2D& point2D : image.Points2D()) { if (!point2D.HasPoint3D() || config_.IsIgnoredPoint(point2D.point3D_id)) { continue; } Point3D& point3D = reconstruction.Point3D(point2D.point3D_id); THROW_CHECK_GT(point3D.track.Length(), 1); // Skip points with track length below minimum. if (options_.min_track_length > 0 && static_cast(point3D.track.Length()) < options_.min_track_length) { continue; } num_observations += 1; point3D_num_observations_[point2D.point3D_id] += 1; // The !constant_sensor_from_rig && constant_rig_from_world is // rare enough that we do not have a specialized cost function for it. if (constant_sensor_from_rig && constant_rig_from_world) { problem_->AddResidualBlock( CreateCameraCostFunction( camera.model_id, point2D.xy, cam_from_world.value()), loss_function_.get(), point3D.xyz.data(), camera.params.data()); } else if (!constant_rig_from_world && constant_sensor_from_rig) { problem_->AddResidualBlock( CreateCameraCostFunction( camera.model_id, point2D.xy, sensor_from_rig), loss_function_.get(), point3D.xyz.data(), rig_from_world.params.data(), camera.params.data()); } else { problem_->AddResidualBlock( CreateCameraCostFunction(camera.model_id, point2D.xy), loss_function_.get(), point3D.xyz.data(), sensor_from_rig.params.data(), rig_from_world.params.data(), camera.params.data()); } } if (num_observations > 0) { parameterized_camera_ids_.insert(image.CameraId()); parameterized_image_ids_.insert(image.ImageId()); } } void AddPointToProblem(const point3D_t point3D_id, Reconstruction& reconstruction) { THROW_CHECK(!config_.IsIgnoredPoint(point3D_id)); Point3D& point3D = reconstruction.Point3D(point3D_id); // Skip points with track length below minimum. if (options_.min_track_length > 0 && static_cast(point3D.track.Length()) < options_.min_track_length) { return; } size_t& num_observations = point3D_num_observations_[point3D_id]; // Is 3D point already fully contained in the problem? I.e. its entire // track is contained in `variable_image_ids`, `constant_image_ids`, // `constant_x_image_ids`. if (num_observations == point3D.track.Length()) { return; } for (const auto& track_el : point3D.track.Elements()) { // Skip observations that were already added in `FillImages`. if (config_.HasImage(track_el.image_id)) { continue; } num_observations += 1; Image& image = reconstruction.Image(track_el.image_id); Camera& camera = *image.CameraPtr(); const Point2D& point2D = image.Point2D(track_el.point2D_idx); if (image.IsRefInFrame()) { Rigid3d& cam_from_world = image.FramePtr()->RigFromWorld(); problem_->AddResidualBlock( CreateCameraCostFunction( camera.model_id, point2D.xy, cam_from_world), loss_function_.get(), point3D.xyz.data(), camera.params.data()); } else { Rigid3d& cam_from_rig = image.FramePtr()->RigPtr()->SensorFromRig( image.CameraPtr()->SensorId()); Rigid3d& rig_from_world = image.FramePtr()->RigFromWorld(); problem_->AddResidualBlock( CreateCameraCostFunction( camera.model_id, point2D.xy, cam_from_rig * rig_from_world), loss_function_.get(), point3D.xyz.data(), camera.params.data()); } // Do not optimize intrinsics if th corresponding images // were not included explicitly in the config. if (parameterized_camera_ids_.insert(image.CameraId()).second) { config_.SetConstantCamIntrinsics(image.CameraId()); } } } private: std::shared_ptr problem_; std::unique_ptr loss_function_; std::set parameterized_camera_ids_; std::set parameterized_image_ids_; FlatHashMap point3D_num_observations_; }; class PosePriorBundleAdjuster : public CeresBundleAdjuster { public: PosePriorBundleAdjuster(const BundleAdjustmentOptions& options, const PosePriorBundleAdjustmentOptions& prior_options, const BundleAdjustmentConfig& config, std::vector pose_priors, Reconstruction& reconstruction) : CeresBundleAdjuster(options, config), prior_options_(prior_options), pose_priors_(std::move(pose_priors)), reconstruction_(reconstruction) { VLOG(2) << "Creating Ceres pose prior bundle adjuster"; THROW_CHECK(prior_options_.Check()); // Filter irrelevant pose priors. pose_priors_.erase( std::remove_if(pose_priors_.begin(), pose_priors_.end(), [this](const auto& pose_prior) { return !pose_prior.HasPosition() || pose_prior.corr_data_id.sensor_id.type != SensorType::CAMERA || !config_.HasImage(pose_prior.corr_data_id.id); }), pose_priors_.end()); const bool use_prior_position = pose_priors_.size() >= 3 && AlignReconstruction(); // Fix 7-DOFs of the BA problem if the pose priors cannot constrain them. if (use_prior_position) { // Normalize the reconstruction to avoid any numerical instability but // do not transform priors as they will be transformed when added to // ceres::Problem. normalized_from_metric_ = reconstruction_.Normalize(/*fixed_scale=*/true); } else { config_.FixGauge(BundleAdjustmentGauge::TWO_CAMS_FROM_WORLD); } // WARNING: Do not move this above the reconstruction normalization. default_bundle_adjuster_ = std::make_unique( options_, config_, reconstruction); if (use_prior_position) { prior_loss_function_ = CreateLossFunction( prior_options_.ceres->prior_position_loss_function_type, prior_options_.ceres->prior_position_loss_scale); // Only consider parameterized images for pose priors. Notice that some // images may be configured to be included in the BA problem but have no // reprojection constraints, etc. const std::set& parameterized_image_ids = default_bundle_adjuster_->ParameterizedImageIds(); for (const auto& pose_prior : pose_priors_) { if (parameterized_image_ids.count(pose_prior.corr_data_id.id) > 0) { AddImagePosePriorToProblem( pose_prior.corr_data_id.id, pose_prior, reconstruction); } } } } std::shared_ptr Solve() override { std::shared_ptr problem = default_bundle_adjuster_->Problem(); if (problem->NumResiduals() == 0) { return std::make_shared(); } ceres::Solver::Summary ceres_summary = SolveWithGpuFallback(options_, config_, problem.get()); reconstruction_.Transform(Inverse(normalized_from_metric_)); if (options_.print_summary || VLOG_IS_ON(1)) { PrintSolverSummary(ceres_summary, "Pose Prior Bundle adjustment report"); } return CreateSummaryAndLogFailure(std::move(ceres_summary), "Pose prior bundle adjustment"); } std::shared_ptr& Problem() override { return default_bundle_adjuster_->Problem(); } void AddImagePosePriorToProblem(image_t image_id, const PosePrior& pose_prior, Reconstruction& reconstruction) { Image& image = reconstruction.Image(image_id); const bool constant_sensor_from_rig = image.IsRefInFrame() || !options_.refine_sensor_from_rig || config_.HasConstantSensorFromRigPose(image.CameraPtr()->SensorId()); const bool constant_rig_from_world = !options_.refine_rig_from_world || config_.HasConstantRigFromWorldPose(image.FrameId()); if (constant_sensor_from_rig && constant_rig_from_world) { return; } ceres::Problem& problem = *default_bundle_adjuster_->Problem(); Frame& frame = *image.FramePtr(); Rigid3d& rig_from_world = frame.RigFromWorld(); const Eigen::Vector3d normalized_position = normalized_from_metric_ * pose_prior.position; const Eigen::Matrix3d normalized_from_metric_scaled_rotation = normalized_from_metric_.scale() * normalized_from_metric_.rotation().toRotationMatrix(); const Eigen::Matrix3d position_cov = pose_prior.HasPositionCov() ? pose_prior.position_covariance : (prior_options_.prior_position_fallback_stddev * prior_options_.prior_position_fallback_stddev * Eigen::Matrix3d::Identity()); const Eigen::Matrix3d normalized_position_cov = normalized_from_metric_scaled_rotation * position_cov * normalized_from_metric_scaled_rotation.transpose(); if (image.IsRefInFrame()) { problem.AddResidualBlock( CovarianceWeightedCostFunctor:: Create(normalized_position_cov, normalized_position), prior_loss_function_.get(), rig_from_world.params.data()); } else { Rigid3d& cam_from_rig = frame.RigPtr()->SensorFromRig(image.CameraPtr()->SensorId()); problem.AddResidualBlock( CovarianceWeightedCostFunctor< AbsoluteRigPosePositionPriorCostFunctor>:: Create(normalized_position_cov, normalized_position), prior_loss_function_.get(), cam_from_rig.params.data(), rig_from_world.params.data()); // Reprojection residuals may omit constant poses, so the prior can add // their parameter blocks after the default parameterization pass. if (constant_sensor_from_rig) { problem.SetParameterBlockConstant(cam_from_rig.params.data()); } } if (constant_rig_from_world) { problem.SetParameterBlockConstant(rig_from_world.params.data()); } } bool AlignReconstruction() { Sim3d metric_from_orig; if (!AlignReconstructionToPosePriors( reconstruction_, pose_priors_, prior_options_.alignment_ransac_options, prior_options_.prior_position_fallback_stddev, &metric_from_orig)) { LOG(WARNING) << "Alignment w.r.t. prior positions failed"; return false; } reconstruction_.Transform(metric_from_orig); // Compute alignment error w.r.t. prior positions. if (VLOG_IS_ON(2)) { std::vector verr2_wrt_prior; verr2_wrt_prior.reserve(config_.NumImages()); for (const auto& pose_prior : pose_priors_) { const auto& image = reconstruction_.Image(pose_prior.corr_data_id.id); verr2_wrt_prior.push_back( (image.ProjectionCenter() - pose_prior.position).squaredNorm()); } VLOG(2) << "Alignment error w.r.t. prior positions:\n" << " - rmse: " << std::sqrt(Mean(verr2_wrt_prior)) << '\n' << " - median: " << std::sqrt(Median(verr2_wrt_prior)) << '\n'; } return true; } private: PosePriorBundleAdjustmentOptions prior_options_; std::vector pose_priors_; Reconstruction& reconstruction_; std::unique_ptr default_bundle_adjuster_; std::unique_ptr prior_loss_function_; Sim3d normalized_from_metric_; }; } // namespace std::unique_ptr CreateDefaultCeresBundleAdjuster( const BundleAdjustmentOptions& options, const BundleAdjustmentConfig& config, Reconstruction& reconstruction) { return std::make_unique( options, config, reconstruction); } std::unique_ptr CreatePosePriorCeresBundleAdjuster( const BundleAdjustmentOptions& options, const PosePriorBundleAdjustmentOptions& prior_options, const BundleAdjustmentConfig& config, std::vector pose_priors, Reconstruction& reconstruction) { return std::make_unique( options, prior_options, config, std::move(pose_priors), reconstruction); } void PrintSolverSummary(const ceres::Solver::Summary& summary, const std::string& header) { if (VLOG_IS_ON(3)) { LOG(INFO) << summary.FullReport(); } std::ostringstream log; log << header << '\n'; log << std::right << std::setw(16) << "Residuals : "; log << std::left << summary.num_residuals_reduced << '\n'; log << std::right << std::setw(16) << "Parameters : "; log << std::left << summary.num_effective_parameters_reduced << '\n'; log << std::right << std::setw(16) << "Iterations : "; log << std::left << summary.num_successful_steps + summary.num_unsuccessful_steps << '\n'; log << std::right << std::setw(16) << "Time : "; log << std::left << summary.total_time_in_seconds << " [s]\n"; log << std::right << std::setw(16) << "Initial cost : "; log << std::right << std::setprecision(6) << std::sqrt(summary.initial_cost / summary.num_residuals_reduced) << " [px]\n"; log << std::right << std::setw(16) << "Final cost : "; log << std::right << std::setprecision(6) << std::sqrt(summary.final_cost / summary.num_residuals_reduced) << " [px]\n"; log << std::right << std::setw(16) << "Termination : "; log << std::right << ceres::TerminationTypeToString(summary.termination_type) << "\n\n"; LOG(INFO) << log.str(); } } // namespace colmap colmap-4.2.0/src/colmap/estimators/bundle_adjustment_ceres.h000066400000000000000000000127551524536416500243120ustar00rootroot00000000000000// Copyright (c), ETH Zurich and UNC Chapel Hill. // All rights reserved. // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are met: // // * Redistributions of source code must retain the above copyright // notice, this list of conditions and the following disclaimer. // // * Redistributions in binary form must reproduce the above copyright // notice, this list of conditions and the following disclaimer in the // documentation and/or other materials provided with the distribution. // // * Neither the name of ETH Zurich and UNC Chapel Hill nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE // ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE // LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR // CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF // SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS // INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN // CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) // ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE // POSSIBILITY OF SUCH DAMAGE. #pragma once #include "colmap/estimators/bundle_adjustment.h" #include "colmap/math/math.h" #include namespace colmap { // Ceres-specific bundle adjustment options. struct CeresBundleAdjustmentOptions { // Loss function types: Trivial (non-robust) and robust loss functions. enum class LossFunctionType { TRIVIAL, SOFT_L1, CAUCHY, HUBER }; LossFunctionType loss_function_type = LossFunctionType::TRIVIAL; // Scaling factor determines residual at which robustification takes place. double loss_function_scale = 1.0; // Whether to use Ceres' CUDA linear algebra library, if available. bool use_gpu = false; std::string gpu_index = "-1"; // Ceres-Solver options. ceres::Solver::Options solver_options; // Heuristic threshold to switch from CPU to GPU based solvers. // Typically, the GPU is faster for large problems but the overhead of // transferring memory from the CPU to the GPU leads to better CPU performance // for small problems. This depends on the specific problem and hardware. int min_num_images_gpu_solver = 50; // Heuristic threshold on the minimum number of residuals to enable // multi-threading. Note that single-threaded is typically better for small // bundle adjustment problems due to the overhead of threading. int min_num_residuals_for_cpu_multi_threading = 50000; // Heuristic thresholds to switch between direct, sparse, and iterative // solvers. These thresholds may not be optimal for all types of problems. int max_num_images_direct_dense_cpu_solver = 50; int max_num_images_direct_sparse_cpu_solver = 1000; int max_num_images_direct_dense_gpu_solver = 200; int max_num_images_direct_sparse_gpu_solver = 4000; // Whether to automatically select solver type based on problem size. // When false, uses the linear_solver_type and preconditioner_type // from solver_options directly. bool auto_select_solver_type = true; CeresBundleAdjustmentOptions(); // Create loss function for given options. std::unique_ptr CreateLossFunction() const; // Create options tailored for given bundle adjustment config and problem. ceres::Solver::Options CreateSolverOptions( const BundleAdjustmentConfig& config, const ceres::Problem& problem) const; bool Check() const; }; // Ceres-specific bundle adjustment summary with access to full solver details. struct CeresBundleAdjustmentSummary : public BundleAdjustmentSummary { ceres::Solver::Summary ceres_summary; std::string BriefReport() const override; static std::shared_ptr Create( ceres::Solver::Summary ceres_summary); }; // Ceres-specific pose prior bundle adjustment options. struct CeresPosePriorBundleAdjustmentOptions { // Loss function for prior position loss. CeresBundleAdjustmentOptions::LossFunctionType prior_position_loss_function_type = CeresBundleAdjustmentOptions::LossFunctionType::TRIVIAL; // Threshold on the residual for the robust loss. double prior_position_loss_scale = std::sqrt(kChiSquare95ThreeDof); bool Check() const; }; // Ceres-specific bundle adjuster with access to the underlying problem. class CeresBundleAdjuster : public BundleAdjuster { public: using BundleAdjuster::BundleAdjuster; virtual std::shared_ptr& Problem() = 0; }; std::unique_ptr CreateDefaultCeresBundleAdjuster( const BundleAdjustmentOptions& options, const BundleAdjustmentConfig& config, Reconstruction& reconstruction); std::unique_ptr CreatePosePriorCeresBundleAdjuster( const BundleAdjustmentOptions& options, const PosePriorBundleAdjustmentOptions& prior_options, const BundleAdjustmentConfig& config, std::vector pose_priors, Reconstruction& reconstruction); void PrintSolverSummary(const ceres::Solver::Summary& summary, const std::string& header); } // namespace colmap colmap-4.2.0/src/colmap/estimators/bundle_adjustment_ceres_test.cc000066400000000000000000001414021524536416500254770ustar00rootroot00000000000000// Copyright (c), ETH Zurich and UNC Chapel Hill. // All rights reserved. // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are met: // // * Redistributions of source code must retain the above copyright // notice, this list of conditions and the following disclaimer. // // * Redistributions in binary form must reproduce the above copyright // notice, this list of conditions and the following disclaimer in the // documentation and/or other materials provided with the distribution. // // * Neither the name of ETH Zurich and UNC Chapel Hill nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE // ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE // LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR // CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF // SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS // INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN // CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) // ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE // POSSIBILITY OF SUCH DAMAGE. #include "colmap/estimators/bundle_adjustment_ceres.h" #include "colmap/geometry/rigid3_matchers.h" #include "colmap/scene/reconstruction_matchers.h" #include "colmap/scene/synthetic.h" #include "colmap/sensor/models.h" #include "colmap/util/cuda.h" #include "colmap/util/testing.h" #include // Due to pose normalization operations, constant variables may not be perfectly // fixed during bundle adjustment. constexpr double kConstantPoseVarEps = 1e-9; #define CheckVariableCamera(camera, orig_camera) \ { \ const size_t focal_length_idx = \ SimpleRadialCameraModel::focal_length_idxs[0]; \ const size_t extra_param_idx = \ SimpleRadialCameraModel::extra_params_idxs[0]; \ EXPECT_NE((camera).params[focal_length_idx], \ (orig_camera).params[focal_length_idx]); \ EXPECT_NE((camera).params[extra_param_idx], \ (orig_camera).params[extra_param_idx]); \ } #define CheckConstantCamera(camera, orig_camera) \ { \ const size_t focal_length_idx = \ SimpleRadialCameraModel::focal_length_idxs[0]; \ const size_t extra_param_idx = \ SimpleRadialCameraModel::extra_params_idxs[0]; \ EXPECT_EQ((camera).params[focal_length_idx], \ (orig_camera).params[focal_length_idx]); \ EXPECT_EQ((camera).params[extra_param_idx], \ (orig_camera).params[extra_param_idx]); \ } #define CheckVariableCamFromWorld(image, orig_image) \ { \ EXPECT_THAT((image).CamFromWorld(), \ testing::Not(Rigid3dEq((orig_image).CamFromWorld()))); \ } #define CheckConstantCamFromWorld(image, orig_image) \ { \ EXPECT_THAT((image).CamFromWorld(), \ Rigid3dNear((orig_image).CamFromWorld(), \ kConstantPoseVarEps, \ kConstantPoseVarEps)); \ } #define CheckConstantCamFromWorldTranslationCoord(image, orig_image) \ { \ size_t num_constant_coords = 0; \ for (int i = 0; i < 3; ++i) { \ if (std::abs((image).CamFromWorld().translation()(i) - \ (orig_image).CamFromWorld().translation()(i)) < \ kConstantPoseVarEps) { \ ++num_constant_coords; \ } \ } \ EXPECT_EQ(num_constant_coords, 1); \ } #define CheckVariablePoint(point, orig_point) \ { \ EXPECT_NE((point).xyz, (orig_point).xyz); \ } #define CheckConstantPoint(point, orig_point) \ { \ EXPECT_EQ((point).xyz, (orig_point).xyz); \ } namespace colmap { namespace { // Helper to get Problem from BundleAdjuster (requires casting to Ceres impl) inline ceres::Problem& GetCeresProblem(BundleAdjuster& bundle_adjuster) { auto* ceres_ba = dynamic_cast(&bundle_adjuster); CHECK_NOTNULL(ceres_ba); return *ceres_ba->Problem(); } // Helper to get ceres::Solver::Summary from base summary inline const ceres::Solver::Summary& GetCeresSummary( const BundleAdjustmentSummary* summary) { auto* ceres_summary = dynamic_cast(summary); CHECK_NOTNULL(ceres_summary); return ceres_summary->ceres_summary; } #ifdef COLMAP_CUDA_ENABLED TEST(CeresBundleAdjustmentOptions, FallsBackToCpuWithoutCudaDevice) { if (GetNumCudaDevices() > 0) { GTEST_SKIP() << "CUDA GPU is available"; } CeresBundleAdjustmentOptions options; options.use_gpu = true; options.min_num_images_gpu_solver = 0; const ceres::Solver::Options solver_options = options.CreateSolverOptions(BundleAdjustmentConfig(), ceres::Problem()); EXPECT_EQ(solver_options.dense_linear_algebra_library_type, options.solver_options.dense_linear_algebra_library_type); EXPECT_EQ(solver_options.sparse_linear_algebra_library_type, options.solver_options.sparse_linear_algebra_library_type); } #endif // COLMAP_CUDA_ENABLED TEST(DefaultBundleAdjuster, NominalMultiCameraRig) { Reconstruction gt_reconstruction; SyntheticDatasetOptions synthetic_dataset_options; synthetic_dataset_options.num_rigs = 2; synthetic_dataset_options.num_cameras_per_rig = 3; synthetic_dataset_options.num_frames_per_rig = 5; synthetic_dataset_options.num_points3D = 200; SynthesizeDataset(synthetic_dataset_options, >_reconstruction); Reconstruction reconstruction = gt_reconstruction; SyntheticNoiseOptions synthetic_noise_options; synthetic_noise_options.point2D_stddev = 0.5; synthetic_noise_options.point3D_stddev = 0.1; synthetic_noise_options.rig_from_world_rotation_stddev = 0.5; synthetic_noise_options.rig_from_world_translation_stddev = 0.1; SynthesizeNoise(synthetic_noise_options, &reconstruction); BundleAdjustmentConfig config; for (const image_t image_id : reconstruction.RegImageIds()) { config.AddImage(image_id); } config.FixGauge(BundleAdjustmentGauge::TWO_CAMS_FROM_WORLD); BundleAdjustmentOptions options; std::unique_ptr bundle_adjuster = CreateDefaultCeresBundleAdjuster(options, config, reconstruction); const auto summary = bundle_adjuster->Solve(); ASSERT_NE(summary->termination_type, BundleAdjustmentTerminationType::FAILURE); EXPECT_THAT(gt_reconstruction, ReconstructionNear(reconstruction, /*max_rotation_error_deg=*/0.1, /*max_proj_center_error=*/0.1, /*max_scale_error=*/std::nullopt, /*num_obs_tolerance=*/0.0)); } TEST(DefaultBundleAdjuster, Cancellation) { Reconstruction reconstruction; SyntheticDatasetOptions synthetic_dataset_options; synthetic_dataset_options.num_rigs = 1; synthetic_dataset_options.num_cameras_per_rig = 1; synthetic_dataset_options.num_frames_per_rig = 3; synthetic_dataset_options.num_points3D = 20; SynthesizeDataset(synthetic_dataset_options, &reconstruction); BundleAdjustmentConfig config; for (const image_t image_id : reconstruction.RegImageIds()) { config.AddImage(image_id); } config.FixGauge(BundleAdjustmentGauge::TWO_CAMS_FROM_WORLD); int num_checks = 0; BundleAdjustmentOptions options; options.check_if_stopped = [&num_checks]() { ++num_checks; return true; }; const auto summary = CreateDefaultCeresBundleAdjuster(options, config, reconstruction) ->Solve(); EXPECT_EQ(num_checks, 1); EXPECT_EQ(summary->termination_type, BundleAdjustmentTerminationType::USER_SUCCESS); } TEST(DefaultBundleAdjuster, ThreeViewSpherical) { Reconstruction reconstruction; SyntheticDatasetOptions synthetic_dataset_options; synthetic_dataset_options.num_rigs = 3; synthetic_dataset_options.num_cameras_per_rig = 1; synthetic_dataset_options.num_frames_per_rig = 1; synthetic_dataset_options.num_points3D = 100; synthetic_dataset_options.camera_model_id = EquirectangularCameraModel::model_id; synthetic_dataset_options.camera_width = 1000; synthetic_dataset_options.camera_height = 500; synthetic_dataset_options.camera_params = {1000, 500}; SynthesizeDataset(synthetic_dataset_options, &reconstruction); ASSERT_TRUE(reconstruction.Camera(1).IsSpherical()); SyntheticNoiseOptions synthetic_noise_options; synthetic_noise_options.point2D_stddev = 1; SynthesizeNoise(synthetic_noise_options, &reconstruction); const Reconstruction orig_reconstruction = reconstruction; BundleAdjustmentConfig config; config.AddImage(1); config.AddImage(2); config.AddImage(3); config.FixGauge(BundleAdjustmentGauge::TWO_CAMS_FROM_WORLD); BundleAdjustmentOptions options; std::unique_ptr bundle_adjuster = CreateDefaultCeresBundleAdjuster(options, config, reconstruction); const auto summary = bundle_adjuster->Solve(); ASSERT_NE(summary->termination_type, BundleAdjustmentTerminationType::FAILURE); EXPECT_EQ(config.NumResiduals(reconstruction), GetCeresProblem(*bundle_adjuster).NumResiduals()); // The spherical model has no focal length; its (w, h) parameters are held // constant during bundle adjustment. for (const auto& [camera_id, camera] : reconstruction.Cameras()) { EXPECT_EQ(camera.params, orig_reconstruction.Camera(camera_id).params); } CheckConstantCamFromWorld(reconstruction.Image(1), orig_reconstruction.Image(1)); CheckConstantCamFromWorldTranslationCoord(reconstruction.Image(2), orig_reconstruction.Image(2)); CheckVariableCamFromWorld(reconstruction.Image(3), orig_reconstruction.Image(3)); for (const auto& [point3D_id, point3D] : reconstruction.Points3D()) { CheckVariablePoint(point3D, orig_reconstruction.Point3D(point3D_id)); } } TEST(DefaultBundleAdjuster, TwoViewRig) { Reconstruction reconstruction; SyntheticDatasetOptions synthetic_dataset_options; synthetic_dataset_options.num_rigs = 1; synthetic_dataset_options.num_cameras_per_rig = 2; synthetic_dataset_options.num_frames_per_rig = 2; synthetic_dataset_options.num_points3D = 100; SynthesizeDataset(synthetic_dataset_options, &reconstruction); SyntheticNoiseOptions synthetic_noise_options; synthetic_noise_options.point2D_stddev = 1; SynthesizeNoise(synthetic_noise_options, &reconstruction); const Reconstruction orig_reconstruction = reconstruction; BundleAdjustmentConfig config; for (const image_t image_id : reconstruction.RegImageIds()) { config.AddImage(image_id); } config.FixGauge(BundleAdjustmentGauge::THREE_POINTS); BundleAdjustmentOptions options; std::unique_ptr bundle_adjuster = CreateDefaultCeresBundleAdjuster(options, config, reconstruction); const auto summary = bundle_adjuster->Solve(); ASSERT_NE(summary->termination_type, BundleAdjustmentTerminationType::FAILURE); EXPECT_EQ(config.NumResiduals(reconstruction), GetCeresProblem(*bundle_adjuster).NumResiduals()); // 100 points, 4 images, 2 residuals per point per image EXPECT_EQ(GetCeresSummary(summary.get()).num_residuals_reduced, 800); // 97 x 3 point parameters (3 fixed for gauge) // + 2 x 6 rig_from_world parameters // + 1 x 6 sensor_from_rig parameters // + 2 x 2 camera parameters EXPECT_EQ(GetCeresSummary(summary.get()).num_effective_parameters_reduced, 313); CheckVariableCamera(reconstruction.Camera(1), orig_reconstruction.Camera(1)); CheckVariableCamFromWorld(reconstruction.Image(1), orig_reconstruction.Image(1)); CheckVariableCamera(reconstruction.Camera(2), orig_reconstruction.Camera(2)); CheckVariableCamFromWorld(reconstruction.Image(2), orig_reconstruction.Image(2)); size_t num_variable_points = 0; for (const auto& [point3D_id, point3D] : reconstruction.Points3D()) { if (point3D != orig_reconstruction.Point3D(point3D_id)) { ++num_variable_points; } } EXPECT_EQ(num_variable_points, 97); } TEST(DefaultBundleAdjuster, ManyViewRig) { Reconstruction reconstruction; SyntheticDatasetOptions synthetic_dataset_options; synthetic_dataset_options.num_rigs = 2; synthetic_dataset_options.num_cameras_per_rig = 3; synthetic_dataset_options.num_frames_per_rig = 5; synthetic_dataset_options.num_points3D = 100; SynthesizeDataset(synthetic_dataset_options, &reconstruction); SyntheticNoiseOptions synthetic_noise_options; synthetic_noise_options.point2D_stddev = 1; SynthesizeNoise(synthetic_noise_options, &reconstruction); const Reconstruction orig_reconstruction = reconstruction; BundleAdjustmentConfig config; for (const image_t image_id : reconstruction.RegImageIds()) { config.AddImage(image_id); } config.FixGauge(BundleAdjustmentGauge::THREE_POINTS); BundleAdjustmentOptions options; std::unique_ptr bundle_adjuster = CreateDefaultCeresBundleAdjuster(options, config, reconstruction); const auto summary = bundle_adjuster->Solve(); ASSERT_NE(summary->termination_type, BundleAdjustmentTerminationType::FAILURE); EXPECT_EQ(config.NumResiduals(reconstruction), GetCeresProblem(*bundle_adjuster).NumResiduals()); // 100 points, 30 images, 2 residuals per point per image EXPECT_EQ(GetCeresSummary(summary.get()).num_residuals_reduced, 6000); // 97 x 3 point parameters (3 fixed for gauge) // + 10 x 6 rig_from_world parameters // + 4 x 6 sensor_from_rig parameters // + 6 x 2 camera parameters EXPECT_EQ(GetCeresSummary(summary.get()).num_effective_parameters_reduced, 387); for (const auto& [camera_id, camera] : reconstruction.Cameras()) { CheckVariableCamera(camera, orig_reconstruction.Camera(camera_id)); } for (const image_t image_id : reconstruction.RegImageIds()) { CheckVariableCamFromWorld(reconstruction.Image(image_id), orig_reconstruction.Image(image_id)); } size_t num_variable_points = 0; for (const auto& [point3D_id, point3D] : reconstruction.Points3D()) { if (point3D != orig_reconstruction.Point3D(point3D_id)) { ++num_variable_points; } } EXPECT_EQ(num_variable_points, 97); } TEST(DefaultBundleAdjuster, ManyViewRigConstantSensorFromRig) { Reconstruction reconstruction; SyntheticDatasetOptions synthetic_dataset_options; synthetic_dataset_options.num_rigs = 2; synthetic_dataset_options.num_cameras_per_rig = 3; synthetic_dataset_options.num_frames_per_rig = 5; synthetic_dataset_options.num_points3D = 100; SynthesizeDataset(synthetic_dataset_options, &reconstruction); SyntheticNoiseOptions synthetic_noise_options; synthetic_noise_options.point2D_stddev = 1; SynthesizeNoise(synthetic_noise_options, &reconstruction); const Reconstruction orig_reconstruction = reconstruction; BundleAdjustmentConfig config; for (const image_t image_id : reconstruction.RegImageIds()) { config.AddImage(image_id); } config.SetConstantSensorFromRigPose(reconstruction.Camera(2).SensorId()); config.FixGauge(BundleAdjustmentGauge::THREE_POINTS); BundleAdjustmentOptions options; std::unique_ptr bundle_adjuster = CreateDefaultCeresBundleAdjuster(options, config, reconstruction); const auto summary = bundle_adjuster->Solve(); ASSERT_NE(summary->termination_type, BundleAdjustmentTerminationType::FAILURE); EXPECT_EQ(config.NumResiduals(reconstruction), GetCeresProblem(*bundle_adjuster).NumResiduals()); // 100 points, 30 images, 2 residuals per point per image EXPECT_EQ(GetCeresSummary(summary.get()).num_residuals_reduced, 6000); // 97 x 3 point parameters (3 fixed for gauge) // + 10 x 6 rig_from_world parameters // + 3 x 6 sensor_from_rig parameters // + 6 x 2 camera parameters EXPECT_EQ(GetCeresSummary(summary.get()).num_effective_parameters_reduced, 381); for (const auto& [camera_id, camera] : reconstruction.Cameras()) { CheckVariableCamera(camera, orig_reconstruction.Camera(camera_id)); } for (const image_t image_id : reconstruction.RegImageIds()) { CheckVariableCamFromWorld(reconstruction.Image(image_id), orig_reconstruction.Image(image_id)); } size_t num_variable_points = 0; for (const auto& [point3D_id, point3D] : reconstruction.Points3D()) { if (point3D != orig_reconstruction.Point3D(point3D_id)) { ++num_variable_points; } } EXPECT_EQ(num_variable_points, 97); } TEST(DefaultBundleAdjuster, ManyViewRigConstantRigFromWorld) { Reconstruction reconstruction; SyntheticDatasetOptions synthetic_dataset_options; synthetic_dataset_options.num_rigs = 2; synthetic_dataset_options.num_cameras_per_rig = 3; synthetic_dataset_options.num_frames_per_rig = 5; synthetic_dataset_options.num_points3D = 100; SynthesizeDataset(synthetic_dataset_options, &reconstruction); SyntheticNoiseOptions synthetic_noise_options; synthetic_noise_options.point2D_stddev = 1; SynthesizeNoise(synthetic_noise_options, &reconstruction); const Reconstruction orig_reconstruction = reconstruction; BundleAdjustmentConfig config; for (const image_t image_id : reconstruction.RegImageIds()) { config.AddImage(image_id); } const frame_t constant_frame_id = 1; config.SetConstantRigFromWorldPose(constant_frame_id); config.FixGauge(BundleAdjustmentGauge::THREE_POINTS); BundleAdjustmentOptions options; std::unique_ptr bundle_adjuster = CreateDefaultCeresBundleAdjuster(options, config, reconstruction); const auto summary = bundle_adjuster->Solve(); ASSERT_NE(summary->termination_type, BundleAdjustmentTerminationType::FAILURE); EXPECT_EQ(config.NumResiduals(reconstruction), GetCeresProblem(*bundle_adjuster).NumResiduals()); // 100 points, 30 images, 2 residuals per point per image EXPECT_EQ(GetCeresSummary(summary.get()).num_residuals_reduced, 6000); // 97 x 3 point parameters (3 fixed for gauge) // + 9 x 6 rig_from_world parameters // + 4 x 6 sensor_from_rig parameters // + 6 x 2 camera parameters EXPECT_EQ(GetCeresSummary(summary.get()).num_effective_parameters_reduced, 381); for (const auto& [camera_id, camera] : reconstruction.Cameras()) { CheckVariableCamera(camera, orig_reconstruction.Camera(camera_id)); } for (const image_t image_id : reconstruction.RegImageIds()) { const auto& image = reconstruction.Image(image_id); if (image.FrameId() == constant_frame_id && image.FramePtr()->RigPtr()->IsRefSensor( image.CameraPtr()->SensorId())) { CheckConstantCamFromWorld(image, orig_reconstruction.Image(image_id)); } else { CheckVariableCamFromWorld(image, orig_reconstruction.Image(image_id)); } } size_t num_variable_points = 0; for (const auto& [point3D_id, point3D] : reconstruction.Points3D()) { if (point3D != orig_reconstruction.Point3D(point3D_id)) { ++num_variable_points; } } EXPECT_EQ(num_variable_points, 97); } TEST(DefaultBundleAdjuster, ConstantRigFromWorldRotation) { Reconstruction reconstruction; SyntheticDatasetOptions synthetic_dataset_options; synthetic_dataset_options.num_rigs = 3; synthetic_dataset_options.num_cameras_per_rig = 1; synthetic_dataset_options.num_frames_per_rig = 1; synthetic_dataset_options.num_points3D = 100; SynthesizeDataset(synthetic_dataset_options, &reconstruction); SyntheticNoiseOptions synthetic_noise_options; synthetic_noise_options.point2D_stddev = 1; SynthesizeNoise(synthetic_noise_options, &reconstruction); const Reconstruction orig_reconstruction = reconstruction; BundleAdjustmentConfig config; config.AddImage(1); config.AddImage(2); config.AddImage(3); config.FixGauge(BundleAdjustmentGauge::TWO_CAMS_FROM_WORLD); BundleAdjustmentOptions options; options.constant_rig_from_world_rotation = true; std::unique_ptr bundle_adjuster = CreateDefaultCeresBundleAdjuster(options, config, reconstruction); const auto summary = bundle_adjuster->Solve(); ASSERT_NE(summary->termination_type, BundleAdjustmentTerminationType::FAILURE); EXPECT_EQ(config.NumResiduals(reconstruction), GetCeresProblem(*bundle_adjuster).NumResiduals()); // 100 points, 3 images, 2 residuals per point per image EXPECT_EQ(GetCeresSummary(summary.get()).num_residuals_reduced, 600); // 100 x 3 point parameters // + 2 translation parameters (second image, one coord fixed for gauge) // + 3 translation parameters (third image) // + 3 x 2 camera parameters EXPECT_EQ(GetCeresSummary(summary.get()).num_effective_parameters_reduced, 311); // Check rotations are constant for all images for (const image_t image_id : reconstruction.RegImageIds()) { const auto& image = reconstruction.Image(image_id); const auto& orig_image = orig_reconstruction.Image(image_id); // Rotation should be nearly unchanged (use angular distance) EXPECT_LE(image.CamFromWorld().rotation().angularDistance( orig_image.CamFromWorld().rotation()), kConstantPoseVarEps); } // Check translations are variable (except for gauge-fixed parts) // At least one image should have changed translation bool has_variable_translation = false; for (const image_t image_id : reconstruction.RegImageIds()) { const auto& image = reconstruction.Image(image_id); const auto& orig_image = orig_reconstruction.Image(image_id); if ((image.CamFromWorld().translation() - orig_image.CamFromWorld().translation()) .norm() > kConstantPoseVarEps) { has_variable_translation = true; break; } } EXPECT_TRUE(has_variable_translation); for (const auto& [point3D_id, point3D] : reconstruction.Points3D()) { CheckVariablePoint(point3D, orig_reconstruction.Point3D(point3D_id)); } } TEST(DefaultBundleAdjuster, PartiallyContainedTracksForceToOptimizePoint) { Reconstruction reconstruction; SyntheticDatasetOptions synthetic_dataset_options; synthetic_dataset_options.num_rigs = 3; synthetic_dataset_options.num_cameras_per_rig = 1; synthetic_dataset_options.num_frames_per_rig = 1; synthetic_dataset_options.num_points3D = 100; synthetic_dataset_options.num_points2D_without_point3D = 0; SynthesizeDataset(synthetic_dataset_options, &reconstruction); SyntheticNoiseOptions synthetic_noise_options; synthetic_noise_options.point2D_stddev = 1; SynthesizeNoise(synthetic_noise_options, &reconstruction); const point3D_t variable_point3D_id = reconstruction.Image(3).Point2D(0).point3D_id; const point3D_t add_variable_point3D_id = reconstruction.Image(3).Point2D(1).point3D_id; const point3D_t add_constant_point3D_id = reconstruction.Image(3).Point2D(2).point3D_id; reconstruction.DeleteObservation(3, 0); const auto orig_reconstruction = reconstruction; BundleAdjustmentConfig config; config.AddImage(1); config.AddImage(2); config.SetConstantRigFromWorldPose(1); config.SetConstantRigFromWorldPose(2); config.AddVariablePoint(add_variable_point3D_id); config.AddConstantPoint(add_constant_point3D_id); BundleAdjustmentOptions options; std::unique_ptr bundle_adjuster = CreateDefaultCeresBundleAdjuster(options, config, reconstruction); const auto summary = bundle_adjuster->Solve(); ASSERT_NE(summary->termination_type, BundleAdjustmentTerminationType::FAILURE); EXPECT_EQ(config.NumResiduals(reconstruction), GetCeresProblem(*bundle_adjuster).NumResiduals()); // 100 points, 2 images, 2 residuals per point per image // + 2 residuals in 3rd image for added variable 3D point // (added constant point does not add residuals since the image/camera // is also constant). EXPECT_EQ(GetCeresSummary(summary.get()).num_residuals_reduced, 402); // 2 x 3 point parameters // 2 x 2 camera parameters EXPECT_EQ(GetCeresSummary(summary.get()).num_effective_parameters_reduced, 10); CheckVariableCamera(reconstruction.Camera(1), orig_reconstruction.Camera(1)); CheckConstantCamFromWorld(reconstruction.Image(1), orig_reconstruction.Image(1)); CheckVariableCamera(reconstruction.Camera(2), orig_reconstruction.Camera(2)); CheckConstantCamFromWorld(reconstruction.Image(2), orig_reconstruction.Image(2)); CheckConstantCamera(reconstruction.Camera(3), orig_reconstruction.Camera(3)); CheckConstantCamFromWorld(reconstruction.Image(3), orig_reconstruction.Image(3)); for (const auto& [point3D_id, point3D] : reconstruction.Points3D()) { if (point3D_id == variable_point3D_id || point3D_id == add_variable_point3D_id) { CheckVariablePoint(point3D, orig_reconstruction.Point3D(point3D_id)); } else { CheckConstantPoint(point3D, orig_reconstruction.Point3D(point3D_id)); } } } TEST(DefaultBundleAdjuster, ConstantFocalLength) { Reconstruction reconstruction; SyntheticDatasetOptions synthetic_dataset_options; synthetic_dataset_options.num_rigs = 2; synthetic_dataset_options.num_cameras_per_rig = 1; synthetic_dataset_options.num_frames_per_rig = 1; synthetic_dataset_options.num_points3D = 100; SynthesizeDataset(synthetic_dataset_options, &reconstruction); SyntheticNoiseOptions synthetic_noise_options; synthetic_noise_options.point2D_stddev = 1; SynthesizeNoise(synthetic_noise_options, &reconstruction); const auto orig_reconstruction = reconstruction; BundleAdjustmentConfig config; config.AddImage(1); config.AddImage(2); config.FixGauge(BundleAdjustmentGauge::TWO_CAMS_FROM_WORLD); BundleAdjustmentOptions options; options.refine_focal_length = false; std::unique_ptr bundle_adjuster = CreateDefaultCeresBundleAdjuster(options, config, reconstruction); const auto summary = bundle_adjuster->Solve(); ASSERT_NE(summary->termination_type, BundleAdjustmentTerminationType::FAILURE); EXPECT_EQ(config.NumResiduals(reconstruction), GetCeresProblem(*bundle_adjuster).NumResiduals()); // 100 points, 3 images, 2 residuals per point per image EXPECT_EQ(GetCeresSummary(summary.get()).num_residuals_reduced, 400); // 100 x 3 point parameters // + 5 rig_from_world parameters (pose of second image) // + 2 camera parameters EXPECT_EQ(GetCeresSummary(summary.get()).num_effective_parameters_reduced, 307); CheckConstantCamFromWorld(reconstruction.Image(1), orig_reconstruction.Image(1)); CheckConstantCamFromWorldTranslationCoord(reconstruction.Image(2), orig_reconstruction.Image(2)); const size_t focal_length_idx = SimpleRadialCameraModel::focal_length_idxs[0]; const size_t extra_param_idx = SimpleRadialCameraModel::extra_params_idxs[0]; const auto& camera0 = reconstruction.Camera(1); const auto& orig_camera0 = orig_reconstruction.Camera(1); EXPECT_TRUE(camera0.params[focal_length_idx] == orig_camera0.params[focal_length_idx]); EXPECT_TRUE(camera0.params[extra_param_idx] != orig_camera0.params[extra_param_idx]); const auto& camera1 = reconstruction.Camera(2); const auto& orig_camera1 = orig_reconstruction.Camera(2); EXPECT_TRUE(camera1.params[focal_length_idx] == orig_camera1.params[focal_length_idx]); EXPECT_TRUE(camera1.params[extra_param_idx] != orig_camera1.params[extra_param_idx]); for (const auto& [point3D_id, point3D] : reconstruction.Points3D()) { CheckVariablePoint(point3D, orig_reconstruction.Point3D(point3D_id)); } } TEST(DefaultBundleAdjuster, ConstantExtraParam) { Reconstruction reconstruction; SyntheticDatasetOptions synthetic_dataset_options; synthetic_dataset_options.num_rigs = 2; synthetic_dataset_options.num_cameras_per_rig = 1; synthetic_dataset_options.num_frames_per_rig = 1; synthetic_dataset_options.num_points3D = 100; SynthesizeDataset(synthetic_dataset_options, &reconstruction); SyntheticNoiseOptions synthetic_noise_options; synthetic_noise_options.point2D_stddev = 1; SynthesizeNoise(synthetic_noise_options, &reconstruction); const auto orig_reconstruction = reconstruction; BundleAdjustmentConfig config; config.AddImage(1); config.AddImage(2); config.FixGauge(BundleAdjustmentGauge::TWO_CAMS_FROM_WORLD); BundleAdjustmentOptions options; options.refine_extra_params = false; std::unique_ptr bundle_adjuster = CreateDefaultCeresBundleAdjuster(options, config, reconstruction); const auto summary = bundle_adjuster->Solve(); ASSERT_NE(summary->termination_type, BundleAdjustmentTerminationType::FAILURE); EXPECT_EQ(config.NumResiduals(reconstruction), GetCeresProblem(*bundle_adjuster).NumResiduals()); // 100 points, 3 images, 2 residuals per point per image EXPECT_EQ(GetCeresSummary(summary.get()).num_residuals_reduced, 400); // 100 x 3 point parameters // + 5 rig_from_world parameters (pose of second image) // + 2 camera parameters EXPECT_EQ(GetCeresSummary(summary.get()).num_effective_parameters_reduced, 307); CheckConstantCamFromWorld(reconstruction.Image(1), orig_reconstruction.Image(1)); CheckConstantCamFromWorldTranslationCoord(reconstruction.Image(2), orig_reconstruction.Image(2)); const size_t focal_length_idx = SimpleRadialCameraModel::focal_length_idxs[0]; const size_t extra_param_idx = SimpleRadialCameraModel::extra_params_idxs[0]; const auto& camera0 = reconstruction.Camera(1); const auto& orig_camera0 = orig_reconstruction.Camera(1); EXPECT_TRUE(camera0.params[focal_length_idx] != orig_camera0.params[focal_length_idx]); EXPECT_TRUE(camera0.params[extra_param_idx] == orig_camera0.params[extra_param_idx]); const auto& camera1 = reconstruction.Camera(2); const auto& orig_camera1 = orig_reconstruction.Camera(2); EXPECT_TRUE(camera1.params[focal_length_idx] != orig_camera1.params[focal_length_idx]); EXPECT_TRUE(camera1.params[extra_param_idx] == orig_camera1.params[extra_param_idx]); for (const auto& [point3D_id, point3D] : reconstruction.Points3D()) { CheckVariablePoint(point3D, orig_reconstruction.Point3D(point3D_id)); } } TEST(DefaultBundleAdjuster, FixGaugeWithThreePoints) { Reconstruction reconstruction; SyntheticDatasetOptions synthetic_dataset_options; synthetic_dataset_options.num_rigs = 2; synthetic_dataset_options.num_cameras_per_rig = 1; synthetic_dataset_options.num_frames_per_rig = 1; synthetic_dataset_options.num_points3D = 100; SynthesizeDataset(synthetic_dataset_options, &reconstruction); const Reconstruction orig_reconstruction = reconstruction; BundleAdjustmentConfig config; config.AddImage(1); config.AddImage(2); auto ExpectValidSolve = [&config, &reconstruction]( const int num_effective_parameters_reduced) { const auto summary1 = CreateDefaultCeresBundleAdjuster( BundleAdjustmentOptions(), config, reconstruction) ->Solve(); ASSERT_NE(summary1->termination_type, BundleAdjustmentTerminationType::FAILURE); EXPECT_EQ(GetCeresSummary(summary1.get()).num_effective_parameters_reduced, num_effective_parameters_reduced); }; ExpectValidSolve(316); config.FixGauge(BundleAdjustmentGauge::THREE_POINTS); ExpectValidSolve(307); config.AddConstantPoint(1); ExpectValidSolve(307); config.AddConstantPoint(2); config.AddConstantPoint(3); ExpectValidSolve(307); config.AddConstantPoint(4); ExpectValidSolve(304); } TEST(DefaultBundleAdjuster, FixGaugeWithTwoCamsFromWorld) { Reconstruction reconstruction; SyntheticDatasetOptions synthetic_dataset_options; synthetic_dataset_options.num_rigs = 2; synthetic_dataset_options.num_cameras_per_rig = 2; synthetic_dataset_options.num_frames_per_rig = 1; synthetic_dataset_options.num_points3D = 100; SynthesizeDataset(synthetic_dataset_options, &reconstruction); const Reconstruction orig_reconstruction = reconstruction; BundleAdjustmentOptions options; BundleAdjustmentConfig config; config.AddImage(1); config.AddImage(2); config.AddImage(3); config.AddImage(4); auto ExpectValidSolve = [&options, &config, &reconstruction]( const int num_effective_parameters_reduced) { const auto summary1 = CreateDefaultCeresBundleAdjuster(options, config, reconstruction) ->Solve(); ASSERT_NE(summary1->termination_type, BundleAdjustmentTerminationType::FAILURE); EXPECT_EQ(GetCeresSummary(summary1.get()).num_effective_parameters_reduced, num_effective_parameters_reduced); }; options.refine_rig_from_world = false; ExpectValidSolve(320); options.refine_rig_from_world = true; ExpectValidSolve(332); options.refine_rig_from_world = false; config.FixGauge(BundleAdjustmentGauge::TWO_CAMS_FROM_WORLD); ExpectValidSolve(320); options.refine_rig_from_world = true; ExpectValidSolve(325); config.SetConstantRigFromWorldPose(1); ExpectValidSolve(325); config.SetConstantRigFromWorldPose(2); ExpectValidSolve(320); } TEST(DefaultBundleAdjuster, FixGaugeWithTwoCamsFromWorldFixSensorFromRig) { Reconstruction reconstruction; SyntheticDatasetOptions synthetic_dataset_options; synthetic_dataset_options.num_rigs = 2; synthetic_dataset_options.num_cameras_per_rig = 2; synthetic_dataset_options.num_frames_per_rig = 1; synthetic_dataset_options.num_points3D = 100; SynthesizeDataset(synthetic_dataset_options, &reconstruction); const Reconstruction orig_reconstruction = reconstruction; BundleAdjustmentOptions options; BundleAdjustmentConfig config; config.AddImage(1); config.AddImage(2); config.AddImage(3); config.AddImage(4); auto ExpectValidSolve = [&options, &config, &reconstruction]( const int num_effective_parameters_reduced) { const auto summary1 = CreateDefaultCeresBundleAdjuster(options, config, reconstruction) ->Solve(); ASSERT_NE(summary1->termination_type, BundleAdjustmentTerminationType::FAILURE); EXPECT_EQ(GetCeresSummary(summary1.get()).num_effective_parameters_reduced, num_effective_parameters_reduced); }; options.refine_rig_from_world = false; options.refine_sensor_from_rig = false; ExpectValidSolve(308); options.refine_rig_from_world = true; ExpectValidSolve(320); options.refine_rig_from_world = false; config.FixGauge(BundleAdjustmentGauge::TWO_CAMS_FROM_WORLD); ExpectValidSolve(308); options.refine_rig_from_world = true; ExpectValidSolve(313); config.SetConstantRigFromWorldPose(1); ExpectValidSolve(313); config.SetConstantRigFromWorldPose(2); ExpectValidSolve(308); } TEST(DefaultBundleAdjuster, FixGaugeWithTwoCamsFromWorldNoReferenceSensor) { Reconstruction reconstruction; SyntheticDatasetOptions synthetic_dataset_options; synthetic_dataset_options.num_rigs = 2; synthetic_dataset_options.num_cameras_per_rig = 2; synthetic_dataset_options.num_frames_per_rig = 1; synthetic_dataset_options.num_points3D = 100; SynthesizeDataset(synthetic_dataset_options, &reconstruction); const Reconstruction orig_reconstruction = reconstruction; // Delete observations from the two reference images. THROW_CHECK(reconstruction.Image(1).IsRefInFrame()); THROW_CHECK(reconstruction.Image(3).IsRefInFrame()); for (point2D_t i = 0; i < reconstruction.Image(1).NumPoints2D(); ++i) { if (reconstruction.Image(1).Point2D(i).HasPoint3D()) { reconstruction.DeleteObservation(1, i); } } for (point2D_t i = 0; i < reconstruction.Image(3).NumPoints2D(); ++i) { if (reconstruction.Image(3).Point2D(i).HasPoint3D()) { reconstruction.DeleteObservation(3, i); } } // Only add two non-reference images. BundleAdjustmentOptions options; BundleAdjustmentConfig config; config.AddImage(2); config.AddImage(4); auto ExpectValidSolve = [&options, &config, &reconstruction]( const int num_effective_parameters_reduced) { const auto summary1 = CreateDefaultCeresBundleAdjuster(options, config, reconstruction) ->Solve(); THROW_CHECK_NE(summary1->termination_type, BundleAdjustmentTerminationType::FAILURE); THROW_CHECK_EQ( GetCeresSummary(summary1.get()).num_effective_parameters_reduced, num_effective_parameters_reduced); }; // refine_sensor_from_rig should have no effect when there are no reference // sensors options.refine_rig_from_world = true; options.refine_sensor_from_rig = true; ExpectValidSolve(316); options.refine_rig_from_world = false; ExpectValidSolve(304); options.refine_rig_from_world = true; ExpectValidSolve(316); options.refine_rig_from_world = false; config.FixGauge(BundleAdjustmentGauge::TWO_CAMS_FROM_WORLD); ExpectValidSolve(304); options.refine_sensor_from_rig = false; ExpectValidSolve(304); options.refine_rig_from_world = true; ExpectValidSolve(309); config.SetConstantRigFromWorldPose(1); ExpectValidSolve(309); options.refine_rig_from_world = false; ExpectValidSolve(304); config.SetConstantRigFromWorldPose(2); options.refine_rig_from_world = true; ExpectValidSolve(304); options.refine_rig_from_world = false; ExpectValidSolve(304); } TEST(DefaultBundleAdjuster, FixGaugeWithTwoCamsFromWorldFallback) { Reconstruction reconstruction; SyntheticDatasetOptions synthetic_dataset_options; synthetic_dataset_options.num_rigs = 1; synthetic_dataset_options.num_cameras_per_rig = 2; synthetic_dataset_options.num_frames_per_rig = 1; synthetic_dataset_options.num_points3D = 100; SynthesizeDataset(synthetic_dataset_options, &reconstruction); const Reconstruction orig_reconstruction = reconstruction; BundleAdjustmentOptions options; BundleAdjustmentConfig config; config.AddImage(1); config.AddImage(2); // The current implementation needs two reference cameras in different frames // to fix the gauge. If there are none, it falls back to fixing the gauge with // three points. config.FixGauge(BundleAdjustmentGauge::TWO_CAMS_FROM_WORLD); const auto summary = CreateDefaultCeresBundleAdjuster(options, config, reconstruction) ->Solve(); ASSERT_NE(summary->termination_type, BundleAdjustmentTerminationType::FAILURE); EXPECT_EQ(GetCeresSummary(summary.get()).num_effective_parameters, 316); EXPECT_EQ(GetCeresSummary(summary.get()).num_effective_parameters_reduced, 307); } TEST(PosePriorBundleAdjuster, AlignmentRobustToOutliers) { Reconstruction gt_reconstruction; SyntheticDatasetOptions synthetic_options; synthetic_options.num_rigs = 1; synthetic_options.num_cameras_per_rig = 1; synthetic_options.num_frames_per_rig = 7; synthetic_options.num_points3D = 50; synthetic_options.prior_position = true; const auto database_path = CreateTestDir() / "database.db"; auto database = Database::Open(database_path); SynthesizeDataset(synthetic_options, >_reconstruction, database.get()); Reconstruction reconstruction = gt_reconstruction; SyntheticNoiseOptions synthetic_noise_options; synthetic_noise_options.point3D_stddev = 0.2; synthetic_noise_options.rig_from_world_rotation_stddev = 1.0; synthetic_noise_options.rig_from_world_translation_stddev = 0.2; synthetic_noise_options.prior_position_stddev = 0.05; SynthesizeNoise(synthetic_noise_options, &reconstruction); std::vector pose_priors = database->ReadAllPosePriors(); // Add 2 outlier priors with very large covariance pose_priors.at(0).position += Eigen::Vector3d::Constant(10); pose_priors.at(0).position_covariance = Eigen::Matrix3d::Identity() * 1e6; pose_priors.at(1).position += Eigen::Vector3d::Constant(1); pose_priors.at(1).position_covariance = Eigen::Matrix3d::Identity() * 1e2; PosePriorBundleAdjustmentOptions prior_ba_options; prior_ba_options.alignment_ransac_options.random_seed = 0; prior_ba_options.alignment_ransac_options.max_error = 0.0; BundleAdjustmentOptions ba_options; BundleAdjustmentConfig ba_config; for (const frame_t frame_id : reconstruction.RegFrameIds()) { const Frame& frame = reconstruction.Frame(frame_id); for (const data_t& data_id : frame.ImageIds()) { ba_config.AddImage(data_id.id); } } auto adjuster = CreatePosePriorBundleAdjuster( ba_options, prior_ba_options, ba_config, pose_priors, reconstruction); auto summary = adjuster->Solve(); ASSERT_TRUE(summary->IsSolutionUsable()); EXPECT_THAT(gt_reconstruction, ReconstructionNear(reconstruction, /*max_rotation_error_deg=*/0.1, /*max_proj_center_error=*/0.1, /*max_scale_error=*/std::nullopt, /*num_obs_tolerance=*/0.02)); } TEST(PosePriorBundleAdjuster, InsufficientPriorsUseTwoCameraGauge) { Reconstruction reconstruction; SyntheticDatasetOptions synthetic_options; synthetic_options.num_rigs = 1; synthetic_options.num_cameras_per_rig = 1; synthetic_options.num_frames_per_rig = 3; synthetic_options.num_points3D = 50; synthetic_options.prior_position = true; const auto database_path = CreateTestDir() / "database.db"; auto database = Database::Open(database_path); SynthesizeDataset(synthetic_options, &reconstruction, database.get()); BundleAdjustmentConfig config; for (const image_t image_id : reconstruction.RegImageIds()) { config.AddImage(image_id); } std::vector pose_priors = database->ReadAllPosePriors(); pose_priors.resize(2); auto adjuster = CreatePosePriorBundleAdjuster(BundleAdjustmentOptions(), PosePriorBundleAdjustmentOptions(), config, std::move(pose_priors), reconstruction); EXPECT_EQ(adjuster->Config().FixedGauge(), BundleAdjustmentGauge::TWO_CAMS_FROM_WORLD); EXPECT_TRUE(adjuster->Solve()->IsSolutionUsable()); } TEST(PosePriorBundleAdjuster, MissingPositionCov) { Reconstruction gt_reconstruction; SyntheticDatasetOptions synthetic_options; synthetic_options.num_rigs = 1; synthetic_options.num_cameras_per_rig = 1; synthetic_options.num_frames_per_rig = 7; synthetic_options.num_points3D = 100; synthetic_options.prior_position = true; const auto database_path = CreateTestDir() / "database.db"; auto database = Database::Open(database_path); SynthesizeDataset(synthetic_options, >_reconstruction, database.get()); Reconstruction reconstruction = gt_reconstruction; std::vector pose_priors = database->ReadAllPosePriors(); for (PosePrior& pose_prior : pose_priors) { EXPECT_FALSE(pose_prior.HasPositionCov()); } PosePriorBundleAdjustmentOptions prior_ba_options; prior_ba_options.alignment_ransac_options.random_seed = 0; prior_ba_options.ceres->prior_position_loss_function_type = CeresBundleAdjustmentOptions::LossFunctionType::CAUCHY; BundleAdjustmentOptions ba_options; BundleAdjustmentConfig ba_config; for (const frame_t frame_id : reconstruction.RegFrameIds()) { const Frame& frame = reconstruction.Frame(frame_id); for (const data_t& data_id : frame.ImageIds()) { ba_config.AddImage(data_id.id); } } auto adjuster = CreatePosePriorBundleAdjuster( ba_options, prior_ba_options, ba_config, pose_priors, reconstruction); auto summary = adjuster->Solve(); ASSERT_TRUE(summary->IsSolutionUsable()); EXPECT_THAT(gt_reconstruction, ReconstructionNear(reconstruction, /*max_rotation_error_deg=*/0.1, /*max_proj_center_error=*/0.1, /*max_scale_error=*/std::nullopt, /*num_obs_tolerance=*/0.02)); } TEST(PosePriorBundleAdjuster, ConstantSensorFromRigWithMissingPositionCov) { Reconstruction reconstruction; SyntheticDatasetOptions synthetic_options; synthetic_options.num_rigs = 1; synthetic_options.num_cameras_per_rig = 2; synthetic_options.num_frames_per_rig = 3; synthetic_options.num_points3D = 50; synthetic_options.prior_position = true; const auto database_path = CreateTestDir() / "database.db"; auto database = Database::Open(database_path); SynthesizeDataset(synthetic_options, &reconstruction, database.get()); std::vector pose_priors = database->ReadAllPosePriors(); for (const PosePrior& pose_prior : pose_priors) { EXPECT_FALSE(pose_prior.HasPositionCov()); } BundleAdjustmentOptions ba_options; ba_options.refine_sensor_from_rig = false; BundleAdjustmentConfig ba_config; for (const image_t image_id : reconstruction.RegImageIds()) { ba_config.AddImage(image_id); } auto adjuster = CreatePosePriorBundleAdjuster(ba_options, PosePriorBundleAdjustmentOptions(), ba_config, std::move(pose_priors), reconstruction); ceres::Problem& problem = GetCeresProblem(*adjuster); for (auto& [_, rig] : reconstruction.Rigs()) { for (auto& [_, sensor_from_rig] : rig.NonRefSensors()) { ASSERT_TRUE(sensor_from_rig.has_value()); ASSERT_TRUE(problem.HasParameterBlock(sensor_from_rig->params.data())); EXPECT_TRUE( problem.IsParameterBlockConstant(sensor_from_rig->params.data())); } } } TEST(PosePriorBundleAdjuster, OptimizationRobustToOutliers) { Reconstruction gt_reconstruction; SyntheticDatasetOptions synthetic_options; synthetic_options.num_rigs = 1; synthetic_options.num_cameras_per_rig = 1; synthetic_options.num_frames_per_rig = 7; synthetic_options.num_points3D = 100; synthetic_options.prior_position = true; const auto database_path = CreateTestDir() / "database.db"; auto database = Database::Open(database_path); SynthesizeDataset(synthetic_options, >_reconstruction, database.get()); Reconstruction reconstruction = gt_reconstruction; SyntheticNoiseOptions synthetic_noise_options; synthetic_noise_options.point3D_stddev = 0.2; synthetic_noise_options.rig_from_world_rotation_stddev = 1.0; synthetic_noise_options.rig_from_world_translation_stddev = 0.2; synthetic_noise_options.prior_position_stddev = 0.05; SynthesizeNoise(synthetic_noise_options, &reconstruction); std::vector pose_priors = database->ReadAllPosePriors(); // Add 2 confident but wrong priors. pose_priors[0].position_covariance = Eigen::Matrix3d::Identity() * 0.01; pose_priors[0].position += Eigen::Vector3d::Constant(10); pose_priors[1].position_covariance = Eigen::Matrix3d::Identity() * 1.01; pose_priors[1].position += Eigen::Vector3d::Constant(10); PosePriorBundleAdjustmentOptions prior_ba_options; prior_ba_options.alignment_ransac_options.random_seed = 0; prior_ba_options.ceres->prior_position_loss_function_type = CeresBundleAdjustmentOptions::LossFunctionType::CAUCHY; BundleAdjustmentOptions ba_options; BundleAdjustmentConfig ba_config; for (const frame_t frame_id : reconstruction.RegFrameIds()) { const Frame& frame = reconstruction.Frame(frame_id); for (const data_t& data_id : frame.ImageIds()) { ba_config.AddImage(data_id.id); } } auto adjuster = CreatePosePriorBundleAdjuster( ba_options, prior_ba_options, ba_config, pose_priors, reconstruction); auto summary = adjuster->Solve(); ASSERT_TRUE(summary->IsSolutionUsable()); EXPECT_THAT(gt_reconstruction, ReconstructionNear(reconstruction, /*max_rotation_error_deg=*/0.1, /*max_proj_center_error=*/0.1, /*max_scale_error=*/std::nullopt, /*num_obs_tolerance=*/0.02)); } } // namespace } // namespace colmap colmap-4.2.0/src/colmap/estimators/bundle_adjustment_test.cc000066400000000000000000001051211524536416500243140ustar00rootroot00000000000000// Copyright (c), ETH Zurich and UNC Chapel Hill. // All rights reserved. // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are met: // // * Redistributions of source code must retain the above copyright // notice, this list of conditions and the following disclaimer. // // * Redistributions in binary form must reproduce the above copyright // notice, this list of conditions and the following disclaimer in the // documentation and/or other materials provided with the distribution. // // * Neither the name of ETH Zurich and UNC Chapel Hill nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE // ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE // LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR // CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF // SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS // INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN // CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) // ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE // POSSIBILITY OF SUCH DAMAGE. #include "colmap/estimators/bundle_adjustment.h" #include "colmap/estimators/bundle_adjustment_ceres.h" #include "colmap/geometry/rigid3_matchers.h" #include "colmap/scene/database.h" #include "colmap/scene/reconstruction_matchers.h" #include "colmap/scene/synthetic.h" #include "colmap/sensor/models.h" #include "colmap/util/testing.h" #ifdef CASPAR_ENABLED #include "colmap/util/cuda.h" #endif #include namespace colmap { namespace { constexpr double kConstantPoseVarEps = 1e-9; void ExpectVariableCamera(const Camera& camera, const Camera& orig_camera) { const size_t focal_length_idx = SimpleRadialCameraModel::focal_length_idxs[0]; const size_t extra_param_idx = SimpleRadialCameraModel::extra_params_idxs[0]; EXPECT_NE(camera.params[focal_length_idx], orig_camera.params[focal_length_idx]); EXPECT_NE(camera.params[extra_param_idx], orig_camera.params[extra_param_idx]); } void ExpectConstantCamera(const Camera& camera, const Camera& orig_camera) { const size_t focal_length_idx = SimpleRadialCameraModel::focal_length_idxs[0]; const size_t extra_param_idx = SimpleRadialCameraModel::extra_params_idxs[0]; EXPECT_EQ(camera.params[focal_length_idx], orig_camera.params[focal_length_idx]); EXPECT_EQ(camera.params[extra_param_idx], orig_camera.params[extra_param_idx]); } void ExpectVariableCamFromWorld(const Image& image, const Image& orig_image) { EXPECT_THAT(image.CamFromWorld(), testing::Not(Rigid3dEq(orig_image.CamFromWorld()))); } void ExpectConstantCamFromWorld(const Image& image, const Image& orig_image) { EXPECT_THAT( image.CamFromWorld(), Rigid3dNear( orig_image.CamFromWorld(), kConstantPoseVarEps, kConstantPoseVarEps)); } void ExpectVariablePoint(const Point3D& point, const Point3D& orig_point) { EXPECT_NE(point.xyz, orig_point.xyz); } void ExpectConstantPoint(const Point3D& point, const Point3D& orig_point) { EXPECT_EQ(point.xyz, orig_point.xyz); } std::vector BundleAdjustmentBackends() { std::vector backends = { BundleAdjustmentBackend::CERES}; #ifdef CASPAR_ENABLED backends.push_back(BundleAdjustmentBackend::CASPAR); #endif return backends; } TEST(BundleAdjustmentOptions, Copy) { BundleAdjustmentOptions options; options.refine_focal_length = false; options.refine_principal_point = true; options.min_track_length = 5; options.ceres->solver_options.max_num_iterations = 42; BundleAdjustmentOptions copy = options; // Verify fields are copied EXPECT_EQ(copy.refine_focal_length, false); EXPECT_EQ(copy.refine_principal_point, true); EXPECT_EQ(copy.min_track_length, 5); EXPECT_EQ(copy.ceres->solver_options.max_num_iterations, 42); // Verify deep copy of shared_ptr (different pointer instances) EXPECT_NE(options.ceres.get(), copy.ceres.get()); } TEST(PosePriorBundleAdjustmentOptions, Copy) { PosePriorBundleAdjustmentOptions options; options.prior_position_fallback_stddev = 2.5; options.alignment_ransac_options.max_error = 1.0; options.ceres->prior_position_loss_scale = 0.42; PosePriorBundleAdjustmentOptions copy = options; // Verify fields are copied EXPECT_EQ(copy.prior_position_fallback_stddev, 2.5); EXPECT_EQ(copy.alignment_ransac_options.max_error, 1.0); EXPECT_EQ(copy.ceres->prior_position_loss_scale, 0.42); // Verify deep copy of shared_ptr (different pointer instances) EXPECT_NE(options.ceres.get(), copy.ceres.get()); } TEST(BundleAdjustmentSummary, IsSolutionUsable) { BundleAdjustmentSummary summary; summary.termination_type = BundleAdjustmentTerminationType::CONVERGENCE; EXPECT_TRUE(summary.IsSolutionUsable()); summary.termination_type = BundleAdjustmentTerminationType::NO_CONVERGENCE; EXPECT_TRUE(summary.IsSolutionUsable()); summary.termination_type = BundleAdjustmentTerminationType::USER_SUCCESS; EXPECT_TRUE(summary.IsSolutionUsable()); summary.termination_type = BundleAdjustmentTerminationType::FAILURE; EXPECT_FALSE(summary.IsSolutionUsable()); summary.termination_type = BundleAdjustmentTerminationType::USER_FAILURE; EXPECT_FALSE(summary.IsSolutionUsable()); } TEST(BundleAdjustmentConfig, NumResiduals) { Reconstruction reconstruction; SyntheticDatasetOptions synthetic_dataset_options; synthetic_dataset_options.num_rigs = 4; synthetic_dataset_options.num_cameras_per_rig = 1; synthetic_dataset_options.num_frames_per_rig = 1; synthetic_dataset_options.num_points3D = 100; SynthesizeDataset(synthetic_dataset_options, &reconstruction); const std::vector image_ids = reconstruction.RegImageIds(); CHECK_EQ(image_ids.size(), 4); BundleAdjustmentConfig config; config.AddImage(image_ids[0]); config.AddImage(image_ids[1]); EXPECT_EQ(config.NumResiduals(reconstruction), 400); config.AddVariablePoint(1); EXPECT_EQ(config.NumResiduals(reconstruction), 404); config.AddConstantPoint(2); EXPECT_EQ(config.NumResiduals(reconstruction), 408); config.AddImage(image_ids[2]); EXPECT_EQ(config.NumResiduals(reconstruction), 604); config.AddImage(image_ids[3]); EXPECT_EQ(config.NumResiduals(reconstruction), 800); config.IgnorePoint(3); EXPECT_EQ(config.NumResiduals(reconstruction), 792); } TEST(BundleAdjustmentConfig, AddRemoveImage) { BundleAdjustmentConfig config; EXPECT_EQ(config.NumImages(), 0); config.AddImage(1); config.AddImage(2); config.AddImage(3); EXPECT_EQ(config.NumImages(), 3); EXPECT_TRUE(config.HasImage(1)); EXPECT_TRUE(config.HasImage(2)); EXPECT_TRUE(config.HasImage(3)); EXPECT_FALSE(config.HasImage(4)); config.RemoveImage(2); EXPECT_EQ(config.NumImages(), 2); EXPECT_TRUE(config.HasImage(1)); EXPECT_FALSE(config.HasImage(2)); EXPECT_TRUE(config.HasImage(3)); // Removing non-existent image is a no-op config.RemoveImage(99); EXPECT_EQ(config.NumImages(), 2); } TEST(BundleAdjustmentConfig, ConstantVariableCamIntrinsics) { BundleAdjustmentConfig config; EXPECT_EQ(config.NumConstantCamIntrinsics(), 0); config.SetConstantCamIntrinsics(1); config.SetConstantCamIntrinsics(2); EXPECT_EQ(config.NumConstantCamIntrinsics(), 2); EXPECT_TRUE(config.HasConstantCamIntrinsics(1)); EXPECT_TRUE(config.HasConstantCamIntrinsics(2)); EXPECT_FALSE(config.HasConstantCamIntrinsics(3)); config.SetVariableCamIntrinsics(1); EXPECT_EQ(config.NumConstantCamIntrinsics(), 1); EXPECT_FALSE(config.HasConstantCamIntrinsics(1)); EXPECT_TRUE(config.HasConstantCamIntrinsics(2)); const auto& constant_cams = config.ConstantCamIntrinsics(); EXPECT_EQ(constant_cams.size(), 1); EXPECT_EQ(constant_cams.count(2), 1); } TEST(BundleAdjustmentConfig, ConstantVariableSensorFromRigPose) { BundleAdjustmentConfig config; EXPECT_EQ(config.NumConstantSensorFromRigPoses(), 0); sensor_t sensor1(SensorType::CAMERA, 1); sensor_t sensor2(SensorType::CAMERA, 2); config.SetConstantSensorFromRigPose(sensor1); config.SetConstantSensorFromRigPose(sensor2); EXPECT_EQ(config.NumConstantSensorFromRigPoses(), 2); EXPECT_TRUE(config.HasConstantSensorFromRigPose(sensor1)); EXPECT_TRUE(config.HasConstantSensorFromRigPose(sensor2)); config.SetVariableSensorFromRigPose(sensor1); EXPECT_EQ(config.NumConstantSensorFromRigPoses(), 1); EXPECT_FALSE(config.HasConstantSensorFromRigPose(sensor1)); EXPECT_TRUE(config.HasConstantSensorFromRigPose(sensor2)); const auto& constant_poses = config.ConstantSensorFromRigPoses(); EXPECT_EQ(constant_poses.size(), 1); EXPECT_EQ(constant_poses.count(sensor2), 1); } TEST(BundleAdjustmentConfig, ConstantVariableRigFromWorldPose) { BundleAdjustmentConfig config; EXPECT_EQ(config.NumConstantRigFromWorldPoses(), 0); config.SetConstantRigFromWorldPose(1); config.SetConstantRigFromWorldPose(2); EXPECT_EQ(config.NumConstantRigFromWorldPoses(), 2); EXPECT_TRUE(config.HasConstantRigFromWorldPose(1)); EXPECT_TRUE(config.HasConstantRigFromWorldPose(2)); config.SetVariableRigFromWorldPose(1); EXPECT_EQ(config.NumConstantRigFromWorldPoses(), 1); EXPECT_FALSE(config.HasConstantRigFromWorldPose(1)); EXPECT_TRUE(config.HasConstantRigFromWorldPose(2)); const auto& constant_rig_poses = config.ConstantRigFromWorldPoses(); EXPECT_EQ(constant_rig_poses.size(), 1); EXPECT_EQ(constant_rig_poses.count(2), 1); } TEST(BundleAdjustmentConfig, ConstantVariablePoints) { BundleAdjustmentConfig config; EXPECT_EQ(config.NumPoints(), 0); EXPECT_EQ(config.NumVariablePoints(), 0); EXPECT_EQ(config.NumConstantPoints(), 0); config.AddVariablePoint(1); config.AddVariablePoint(2); EXPECT_EQ(config.NumPoints(), 2); EXPECT_EQ(config.NumVariablePoints(), 2); EXPECT_EQ(config.NumConstantPoints(), 0); EXPECT_TRUE(config.HasPoint(1)); EXPECT_TRUE(config.HasVariablePoint(1)); EXPECT_FALSE(config.HasConstantPoint(1)); config.AddConstantPoint(3); EXPECT_EQ(config.NumPoints(), 3); EXPECT_EQ(config.NumVariablePoints(), 2); EXPECT_EQ(config.NumConstantPoints(), 1); EXPECT_TRUE(config.HasPoint(3)); EXPECT_FALSE(config.HasVariablePoint(3)); EXPECT_TRUE(config.HasConstantPoint(3)); config.RemoveVariablePoint(1); EXPECT_EQ(config.NumVariablePoints(), 1); EXPECT_FALSE(config.HasPoint(1)); config.RemoveConstantPoint(3); EXPECT_EQ(config.NumConstantPoints(), 0); EXPECT_FALSE(config.HasPoint(3)); const auto& var_points = config.VariablePoints(); EXPECT_EQ(var_points.size(), 1); EXPECT_EQ(var_points.count(2), 1); const auto& const_points = config.ConstantPoints(); EXPECT_TRUE(const_points.empty()); } TEST(BundleAdjustmentConfig, IgnoredPoints) { BundleAdjustmentConfig config; EXPECT_FALSE(config.IsIgnoredPoint(1)); config.IgnorePoint(1); EXPECT_TRUE(config.IsIgnoredPoint(1)); EXPECT_FALSE(config.IsIgnoredPoint(2)); } TEST(BundleAdjustmentConfig, FixGauge) { BundleAdjustmentConfig config; EXPECT_EQ(config.FixedGauge(), BundleAdjustmentGauge::UNSPECIFIED); config.FixGauge(BundleAdjustmentGauge::TWO_CAMS_FROM_WORLD); EXPECT_EQ(config.FixedGauge(), BundleAdjustmentGauge::TWO_CAMS_FROM_WORLD); config.FixGauge(BundleAdjustmentGauge::THREE_POINTS); EXPECT_EQ(config.FixedGauge(), BundleAdjustmentGauge::THREE_POINTS); } TEST(BundleAdjustmentConfig, Images) { BundleAdjustmentConfig config; config.AddImage(5); config.AddImage(10); const auto& images = config.Images(); EXPECT_EQ(images.size(), 2); EXPECT_EQ(images.count(5), 1); EXPECT_EQ(images.count(10), 1); } TEST(BundleAdjustmentSummary, BriefReport) { BundleAdjustmentSummary summary; summary.termination_type = BundleAdjustmentTerminationType::CONVERGENCE; summary.num_residuals = 42; const std::string report = summary.BriefReport(); EXPECT_NE(report.find("CONVERGENCE"), std::string::npos); EXPECT_NE(report.find("42"), std::string::npos); } // Parameterized test for generic BundleAdjuster interface across backends. class BundleAdjusterBackendTest : public ::testing::TestWithParam { protected: void SetUp() override { #ifdef CASPAR_ENABLED if (GetParam() == BundleAdjustmentBackend::CASPAR && GetNumCudaDevices() == 0) { GTEST_SKIP() << "No CUDA devices available"; } #endif } }; TEST_P(BundleAdjusterBackendTest, Nominal) { Reconstruction gt_reconstruction; SyntheticDatasetOptions synthetic_dataset_options; synthetic_dataset_options.num_rigs = 1; synthetic_dataset_options.num_cameras_per_rig = 1; synthetic_dataset_options.num_frames_per_rig = 10; synthetic_dataset_options.num_points3D = 200; SynthesizeDataset(synthetic_dataset_options, >_reconstruction); Reconstruction reconstruction = gt_reconstruction; SyntheticNoiseOptions synthetic_noise_options; synthetic_noise_options.point2D_stddev = 0.5; synthetic_noise_options.point3D_stddev = 0.1; synthetic_noise_options.rig_from_world_rotation_stddev = 0.5; synthetic_noise_options.rig_from_world_translation_stddev = 0.1; SynthesizeNoise(synthetic_noise_options, &reconstruction); BundleAdjustmentConfig config; for (const image_t image_id : reconstruction.RegImageIds()) { config.AddImage(image_id); } config.FixGauge(BundleAdjustmentGauge::TWO_CAMS_FROM_WORLD); BundleAdjustmentOptions options; options.backend = GetParam(); std::unique_ptr bundle_adjuster = CreateDefaultBundleAdjuster(options, config, reconstruction); // Test abstract interface accessors EXPECT_EQ(bundle_adjuster->Options().backend, GetParam()); EXPECT_EQ(bundle_adjuster->Config().NumImages(), 10); // Solve and verify through abstract interface const auto summary = bundle_adjuster->Solve(); EXPECT_TRUE(summary->IsSolutionUsable()); EXPECT_GT(summary->num_residuals, 0); EXPECT_THAT(gt_reconstruction, ReconstructionNear(reconstruction, /*max_rotation_error_deg=*/0.1, /*max_proj_center_error=*/0.1, /*max_scale_error=*/std::nullopt, /*num_obs_tolerance=*/0.0)); } TEST_P(BundleAdjusterBackendTest, NominalMultiCameraRigConstantSensorFromRig) { Reconstruction gt_reconstruction; SyntheticDatasetOptions synthetic_dataset_options; synthetic_dataset_options.num_rigs = 1; synthetic_dataset_options.num_cameras_per_rig = 2; synthetic_dataset_options.num_frames_per_rig = 10; synthetic_dataset_options.num_points3D = 200; SynthesizeDataset(synthetic_dataset_options, >_reconstruction); Reconstruction reconstruction = gt_reconstruction; SyntheticNoiseOptions synthetic_noise_options; synthetic_noise_options.point2D_stddev = 0.5; synthetic_noise_options.point3D_stddev = 0.1; synthetic_noise_options.rig_from_world_rotation_stddev = 0.5; synthetic_noise_options.rig_from_world_translation_stddev = 0.1; SynthesizeNoise(synthetic_noise_options, &reconstruction); BundleAdjustmentConfig config; for (const image_t image_id : reconstruction.RegImageIds()) { config.AddImage(image_id); } config.FixGauge(BundleAdjustmentGauge::TWO_CAMS_FROM_WORLD); BundleAdjustmentOptions options; options.backend = GetParam(); options.refine_sensor_from_rig = false; const auto summary = CreateDefaultBundleAdjuster(options, config, reconstruction)->Solve(); ASSERT_TRUE(summary->IsSolutionUsable()); EXPECT_THAT(gt_reconstruction, ReconstructionNear(reconstruction, /*max_rotation_error_deg=*/0.1, /*max_proj_center_error=*/0.1, /*max_scale_error=*/std::nullopt, /*num_obs_tolerance=*/0.0)); } TEST_P(BundleAdjusterBackendTest, TwoView) { Reconstruction reconstruction; SyntheticDatasetOptions synthetic_dataset_options; synthetic_dataset_options.num_rigs = 2; synthetic_dataset_options.num_cameras_per_rig = 1; synthetic_dataset_options.num_frames_per_rig = 1; synthetic_dataset_options.num_points3D = 100; SynthesizeDataset(synthetic_dataset_options, &reconstruction); SyntheticNoiseOptions synthetic_noise_options; synthetic_noise_options.point2D_stddev = 1; SynthesizeNoise(synthetic_noise_options, &reconstruction); const Reconstruction orig_reconstruction = reconstruction; BundleAdjustmentConfig config; config.AddImage(1); config.AddImage(2); config.FixGauge(BundleAdjustmentGauge::TWO_CAMS_FROM_WORLD); BundleAdjustmentOptions options; options.backend = GetParam(); const auto summary = CreateDefaultBundleAdjuster(options, config, reconstruction)->Solve(); ASSERT_TRUE(summary->IsSolutionUsable()); EXPECT_EQ(summary->num_residuals, 400); ExpectConstantCamFromWorld(reconstruction.Image(1), orig_reconstruction.Image(1)); for (const auto& [point3D_id, point3D] : reconstruction.Points3D()) { ExpectVariablePoint(point3D, orig_reconstruction.Point3D(point3D_id)); } } TEST_P(BundleAdjusterBackendTest, TwoViewConstantCamera) { Reconstruction reconstruction; SyntheticDatasetOptions synthetic_dataset_options; synthetic_dataset_options.num_rigs = 2; synthetic_dataset_options.num_cameras_per_rig = 1; synthetic_dataset_options.num_frames_per_rig = 1; synthetic_dataset_options.num_points3D = 100; SynthesizeDataset(synthetic_dataset_options, &reconstruction); SyntheticNoiseOptions synthetic_noise_options; synthetic_noise_options.point2D_stddev = 1; SynthesizeNoise(synthetic_noise_options, &reconstruction); const Reconstruction orig_reconstruction = reconstruction; BundleAdjustmentConfig config; config.AddImage(1); config.AddImage(2); config.SetConstantRigFromWorldPose(1); config.SetConstantRigFromWorldPose(2); config.SetConstantCamIntrinsics(1); BundleAdjustmentOptions options; options.backend = GetParam(); const auto summary = CreateDefaultBundleAdjuster(options, config, reconstruction)->Solve(); ASSERT_TRUE(summary->IsSolutionUsable()); EXPECT_EQ(summary->num_residuals, 400); ExpectConstantCamera(reconstruction.Camera(1), orig_reconstruction.Camera(1)); ExpectVariableCamera(reconstruction.Camera(2), orig_reconstruction.Camera(2)); ExpectConstantCamFromWorld(reconstruction.Image(1), orig_reconstruction.Image(1)); ExpectConstantCamFromWorld(reconstruction.Image(2), orig_reconstruction.Image(2)); for (const auto& [point3D_id, point3D] : reconstruction.Points3D()) { ExpectVariablePoint(point3D, orig_reconstruction.Point3D(point3D_id)); } } TEST_P(BundleAdjusterBackendTest, PartiallyContainedTracks) { Reconstruction reconstruction; SyntheticDatasetOptions synthetic_dataset_options; synthetic_dataset_options.num_rigs = 3; synthetic_dataset_options.num_cameras_per_rig = 1; synthetic_dataset_options.num_frames_per_rig = 1; synthetic_dataset_options.num_points3D = 100; synthetic_dataset_options.num_points2D_without_point3D = 0; SynthesizeDataset(synthetic_dataset_options, &reconstruction); SyntheticNoiseOptions synthetic_noise_options; synthetic_noise_options.point2D_stddev = 1; SynthesizeNoise(synthetic_noise_options, &reconstruction); const point3D_t variable_point3D_id = reconstruction.Image(3).Point2D(0).point3D_id; reconstruction.DeleteObservation(3, 0); const Reconstruction orig_reconstruction = reconstruction; BundleAdjustmentConfig config; config.AddImage(1); config.AddImage(2); config.SetConstantRigFromWorldPose(1); config.SetConstantRigFromWorldPose(2); BundleAdjustmentOptions options; options.backend = GetParam(); const auto summary = CreateDefaultBundleAdjuster(options, config, reconstruction)->Solve(); ASSERT_TRUE(summary->IsSolutionUsable()); EXPECT_EQ(summary->num_residuals, 400); ExpectVariableCamera(reconstruction.Camera(1), orig_reconstruction.Camera(1)); ExpectVariableCamera(reconstruction.Camera(2), orig_reconstruction.Camera(2)); ExpectConstantCamera(reconstruction.Camera(3), orig_reconstruction.Camera(3)); ExpectConstantCamFromWorld(reconstruction.Image(3), orig_reconstruction.Image(3)); for (const auto& [point3D_id, point3D] : reconstruction.Points3D()) { if (point3D_id == variable_point3D_id) { ExpectVariablePoint(point3D, orig_reconstruction.Point3D(point3D_id)); } else { ExpectConstantPoint(point3D, orig_reconstruction.Point3D(point3D_id)); } } } TEST_P(BundleAdjusterBackendTest, MinimumTrackLength) { Reconstruction reconstruction; SyntheticDatasetOptions synthetic_dataset_options; synthetic_dataset_options.num_rigs = 3; synthetic_dataset_options.num_cameras_per_rig = 1; synthetic_dataset_options.num_frames_per_rig = 1; synthetic_dataset_options.num_points3D = 100; synthetic_dataset_options.num_points2D_without_point3D = 0; SynthesizeDataset(synthetic_dataset_options, &reconstruction); SyntheticNoiseOptions synthetic_noise_options; synthetic_noise_options.point2D_stddev = 1; SynthesizeNoise(synthetic_noise_options, &reconstruction); reconstruction.DeleteObservation(3, 0); BundleAdjustmentConfig config; for (const image_t image_id : reconstruction.RegImageIds()) { config.AddImage(image_id); } config.FixGauge(BundleAdjustmentGauge::TWO_CAMS_FROM_WORLD); BundleAdjustmentOptions options; options.backend = GetParam(); options.min_track_length = 3; const auto summary = CreateDefaultBundleAdjuster(options, config, reconstruction)->Solve(); ASSERT_TRUE(summary->IsSolutionUsable()); // 99 points x 3 observations x 2 residuals per observation. The point with // a two-observation track is excluded. EXPECT_EQ(summary->num_residuals, 594); } TEST_P(BundleAdjusterBackendTest, MinimumTrackLengthWithExternalObservations) { Reconstruction reconstruction; SyntheticDatasetOptions synthetic_dataset_options; synthetic_dataset_options.num_rigs = 3; synthetic_dataset_options.num_cameras_per_rig = 1; synthetic_dataset_options.num_frames_per_rig = 1; synthetic_dataset_options.num_points3D = 100; synthetic_dataset_options.num_points2D_without_point3D = 0; SynthesizeDataset(synthetic_dataset_options, &reconstruction); SyntheticNoiseOptions synthetic_noise_options; synthetic_noise_options.point2D_stddev = 1; SynthesizeNoise(synthetic_noise_options, &reconstruction); // Shorten one track from three to two observations. The remaining // observations are split between a configured image and an external image. reconstruction.DeleteObservation(2, 0); BundleAdjustmentConfig config; config.AddImage(1); config.AddImage(2); for (const auto& [point3D_id, _] : reconstruction.Points3D()) { config.AddVariablePoint(point3D_id); } config.FixGauge(BundleAdjustmentGauge::TWO_CAMS_FROM_WORLD); BundleAdjustmentOptions options; options.backend = GetParam(); options.min_track_length = 3; const auto summary = CreateDefaultBundleAdjuster(options, config, reconstruction)->Solve(); ASSERT_TRUE(summary->IsSolutionUsable()); // 99 points x 3 observations x 2 residuals per observation. The point with // a two-observation track is excluded from both configured and external // images. EXPECT_EQ(summary->num_residuals, 594); } TEST_P(BundleAdjusterBackendTest, ConstantPoints) { Reconstruction reconstruction; SyntheticDatasetOptions synthetic_dataset_options; synthetic_dataset_options.num_rigs = 2; synthetic_dataset_options.num_cameras_per_rig = 1; synthetic_dataset_options.num_frames_per_rig = 1; synthetic_dataset_options.num_points3D = 100; SynthesizeDataset(synthetic_dataset_options, &reconstruction); SyntheticNoiseOptions synthetic_noise_options; synthetic_noise_options.point2D_stddev = 1; SynthesizeNoise(synthetic_noise_options, &reconstruction); const Reconstruction orig_reconstruction = reconstruction; const point3D_t constant_point3D_id1 = 1; const point3D_t constant_point3D_id2 = 2; BundleAdjustmentConfig config; config.AddImage(1); config.AddImage(2); config.AddConstantPoint(constant_point3D_id1); config.AddConstantPoint(constant_point3D_id2); config.FixGauge(BundleAdjustmentGauge::TWO_CAMS_FROM_WORLD); BundleAdjustmentOptions options; options.backend = GetParam(); const auto summary = CreateDefaultBundleAdjuster(options, config, reconstruction)->Solve(); ASSERT_TRUE(summary->IsSolutionUsable()); EXPECT_EQ(summary->num_residuals, 400); for (const auto& [point3D_id, point3D] : reconstruction.Points3D()) { if (point3D_id == constant_point3D_id1 || point3D_id == constant_point3D_id2) { ExpectConstantPoint(point3D, orig_reconstruction.Point3D(point3D_id)); } else { ExpectVariablePoint(point3D, orig_reconstruction.Point3D(point3D_id)); } } } TEST_P(BundleAdjusterBackendTest, ConstantPoints3D) { Reconstruction reconstruction; SyntheticDatasetOptions synthetic_dataset_options; synthetic_dataset_options.num_rigs = 2; synthetic_dataset_options.num_cameras_per_rig = 1; synthetic_dataset_options.num_frames_per_rig = 1; synthetic_dataset_options.num_points3D = 20; SynthesizeDataset(synthetic_dataset_options, &reconstruction); SyntheticNoiseOptions synthetic_noise_options; synthetic_noise_options.point2D_stddev = 1; SynthesizeNoise(synthetic_noise_options, &reconstruction); const Reconstruction original_reconstruction = reconstruction; BundleAdjustmentConfig config; for (const image_t image_id : reconstruction.RegImageIds()) { config.AddImage(image_id); } BundleAdjustmentOptions options; options.backend = GetParam(); options.refine_points3D = false; const auto summary = CreateDefaultBundleAdjuster(options, config, reconstruction)->Solve(); ASSERT_TRUE(summary->IsSolutionUsable()); EXPECT_EQ(summary->num_residuals, 80); for (const auto& [point3D_id, point3D] : reconstruction.Points3D()) { ExpectConstantPoint(point3D, original_reconstruction.Point3D(point3D_id)); } } TEST_P(BundleAdjusterBackendTest, VariableImage) { Reconstruction reconstruction; SyntheticDatasetOptions synthetic_dataset_options; synthetic_dataset_options.num_rigs = 3; synthetic_dataset_options.num_cameras_per_rig = 1; synthetic_dataset_options.num_frames_per_rig = 1; synthetic_dataset_options.num_points3D = 100; SynthesizeDataset(synthetic_dataset_options, &reconstruction); SyntheticNoiseOptions synthetic_noise_options; synthetic_noise_options.point2D_stddev = 1; SynthesizeNoise(synthetic_noise_options, &reconstruction); const Reconstruction orig_reconstruction = reconstruction; BundleAdjustmentConfig config; config.AddImage(1); config.AddImage(2); config.AddImage(3); config.FixGauge(BundleAdjustmentGauge::TWO_CAMS_FROM_WORLD); BundleAdjustmentOptions options; options.backend = GetParam(); const auto summary = CreateDefaultBundleAdjuster(options, config, reconstruction)->Solve(); ASSERT_TRUE(summary->IsSolutionUsable()); EXPECT_EQ(summary->num_residuals, 600); ExpectConstantCamFromWorld(reconstruction.Image(1), orig_reconstruction.Image(1)); ExpectVariableCamFromWorld(reconstruction.Image(3), orig_reconstruction.Image(3)); for (const auto& [point3D_id, point3D] : reconstruction.Points3D()) { ExpectVariablePoint(point3D, orig_reconstruction.Point3D(point3D_id)); } } TEST_P(BundleAdjusterBackendTest, ConstantFocalLengthAndExtraParams) { Reconstruction reconstruction; SyntheticDatasetOptions synthetic_dataset_options; synthetic_dataset_options.num_rigs = 2; synthetic_dataset_options.num_cameras_per_rig = 1; synthetic_dataset_options.num_frames_per_rig = 1; synthetic_dataset_options.num_points3D = 100; SynthesizeDataset(synthetic_dataset_options, &reconstruction); SyntheticNoiseOptions synthetic_noise_options; synthetic_noise_options.point2D_stddev = 1; SynthesizeNoise(synthetic_noise_options, &reconstruction); const Reconstruction orig_reconstruction = reconstruction; BundleAdjustmentConfig config; config.AddImage(1); config.AddImage(2); config.FixGauge(BundleAdjustmentGauge::TWO_CAMS_FROM_WORLD); BundleAdjustmentOptions options; options.backend = GetParam(); options.refine_focal_length = false; options.refine_extra_params = false; const auto summary = CreateDefaultBundleAdjuster(options, config, reconstruction)->Solve(); ASSERT_TRUE(summary->IsSolutionUsable()); EXPECT_EQ(summary->num_residuals, 400); const size_t focal_length_idx = SimpleRadialCameraModel::focal_length_idxs[0]; const size_t extra_param_idx = SimpleRadialCameraModel::extra_params_idxs[0]; for (const auto& [camera_id, camera] : reconstruction.Cameras()) { const Camera& orig_camera = orig_reconstruction.Camera(camera_id); EXPECT_EQ(camera.params[focal_length_idx], orig_camera.params[focal_length_idx]); EXPECT_EQ(camera.params[extra_param_idx], orig_camera.params[extra_param_idx]); } } TEST_P(BundleAdjusterBackendTest, VariablePrincipalPoint) { Reconstruction reconstruction; SyntheticDatasetOptions synthetic_dataset_options; synthetic_dataset_options.num_rigs = 2; synthetic_dataset_options.num_cameras_per_rig = 1; synthetic_dataset_options.num_frames_per_rig = 1; synthetic_dataset_options.num_points3D = 100; SynthesizeDataset(synthetic_dataset_options, &reconstruction); SyntheticNoiseOptions synthetic_noise_options; synthetic_noise_options.point2D_stddev = 1; SynthesizeNoise(synthetic_noise_options, &reconstruction); const Reconstruction orig_reconstruction = reconstruction; BundleAdjustmentConfig config; config.AddImage(1); config.AddImage(2); config.FixGauge(BundleAdjustmentGauge::TWO_CAMS_FROM_WORLD); BundleAdjustmentOptions options; options.backend = GetParam(); options.refine_principal_point = true; const auto summary = CreateDefaultBundleAdjuster(options, config, reconstruction)->Solve(); ASSERT_TRUE(summary->IsSolutionUsable()); EXPECT_EQ(summary->num_residuals, 400); const size_t principal_point_idx_x = SimpleRadialCameraModel::principal_point_idxs[0]; const size_t principal_point_idx_y = SimpleRadialCameraModel::principal_point_idxs[1]; for (const auto& [camera_id, camera] : reconstruction.Cameras()) { const Camera& orig_camera = orig_reconstruction.Camera(camera_id); EXPECT_NE(camera.params[principal_point_idx_x], orig_camera.params[principal_point_idx_x]); EXPECT_NE(camera.params[principal_point_idx_y], orig_camera.params[principal_point_idx_y]); } } TEST_P(BundleAdjusterBackendTest, IgnorePoint) { Reconstruction reconstruction; SyntheticDatasetOptions synthetic_dataset_options; synthetic_dataset_options.num_rigs = 2; synthetic_dataset_options.num_cameras_per_rig = 1; synthetic_dataset_options.num_frames_per_rig = 1; synthetic_dataset_options.num_points3D = 100; SynthesizeDataset(synthetic_dataset_options, &reconstruction); SyntheticNoiseOptions synthetic_noise_options; synthetic_noise_options.point2D_stddev = 1; SynthesizeNoise(synthetic_noise_options, &reconstruction); BundleAdjustmentConfig config; config.AddImage(1); config.AddImage(2); config.IgnorePoint(42); config.FixGauge(BundleAdjustmentGauge::TWO_CAMS_FROM_WORLD); BundleAdjustmentOptions options; options.backend = GetParam(); const auto summary = CreateDefaultBundleAdjuster(options, config, reconstruction)->Solve(); ASSERT_TRUE(summary->IsSolutionUsable()); // 99 points (point 42 ignored), 2 images, 2 residuals per observation. EXPECT_EQ(summary->num_residuals, 396); } INSTANTIATE_TEST_SUITE_P(BundleAdjusterBackends, BundleAdjusterBackendTest, ::testing::ValuesIn(BundleAdjustmentBackends())); // Parameterized test for generic PosePriorBundleAdjuster interface across // backends. class PosePriorBundleAdjusterBackendTest : public ::testing::TestWithParam {}; TEST_P(PosePriorBundleAdjusterBackendTest, Nominal) { Reconstruction gt_reconstruction; SyntheticDatasetOptions synthetic_dataset_options; synthetic_dataset_options.num_rigs = 1; synthetic_dataset_options.num_cameras_per_rig = 1; synthetic_dataset_options.num_frames_per_rig = 7; synthetic_dataset_options.num_points3D = 100; synthetic_dataset_options.prior_position = true; const auto database_path = CreateTestDir() / "database.db"; auto database = Database::Open(database_path); SynthesizeDataset( synthetic_dataset_options, >_reconstruction, database.get()); Reconstruction reconstruction = gt_reconstruction; SyntheticNoiseOptions synthetic_noise_options; synthetic_noise_options.point2D_stddev = 0.5; synthetic_noise_options.point3D_stddev = 0.1; synthetic_noise_options.rig_from_world_rotation_stddev = 0.5; synthetic_noise_options.rig_from_world_translation_stddev = 0.1; synthetic_noise_options.prior_position_stddev = 0.05; SynthesizeNoise(synthetic_noise_options, &reconstruction); std::vector pose_priors = database->ReadAllPosePriors(); BundleAdjustmentConfig config; for (const frame_t frame_id : reconstruction.RegFrameIds()) { const Frame& frame = reconstruction.Frame(frame_id); for (const data_t& data_id : frame.ImageIds()) { config.AddImage(data_id.id); } } BundleAdjustmentOptions options; options.backend = GetParam(); PosePriorBundleAdjustmentOptions prior_options; prior_options.alignment_ransac_options.random_seed = 0; std::unique_ptr bundle_adjuster = CreatePosePriorBundleAdjuster( options, prior_options, config, pose_priors, reconstruction); // Test abstract interface accessors EXPECT_EQ(bundle_adjuster->Options().backend, GetParam()); EXPECT_EQ(bundle_adjuster->Config().NumImages(), 7); // Solve and verify through abstract interface const auto summary = bundle_adjuster->Solve(); EXPECT_TRUE(summary->IsSolutionUsable()); EXPECT_GT(summary->num_residuals, 0); EXPECT_THAT(gt_reconstruction, ReconstructionNear(reconstruction, /*max_rotation_error_deg=*/0.1, /*max_proj_center_error=*/0.1, /*max_scale_error=*/std::nullopt, /*num_obs_tolerance=*/0.02)); } INSTANTIATE_TEST_SUITE_P(PosePriorBundleAdjusterBackends, PosePriorBundleAdjusterBackendTest, ::testing::Values(BundleAdjustmentBackend::CERES)); } // namespace } // namespace colmap colmap-4.2.0/src/colmap/estimators/caspar/000077500000000000000000000000001524536416500205105ustar00rootroot00000000000000colmap-4.2.0/src/colmap/estimators/caspar/caspar_model_adapter.h000066400000000000000000001375571524536416500250340ustar00rootroot00000000000000#pragma once #include "colmap/estimators/bundle_adjustment_caspar.h" #include "colmap/scene/camera.h" #include "colmap/sensor/models.h" #include #include #ifdef CASPAR_USE_DOUBLE #include "thirdparty/Symforce-Caspar/generated/f64/solver.h" #else #include "thirdparty/Symforce-Caspar/generated/f32/solver.h" #endif namespace colmap { struct CasparSolverSizing { // Pose pools are per-model to prevent cross-model factor batching. size_t num_simple_radial_poses = 0; size_t num_pinhole_poses = 0; size_t num_points = 0; // SimpleRadial: num_calibs is shared by the merged Calib pool and the split // FocalAndExtra / PrincipalPoint pools, one entry per camera. // Merged variants (both intrinsic groups tunable): 4 counts below. // Split variants (at least one group fixed): 11 counts below. size_t num_simple_radial_calibs = 0; size_t num_simple_radial = 0; size_t num_simple_radial_fixed_pose = 0; size_t num_simple_radial_fixed_point = 0; size_t num_simple_radial_fixed_pose_fixed_point = 0; size_t num_simple_radial_split_fixed_focal_and_extra = 0; size_t num_simple_radial_split_fixed_principal_point = 0; size_t num_simple_radial_split_fixed_pose_fixed_focal_and_extra = 0; size_t num_simple_radial_split_fixed_pose_fixed_principal_point = 0; size_t num_simple_radial_split_fixed_focal_and_extra_fixed_principal_point = 0; size_t num_simple_radial_split_fixed_focal_and_extra_fixed_point = 0; size_t num_simple_radial_split_fixed_principal_point_fixed_point = 0; size_t num_simple_radial_split_fixed_pose_fixed_focal_and_extra_fixed_principal_point = 0; size_t num_simple_radial_split_fixed_pose_fixed_focal_and_extra_fixed_point = 0; size_t num_simple_radial_split_fixed_pose_fixed_principal_point_fixed_point = 0; size_t num_simple_radial_split_fixed_focal_and_extra_fixed_principal_point_fixed_point = 0; // Pinhole: same layout as SimpleRadial above. size_t num_pinhole_calibs = 0; size_t num_pinhole = 0; size_t num_pinhole_fixed_pose = 0; size_t num_pinhole_fixed_point = 0; size_t num_pinhole_fixed_pose_fixed_point = 0; size_t num_pinhole_split_fixed_focal = 0; size_t num_pinhole_split_fixed_principal_point = 0; size_t num_pinhole_split_fixed_pose_fixed_focal = 0; size_t num_pinhole_split_fixed_pose_fixed_principal_point = 0; size_t num_pinhole_split_fixed_focal_fixed_principal_point = 0; size_t num_pinhole_split_fixed_focal_fixed_point = 0; size_t num_pinhole_split_fixed_principal_point_fixed_point = 0; size_t num_pinhole_split_fixed_pose_fixed_focal_fixed_principal_point = 0; size_t num_pinhole_split_fixed_pose_fixed_focal_fixed_point = 0; size_t num_pinhole_split_fixed_pose_fixed_principal_point_fixed_point = 0; size_t num_pinhole_split_fixed_focal_fixed_principal_point_fixed_point = 0; }; // One implementation per camera model. class ICasparModelAdapter { public: virtual ~ICasparModelAdapter() = default; virtual CameraModelId ModelId() const = 0; // Number of floats in the focal_and_extra/focal and principal_point // node arrays per camera. virtual size_t FocalAndExtraSize() const = 0; virtual size_t PrincipalPointSize() const = 0; // Number of floats in the merged Calib node per camera // (= FocalAndExtraSize() + PrincipalPointSize()). virtual size_t CalibSize() const = 0; virtual void SetCalibNodes(caspar::GraphSolver& solver, StorageType* data, size_t n) const = 0; virtual void GetCalibNodes(caspar::GraphSolver& solver, StorageType* data, size_t n) const = 0; virtual void FillSizing(CasparSolverSizing& sz, const ModelData& md, size_t num_calibs) const = 0; // Append focal_and_extra / focal / principal_point params from a // camera into a flat output vector. virtual void ExtractFocalAndExtra(const Camera& camera, std::vector& out) const = 0; virtual void ExtractPrincipalPoint(const Camera& camera, std::vector& out) const = 0; virtual void WriteFocalAndExtra(Camera& camera, const StorageType* focal_and_extra_data, size_t idx) const = 0; virtual void WritePrincipalPoint(Camera& camera, const StorageType* principal_point_data, size_t idx) const = 0; virtual void SetPoseNodes(caspar::GraphSolver& solver, StorageType* data, size_t n) const = 0; virtual void GetPoseNodes(caspar::GraphSolver& solver, StorageType* data, size_t n) const = 0; virtual void SetFocalAndExtraNodes(caspar::GraphSolver& solver, StorageType* data, size_t n) const = 0; virtual void GetFocalAndExtraNodes(caspar::GraphSolver& solver, StorageType* data, size_t n) const = 0; virtual void SetPrincipalPointNodes(caspar::GraphSolver& solver, StorageType* data, size_t n) const = 0; virtual void GetPrincipalPointNodes(caspar::GraphSolver& solver, StorageType* data, size_t n) const = 0; virtual void SetVariantFactors(caspar::GraphSolver& solver, FactorVariant variant, const VariantData& data) const = 0; }; // SimpleRadial implementation class SimpleRadialAdapter : public ICasparModelAdapter { public: CameraModelId ModelId() const override { return CameraModelId::kSimpleRadial; } // SimpleRadial: params = [f, cx, cy, k] // focal_and_extra = [f, k] (non-contiguous in params array) // principal_point = [cx, cy] // merged calib = [f, k, cx, cy] size_t FocalAndExtraSize() const override { return 2; } size_t PrincipalPointSize() const override { return 2; } size_t CalibSize() const override { return FocalAndExtraSize() + PrincipalPointSize(); } void FillSizing(CasparSolverSizing& sz, const ModelData& md, size_t num_calibs) const override { sz.num_simple_radial_calibs = num_calibs; for (int v = 0; v < CASPAR_NUM_VARIANTS; ++v) { const size_t n = md.variants[v].num_factors; switch (static_cast(v)) { // Merged variants: both focal_and_extra and principal_point are // tunable. case FactorVariant::BASE: sz.num_simple_radial = n; break; case FactorVariant::FIXED_POSE: sz.num_simple_radial_fixed_pose = n; break; case FactorVariant::FIXED_POINT: sz.num_simple_radial_fixed_point = n; break; case FactorVariant::FIXED_POSE_FIXED_POINT: sz.num_simple_radial_fixed_pose_fixed_point = n; break; // Split variants: at least one intrinsic group is fixed. case FactorVariant::FIXED_FOCAL_AND_EXTRA: sz.num_simple_radial_split_fixed_focal_and_extra = n; break; case FactorVariant::FIXED_PRINCIPAL_POINT: sz.num_simple_radial_split_fixed_principal_point = n; break; case FactorVariant::FIXED_POSE_FIXED_FOCAL_AND_EXTRA: sz.num_simple_radial_split_fixed_pose_fixed_focal_and_extra = n; break; case FactorVariant::FIXED_POSE_FIXED_PRINCIPAL_POINT: sz.num_simple_radial_split_fixed_pose_fixed_principal_point = n; break; case FactorVariant::FIXED_FOCAL_AND_EXTRA_FIXED_PRINCIPAL_POINT: sz.num_simple_radial_split_fixed_focal_and_extra_fixed_principal_point = n; break; case FactorVariant::FIXED_FOCAL_AND_EXTRA_FIXED_POINT: sz.num_simple_radial_split_fixed_focal_and_extra_fixed_point = n; break; case FactorVariant::FIXED_PRINCIPAL_POINT_FIXED_POINT: sz.num_simple_radial_split_fixed_principal_point_fixed_point = n; break; case FactorVariant:: FIXED_POSE_FIXED_FOCAL_AND_EXTRA_FIXED_PRINCIPAL_POINT: sz.num_simple_radial_split_fixed_pose_fixed_focal_and_extra_fixed_principal_point = n; break; case FactorVariant::FIXED_POSE_FIXED_FOCAL_AND_EXTRA_FIXED_POINT: sz.num_simple_radial_split_fixed_pose_fixed_focal_and_extra_fixed_point = n; break; case FactorVariant::FIXED_POSE_FIXED_PRINCIPAL_POINT_FIXED_POINT: sz.num_simple_radial_split_fixed_pose_fixed_principal_point_fixed_point = n; break; case FactorVariant:: FIXED_FOCAL_AND_EXTRA_FIXED_PRINCIPAL_POINT_FIXED_POINT: sz.num_simple_radial_split_fixed_focal_and_extra_fixed_principal_point_fixed_point = n; break; } } } void ExtractFocalAndExtra(const Camera& camera, std::vector& out) const override { out.push_back(static_cast(camera.params[0])); // f out.push_back(static_cast(camera.params[3])); // k } void ExtractPrincipalPoint(const Camera& camera, std::vector& out) const override { out.push_back(static_cast(camera.params[1])); // cx out.push_back(static_cast(camera.params[2])); // cy } void WriteFocalAndExtra(Camera& camera, const StorageType* data, size_t idx) const override { camera.params[0] = static_cast(data[idx * FocalAndExtraSize() + 0]); // f camera.params[3] = static_cast(data[idx * FocalAndExtraSize() + 1]); // k } void WritePrincipalPoint(Camera& camera, const StorageType* data, size_t idx) const override { camera.params[1] = static_cast(data[idx * PrincipalPointSize() + 0]); // cx camera.params[2] = static_cast(data[idx * PrincipalPointSize() + 1]); // cy } void SetPoseNodes(caspar::GraphSolver& s, StorageType* data, size_t n) const override { s.SetSimpleRadialPoseNodesFromStackedHost(data, 0, n); } void GetPoseNodes(caspar::GraphSolver& s, StorageType* data, size_t n) const override { s.GetSimpleRadialPoseNodesToStackedHost(data, 0, n); } void SetFocalAndExtraNodes(caspar::GraphSolver& s, StorageType* data, size_t n) const override { s.SetSimpleRadialFocalAndExtraNodesFromStackedHost(data, 0, n); } void GetFocalAndExtraNodes(caspar::GraphSolver& s, StorageType* data, size_t n) const override { s.GetSimpleRadialFocalAndExtraNodesToStackedHost(data, 0, n); } void SetPrincipalPointNodes(caspar::GraphSolver& s, StorageType* data, size_t n) const override { s.SetSimpleRadialPrincipalPointNodesFromStackedHost(data, 0, n); } void GetPrincipalPointNodes(caspar::GraphSolver& s, StorageType* data, size_t n) const override { s.GetSimpleRadialPrincipalPointNodesToStackedHost(data, 0, n); } void SetCalibNodes(caspar::GraphSolver& s, StorageType* data, size_t n) const override { s.SetSimpleRadialCalibNodesFromStackedHost(data, 0, n); } void GetCalibNodes(caspar::GraphSolver& s, StorageType* data, size_t n) const override { s.GetSimpleRadialCalibNodesToStackedHost(data, 0, n); } void SetVariantFactors(caspar::GraphSolver& s, FactorVariant variant, const VariantData& d) const override { const size_t n = d.num_factors; switch (variant) { // Merged variants: the calib index is the same as // focal_and_extra_index, so no VariantData changes are needed for // these cases. case FactorVariant::BASE: s.SetSimpleRadialNum(n); s.SetSimpleRadialPoseIndicesFromHost(d.pose_indices.data(), n); s.SetSimpleRadialSensorFromRigDataFromStackedHost( d.sensor_from_rig_data.data(), 0, n); s.SetSimpleRadialCalibIndicesFromHost(d.focal_and_extra_indices.data(), n); s.SetSimpleRadialPointIndicesFromHost(d.point_indices.data(), n); s.SetSimpleRadialPixelDataFromStackedHost(d.pixels.data(), 0, n); break; case FactorVariant::FIXED_POSE: s.SetSimpleRadialFixedPoseNum(n); s.SetSimpleRadialFixedPoseSensorFromRigDataFromStackedHost( d.sensor_from_rig_data.data(), 0, n); s.SetSimpleRadialFixedPoseCalibIndicesFromHost( d.focal_and_extra_indices.data(), n); s.SetSimpleRadialFixedPosePointIndicesFromHost(d.point_indices.data(), n); s.SetSimpleRadialFixedPosePoseDataFromStackedHost( d.const_poses.data(), 0, n); s.SetSimpleRadialFixedPosePixelDataFromStackedHost( d.pixels.data(), 0, n); break; case FactorVariant::FIXED_FOCAL_AND_EXTRA: s.SetSimpleRadialSplitFixedFocalAndExtraNum(n); s.SetSimpleRadialSplitFixedFocalAndExtraPoseIndicesFromHost( d.pose_indices.data(), n); s.SetSimpleRadialSplitFixedFocalAndExtraSensorFromRigDataFromStackedHost( d.sensor_from_rig_data.data(), 0, n); s.SetSimpleRadialSplitFixedFocalAndExtraPrincipalPointIndicesFromHost( d.principal_point_indices.data(), n); s.SetSimpleRadialSplitFixedFocalAndExtraPointIndicesFromHost( d.point_indices.data(), n); s.SetSimpleRadialSplitFixedFocalAndExtraFocalAndExtraDataFromStackedHost( d.const_focal_and_extra.data(), 0, n); s.SetSimpleRadialSplitFixedFocalAndExtraPixelDataFromStackedHost( d.pixels.data(), 0, n); break; case FactorVariant::FIXED_PRINCIPAL_POINT: s.SetSimpleRadialSplitFixedPrincipalPointNum(n); s.SetSimpleRadialSplitFixedPrincipalPointPoseIndicesFromHost( d.pose_indices.data(), n); s.SetSimpleRadialSplitFixedPrincipalPointSensorFromRigDataFromStackedHost( d.sensor_from_rig_data.data(), 0, n); s.SetSimpleRadialSplitFixedPrincipalPointFocalAndExtraIndicesFromHost( d.focal_and_extra_indices.data(), n); s.SetSimpleRadialSplitFixedPrincipalPointPointIndicesFromHost( d.point_indices.data(), n); s.SetSimpleRadialSplitFixedPrincipalPointPrincipalPointDataFromStackedHost( d.const_principal_point.data(), 0, n); s.SetSimpleRadialSplitFixedPrincipalPointPixelDataFromStackedHost( d.pixels.data(), 0, n); break; case FactorVariant::FIXED_POINT: s.SetSimpleRadialFixedPointNum(n); s.SetSimpleRadialFixedPointPoseIndicesFromHost(d.pose_indices.data(), n); s.SetSimpleRadialFixedPointSensorFromRigDataFromStackedHost( d.sensor_from_rig_data.data(), 0, n); s.SetSimpleRadialFixedPointCalibIndicesFromHost( d.focal_and_extra_indices.data(), n); s.SetSimpleRadialFixedPointPointDataFromStackedHost( d.const_points.data(), 0, n); s.SetSimpleRadialFixedPointPixelDataFromStackedHost( d.pixels.data(), 0, n); break; case FactorVariant::FIXED_POSE_FIXED_FOCAL_AND_EXTRA: s.SetSimpleRadialSplitFixedPoseFixedFocalAndExtraNum(n); s.SetSimpleRadialSplitFixedPoseFixedFocalAndExtraSensorFromRigDataFromStackedHost( d.sensor_from_rig_data.data(), 0, n); s.SetSimpleRadialSplitFixedPoseFixedFocalAndExtraPrincipalPointIndicesFromHost( d.principal_point_indices.data(), n); s.SetSimpleRadialSplitFixedPoseFixedFocalAndExtraPointIndicesFromHost( d.point_indices.data(), n); s.SetSimpleRadialSplitFixedPoseFixedFocalAndExtraPoseDataFromStackedHost( d.const_poses.data(), 0, n); s.SetSimpleRadialSplitFixedPoseFixedFocalAndExtraFocalAndExtraDataFromStackedHost( d.const_focal_and_extra.data(), 0, n); s.SetSimpleRadialSplitFixedPoseFixedFocalAndExtraPixelDataFromStackedHost( d.pixels.data(), 0, n); break; case FactorVariant::FIXED_POSE_FIXED_PRINCIPAL_POINT: s.SetSimpleRadialSplitFixedPoseFixedPrincipalPointNum(n); s.SetSimpleRadialSplitFixedPoseFixedPrincipalPointSensorFromRigDataFromStackedHost( d.sensor_from_rig_data.data(), 0, n); s.SetSimpleRadialSplitFixedPoseFixedPrincipalPointFocalAndExtraIndicesFromHost( d.focal_and_extra_indices.data(), n); s.SetSimpleRadialSplitFixedPoseFixedPrincipalPointPointIndicesFromHost( d.point_indices.data(), n); s.SetSimpleRadialSplitFixedPoseFixedPrincipalPointPoseDataFromStackedHost( d.const_poses.data(), 0, n); s.SetSimpleRadialSplitFixedPoseFixedPrincipalPointPrincipalPointDataFromStackedHost( d.const_principal_point.data(), 0, n); s.SetSimpleRadialSplitFixedPoseFixedPrincipalPointPixelDataFromStackedHost( d.pixels.data(), 0, n); break; case FactorVariant::FIXED_POSE_FIXED_POINT: s.SetSimpleRadialFixedPoseFixedPointNum(n); s.SetSimpleRadialFixedPoseFixedPointSensorFromRigDataFromStackedHost( d.sensor_from_rig_data.data(), 0, n); s.SetSimpleRadialFixedPoseFixedPointCalibIndicesFromHost( d.focal_and_extra_indices.data(), n); s.SetSimpleRadialFixedPoseFixedPointPoseDataFromStackedHost( d.const_poses.data(), 0, n); s.SetSimpleRadialFixedPoseFixedPointPointDataFromStackedHost( d.const_points.data(), 0, n); s.SetSimpleRadialFixedPoseFixedPointPixelDataFromStackedHost( d.pixels.data(), 0, n); break; case FactorVariant::FIXED_FOCAL_AND_EXTRA_FIXED_PRINCIPAL_POINT: s.SetSimpleRadialSplitFixedFocalAndExtraFixedPrincipalPointNum(n); s.SetSimpleRadialSplitFixedFocalAndExtraFixedPrincipalPointPoseIndicesFromHost( d.pose_indices.data(), n); s.SetSimpleRadialSplitFixedFocalAndExtraFixedPrincipalPointSensorFromRigDataFromStackedHost( d.sensor_from_rig_data.data(), 0, n); s.SetSimpleRadialSplitFixedFocalAndExtraFixedPrincipalPointPointIndicesFromHost( d.point_indices.data(), n); s.SetSimpleRadialSplitFixedFocalAndExtraFixedPrincipalPointFocalAndExtraDataFromStackedHost( d.const_focal_and_extra.data(), 0, n); s.SetSimpleRadialSplitFixedFocalAndExtraFixedPrincipalPointPrincipalPointDataFromStackedHost( d.const_principal_point.data(), 0, n); s.SetSimpleRadialSplitFixedFocalAndExtraFixedPrincipalPointPixelDataFromStackedHost( d.pixels.data(), 0, n); break; case FactorVariant::FIXED_FOCAL_AND_EXTRA_FIXED_POINT: s.SetSimpleRadialSplitFixedFocalAndExtraFixedPointNum(n); s.SetSimpleRadialSplitFixedFocalAndExtraFixedPointPoseIndicesFromHost( d.pose_indices.data(), n); s.SetSimpleRadialSplitFixedFocalAndExtraFixedPointSensorFromRigDataFromStackedHost( d.sensor_from_rig_data.data(), 0, n); s.SetSimpleRadialSplitFixedFocalAndExtraFixedPointPrincipalPointIndicesFromHost( d.principal_point_indices.data(), n); s.SetSimpleRadialSplitFixedFocalAndExtraFixedPointFocalAndExtraDataFromStackedHost( d.const_focal_and_extra.data(), 0, n); s.SetSimpleRadialSplitFixedFocalAndExtraFixedPointPointDataFromStackedHost( d.const_points.data(), 0, n); s.SetSimpleRadialSplitFixedFocalAndExtraFixedPointPixelDataFromStackedHost( d.pixels.data(), 0, n); break; case FactorVariant::FIXED_PRINCIPAL_POINT_FIXED_POINT: s.SetSimpleRadialSplitFixedPrincipalPointFixedPointNum(n); s.SetSimpleRadialSplitFixedPrincipalPointFixedPointPoseIndicesFromHost( d.pose_indices.data(), n); s.SetSimpleRadialSplitFixedPrincipalPointFixedPointSensorFromRigDataFromStackedHost( d.sensor_from_rig_data.data(), 0, n); s.SetSimpleRadialSplitFixedPrincipalPointFixedPointFocalAndExtraIndicesFromHost( d.focal_and_extra_indices.data(), n); s.SetSimpleRadialSplitFixedPrincipalPointFixedPointPrincipalPointDataFromStackedHost( d.const_principal_point.data(), 0, n); s.SetSimpleRadialSplitFixedPrincipalPointFixedPointPointDataFromStackedHost( d.const_points.data(), 0, n); s.SetSimpleRadialSplitFixedPrincipalPointFixedPointPixelDataFromStackedHost( d.pixels.data(), 0, n); break; case FactorVariant:: FIXED_POSE_FIXED_FOCAL_AND_EXTRA_FIXED_PRINCIPAL_POINT: s.SetSimpleRadialSplitFixedPoseFixedFocalAndExtraFixedPrincipalPointNum( n); s.SetSimpleRadialSplitFixedPoseFixedFocalAndExtraFixedPrincipalPointSensorFromRigDataFromStackedHost( d.sensor_from_rig_data.data(), 0, n); s.SetSimpleRadialSplitFixedPoseFixedFocalAndExtraFixedPrincipalPointPointIndicesFromHost( d.point_indices.data(), n); s.SetSimpleRadialSplitFixedPoseFixedFocalAndExtraFixedPrincipalPointPoseDataFromStackedHost( d.const_poses.data(), 0, n); s.SetSimpleRadialSplitFixedPoseFixedFocalAndExtraFixedPrincipalPointFocalAndExtraDataFromStackedHost( d.const_focal_and_extra.data(), 0, n); s.SetSimpleRadialSplitFixedPoseFixedFocalAndExtraFixedPrincipalPointPrincipalPointDataFromStackedHost( d.const_principal_point.data(), 0, n); s.SetSimpleRadialSplitFixedPoseFixedFocalAndExtraFixedPrincipalPointPixelDataFromStackedHost( d.pixels.data(), 0, n); break; case FactorVariant::FIXED_POSE_FIXED_FOCAL_AND_EXTRA_FIXED_POINT: s.SetSimpleRadialSplitFixedPoseFixedFocalAndExtraFixedPointNum(n); s.SetSimpleRadialSplitFixedPoseFixedFocalAndExtraFixedPointSensorFromRigDataFromStackedHost( d.sensor_from_rig_data.data(), 0, n); s.SetSimpleRadialSplitFixedPoseFixedFocalAndExtraFixedPointPrincipalPointIndicesFromHost( d.principal_point_indices.data(), n); s.SetSimpleRadialSplitFixedPoseFixedFocalAndExtraFixedPointPoseDataFromStackedHost( d.const_poses.data(), 0, n); s.SetSimpleRadialSplitFixedPoseFixedFocalAndExtraFixedPointFocalAndExtraDataFromStackedHost( d.const_focal_and_extra.data(), 0, n); s.SetSimpleRadialSplitFixedPoseFixedFocalAndExtraFixedPointPointDataFromStackedHost( d.const_points.data(), 0, n); s.SetSimpleRadialSplitFixedPoseFixedFocalAndExtraFixedPointPixelDataFromStackedHost( d.pixels.data(), 0, n); break; case FactorVariant::FIXED_POSE_FIXED_PRINCIPAL_POINT_FIXED_POINT: s.SetSimpleRadialSplitFixedPoseFixedPrincipalPointFixedPointNum(n); s.SetSimpleRadialSplitFixedPoseFixedPrincipalPointFixedPointSensorFromRigDataFromStackedHost( d.sensor_from_rig_data.data(), 0, n); s.SetSimpleRadialSplitFixedPoseFixedPrincipalPointFixedPointFocalAndExtraIndicesFromHost( d.focal_and_extra_indices.data(), n); s.SetSimpleRadialSplitFixedPoseFixedPrincipalPointFixedPointPoseDataFromStackedHost( d.const_poses.data(), 0, n); s.SetSimpleRadialSplitFixedPoseFixedPrincipalPointFixedPointPrincipalPointDataFromStackedHost( d.const_principal_point.data(), 0, n); s.SetSimpleRadialSplitFixedPoseFixedPrincipalPointFixedPointPointDataFromStackedHost( d.const_points.data(), 0, n); s.SetSimpleRadialSplitFixedPoseFixedPrincipalPointFixedPointPixelDataFromStackedHost( d.pixels.data(), 0, n); break; case FactorVariant:: FIXED_FOCAL_AND_EXTRA_FIXED_PRINCIPAL_POINT_FIXED_POINT: s.SetSimpleRadialSplitFixedFocalAndExtraFixedPrincipalPointFixedPointNum( n); s.SetSimpleRadialSplitFixedFocalAndExtraFixedPrincipalPointFixedPointPoseIndicesFromHost( d.pose_indices.data(), n); s.SetSimpleRadialSplitFixedFocalAndExtraFixedPrincipalPointFixedPointSensorFromRigDataFromStackedHost( d.sensor_from_rig_data.data(), 0, n); s.SetSimpleRadialSplitFixedFocalAndExtraFixedPrincipalPointFixedPointFocalAndExtraDataFromStackedHost( d.const_focal_and_extra.data(), 0, n); s.SetSimpleRadialSplitFixedFocalAndExtraFixedPrincipalPointFixedPointPrincipalPointDataFromStackedHost( d.const_principal_point.data(), 0, n); s.SetSimpleRadialSplitFixedFocalAndExtraFixedPrincipalPointFixedPointPointDataFromStackedHost( d.const_points.data(), 0, n); s.SetSimpleRadialSplitFixedFocalAndExtraFixedPrincipalPointFixedPointPixelDataFromStackedHost( d.pixels.data(), 0, n); break; } } }; // Pinhole implementation class PinholeAdapter : public ICasparModelAdapter { public: CameraModelId ModelId() const override { return CameraModelId::kPinhole; } // Pinhole: params = [fx, fy, cx, cy] // focal = [fx, fy] // principal_point = [cx, cy] // merged calib = [fx, fy, cx, cy] size_t FocalAndExtraSize() const override { return 2; } size_t PrincipalPointSize() const override { return 2; } size_t CalibSize() const override { return FocalAndExtraSize() + PrincipalPointSize(); } void FillSizing(CasparSolverSizing& sz, const ModelData& md, size_t num_calibs) const override { sz.num_pinhole_calibs = num_calibs; for (int v = 0; v < CASPAR_NUM_VARIANTS; ++v) { const size_t n = md.variants[v].num_factors; switch (static_cast(v)) { // Merged variants: both focal and principal_point are tunable. case FactorVariant::BASE: sz.num_pinhole = n; break; case FactorVariant::FIXED_POSE: sz.num_pinhole_fixed_pose = n; break; case FactorVariant::FIXED_POINT: sz.num_pinhole_fixed_point = n; break; case FactorVariant::FIXED_POSE_FIXED_POINT: sz.num_pinhole_fixed_pose_fixed_point = n; break; // Split variants: at least one intrinsic group is fixed. case FactorVariant::FIXED_FOCAL_AND_EXTRA: sz.num_pinhole_split_fixed_focal = n; break; case FactorVariant::FIXED_PRINCIPAL_POINT: sz.num_pinhole_split_fixed_principal_point = n; break; case FactorVariant::FIXED_POSE_FIXED_FOCAL_AND_EXTRA: sz.num_pinhole_split_fixed_pose_fixed_focal = n; break; case FactorVariant::FIXED_POSE_FIXED_PRINCIPAL_POINT: sz.num_pinhole_split_fixed_pose_fixed_principal_point = n; break; case FactorVariant::FIXED_FOCAL_AND_EXTRA_FIXED_PRINCIPAL_POINT: sz.num_pinhole_split_fixed_focal_fixed_principal_point = n; break; case FactorVariant::FIXED_FOCAL_AND_EXTRA_FIXED_POINT: sz.num_pinhole_split_fixed_focal_fixed_point = n; break; case FactorVariant::FIXED_PRINCIPAL_POINT_FIXED_POINT: sz.num_pinhole_split_fixed_principal_point_fixed_point = n; break; case FactorVariant:: FIXED_POSE_FIXED_FOCAL_AND_EXTRA_FIXED_PRINCIPAL_POINT: sz.num_pinhole_split_fixed_pose_fixed_focal_fixed_principal_point = n; break; case FactorVariant::FIXED_POSE_FIXED_FOCAL_AND_EXTRA_FIXED_POINT: sz.num_pinhole_split_fixed_pose_fixed_focal_fixed_point = n; break; case FactorVariant::FIXED_POSE_FIXED_PRINCIPAL_POINT_FIXED_POINT: sz.num_pinhole_split_fixed_pose_fixed_principal_point_fixed_point = n; break; case FactorVariant:: FIXED_FOCAL_AND_EXTRA_FIXED_PRINCIPAL_POINT_FIXED_POINT: sz.num_pinhole_split_fixed_focal_fixed_principal_point_fixed_point = n; break; } } } void ExtractFocalAndExtra(const Camera& camera, std::vector& out) const override { out.push_back(static_cast(camera.params[0])); // fx out.push_back(static_cast(camera.params[1])); // fy } void ExtractPrincipalPoint(const Camera& camera, std::vector& out) const override { out.push_back(static_cast(camera.params[2])); // cx out.push_back(static_cast(camera.params[3])); // cy } void WriteFocalAndExtra(Camera& camera, const StorageType* data, size_t idx) const override { camera.params[0] = static_cast(data[idx * FocalAndExtraSize() + 0]); // fx camera.params[1] = static_cast(data[idx * FocalAndExtraSize() + 1]); // fy } void WritePrincipalPoint(Camera& camera, const StorageType* data, size_t idx) const override { camera.params[2] = static_cast(data[idx * PrincipalPointSize() + 0]); // cx camera.params[3] = static_cast(data[idx * PrincipalPointSize() + 1]); // cy } void SetPoseNodes(caspar::GraphSolver& s, StorageType* data, size_t n) const override { s.SetPinholePoseNodesFromStackedHost(data, 0, n); } void GetPoseNodes(caspar::GraphSolver& s, StorageType* data, size_t n) const override { s.GetPinholePoseNodesToStackedHost(data, 0, n); } void SetFocalAndExtraNodes(caspar::GraphSolver& s, StorageType* data, size_t n) const override { s.SetPinholeFocalNodesFromStackedHost(data, 0, n); } void GetFocalAndExtraNodes(caspar::GraphSolver& s, StorageType* data, size_t n) const override { s.GetPinholeFocalNodesToStackedHost(data, 0, n); } void SetPrincipalPointNodes(caspar::GraphSolver& s, StorageType* data, size_t n) const override { s.SetPinholePrincipalPointNodesFromStackedHost(data, 0, n); } void GetPrincipalPointNodes(caspar::GraphSolver& s, StorageType* data, size_t n) const override { s.GetPinholePrincipalPointNodesToStackedHost(data, 0, n); } void SetCalibNodes(caspar::GraphSolver& s, StorageType* data, size_t n) const override { s.SetPinholeCalibNodesFromStackedHost(data, 0, n); } void GetCalibNodes(caspar::GraphSolver& s, StorageType* data, size_t n) const override { s.GetPinholeCalibNodesToStackedHost(data, 0, n); } void SetVariantFactors(caspar::GraphSolver& s, FactorVariant variant, const VariantData& d) const override { const size_t n = d.num_factors; switch (variant) { // Merged variants: the calib index is the same as focal_index, // so no VariantData changes are needed for these cases. case FactorVariant::BASE: s.SetPinholeNum(n); s.SetPinholePoseIndicesFromHost(d.pose_indices.data(), n); s.SetPinholeSensorFromRigDataFromStackedHost( d.sensor_from_rig_data.data(), 0, n); s.SetPinholeCalibIndicesFromHost(d.focal_and_extra_indices.data(), n); s.SetPinholePointIndicesFromHost(d.point_indices.data(), n); s.SetPinholePixelDataFromStackedHost(d.pixels.data(), 0, n); break; case FactorVariant::FIXED_POSE: s.SetPinholeFixedPoseNum(n); s.SetPinholeFixedPoseSensorFromRigDataFromStackedHost( d.sensor_from_rig_data.data(), 0, n); s.SetPinholeFixedPoseCalibIndicesFromHost( d.focal_and_extra_indices.data(), n); s.SetPinholeFixedPosePointIndicesFromHost(d.point_indices.data(), n); s.SetPinholeFixedPosePoseDataFromStackedHost( d.const_poses.data(), 0, n); s.SetPinholeFixedPosePixelDataFromStackedHost(d.pixels.data(), 0, n); break; case FactorVariant::FIXED_FOCAL_AND_EXTRA: s.SetPinholeSplitFixedFocalNum(n); s.SetPinholeSplitFixedFocalPoseIndicesFromHost(d.pose_indices.data(), n); s.SetPinholeSplitFixedFocalSensorFromRigDataFromStackedHost( d.sensor_from_rig_data.data(), 0, n); s.SetPinholeSplitFixedFocalPrincipalPointIndicesFromHost( d.principal_point_indices.data(), n); s.SetPinholeSplitFixedFocalPointIndicesFromHost(d.point_indices.data(), n); s.SetPinholeSplitFixedFocalFocalDataFromStackedHost( d.const_focal_and_extra.data(), 0, n); s.SetPinholeSplitFixedFocalPixelDataFromStackedHost( d.pixels.data(), 0, n); break; case FactorVariant::FIXED_PRINCIPAL_POINT: s.SetPinholeSplitFixedPrincipalPointNum(n); s.SetPinholeSplitFixedPrincipalPointPoseIndicesFromHost( d.pose_indices.data(), n); s.SetPinholeSplitFixedPrincipalPointSensorFromRigDataFromStackedHost( d.sensor_from_rig_data.data(), 0, n); s.SetPinholeSplitFixedPrincipalPointFocalIndicesFromHost( d.focal_and_extra_indices.data(), n); s.SetPinholeSplitFixedPrincipalPointPointIndicesFromHost( d.point_indices.data(), n); s.SetPinholeSplitFixedPrincipalPointPrincipalPointDataFromStackedHost( d.const_principal_point.data(), 0, n); s.SetPinholeSplitFixedPrincipalPointPixelDataFromStackedHost( d.pixels.data(), 0, n); break; case FactorVariant::FIXED_POINT: s.SetPinholeFixedPointNum(n); s.SetPinholeFixedPointPoseIndicesFromHost(d.pose_indices.data(), n); s.SetPinholeFixedPointSensorFromRigDataFromStackedHost( d.sensor_from_rig_data.data(), 0, n); s.SetPinholeFixedPointCalibIndicesFromHost( d.focal_and_extra_indices.data(), n); s.SetPinholeFixedPointPointDataFromStackedHost( d.const_points.data(), 0, n); s.SetPinholeFixedPointPixelDataFromStackedHost(d.pixels.data(), 0, n); break; case FactorVariant::FIXED_POSE_FIXED_FOCAL_AND_EXTRA: s.SetPinholeSplitFixedPoseFixedFocalNum(n); s.SetPinholeSplitFixedPoseFixedFocalSensorFromRigDataFromStackedHost( d.sensor_from_rig_data.data(), 0, n); s.SetPinholeSplitFixedPoseFixedFocalPrincipalPointIndicesFromHost( d.principal_point_indices.data(), n); s.SetPinholeSplitFixedPoseFixedFocalPointIndicesFromHost( d.point_indices.data(), n); s.SetPinholeSplitFixedPoseFixedFocalPoseDataFromStackedHost( d.const_poses.data(), 0, n); s.SetPinholeSplitFixedPoseFixedFocalFocalDataFromStackedHost( d.const_focal_and_extra.data(), 0, n); s.SetPinholeSplitFixedPoseFixedFocalPixelDataFromStackedHost( d.pixels.data(), 0, n); break; case FactorVariant::FIXED_POSE_FIXED_PRINCIPAL_POINT: s.SetPinholeSplitFixedPoseFixedPrincipalPointNum(n); s.SetPinholeSplitFixedPoseFixedPrincipalPointSensorFromRigDataFromStackedHost( d.sensor_from_rig_data.data(), 0, n); s.SetPinholeSplitFixedPoseFixedPrincipalPointFocalIndicesFromHost( d.focal_and_extra_indices.data(), n); s.SetPinholeSplitFixedPoseFixedPrincipalPointPointIndicesFromHost( d.point_indices.data(), n); s.SetPinholeSplitFixedPoseFixedPrincipalPointPoseDataFromStackedHost( d.const_poses.data(), 0, n); s.SetPinholeSplitFixedPoseFixedPrincipalPointPrincipalPointDataFromStackedHost( d.const_principal_point.data(), 0, n); s.SetPinholeSplitFixedPoseFixedPrincipalPointPixelDataFromStackedHost( d.pixels.data(), 0, n); break; case FactorVariant::FIXED_POSE_FIXED_POINT: s.SetPinholeFixedPoseFixedPointNum(n); s.SetPinholeFixedPoseFixedPointSensorFromRigDataFromStackedHost( d.sensor_from_rig_data.data(), 0, n); s.SetPinholeFixedPoseFixedPointCalibIndicesFromHost( d.focal_and_extra_indices.data(), n); s.SetPinholeFixedPoseFixedPointPoseDataFromStackedHost( d.const_poses.data(), 0, n); s.SetPinholeFixedPoseFixedPointPointDataFromStackedHost( d.const_points.data(), 0, n); s.SetPinholeFixedPoseFixedPointPixelDataFromStackedHost( d.pixels.data(), 0, n); break; case FactorVariant::FIXED_FOCAL_AND_EXTRA_FIXED_PRINCIPAL_POINT: s.SetPinholeSplitFixedFocalFixedPrincipalPointNum(n); s.SetPinholeSplitFixedFocalFixedPrincipalPointPoseIndicesFromHost( d.pose_indices.data(), n); s.SetPinholeSplitFixedFocalFixedPrincipalPointSensorFromRigDataFromStackedHost( d.sensor_from_rig_data.data(), 0, n); s.SetPinholeSplitFixedFocalFixedPrincipalPointPointIndicesFromHost( d.point_indices.data(), n); s.SetPinholeSplitFixedFocalFixedPrincipalPointFocalDataFromStackedHost( d.const_focal_and_extra.data(), 0, n); s.SetPinholeSplitFixedFocalFixedPrincipalPointPrincipalPointDataFromStackedHost( d.const_principal_point.data(), 0, n); s.SetPinholeSplitFixedFocalFixedPrincipalPointPixelDataFromStackedHost( d.pixels.data(), 0, n); break; case FactorVariant::FIXED_FOCAL_AND_EXTRA_FIXED_POINT: s.SetPinholeSplitFixedFocalFixedPointNum(n); s.SetPinholeSplitFixedFocalFixedPointPoseIndicesFromHost( d.pose_indices.data(), n); s.SetPinholeSplitFixedFocalFixedPointSensorFromRigDataFromStackedHost( d.sensor_from_rig_data.data(), 0, n); s.SetPinholeSplitFixedFocalFixedPointPrincipalPointIndicesFromHost( d.principal_point_indices.data(), n); s.SetPinholeSplitFixedFocalFixedPointFocalDataFromStackedHost( d.const_focal_and_extra.data(), 0, n); s.SetPinholeSplitFixedFocalFixedPointPointDataFromStackedHost( d.const_points.data(), 0, n); s.SetPinholeSplitFixedFocalFixedPointPixelDataFromStackedHost( d.pixels.data(), 0, n); break; case FactorVariant::FIXED_PRINCIPAL_POINT_FIXED_POINT: s.SetPinholeSplitFixedPrincipalPointFixedPointNum(n); s.SetPinholeSplitFixedPrincipalPointFixedPointPoseIndicesFromHost( d.pose_indices.data(), n); s.SetPinholeSplitFixedPrincipalPointFixedPointSensorFromRigDataFromStackedHost( d.sensor_from_rig_data.data(), 0, n); s.SetPinholeSplitFixedPrincipalPointFixedPointFocalIndicesFromHost( d.focal_and_extra_indices.data(), n); s.SetPinholeSplitFixedPrincipalPointFixedPointPrincipalPointDataFromStackedHost( d.const_principal_point.data(), 0, n); s.SetPinholeSplitFixedPrincipalPointFixedPointPointDataFromStackedHost( d.const_points.data(), 0, n); s.SetPinholeSplitFixedPrincipalPointFixedPointPixelDataFromStackedHost( d.pixels.data(), 0, n); break; case FactorVariant:: FIXED_POSE_FIXED_FOCAL_AND_EXTRA_FIXED_PRINCIPAL_POINT: s.SetPinholeSplitFixedPoseFixedFocalFixedPrincipalPointNum(n); s.SetPinholeSplitFixedPoseFixedFocalFixedPrincipalPointSensorFromRigDataFromStackedHost( d.sensor_from_rig_data.data(), 0, n); s.SetPinholeSplitFixedPoseFixedFocalFixedPrincipalPointPointIndicesFromHost( d.point_indices.data(), n); s.SetPinholeSplitFixedPoseFixedFocalFixedPrincipalPointPoseDataFromStackedHost( d.const_poses.data(), 0, n); s.SetPinholeSplitFixedPoseFixedFocalFixedPrincipalPointFocalDataFromStackedHost( d.const_focal_and_extra.data(), 0, n); s.SetPinholeSplitFixedPoseFixedFocalFixedPrincipalPointPrincipalPointDataFromStackedHost( d.const_principal_point.data(), 0, n); s.SetPinholeSplitFixedPoseFixedFocalFixedPrincipalPointPixelDataFromStackedHost( d.pixels.data(), 0, n); break; case FactorVariant::FIXED_POSE_FIXED_FOCAL_AND_EXTRA_FIXED_POINT: s.SetPinholeSplitFixedPoseFixedFocalFixedPointNum(n); s.SetPinholeSplitFixedPoseFixedFocalFixedPointSensorFromRigDataFromStackedHost( d.sensor_from_rig_data.data(), 0, n); s.SetPinholeSplitFixedPoseFixedFocalFixedPointPrincipalPointIndicesFromHost( d.principal_point_indices.data(), n); s.SetPinholeSplitFixedPoseFixedFocalFixedPointPoseDataFromStackedHost( d.const_poses.data(), 0, n); s.SetPinholeSplitFixedPoseFixedFocalFixedPointFocalDataFromStackedHost( d.const_focal_and_extra.data(), 0, n); s.SetPinholeSplitFixedPoseFixedFocalFixedPointPointDataFromStackedHost( d.const_points.data(), 0, n); s.SetPinholeSplitFixedPoseFixedFocalFixedPointPixelDataFromStackedHost( d.pixels.data(), 0, n); break; case FactorVariant::FIXED_POSE_FIXED_PRINCIPAL_POINT_FIXED_POINT: s.SetPinholeSplitFixedPoseFixedPrincipalPointFixedPointNum(n); s.SetPinholeSplitFixedPoseFixedPrincipalPointFixedPointSensorFromRigDataFromStackedHost( d.sensor_from_rig_data.data(), 0, n); s.SetPinholeSplitFixedPoseFixedPrincipalPointFixedPointFocalIndicesFromHost( d.focal_and_extra_indices.data(), n); s.SetPinholeSplitFixedPoseFixedPrincipalPointFixedPointPoseDataFromStackedHost( d.const_poses.data(), 0, n); s.SetPinholeSplitFixedPoseFixedPrincipalPointFixedPointPrincipalPointDataFromStackedHost( d.const_principal_point.data(), 0, n); s.SetPinholeSplitFixedPoseFixedPrincipalPointFixedPointPointDataFromStackedHost( d.const_points.data(), 0, n); s.SetPinholeSplitFixedPoseFixedPrincipalPointFixedPointPixelDataFromStackedHost( d.pixels.data(), 0, n); break; case FactorVariant:: FIXED_FOCAL_AND_EXTRA_FIXED_PRINCIPAL_POINT_FIXED_POINT: s.SetPinholeSplitFixedFocalFixedPrincipalPointFixedPointNum(n); s.SetPinholeSplitFixedFocalFixedPrincipalPointFixedPointPoseIndicesFromHost( d.pose_indices.data(), n); s.SetPinholeSplitFixedFocalFixedPrincipalPointFixedPointSensorFromRigDataFromStackedHost( d.sensor_from_rig_data.data(), 0, n); s.SetPinholeSplitFixedFocalFixedPrincipalPointFixedPointFocalDataFromStackedHost( d.const_focal_and_extra.data(), 0, n); s.SetPinholeSplitFixedFocalFixedPrincipalPointFixedPointPrincipalPointDataFromStackedHost( d.const_principal_point.data(), 0, n); s.SetPinholeSplitFixedFocalFixedPrincipalPointFixedPointPointDataFromStackedHost( d.const_points.data(), 0, n); s.SetPinholeSplitFixedFocalFixedPrincipalPointFixedPointPixelDataFromStackedHost( d.pixels.data(), 0, n); break; } } }; inline std::unique_ptr CreateCasparAdapter( const CameraModelId model_id) { switch (model_id) { case CameraModelId::kSimpleRadial: return std::make_unique(); case CameraModelId::kPinhole: return std::make_unique(); default: return nullptr; } } // WARNING: Argument order is opaque and bug-prone and will change in a future // Caspar release. Order: // 1. Node type counts, alphabetical by type name // 2. Factor counts, in registration order from caspar_generate.py: // simple_radial (4) → pinhole (4) → // simple_radial_split (11) → pinhole_split (11) inline caspar::GraphSolver CreateSolver( const caspar::SolverParams& params, const CasparSolverSizing& sz, size_t device_id = 0) { return caspar::GraphSolver( params, // Node type counts (alphabetical): // PinholeCalib, PinholeFocal, PinholePose, // PinholePrincipalPoint, Point, // SimpleRadialCalib, SimpleRadialFocalAndExtra, // SimpleRadialPose, SimpleRadialPrincipalPoint sz.num_pinhole_calibs, // PinholeCalib (merged pool) sz.num_pinhole_calibs, // PinholeFocal (split pool) sz.num_pinhole_poses, // PinholePose sz.num_pinhole_calibs, // PinholePrincipalPoint (split pool) sz.num_points, // Point sz.num_simple_radial_calibs, // SimpleRadialCalib (merged // pool) sz.num_simple_radial_calibs, // SimpleRadialFocalAndExtra (split // pool) sz.num_simple_radial_poses, // SimpleRadialPose sz.num_simple_radial_calibs, // SimpleRadialPrincipalPoint (split // pool) // simple_radial factor counts (r=0..2 over {pose, point}): sz.num_simple_radial, // {} sz.num_simple_radial_fixed_pose, // {pose} sz.num_simple_radial_fixed_point, // {point} sz.num_simple_radial_fixed_pose_fixed_point, // {pose, point} // pinhole factor counts (same order): sz.num_pinhole, // {} sz.num_pinhole_fixed_pose, // {pose} sz.num_pinhole_fixed_point, // {point} sz.num_pinhole_fixed_pose_fixed_point, // {pose, point} // simple_radial_split factor counts (11 variants, must_fix_one_of): sz.num_simple_radial_split_fixed_focal_and_extra, // r=1 {fae} sz.num_simple_radial_split_fixed_principal_point, // r=1 {pp} sz.num_simple_radial_split_fixed_pose_fixed_focal_and_extra, // r=2 // {pose,fad} sz.num_simple_radial_split_fixed_pose_fixed_principal_point, // r=2 // {pose,pp} sz.num_simple_radial_split_fixed_focal_and_extra_fixed_principal_point, // r=2 {fae,pp} sz.num_simple_radial_split_fixed_focal_and_extra_fixed_point, // r=2 // {fae,pt} sz.num_simple_radial_split_fixed_principal_point_fixed_point, // r=2 // {pp,pt} sz.num_simple_radial_split_fixed_pose_fixed_focal_and_extra_fixed_principal_point, // r=3 sz.num_simple_radial_split_fixed_pose_fixed_focal_and_extra_fixed_point, // r=3 sz.num_simple_radial_split_fixed_pose_fixed_principal_point_fixed_point, // r=3 sz.num_simple_radial_split_fixed_focal_and_extra_fixed_principal_point_fixed_point, // r=3 // pinhole_split factor counts (same 11-variant order): sz.num_pinhole_split_fixed_focal, // r=1 {f} sz.num_pinhole_split_fixed_principal_point, // r=1 {pp} sz.num_pinhole_split_fixed_pose_fixed_focal, // r=2 {pose,f} sz.num_pinhole_split_fixed_pose_fixed_principal_point, // r=2 {pose,pp} sz.num_pinhole_split_fixed_focal_fixed_principal_point, // r=2 {f,pp} sz.num_pinhole_split_fixed_focal_fixed_point, // r=2 {f,pt} sz.num_pinhole_split_fixed_principal_point_fixed_point, // r=2 {pp,pt} sz.num_pinhole_split_fixed_pose_fixed_focal_fixed_principal_point, // r=3 sz.num_pinhole_split_fixed_pose_fixed_focal_fixed_point, // r=3 sz.num_pinhole_split_fixed_pose_fixed_principal_point_fixed_point, // r=3 sz.num_pinhole_split_fixed_focal_fixed_principal_point_fixed_point, // r=3 device_id); } } // namespace colmap colmap-4.2.0/src/colmap/estimators/coordinate_frame.cc000066400000000000000000000344531524536416500230600ustar00rootroot00000000000000// Copyright (c), ETH Zurich and UNC Chapel Hill. // All rights reserved. // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are met: // // * Redistributions of source code must retain the above copyright // notice, this list of conditions and the following disclaimer. // // * Redistributions in binary form must reproduce the above copyright // notice, this list of conditions and the following disclaimer in the // documentation and/or other materials provided with the distribution. // // * Neither the name of ETH Zurich and UNC Chapel Hill nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE // ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE // LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR // CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF // SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS // INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN // CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) // ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE // POSSIBILITY OF SUCH DAMAGE. #include "colmap/estimators/coordinate_frame.h" #include "colmap/geometry/gps.h" #include "colmap/geometry/pose.h" #include "colmap/image/line.h" #include "colmap/image/undistortion.h" #include "colmap/math/math.h" #include "colmap/optim/ransac.h" #include "colmap/util/logging.h" #include "colmap/util/misc.h" namespace colmap { namespace { Eigen::Vector3d FindBestConsensusAxis(const std::vector& axes, const double max_distance) { if (axes.empty()) { return Eigen::Vector3d::Zero(); } std::vector inlier_idxs; inlier_idxs.reserve(axes.size()); std::vector best_inlier_idxs; best_inlier_idxs.reserve(axes.size()); double best_inlier_distance_sum = std::numeric_limits::max(); for (size_t i = 0; i < axes.size(); ++i) { const Eigen::Vector3d& ref_axis = axes[i]; double inlier_distance_sum = 0; inlier_idxs.clear(); for (size_t j = 0; j < axes.size(); ++j) { if (i == j) { inlier_idxs.push_back(j); } else { const double distance = 1 - ref_axis.dot(axes[j]); if (distance <= max_distance) { inlier_distance_sum += distance; inlier_idxs.push_back(j); } } } if (inlier_idxs.size() > best_inlier_idxs.size() || (inlier_idxs.size() == best_inlier_idxs.size() && inlier_distance_sum < best_inlier_distance_sum)) { best_inlier_distance_sum = inlier_distance_sum; best_inlier_idxs = inlier_idxs; } } if (best_inlier_idxs.empty()) { return Eigen::Vector3d::Zero(); } Eigen::Vector3d best_axis(0, 0, 0); for (const auto idx : best_inlier_idxs) { best_axis += axes[idx]; } best_axis /= best_inlier_idxs.size(); return best_axis; } } // namespace Eigen::Vector3d EstimateGravityVectorFromImageOrientation( const Reconstruction& reconstruction, const double max_axis_distance) { std::vector downward_axes; downward_axes.reserve(reconstruction.NumRegImages()); for (const auto image_id : reconstruction.RegImageIds()) { const auto& image = reconstruction.Image(image_id); downward_axes.push_back( image.CamFromWorld().rotation().toRotationMatrix().row(1)); } return FindBestConsensusAxis(downward_axes, max_axis_distance); } #ifdef COLMAP_LSD_ENABLED struct VanishingPointEstimator { // The line segments. using X_t = LineSegment; // The line representation of the segments. using Y_t = Eigen::Vector3d; // The vanishing point. using M_t = Eigen::Vector3d; // The minimum number of samples needed to estimate a model. static const int kMinNumSamples = 2; // Estimate the vanishing point from at least two line segments. static void Estimate(const std::vector& line_segments, const std::vector& lines, std::vector* models) { THROW_CHECK_EQ(line_segments.size(), 2); THROW_CHECK_EQ(lines.size(), 2); THROW_CHECK(models != nullptr); models->resize(1); (*models)[0] = lines[0].cross(lines[1]); } // Calculate the squared distance of each line segment's end point to the line // connecting the vanishing point and the midpoint of the line segment. static void Residuals(const std::vector& line_segments, const std::vector& lines, const M_t& vanishing_point, std::vector* residuals) { residuals->resize(line_segments.size()); // Check if vanishing point is at infinity. if (vanishing_point[2] == 0) { std::fill(residuals->begin(), residuals->end(), std::numeric_limits::max()); return; } for (size_t i = 0; i < lines.size(); ++i) { const Eigen::Vector3d midpoint = (0.5 * (line_segments[i].start + line_segments[i].end)).homogeneous(); const Eigen::Vector3d connecting_line = midpoint.cross(vanishing_point); const double signed_distance = connecting_line.dot(line_segments[i].end.homogeneous()) / connecting_line.head<2>().norm(); (*residuals)[i] = signed_distance * signed_distance; } } }; Eigen::Matrix3d EstimateManhattanWorldFrame( const ManhattanWorldFrameEstimationOptions& options, const Reconstruction& reconstruction, const std::filesystem::path& image_path) { std::vector rightward_axes; std::vector downward_axes; size_t image_idx = 0; for (const image_t image_id : reconstruction.RegImageIds()) { const auto& image = reconstruction.Image(image_id); const auto& camera = *image.CameraPtr(); LOG_HEADING1(StringPrintf("Processing image %s (%d / %d)", image.Name().c_str(), ++image_idx, reconstruction.NumRegImages())); LOG(INFO) << "Reading image..."; Bitmap bitmap; THROW_CHECK(bitmap.Read(image_path / image.Name())); LOG(INFO) << "Undistorting image..."; UndistortCameraOptions undistortion_options; undistortion_options.max_image_size = options.max_image_size; Bitmap undistorted_bitmap; Camera undistorted_camera; UndistortImage(undistortion_options, bitmap, camera, &undistorted_bitmap, &undistorted_camera); LOG(INFO) << "Detecting lines..."; const std::vector line_segments = DetectLineSegments(undistorted_bitmap, options.min_line_length); const std::vector line_orientations = ClassifyLineSegmentOrientations(line_segments, options.line_orientation_tolerance); LOG(INFO) << StringPrintf(" %d", line_segments.size()); std::vector horizontal_line_segments; std::vector vertical_line_segments; std::vector horizontal_lines; std::vector vertical_lines; for (size_t i = 0; i < line_segments.size(); ++i) { const auto& line_segment = line_segments[i]; const Eigen::Vector3d line_segment_start = line_segment.start.homogeneous(); const Eigen::Vector3d line_segment_end = line_segment.end.homogeneous(); const Eigen::Vector3d line = line_segment_start.cross(line_segment_end); if (line_orientations[i] == LineSegmentOrientation::HORIZONTAL) { horizontal_line_segments.push_back(line_segment); horizontal_lines.push_back(line); } else if (line_orientations[i] == LineSegmentOrientation::VERTICAL) { vertical_line_segments.push_back(line_segment); vertical_lines.push_back(line); } } LOG(INFO) << StringPrintf(" (%d horizontal, %d vertical)", horizontal_lines.size(), vertical_lines.size()); LOG(INFO) << "Estimating vanishing points..."; RANSACOptions ransac_options; ransac_options.max_error = options.max_line_vp_distance; RANSAC ransac(ransac_options); const auto horizontal_report = ransac.Estimate(horizontal_line_segments, horizontal_lines); const auto vertical_report = ransac.Estimate(vertical_line_segments, vertical_lines); LOG(INFO) << StringPrintf(" (%d horizontal inliers, %d vertical inliers)", horizontal_report.support.num_inliers, vertical_report.support.num_inliers); LOG(INFO) << "Composing coordinate axes..."; const Eigen::Matrix3d inv_calib_matrix = undistorted_camera.CalibrationMatrix().inverse(); const Eigen::Quaterniond world_from_cam_rotation = image.CamFromWorld().rotation().inverse(); if (horizontal_report.success) { Eigen::Vector3d horizontal_axis_in_world = world_from_cam_rotation * (inv_calib_matrix * horizontal_report.model).normalized(); // Make sure all axes point into the same direction. if (rightward_axes.size() > 0 && rightward_axes[0].dot(horizontal_axis_in_world) < 0) { horizontal_axis_in_world = -horizontal_axis_in_world; } rightward_axes.push_back(horizontal_axis_in_world); LOG(INFO) << "Horizontal: " << horizontal_axis_in_world.transpose(); } if (vertical_report.success) { const Eigen::Vector3d vertical_axis_in_cam = (inv_calib_matrix * vertical_report.model).normalized(); Eigen::Vector3d vertical_axis_in_world = (world_from_cam_rotation * vertical_axis_in_cam).normalized(); // Make sure axis points downwards in the image, assuming that the image // was taken in upright orientation. if (vertical_axis_in_world.dot(Eigen::Vector3d(0, 1, 0)) < 0) { vertical_axis_in_world = -vertical_axis_in_world; } downward_axes.push_back(vertical_axis_in_world); LOG(INFO) << "Vertical: " << vertical_axis_in_world.transpose(); } } LOG_HEADING1("Computing coordinate frame"); Eigen::Matrix3d frame = Eigen::Matrix3d::Zero(); if (rightward_axes.size() > 0) { frame.col(0) = FindBestConsensusAxis(rightward_axes, options.max_axis_distance); } LOG(INFO) << "Found rightward axis: " << frame.col(0).transpose(); if (downward_axes.size() > 0) { frame.col(1) = FindBestConsensusAxis(downward_axes, options.max_axis_distance); } LOG(INFO) << "Found downward axis: " << frame.col(1).transpose(); if (rightward_axes.size() > 0 && downward_axes.size() > 0) { frame.col(2) = frame.col(0).cross(frame.col(1)); Eigen::JacobiSVD svd( frame, Eigen::ComputeFullV | Eigen::ComputeFullU); const Eigen::Matrix3d orthonormal_frame = svd.matrixU() * Eigen::Matrix3d::Identity() * svd.matrixV().transpose(); frame = orthonormal_frame; } LOG(INFO) << "Found orthonormal frame:\n" << frame; return frame; } #endif void AlignToPrincipalPlane(Reconstruction* reconstruction, Sim3d* aligned_from_original) { THROW_CHECK_GT(reconstruction->NumRegFrames(), 0); // Perform SVD on the 3D points to estimate the ground plane basis const Eigen::Vector3d centroid = reconstruction->ComputeCentroid(0.0, 1.0); Eigen::MatrixXd normalized_points3D(3, reconstruction->NumPoints3D()); int pidx = 0; for (const auto& point : reconstruction->Points3D()) { normalized_points3D.col(pidx++) = point.second.xyz - centroid; } #if EIGEN_VERSION_AT_LEAST(5, 0, 0) const Eigen::Matrix3d basis = normalized_points3D.jacobiSvd() .matrixU(); #else const Eigen::Matrix3d basis = normalized_points3D.jacobiSvd(Eigen::ComputeThinU | Eigen::ComputeThinV) .matrixU(); #endif Eigen::Matrix3d rot_mat; rot_mat << basis.col(0), basis.col(1), basis.col(0).cross(basis.col(1)); rot_mat.transposeInPlace(); *aligned_from_original = Sim3d(1.0, Eigen::Quaterniond(rot_mat), -rot_mat * centroid); // If camera plane ends up below ground then flip basis vectors. const Frame& frame0 = reconstruction->Frame(reconstruction->RegFrameIds().front()); const auto frame0_image_ids = frame0.ImageIds(); THROW_CHECK(frame0_image_ids.begin() != frame0_image_ids.end()); const Rigid3d cam0_from_aligned_world = TransformCameraWorld( *aligned_from_original, reconstruction->Image(frame0_image_ids.begin()->id).CamFromWorld()); if (Inverse(cam0_from_aligned_world).translation().z() < 0.0) { rot_mat << basis.col(0), -basis.col(1), basis.col(0).cross(-basis.col(1)); rot_mat.transposeInPlace(); *aligned_from_original = Sim3d(1.0, Eigen::Quaterniond(rot_mat), -rot_mat * centroid); } reconstruction->Transform(*aligned_from_original); } void AlignToENUPlane(Reconstruction* reconstruction, Sim3d* aligned_from_original, bool unscaled) { const Eigen::Vector3d centroid = reconstruction->ComputeCentroid(0.0, 1.0); GPSTransform gps_tform; const Eigen::Vector3d ell_centroid = gps_tform.ECEFToEllipsoid({centroid}).at(0); // Create rotation matrix from ECEF to ENU coordinates const double sin_lat = std::sin(DegToRad(ell_centroid(0))); const double sin_lon = std::sin(DegToRad(ell_centroid(1))); const double cos_lat = std::cos(DegToRad(ell_centroid(0))); const double cos_lon = std::cos(DegToRad(ell_centroid(1))); // Create ECEF to ENU rotation matrix Eigen::Matrix3d rot_mat; rot_mat << -sin_lon, cos_lon, 0, -cos_lon * sin_lat, -sin_lon * sin_lat, cos_lat, cos_lon * cos_lat, sin_lon * cos_lat, sin_lat; const double scale = unscaled ? 1.0 / aligned_from_original->scale() : 1.0; *aligned_from_original = Sim3d(scale, Eigen::Quaterniond(rot_mat), -scale * rot_mat * centroid); reconstruction->Transform(*aligned_from_original); } } // namespace colmap colmap-4.2.0/src/colmap/estimators/coordinate_frame.h000066400000000000000000000100531524536416500227100ustar00rootroot00000000000000// Copyright (c), ETH Zurich and UNC Chapel Hill. // All rights reserved. // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are met: // // * Redistributions of source code must retain the above copyright // notice, this list of conditions and the following disclaimer. // // * Redistributions in binary form must reproduce the above copyright // notice, this list of conditions and the following disclaimer in the // documentation and/or other materials provided with the distribution. // // * Neither the name of ETH Zurich and UNC Chapel Hill nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE // ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE // LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR // CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF // SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS // INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN // CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) // ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE // POSSIBILITY OF SUCH DAMAGE. #pragma once #include "colmap/scene/reconstruction.h" #include "colmap/util/eigen_alignment.h" #include namespace colmap { struct ManhattanWorldFrameEstimationOptions { // The maximum image size for line detection. int max_image_size = 1024; // The minimum length of line segments in pixels. double min_line_length = 3; // The tolerance for classifying lines into horizontal/vertical. double line_orientation_tolerance = 0.2; // The maximum distance in pixels between lines and the vanishing points. double max_line_vp_distance = 0.5; // The maximum cosine distance between estimated axes to be inliers. double max_axis_distance = 0.05; }; // Estimate gravity vector by assuming gravity-aligned image orientation, i.e. // the majority of images is assumed to have the gravity vector aligned with an // upright image plane. Eigen::Vector3d EstimateGravityVectorFromImageOrientation( const Reconstruction& reconstruction, double max_axis_distance = 0.05); // Estimate the coordinate frame of the reconstruction assuming a Manhattan // world by finding the major vanishing points in each image. This function // assumes that the majority of images is taken in upright direction, i.e. // people are standing upright in the image. The orthonormal axes of the // estimated coordinate frame will be given in the columns of the returned // matrix. If one axis could not be determined, the respective column will be // zero. The axes are specified in the world coordinate system in the order // rightward, downward, forward. #ifdef COLMAP_LSD_ENABLED Eigen::Matrix3d EstimateManhattanWorldFrame( const ManhattanWorldFrameEstimationOptions& options, const Reconstruction& reconstruction, const std::filesystem::path& image_path); #endif // Aligns the reconstruction to the plane defined by running PCA on the 3D // points. The model centroid is at the origin of the new coordinate system // and the X axis is the first principal component with the Y axis being the // second principal component void AlignToPrincipalPlane(Reconstruction* recon, Sim3d* tform); // Aligns the reconstruction to the local ENU plane orientation. Rotates the // reconstruction such that the x-y plane aligns with the ENU tangent plane at // the point cloud centroid and translates the origin to the centroid. // If unscaled == true, then the original scale of the model remains unchanged. void AlignToENUPlane(Reconstruction* recon, Sim3d* tform, bool unscaled); } // namespace colmap colmap-4.2.0/src/colmap/estimators/coordinate_frame_test.cc000066400000000000000000000420361524536416500241130ustar00rootroot00000000000000// Copyright (c), ETH Zurich and UNC Chapel Hill. // All rights reserved. // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are met: // // * Redistributions of source code must retain the above copyright // notice, this list of conditions and the following disclaimer. // // * Redistributions in binary form must reproduce the above copyright // notice, this list of conditions and the following disclaimer in the // documentation and/or other materials provided with the distribution. // // * Neither the name of ETH Zurich and UNC Chapel Hill nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE // ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE // LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR // CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF // SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS // INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN // CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) // ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE // POSSIBILITY OF SUCH DAMAGE. #include "colmap/estimators/coordinate_frame.h" #include "colmap/geometry/gps.h" #include "colmap/math/math.h" #include "colmap/math/random.h" #include "colmap/sensor/bitmap.h" #include "colmap/util/eigen_matchers.h" #include "colmap/util/testing.h" #include #include namespace colmap { namespace { TEST(EstimateGravityVectorFromImageOrientation, Nominal) { // Create a reconstruction with multiple upright images. // Upright images have gravity aligned with the camera Y axis, so // row(1) of the rotation matrix should point downward (along +Y world). Reconstruction reconstruction; Camera camera = Camera::CreateFromModelId(1, CameraModelId::kSimplePinhole, 1, 1, 1); reconstruction.AddCameraWithTrivialRig(camera); // Add 5 registered images with random rotation around +Y (upright, gravity // ~ +Y) for (int i = 0; i < 5; ++i) { Image image; image.SetImageId(i); image.SetCameraId(camera.camera_id); const Rigid3d cam_from_world( Eigen::Quaterniond( Eigen::AngleAxisd(RandomUniformReal(-EIGEN_PI, EIGEN_PI), Eigen::Vector3d::UnitY())), Eigen::Vector3d(i, 0, 0)); reconstruction.AddImageWithTrivialFrame(std::move(image), cam_from_world); } // 1 outlier image rotated 90 degrees around Z { Image image; image.SetImageId(6); image.SetCameraId(camera.camera_id); const Rigid3d cam_from_world(Eigen::Quaterniond(Eigen::AngleAxisd( EIGEN_PI / 2, Eigen::Vector3d::UnitZ())), Eigen::Vector3d(6, 0, 0)); reconstruction.AddImageWithTrivialFrame(std::move(image), cam_from_world); } const Eigen::Vector3d gravity = EstimateGravityVectorFromImageOrientation(reconstruction); // Consensus should find gravity ~ (0, 1, 0) despite the outlier EXPECT_NEAR(gravity.norm(), 1.0, 1e-6); EXPECT_NEAR(std::abs(gravity.dot(Eigen::Vector3d(0, 1, 0))), 1.0, 1e-6); } TEST(EstimateGravityVectorFromImageOrientation, Empty) { Reconstruction reconstruction; EXPECT_EQ(EstimateGravityVectorFromImageOrientation(reconstruction), Eigen::Vector3d::Zero()); } #ifdef COLMAP_LSD_ENABLED constexpr int kWidth = 512; constexpr int kHeight = 512; constexpr double kFocal = 400.0; struct Line3D { Eigen::Vector3d beg; Eigen::Vector3d end; }; struct ManhattanScene { Eigen::Matrix3d manhattan_from_world; std::vector vertical_lines; std::vector horizontal_lines; }; ManhattanScene CreateManhattanScene() { // Rotate the Manhattan frame so the solution is not trivially axis-aligned. const Eigen::Matrix3d manhattan_from_world = (Eigen::AngleAxisd(DegToRad(25.0), Eigen::Vector3d::UnitZ()) * Eigen::AngleAxisd(DegToRad(11.0), Eigen::Vector3d::UnitX())) .toRotationMatrix(); // Define axis-aligned 3D lines, then rotate them into the tilted frame. // Vertical lines are parallel to Y; horizontal lines to X. std::vector vertical_lines = { {{-1.5, -2, 5}, {-1.5, 2, 5}}, {{-0.5, -2, 5}, {-0.5, 2, 5}}, {{0.5, -2, 5}, {0.5, 2, 5}}, {{1.5, -2, 5}, {1.5, 2, 5}}, {{-1, -2, 7}, {-1, 2, 7}}, {{0, -2, 7}, {0, 2, 7}}, {{1, -2, 7}, {1, 2, 7}}, }; std::vector horizontal_lines = { {{-2, -1.5, 5}, {2, -1.5, 5}}, {{-2, -0.5, 5}, {2, -0.5, 5}}, {{-2, 0.5, 5}, {2, 0.5, 5}}, {{-2, 1.5, 5}, {2, 1.5, 5}}, {{-2, -1, 7}, {2, -1, 7}}, {{-2, 0, 7}, {2, 0, 7}}, {{-2, 1, 7}, {2, 1, 7}}, }; for (auto& l : vertical_lines) { l.beg = manhattan_from_world * l.beg; l.end = manhattan_from_world * l.end; } for (auto& l : horizontal_lines) { l.beg = manhattan_from_world * l.beg; l.end = manhattan_from_world * l.end; } return {manhattan_from_world, std::move(vertical_lines), std::move(horizontal_lines)}; } void DrawLine(Bitmap& bitmap, const Eigen::Vector2d& a, const Eigen::Vector2d& b, int radius) { const int steps = std::max(1, static_cast(std::ceil((b - a).norm() * 2))); for (int s = 0; s <= steps; ++s) { const double t = static_cast(s) / steps; const double x = a.x() + t * (b.x() - a.x()); const double y = a.y() + t * (b.y() - a.y()); for (int dy = -radius; dy <= radius; ++dy) { for (int dx = -radius; dx <= radius; ++dx) { if (dx * dx + dy * dy > radius * radius) continue; const int px = static_cast(std::round(x + dx)); const int py = static_cast(std::round(y + dy)); if (px >= 0 && px < kWidth && py >= 0 && py < kHeight) { bitmap.SetPixel(px, py, BitmapColor(255)); } } } } } void RenderLineImages(Reconstruction& reconstruction, const ManhattanScene& scene, const std::filesystem::path& test_dir) { // 3 cameras with combined pitch (around X) and yaw (around Y) so that both // the horizontal and vertical vanishing points are at finite image locations. const std::array pitch_deg = {10.0, 10.0, 8.0}; const std::array yaw_deg = {15.0, -15.0, 5.0}; const Camera camera = Camera::CreateFromModelId( 1, CameraModelId::kSimplePinhole, kFocal, kWidth, kHeight); reconstruction.AddCameraWithTrivialRig(camera); for (size_t cam_idx = 0; cam_idx < pitch_deg.size(); ++cam_idx) { const Eigen::Quaterniond cam_from_world_rot( Eigen::AngleAxisd(DegToRad(pitch_deg[cam_idx]), Eigen::Vector3d::UnitX()) * Eigen::AngleAxisd(DegToRad(yaw_deg[cam_idx]), Eigen::Vector3d::UnitY())); const Rigid3d cam_from_world(cam_from_world_rot, Eigen::Vector3d::Zero()); Image image; image.SetImageId(cam_idx); image.SetCameraId(camera.camera_id); image.SetName("image" + std::to_string(cam_idx) + ".png"); reconstruction.AddImageWithTrivialFrame(std::move(image), cam_from_world); const auto& reg_image = reconstruction.Image(cam_idx); // Project 3D lines and draw thick white stripes on a gray background. Bitmap bitmap(kWidth, kHeight, /*as_rgb=*/true); bitmap.Fill(BitmapColor(128)); constexpr int kRadius = 3; for (const auto& line : scene.vertical_lines) { const auto p1 = reg_image.ProjectPoint(line.beg); const auto p2 = reg_image.ProjectPoint(line.end); if (p1 && p2) DrawLine(bitmap, *p1, *p2, kRadius); } for (const auto& line : scene.horizontal_lines) { const auto p1 = reg_image.ProjectPoint(line.beg); const auto p2 = reg_image.ProjectPoint(line.end); if (p1 && p2) DrawLine(bitmap, *p1, *p2, kRadius); } ASSERT_TRUE(bitmap.Write(test_dir / reg_image.Name())); } } TEST(EstimateManhattanWorldFrame, Synthetic) { const auto scene = CreateManhattanScene(); const auto test_dir = CreateTestDir(); Reconstruction reconstruction; ASSERT_NO_FATAL_FAILURE(RenderLineImages(reconstruction, scene, test_dir)); // Run Manhattan world frame estimation. ManhattanWorldFrameEstimationOptions options; const Eigen::Matrix3d frame = EstimateManhattanWorldFrame(options, reconstruction, test_dir); // The estimated frame should recover the tilted Manhattan axes. const Eigen::Vector3d expected_rightward = scene.manhattan_from_world.col(0); const Eigen::Vector3d expected_downward = scene.manhattan_from_world.col(1); const Eigen::Vector3d expected_forward = scene.manhattan_from_world.col(2); // Rightward direction (col 0) must align with the rotated X axis. // The sign of the rightward and forward axes is ambiguous, so check absolute // dot product. EXPECT_LT(std::abs(std::abs(frame.col(0).dot(expected_rightward)) - 1), 1e-3); // Gravity direction (col 1) must align with the rotated Y axis. // The sign is deterministic (flipped to match +Y in the implementation). EXPECT_LT(std::abs(frame.col(1).dot(expected_downward) - 1), 1e-3); // Forward direction (col 2) must align with the rotated Z axis. EXPECT_LT(std::abs(std::abs(frame.col(2).dot(expected_forward)) - 1), 1e-3); // Verify orthonormality. EXPECT_NEAR(frame.col(0).norm(), 1.0, 1e-6); EXPECT_NEAR(frame.col(1).norm(), 1.0, 1e-6); EXPECT_NEAR(frame.col(2).norm(), 1.0, 1e-6); EXPECT_NEAR(std::abs(frame.col(0).dot(frame.col(1))), 0.0, 1e-6); EXPECT_NEAR(std::abs(frame.col(0).dot(frame.col(2))), 0.0, 1e-6); EXPECT_NEAR(std::abs(frame.col(1).dot(frame.col(2))), 0.0, 1e-6); } TEST(EstimateManhattanWorldFrame, Empty) { Reconstruction reconstruction; std::filesystem::path image_path; EXPECT_EQ( EstimateManhattanWorldFrame( ManhattanWorldFrameEstimationOptions(), reconstruction, image_path), Eigen::Matrix3d::Zero()); } #endif TEST(AlignToPrincipalPlane, Nominal) { // Start with reconstruction containing points on the Y-Z plane and cameras // "above" the plane on the positive X axis. After alignment the points should // be on the X-Y plane and the cameras "above" the plane on the positive Z // axis. Sim3d tform; Reconstruction reconstruction; Camera camera = Camera::CreateFromModelId(1, CameraModelId::kSimplePinhole, 1, 1, 1); reconstruction.AddCamera(camera); Rig rig; rig.SetRigId(1); rig.AddRefSensor(sensor_t(SensorType::CAMERA, 1)); reconstruction.AddRig(rig); Frame frame; frame.SetFrameId(1); frame.SetRigId(rig.RigId()); frame.AddDataId(data_t(camera.SensorId(), 1)); frame.SetRigFromWorld( Rigid3d(Eigen::Quaterniond::Identity(), Eigen::Vector3d(-1, 0, 0))); reconstruction.AddFrame(frame); // Setup image with projection center at (1, 0, 0) Image image; image.SetCameraId(camera.camera_id); image.SetImageId(1); image.SetFrameId(1); reconstruction.AddImage(image); // Setup 4 points on the Y-Z plane const point3D_t p1 = reconstruction.AddPoint3D(Eigen::Vector3d(0, -1, 0), Track()); const point3D_t p2 = reconstruction.AddPoint3D(Eigen::Vector3d(0, 1, 0), Track()); const point3D_t p3 = reconstruction.AddPoint3D(Eigen::Vector3d(0, 0, -1), Track()); const point3D_t p4 = reconstruction.AddPoint3D(Eigen::Vector3d(0, 0, 1), Track()); AlignToPrincipalPlane(&reconstruction, &tform); // Note that the final X and Y axes may be inverted after alignment, so we // need to account for both cases when checking for correctness const bool inverted = tform.rotation().y() < 0; // Verify that points lie on the correct locations of the X-Y plane EXPECT_LE((reconstruction.Point3D(p1).xyz - Eigen::Vector3d(inverted ? 1 : -1, 0, 0)) .norm(), 1e-6); EXPECT_LE((reconstruction.Point3D(p2).xyz - Eigen::Vector3d(inverted ? -1 : 1, 0, 0)) .norm(), 1e-6); EXPECT_LE((reconstruction.Point3D(p3).xyz - Eigen::Vector3d(0, inverted ? 1 : -1, 0)) .norm(), 1e-6); EXPECT_LE((reconstruction.Point3D(p4).xyz - Eigen::Vector3d(0, inverted ? -1 : 1, 0)) .norm(), 1e-6); // Verify that projection center is at (0, 0, 1) EXPECT_LE( (reconstruction.Image(1).ProjectionCenter() - Eigen::Vector3d(0, 0, 1)) .norm(), 1e-6); // Verify that transform matrix does shuffling of axes Eigen::Matrix3x4d expected; if (inverted) { expected << 0, -1, 0, 0, 0, 0, -1, 0, 1, 0, 0, 0; } else { expected << 0, 1, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0; } EXPECT_THAT(tform.ToMatrix(), EigenMatrixNear(expected, 1e-6)); } TEST(AlignToENUPlane, Scaled) { // Create reconstruction with 4 points with known LLA coordinates. After the // ENU transform all 4 points should land approximately on the X-Y plane. GPSTransform gps; auto points = gps.EllipsoidToECEF({Eigen::Vector3d(50, 10.1, 100), Eigen::Vector3d(50.1, 10, 100), Eigen::Vector3d(50.1, 10.1, 100), Eigen::Vector3d(50, 10, 100)}); Sim3d tform; Reconstruction reconstruction; std::vector point_ids; for (size_t i = 0; i < points.size(); ++i) { point_ids.push_back(reconstruction.AddPoint3D(points[i], Track())); LOG(INFO) << points[i].transpose(); } AlignToENUPlane(&reconstruction, &tform, false); // Verify final locations of points EXPECT_THAT(reconstruction.Point3D(point_ids[0]).xyz, EigenMatrixNear(Eigen::Vector3d(3584.8433196335045, -5561.5866894473402, -0.0020947810262441635), 1e-6)); EXPECT_THAT(reconstruction.Point3D(point_ids[1]).xyz, EigenMatrixNear(Eigen::Vector3d(-3577.4020366631503, 5561.5866894469982, 0.0020947791635990143), 1e-6)); EXPECT_THAT(reconstruction.Point3D(point_ids[2]).xyz, EigenMatrixNear(Eigen::Vector3d(3577.4020366640707, 5561.5866894467654, 0.0020947791635990143), 1e-6)); EXPECT_THAT(reconstruction.Point3D(point_ids[3]).xyz, EigenMatrixNear(Eigen::Vector3d(-3584.8433196330498, -5561.586689447573, -0.0020947810262441635), 1e-6)); // Verify that straight line distance between points is preserved for (size_t i = 1; i < points.size(); ++i) { const double dist_orig = (points[i] - points[i - 1]).norm(); const double dist_tform = (reconstruction.Point3D(point_ids[i]).xyz - reconstruction.Point3D(point_ids[i - 1]).xyz) .norm(); EXPECT_LE(std::abs(dist_orig - dist_tform), 1e-6); } } TEST(AlignToENUPlane, Unscaled) { // Test unscaled variant: starting from a model with non-unit scale, the // alignment should undo the scale so that original distances are preserved. GPSTransform gps; auto points = gps.EllipsoidToECEF({Eigen::Vector3d(50, 10.1, 100), Eigen::Vector3d(50.1, 10, 100), Eigen::Vector3d(50.1, 10.1, 100), Eigen::Vector3d(50, 10, 100)}); Reconstruction reconstruction; std::vector point3D_ids; point3D_ids.reserve(points.size()); for (size_t i = 0; i < points.size(); ++i) { point3D_ids.push_back(reconstruction.AddPoint3D(points[i], Track())); } // Apply a non-unit scale to simulate a scaled model const double model_scale = 2.0; Sim3d pre_scale( model_scale, Eigen::Quaterniond::Identity(), Eigen::Vector3d::Zero()); reconstruction.Transform(pre_scale); // Align with unscaled=true, passing the pre_scale as the current transform Sim3d tform = pre_scale; AlignToENUPlane(&reconstruction, &tform, /*unscaled=*/true); // The applied transform should have inverse scale to undo pre_scale EXPECT_NEAR(tform.scale(), 1.0 / model_scale, 1e-6); // Original (unscaled) distances between ECEF points should be preserved for (size_t i = 1; i < points.size(); ++i) { const double dist_orig = (points[i] - points[i - 1]).norm(); const double dist_tform = (reconstruction.Point3D(point3D_ids[i]).xyz - reconstruction.Point3D(point3D_ids[i - 1]).xyz) .norm(); EXPECT_NEAR(dist_orig, dist_tform, 1e-4); } } } // namespace } // namespace colmap colmap-4.2.0/src/colmap/estimators/cost_functions/000077500000000000000000000000001524536416500222775ustar00rootroot00000000000000colmap-4.2.0/src/colmap/estimators/cost_functions/CMakeLists.txt000066400000000000000000000064511524536416500250450ustar00rootroot00000000000000# Copyright (c), ETH Zurich and UNC Chapel Hill. # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are met: # # * Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer. # # * Redistributions in binary form must reproduce the above copyright # notice, this list of conditions and the following disclaimer in the # documentation and/or other materials provided with the distribution. # # * Neither the name of ETH Zurich and UNC Chapel Hill nor the names of # its contributors may be used to endorse or promote products derived # from this software without specific prior written permission. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. set(FOLDER_NAME "estimators_cost_functions") COLMAP_ADD_LIBRARY( NAME colmap_estimators_cost_functions TYPE INTERFACE SRCS alignment.h calibration.h manifold.h motion_averaging.h pose_prior.h quaternion_utils.h reprojection_error.h sampson_error.h tiny_manifold.h tiny_sampson_error.h utils.h INTERFACE_LINK_LIBS colmap_geometry colmap_sensor Eigen3::Eigen Ceres::ceres ) COLMAP_ADD_TEST( NAME alignment_test SRCS alignment_test.cc LINK_LIBS colmap_estimators_cost_functions ) COLMAP_ADD_TEST( NAME calibration_test SRCS calibration_test.cc LINK_LIBS colmap_estimators_cost_functions ) COLMAP_ADD_TEST( NAME utils_test SRCS utils_test.cc LINK_LIBS colmap_estimators_cost_functions ) COLMAP_ADD_TEST( NAME reprojection_error_test SRCS reprojection_error_test.cc LINK_LIBS colmap_estimators_cost_functions ) COLMAP_ADD_TEST( NAME sampson_error_test SRCS sampson_error_test.cc LINK_LIBS colmap_estimators_cost_functions ) COLMAP_ADD_TEST( NAME tiny_manifold_test SRCS tiny_manifold_test.cc LINK_LIBS colmap_estimators_cost_functions ) COLMAP_ADD_TEST( NAME tiny_sampson_error_test SRCS tiny_sampson_error_test.cc LINK_LIBS colmap_estimators_cost_functions ) COLMAP_ADD_TEST( NAME pose_prior_test SRCS pose_prior_test.cc LINK_LIBS colmap_estimators_cost_functions ) COLMAP_ADD_TEST( NAME motion_averaging_test SRCS motion_averaging_test.cc LINK_LIBS colmap_estimators_cost_functions ) COLMAP_ADD_TEST( NAME quaternion_utils_test SRCS quaternion_utils_test.cc LINK_LIBS colmap_estimators_cost_functions colmap_math ) colmap-4.2.0/src/colmap/estimators/cost_functions/alignment.h000066400000000000000000000060231524536416500244270ustar00rootroot00000000000000// Copyright (c), ETH Zurich and UNC Chapel Hill. // All rights reserved. // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are met: // // * Redistributions of source code must retain the above copyright // notice, this list of conditions and the following disclaimer. // // * Redistributions in binary form must reproduce the above copyright // notice, this list of conditions and the following disclaimer in the // documentation and/or other materials provided with the distribution. // // * Neither the name of ETH Zurich and UNC Chapel Hill nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE // ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE // LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR // CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF // SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS // INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN // CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) // ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE // POSSIBILITY OF SUCH DAMAGE. #pragma once #include "colmap/estimators/cost_functions/quaternion_utils.h" #include "colmap/estimators/cost_functions/utils.h" #include #include namespace colmap { // Cost function for aligning one 3D point with a reference 3D point with // covariance. The Residual is computed in frame b. Coordinate transformation // convention is equivalent to Sim3d. struct Point3DAlignmentCostFunctor : public AutoDiffCostFunctor { public: explicit Point3DAlignmentCostFunctor(const Eigen::Vector3d& point_in_b_prior, bool use_log_scale = true) : point_in_b_prior_(point_in_b_prior), use_log_scale_(use_log_scale) {} template bool operator()(const T* const point_in_a, const T* const b_from_a, T* residuals_ptr) const { // Select whether to exponentiate const T b_from_a_scale = use_log_scale_ ? ceres::exp(b_from_a[7]) : b_from_a[7]; const Eigen::Matrix point_in_b = EigenQuaternionMap(b_from_a) * EigenVector3Map(point_in_a) * b_from_a_scale + EigenVector3Map(b_from_a + 4); Eigen::Map> residuals(residuals_ptr); residuals = point_in_b - point_in_b_prior_.cast(); return true; } private: const Eigen::Vector3d point_in_b_prior_; const bool use_log_scale_; }; } // namespace colmap colmap-4.2.0/src/colmap/estimators/cost_functions/alignment_test.cc000066400000000000000000000074331524536416500256320ustar00rootroot00000000000000// Copyright (c), ETH Zurich and UNC Chapel Hill. // All rights reserved. // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are met: // // * Redistributions of source code must retain the above copyright // notice, this list of conditions and the following disclaimer. // // * Redistributions in binary form must reproduce the above copyright // notice, this list of conditions and the following disclaimer in the // documentation and/or other materials provided with the distribution. // // * Neither the name of ETH Zurich and UNC Chapel Hill nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE // ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE // LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR // CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF // SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS // INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN // CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) // ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE // POSSIBILITY OF SUCH DAMAGE. #include "colmap/estimators/cost_functions/alignment.h" #include "colmap/geometry/sim3.h" #include "colmap/math/random.h" #include "colmap/math/random_eigen.h" #include "colmap/util/eigen_matchers.h" #include namespace colmap { namespace { TEST(Point3DAlignmentCostFunctor, UseLogScale) { Sim3d b_from_a = Sim3d(RandomUniformReal(0.1, 10), RandomEigenQuaterniond(), RandomEigenVectord<3>()); const Eigen::Vector3d point_in_b_prior(1., 2., 3.); const Eigen::Vector3d point_in_a(3., 2., 1.); const Eigen::Vector3d point_in_b = b_from_a * point_in_a; std::unique_ptr cost_function( Point3DAlignmentCostFunctor::Create(point_in_b_prior, /*use_log_scale=*/true)); b_from_a.scale() = std::log(b_from_a.scale()); const double* parameters_log_scale[2] = {point_in_a.data(), b_from_a.params.data()}; Eigen::Vector3d residuals; EXPECT_TRUE( cost_function->Evaluate(parameters_log_scale, residuals.data(), nullptr)); const Eigen::Vector3d error = point_in_b - point_in_b_prior; EXPECT_THAT(residuals, EigenMatrixNear(error, 1e-6)); } TEST(Point3DAlignmentCostFunctor, DoNotUseLogScale) { const Sim3d b_from_a = Sim3d(RandomUniformReal(0.1, 10), RandomEigenQuaterniond(), RandomEigenVectord<3>()); const Eigen::Vector3d point_in_b_prior(1., 2., 3.); const Eigen::Vector3d point_in_a(3., 2., 1.); const Eigen::Vector3d point_in_b = b_from_a * point_in_a; std::unique_ptr cost_function( Point3DAlignmentCostFunctor::Create(point_in_b_prior, /*use_log_scale=*/false)); const double* parameters_log_scale[2] = {point_in_a.data(), b_from_a.params.data()}; Eigen::Vector3d residuals; EXPECT_TRUE( cost_function->Evaluate(parameters_log_scale, residuals.data(), nullptr)); const Eigen::Vector3d error = point_in_b - point_in_b_prior; EXPECT_THAT(residuals, EigenMatrixNear(error, 1e-6)); } } // namespace } // namespace colmap colmap-4.2.0/src/colmap/estimators/cost_functions/calibration.h000066400000000000000000000210701524536416500247370ustar00rootroot00000000000000// Copyright (c), ETH Zurich and UNC Chapel Hill. // All rights reserved. // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are met: // // * Redistributions of source code must retain the above copyright // notice, this list of conditions and the following disclaimer. // // * Redistributions in binary form must reproduce the above copyright // notice, this list of conditions and the following disclaimer in the // documentation and/or other materials provided with the distribution. // // * Neither the name of ETH Zurich and UNC Chapel Hill nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE // ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE // LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR // CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF // SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS // INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN // CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) // ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE // POSSIBILITY OF SUCH DAMAGE. #pragma once #include #include #include namespace colmap { // Compute polynomial coefficients from cross-products of SVD-derived vectors // for the Fetzer focal length estimation method. The coefficients encode the // relationship between the two focal lengths derived from the fundamental // matrix constraint. // See: "Stable Intrinsic Auto-Calibration from Fundamental Matrices of Devices // with Uncorrelated Camera Parameters", Fetzer et al., WACV 2020. inline Eigen::Vector4d ComputeFetzerPolynomialCoefficients( const Eigen::Vector3d& ai, const Eigen::Vector3d& bi, const Eigen::Vector3d& aj, const Eigen::Vector3d& bj, const int u, const int v) { return {ai(u) * aj(v) - ai(v) * aj(u), ai(u) * bj(v) - ai(v) * bj(u), bi(u) * aj(v) - bi(v) * aj(u), bi(u) * bj(v) - bi(v) * bj(u)}; } // Decompose the fundamental matrix (adjusted by principal points) via SVD and // compute the polynomial coefficients for the Fetzer focal length method. // Returns three coefficient vectors used to estimate the two focal lengths. inline std::array DecomposeFundamentalMatrixForFetzer( const Eigen::Matrix3d& i1_F_i0, const Eigen::Vector2d& principal_point0, const Eigen::Vector2d& principal_point1) { Eigen::Matrix3d K0 = Eigen::Matrix3d::Identity(3, 3); K0(0, 2) = principal_point0(0); K0(1, 2) = principal_point0(1); Eigen::Matrix3d K1 = Eigen::Matrix3d::Identity(3, 3); K1(0, 2) = principal_point1(0); K1(1, 2) = principal_point1(1); // Factoring out the principal points before the SVD appears to be numerically // more stable than the method described in the paper. const Eigen::Matrix3d i1_G_i0 = K1.transpose() * i1_F_i0 * K0; const Eigen::JacobiSVD svd( i1_G_i0, Eigen::ComputeFullU | Eigen::ComputeFullV); const Eigen::Vector3d& s = svd.singularValues(); const Eigen::Vector3d v0 = svd.matrixV().col(0); const Eigen::Vector3d v1 = svd.matrixV().col(1); const Eigen::Vector3d u0 = svd.matrixU().col(0); const Eigen::Vector3d u1 = svd.matrixU().col(1); // Equation 11. Notice there is a sign error in the paper. // Equation 8 shows the sign in aj(1) and bj(1) correctly. const Eigen::Vector3d ai(s(0) * s(0) * (v0(0) * v0(0) + v0(1) * v0(1)), s(0) * s(1) * (v0(0) * v1(0) + v0(1) * v1(1)), s(1) * s(1) * (v1(0) * v1(0) + v1(1) * v1(1))); const Eigen::Vector3d aj(u1(0) * u1(0) + u1(1) * u1(1), -(u0(0) * u1(0) + u0(1) * u1(1)), u0(0) * u0(0) + u0(1) * u0(1)); const Eigen::Vector3d bi(s(0) * s(0) * v0(2) * v0(2), s(0) * s(1) * v0(2) * v1(2), s(1) * s(1) * v1(2) * v1(2)); const Eigen::Vector3d bj(u1(2) * u1(2), -(u0(2) * u1(2)), u0(2) * u0(2)); // Equation 12. // Experiments showed that the d02 term is not useful. // The d10, d21m d20 are redundant to d01, d12, d02. const Eigen::Vector4d d01 = ComputeFetzerPolynomialCoefficients(ai, bi, aj, bj, 1, 0); const Eigen::Vector4d d12 = ComputeFetzerPolynomialCoefficients(ai, bi, aj, bj, 2, 1); return {d01, d12}; } template inline T ComputeFetzerResidual1(const Eigen::Vector& d, const T& fi_sq, const T& fj_sq) { // Equation 13. T denom = fj_sq * d(0) + d(1); denom = denom == T(0) ? T(1e-6) : denom; const T K1 = -(fj_sq * d(2) + d(3)) / denom; return (fi_sq - K1) / fi_sq; } template inline T ComputeFetzerResidual2(const Eigen::Vector& d, const T& fi_sq, const T& fj_sq) { // Equation 14. T denom = fi_sq * d(0) + d(2); denom = denom == T(0) ? T(1e-6) : denom; const T K2 = -(fi_sq * d(1) + d(3)) / denom; return (fj_sq - K2) / fj_sq; } // Cost functor for estimating focal lengths from the fundamental matrix using // the Fetzer method. Used when two images have different cameras (different // focal lengths). The residual measures the relative error between the // estimated and expected focal lengths based on the fundamental matrix // constraint. class FetzerFocalLengthCostFunctor { public: FetzerFocalLengthCostFunctor(const Eigen::Matrix3d& j_F_i, const Eigen::Vector2d& principal_point_i, const Eigen::Vector2d& principal_point_j) : coeffs_(DecomposeFundamentalMatrixForFetzer( j_F_i, principal_point_i, principal_point_j)) {} static ceres::CostFunction* Create(const Eigen::Matrix3d& j_F_i, const Eigen::Vector2d& principal_point_i, const Eigen::Vector2d& principal_point_j) { return new ceres:: AutoDiffCostFunction( new FetzerFocalLengthCostFunctor( j_F_i, principal_point_i, principal_point_j)); } template bool operator()(const T* const focal_length_i, const T* const focal_length_j, T* residuals) const { const T fi_sq = focal_length_i[0] * focal_length_i[0]; const T fj_sq = focal_length_j[0] * focal_length_j[0]; const Eigen::Vector d01 = coeffs_[0].cast(); residuals[0] = ComputeFetzerResidual1(d01, fi_sq, fj_sq); const Eigen::Vector d12 = coeffs_[1].cast(); residuals[1] = ComputeFetzerResidual2(d12, fi_sq, fj_sq); return true; } private: const std::array coeffs_; }; // Cost functor for estimating focal length from the fundamental matrix using // the Fetzer method. Used when two images share the same camera (same focal // length). The residual measures the relative error between the estimated and // expected focal length based on the fundamental matrix constraint. class FetzerFocalLengthSameCameraCostFunctor { public: FetzerFocalLengthSameCameraCostFunctor(const Eigen::Matrix3d& j_F_i, const Eigen::Vector2d& principal_point) : coeffs_(DecomposeFundamentalMatrixForFetzer( j_F_i, principal_point, principal_point)) {} static ceres::CostFunction* Create(const Eigen::Matrix3d& j_F_i, const Eigen::Vector2d& principal_point) { return new ceres:: AutoDiffCostFunction( new FetzerFocalLengthSameCameraCostFunctor(j_F_i, principal_point)); } template bool operator()(const T* const focal_length, T* residuals) const { const T f_sq = focal_length[0] * focal_length[0]; const Eigen::Vector d01 = coeffs_[0].cast(); residuals[0] = ComputeFetzerResidual1(d01, f_sq, f_sq); const Eigen::Vector d12 = coeffs_[1].cast(); residuals[1] = ComputeFetzerResidual2(d12, f_sq, f_sq); return true; } private: const std::array coeffs_; }; } // namespace colmap colmap-4.2.0/src/colmap/estimators/cost_functions/calibration_test.cc000066400000000000000000000124251524536416500261400ustar00rootroot00000000000000// Copyright (c), ETH Zurich and UNC Chapel Hill. // All rights reserved. // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are met: // // * Redistributions of source code must retain the above copyright // notice, this list of conditions and the following disclaimer. // // * Redistributions in binary form must reproduce the above copyright // notice, this list of conditions and the following disclaimer in the // documentation and/or other materials provided with the distribution. // // * Neither the name of ETH Zurich and UNC Chapel Hill nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE // ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE // LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR // CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF // SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS // INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN // CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) // ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE // POSSIBILITY OF SUCH DAMAGE. #include "colmap/estimators/cost_functions/calibration.h" #include "colmap/geometry/essential_matrix.h" #include "colmap/geometry/rigid3.h" #include "colmap/math/random_eigen.h" #include namespace colmap { namespace { TEST(FetzerFocalLengthCostFunctor, ConvexCostLandscape) { constexpr int kNumTrials = 10; for (int i = 0; i < kNumTrials; ++i) { const double focal_length1 = 128; const double focal_length2 = 256; const Eigen::Vector2d pp1(320, 240); const Eigen::Vector2d pp2(480, 320); const Rigid3d cam2_from_cam1(RandomEigenQuaterniond(), RandomEigenVectord<3>()); Eigen::Matrix3d K1; K1 << focal_length1, 0, pp1(0), 0, focal_length1, pp1(1), 0, 0, 1; Eigen::Matrix3d K2; K2 << focal_length2, 0, pp2(0), 0, focal_length2, pp2(1), 0, 0, 1; const Eigen::Matrix3d F = FundamentalFromEssentialMatrix( K2, EssentialMatrixFromPose(cam2_from_cam1), K1); FetzerFocalLengthCostFunctor cost_functor(F, pp1, pp2); Eigen::VectorXd optimal_residual(2); EXPECT_TRUE( cost_functor(&focal_length1, &focal_length2, optimal_residual.data())); EXPECT_LT(optimal_residual.norm(), 1e-8); double previous_cost = -1e-9; double modified_focal_length1 = focal_length1; double modified_focal_length2 = focal_length2; for (int j = 0; j < 10; ++j) { Eigen::VectorXd residual(2); EXPECT_TRUE(cost_functor( &modified_focal_length1, &modified_focal_length2, residual.data())); const double cost = residual.norm(); EXPECT_GT(cost, previous_cost); previous_cost = cost; modified_focal_length1 *= 1.05; modified_focal_length2 *= 1.05; } previous_cost = -1e-9; modified_focal_length1 = focal_length1; modified_focal_length2 = focal_length2; for (int j = 0; j < 10; ++j) { Eigen::VectorXd residual(2); EXPECT_TRUE(cost_functor( &modified_focal_length1, &modified_focal_length2, residual.data())); const double cost = residual.norm(); EXPECT_GT(cost, previous_cost); previous_cost = cost; modified_focal_length1 *= 0.95; modified_focal_length2 *= 0.95; } } } TEST(FetzerFocalLengthSameCameraCostFunctor, ConvexCostLandscape) { constexpr int kNumTrials = 10; for (int i = 0; i < kNumTrials; ++i) { const double focal_length = 128; const Eigen::Vector2d pp(320, 240); const Rigid3d cam2_from_cam1(RandomEigenQuaterniond(), RandomEigenVectord<3>()); Eigen::Matrix3d K; K << focal_length, 0, pp(0), 0, focal_length, pp(1), 0, 0, 1; const Eigen::Matrix3d F = FundamentalFromEssentialMatrix( K, EssentialMatrixFromPose(cam2_from_cam1), K); FetzerFocalLengthSameCameraCostFunctor cost_functor(F, pp); Eigen::VectorXd optimal_residual(2); EXPECT_TRUE(cost_functor(&focal_length, optimal_residual.data())); EXPECT_LT(optimal_residual.norm(), 1e-8); double previous_cost = -1e-9; double modified_focal_length = focal_length; for (int j = 0; j < 10; ++j) { Eigen::VectorXd residual(2); EXPECT_TRUE(cost_functor(&modified_focal_length, residual.data())); const double cost = residual.norm(); EXPECT_GT(cost, previous_cost); previous_cost = cost; modified_focal_length *= 1.05; } previous_cost = -1e-9; modified_focal_length = focal_length; for (int j = 0; j < 10; ++j) { Eigen::VectorXd residual(2); EXPECT_TRUE(cost_functor(&modified_focal_length, residual.data())); const double cost = residual.norm(); EXPECT_GT(cost, previous_cost); previous_cost = cost; modified_focal_length *= 0.95; } } } } // namespace } // namespace colmap colmap-4.2.0/src/colmap/estimators/cost_functions/manifold.h000066400000000000000000000117261524536416500242500ustar00rootroot00000000000000// Copyright (c), ETH Zurich and UNC Chapel Hill. // All rights reserved. // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are met: // // * Redistributions of source code must retain the above copyright // notice, this list of conditions and the following disclaimer. // // * Redistributions in binary form must reproduce the above copyright // notice, this list of conditions and the following disclaimer in the // documentation and/or other materials provided with the distribution. // // * Neither the name of ETH Zurich and UNC Chapel Hill nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE // ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE // LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR // CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF // SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS // INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN // CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) // ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE // POSSIBILITY OF SUCH DAMAGE. #pragma once #include #include namespace colmap { #if CERES_VERSION_MAJOR >= 3 || \ (CERES_VERSION_MAJOR == 2 && CERES_VERSION_MINOR >= 1) inline void SetManifold(ceres::Problem* problem, double* params, ceres::Manifold* manifold) { problem->SetManifold(params, manifold); } inline void SetManifold(ceres::Problem* problem, double* params, std::unique_ptr manifold) { problem->SetManifold(params, manifold.release()); } template inline std::unique_ptr CreateEuclideanManifold() { return std::make_unique>(); } inline std::unique_ptr CreateEigenQuaternionManifold() { return std::make_unique(); } inline std::unique_ptr CreateSubsetManifold( int size, const std::vector& constant_params) { return std::make_unique(size, constant_params); } template inline std::unique_ptr CreateSphereManifold() { return std::make_unique>(); } template inline std::unique_ptr CreateProductManifold( Args&&... manifolds) { // Note: Does not support make_unique due to template constructor. return std::unique_ptr( new ceres::ProductManifold(std::forward(manifolds)...)); } inline int ParameterBlockTangentSize(const ceres::Problem& problem, const double* param) { return problem.ParameterBlockTangentSize(param); } #else // CERES_VERSION_MAJOR < 2.1.0 inline void SetManifold(ceres::Problem* problem, double* params, ceres::LocalParameterization* parameterization) { problem->SetParameterization(params, parameterization); } inline void SetManifold( ceres::Problem* problem, double* params, std::unique_ptr parameterization) { problem->SetParameterization(params, parameterization.release()); } template inline std::unique_ptr CreateEuclideanManifold() { return std::make_unique(size); } inline std::unique_ptr CreateEigenQuaternionManifold() { return std::make_unique(); } inline std::unique_ptr CreateSubsetManifold( int size, const std::vector& constant_params) { return std::make_unique(size, constant_params); } template inline std::unique_ptr CreateSphereManifold() { return std::make_unique(size); } template inline std::unique_ptr CreateProductManifold( Args&&... parameterizations) { // Note: Does not support make_unique due to template constructor. return std::unique_ptr( new ceres::ProductParameterization(parameterizations.release()...)); } inline int ParameterBlockTangentSize(const ceres::Problem& problem, const double* param) { return problem.ParameterBlockLocalSize(param); } #endif } // namespace colmap colmap-4.2.0/src/colmap/estimators/cost_functions/motion_averaging.h000066400000000000000000000115741524536416500260100ustar00rootroot00000000000000 #pragma once #include #include #include namespace colmap { // Computes the error between a translation direction and the direction formed // from two positions such that: t_ij - scale * (p_j - p_i) is minimized. // The positions can either be two camera centers or one camera center and one // 3D point. // Reference: Zhuang et al., "Baseline Desensitizing In Translation Averaging", // CVPR 2018. struct BATAPairwiseDirectionCostFunctor { explicit BATAPairwiseDirectionCostFunctor( const Eigen::Vector3d& pos2_from_pos1_dir) : pos2_from_pos1_dir_(pos2_from_pos1_dir) {} template bool operator()(const T* pos1, const T* pos2, const T* scale, T* residuals) const { Eigen::Map> residuals_vec(residuals); residuals_vec = pos2_from_pos1_dir_.cast() - scale[0] * (Eigen::Map>(pos2) - Eigen::Map>(pos1)); return true; } static ceres::CostFunction* Create( const Eigen::Vector3d& pos2_from_pos1_dir) { return ( new ceres:: AutoDiffCostFunction( new BATAPairwiseDirectionCostFunctor(pos2_from_pos1_dir))); } const Eigen::Vector3d pos2_from_pos1_dir_; }; // Computes the error between a translation direction and the direction formed // from a camera (c) and 3D point (p) with constant rig extrinsics, such that: // t_ij - scale * (p - c + t_rig) is minimized. struct RigBATAPairwiseDirectionConstantRigCostFunctor { RigBATAPairwiseDirectionConstantRigCostFunctor( const Eigen::Vector3d& cam_from_point3D_dir, const Eigen::Vector3d& cam_from_rig_translation) : cam_from_point3D_dir_(cam_from_point3D_dir), cam_from_rig_translation_(cam_from_rig_translation) {} template bool operator()(const T* point3D, const T* rig_in_world, const T* scale, T* residuals) const { Eigen::Map> residuals_vec(residuals); residuals_vec = cam_from_point3D_dir_.cast() - scale[0] * (Eigen::Map>(point3D) - Eigen::Map>(rig_in_world) + cam_from_rig_translation_.cast()); return true; } static ceres::CostFunction* Create( const Eigen::Vector3d& cam_from_point3D_dir, const Eigen::Vector3d& cam_from_rig_translation) { return (new ceres::AutoDiffCostFunction< RigBATAPairwiseDirectionConstantRigCostFunctor, 3, 3, 3, 1>(new RigBATAPairwiseDirectionConstantRigCostFunctor( cam_from_point3D_dir, cam_from_rig_translation))); } const Eigen::Vector3d cam_from_point3D_dir_; const Eigen::Vector3d cam_from_rig_translation_; }; // Computes the error between a translation direction and the direction formed // from a camera (c) and 3D point (p) with variable rig extrinsics, such that: // t_ij - scale * (p - c + t_rig) is minimized. struct RigBATAPairwiseDirectionCostFunctor { RigBATAPairwiseDirectionCostFunctor( const Eigen::Vector3d& cam_from_point3D_dir, const Eigen::Quaterniond& rig_from_world_rot) : cam_from_point3D_dir_(cam_from_point3D_dir), world_from_rig_rot_(rig_from_world_rot.inverse()) {} template bool operator()(const T* point3D, const T* rig_in_world, const T* cam_in_rig, const T* scale, T* residuals) const { const Eigen::Matrix cam_from_rig_translation = world_from_rig_rot_.cast() * Eigen::Map>(cam_in_rig); Eigen::Map> residuals_vec(residuals); residuals_vec = cam_from_point3D_dir_.cast() - scale[0] * (Eigen::Map>(point3D) - Eigen::Map>(rig_in_world) - cam_from_rig_translation); return true; } static ceres::CostFunction* Create( const Eigen::Vector3d& cam_from_point3D_dir, const Eigen::Quaterniond& rig_from_world_rot) { return (new ceres::AutoDiffCostFunction( new RigBATAPairwiseDirectionCostFunctor(cam_from_point3D_dir, rig_from_world_rot))); } const Eigen::Vector3d cam_from_point3D_dir_; const Eigen::Quaterniond world_from_rig_rot_; }; } // namespace colmap colmap-4.2.0/src/colmap/estimators/cost_functions/motion_averaging_test.cc000066400000000000000000000172541524536416500272060ustar00rootroot00000000000000// Copyright (c), ETH Zurich and UNC Chapel Hill. // All rights reserved. // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are met: // // * Redistributions of source code must retain the above copyright // notice, this list of conditions and the following disclaimer. // // * Redistributions in binary form must reproduce the above copyright // notice, this list of conditions and the following disclaimer in the // documentation and/or other materials provided with the distribution. // // * Neither the name of ETH Zurich and UNC Chapel Hill nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE // ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE // LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR // CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF // SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS // INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN // CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) // ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE // POSSIBILITY OF SUCH DAMAGE. #include "colmap/estimators/cost_functions/motion_averaging.h" #include "colmap/util/eigen_matchers.h" #include namespace colmap { namespace { TEST(BATAPairwiseDirectionCostFunctor, ZeroResidual) { const Eigen::Vector3d pos1(1, 2, 3); const Eigen::Vector3d pos2(2, 3, 4); const double scale = 1.0; const Eigen::Vector3d direction = pos2 - pos1; BATAPairwiseDirectionCostFunctor cost_functor(direction); Eigen::Vector3d residuals; EXPECT_TRUE(cost_functor(pos1.data(), pos2.data(), &scale, residuals.data())); EXPECT_THAT(residuals, EigenMatrixNear(Eigen::Vector3d(0, 0, 0), 1e-10)); } TEST(BATAPairwiseDirectionCostFunctor, NonZeroResidual) { const Eigen::Vector3d pos1(1, 2, 3); const Eigen::Vector3d pos2(4, 5, 6); const double scale = 2.0; const Eigen::Vector3d direction(1, 1, 1); BATAPairwiseDirectionCostFunctor cost_functor(direction); Eigen::Vector3d residuals; EXPECT_TRUE(cost_functor(pos1.data(), pos2.data(), &scale, residuals.data())); const Eigen::Vector3d expected_residuals = direction - scale * (pos2 - pos1); EXPECT_THAT(residuals, EigenMatrixNear(expected_residuals, 1e-10)); } TEST(BATAPairwiseDirectionCostFunctor, DifferentScale) { const Eigen::Vector3d pos1(1, 2, 3); const Eigen::Vector3d pos2(2, 4, 6); const double scale = 0.5; const Eigen::Vector3d direction = scale * (pos2 - pos1); BATAPairwiseDirectionCostFunctor cost_functor(direction); Eigen::Vector3d residuals; EXPECT_TRUE(cost_functor(pos1.data(), pos2.data(), &scale, residuals.data())); EXPECT_THAT(residuals, EigenMatrixNear(Eigen::Vector3d(0, 0, 0), 1e-10)); } TEST(BATAPairwiseDirectionCostFunctor, Create) { const Eigen::Vector3d direction(1, 0, 0); std::unique_ptr cost_function( BATAPairwiseDirectionCostFunctor::Create(direction)); ASSERT_NE(cost_function, nullptr); } TEST(RigBATAPairwiseDirectionConstantRigCostFunctor, ZeroResidual) { const Eigen::Vector3d point3D(1, 2, 3); const Eigen::Vector3d rig_in_world(3, 2, 1); const double scale = 1.5; const Eigen::Vector3d cam_from_rig_dir(0.25, 0.5, 0.75); const Eigen::Vector3d cam_from_point3D_dir = scale * (point3D - rig_in_world + cam_from_rig_dir); RigBATAPairwiseDirectionConstantRigCostFunctor cost_functor( cam_from_point3D_dir, cam_from_rig_dir); Eigen::Vector3d residuals; EXPECT_TRUE(cost_functor( point3D.data(), rig_in_world.data(), &scale, residuals.data())); EXPECT_THAT(residuals, EigenMatrixNear(Eigen::Vector3d(0, 0, 0), 1e-10)); } TEST(RigBATAPairwiseDirectionConstantRigCostFunctor, NonZeroResidual) { const Eigen::Vector3d point3D(3, 4, 5); const Eigen::Vector3d rig_in_world(1, 2, 3); const double scale = 2.0; const Eigen::Vector3d cam_from_rig_dir(0.1, 0.2, 0.3); const Eigen::Vector3d cam_from_point3D_dir(1, 1, 1); RigBATAPairwiseDirectionConstantRigCostFunctor cost_functor( cam_from_point3D_dir, cam_from_rig_dir); Eigen::Vector3d residuals; EXPECT_TRUE(cost_functor( point3D.data(), rig_in_world.data(), &scale, residuals.data())); const Eigen::Vector3d expected_residuals = cam_from_point3D_dir - scale * (point3D - rig_in_world + cam_from_rig_dir); EXPECT_THAT(residuals, EigenMatrixNear(expected_residuals, 1e-10)); } TEST(RigBATAPairwiseDirectionConstantRigCostFunctor, Create) { const Eigen::Vector3d cam_from_point3D_dir(1, 0, 0); const Eigen::Vector3d cam_from_rig_dir(0, 1, 0); std::unique_ptr cost_function( RigBATAPairwiseDirectionConstantRigCostFunctor::Create( cam_from_point3D_dir, cam_from_rig_dir)); ASSERT_NE(cost_function, nullptr); } TEST(RigBATAPairwiseDirectionCostFunctor, ZeroResidual) { const Eigen::Vector3d point3D(5, 5, 5); const Eigen::Vector3d rig_in_world(1, 1, 1); const Eigen::Vector3d cam_in_rig(0.5, 0.5, 0.5); const double scale = 1.0; const Eigen::Quaterniond rig_from_world_rot = Eigen::Quaterniond::Identity(); const Eigen::Vector3d cam_from_rig_dir = rig_from_world_rot.inverse() * cam_in_rig; const Eigen::Vector3d cam_from_point3D_dir = scale * (point3D - rig_in_world - cam_from_rig_dir); RigBATAPairwiseDirectionCostFunctor cost_functor(cam_from_point3D_dir, rig_from_world_rot); Eigen::Vector3d residuals; EXPECT_TRUE(cost_functor(point3D.data(), rig_in_world.data(), cam_in_rig.data(), &scale, residuals.data())); EXPECT_THAT(residuals, EigenMatrixNear(Eigen::Vector3d(0, 0, 0), 1e-10)); } TEST(RigBATAPairwiseDirectionCostFunctor, NonZeroResidual) { const Eigen::Vector3d point3D(3, 4, 5); const Eigen::Vector3d rig_in_world(1, 2, 3); const Eigen::Vector3d cam_in_rig(0.2, 0.3, 0.4); const double scale = 2.0; const Eigen::Quaterniond rig_from_world_rot = Eigen::Quaterniond(0.707, 0.707, 0, 0).normalized(); const Eigen::Vector3d cam_from_point3D_dir(1, 1, 1); RigBATAPairwiseDirectionCostFunctor cost_functor(cam_from_point3D_dir, rig_from_world_rot); Eigen::Vector3d residuals; EXPECT_TRUE(cost_functor(point3D.data(), rig_in_world.data(), cam_in_rig.data(), &scale, residuals.data())); const Eigen::Vector3d cam_from_rig_dir = rig_from_world_rot.toRotationMatrix().transpose() * cam_in_rig; const Eigen::Vector3d expected_residuals = cam_from_point3D_dir - scale * (point3D - rig_in_world - cam_from_rig_dir); EXPECT_THAT(residuals, EigenMatrixNear(expected_residuals, 1e-10)); } TEST(RigBATAPairwiseDirectionCostFunctor, Create) { const Eigen::Vector3d cam_from_point3D_dir(1, 0, 0); const Eigen::Quaterniond rig_from_world_rot = Eigen::Quaterniond::Identity(); std::unique_ptr cost_function( RigBATAPairwiseDirectionCostFunctor::Create(cam_from_point3D_dir, rig_from_world_rot)); ASSERT_NE(cost_function, nullptr); } } // namespace } // namespace colmap colmap-4.2.0/src/colmap/estimators/cost_functions/pose_prior.h000066400000000000000000000160671524536416500246430ustar00rootroot00000000000000// Copyright (c), ETH Zurich and UNC Chapel Hill. // All rights reserved. // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are met: // // * Redistributions of source code must retain the above copyright // notice, this list of conditions and the following disclaimer. // // * Redistributions in binary form must reproduce the above copyright // notice, this list of conditions and the following disclaimer in the // documentation and/or other materials provided with the distribution. // // * Neither the name of ETH Zurich and UNC Chapel Hill nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE // ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE // LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR // CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF // SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS // INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN // CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) // ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE // POSSIBILITY OF SUCH DAMAGE. #pragma once #include "colmap/estimators/cost_functions/quaternion_utils.h" #include "colmap/estimators/cost_functions/utils.h" #include "colmap/geometry/rigid3.h" #include #include #include namespace colmap { // 6-DoF error on the absolute sensor pose. The residual is the log of the error // pose, splitting SE(3) into SO(3) x R^3. The residual is computed in the // sensor frame. Its first and last three components correspond to the rotation // and translation errors, respectively. struct AbsolutePosePriorCostFunctor : public AutoDiffCostFunctor { public: explicit AbsolutePosePriorCostFunctor(const Rigid3d& sensor_from_world_prior) : world_from_sensor_prior_(Inverse(sensor_from_world_prior)) {} template bool operator()(const T* const sensor_from_world, T* residuals_ptr) const { const Eigen::Quaternion param_from_prior_rotation = EigenQuaternionMap(sensor_from_world) * world_from_sensor_prior_.rotation().cast(); AngleAxisFromEigenQuaternion(param_from_prior_rotation.coeffs().data(), residuals_ptr); Eigen::Map> param_from_prior_translation( residuals_ptr + 3); param_from_prior_translation = EigenVector3Map(sensor_from_world + 4) + EigenQuaternionMap(sensor_from_world) * world_from_sensor_prior_.translation().cast(); return true; } private: const Rigid3d world_from_sensor_prior_; }; // 3-DoF error on the sensor position in the world coordinate frame. struct AbsolutePosePositionPriorCostFunctor : public AutoDiffCostFunctor { public: explicit AbsolutePosePositionPriorCostFunctor( const Eigen::Vector3d& position_in_world_prior) : position_in_world_prior_(position_in_world_prior) {} template bool operator()(const T* const sensor_from_world, T* residuals_ptr) const { Eigen::Map> residuals(residuals_ptr); residuals = position_in_world_prior_.cast() + EigenQuaternionMap(sensor_from_world).inverse() * EigenVector3Map(sensor_from_world + 4); return true; } private: const Eigen::Vector3d position_in_world_prior_; }; // 3-DoF error on the rig sensor position in the world coordinate frame. struct AbsoluteRigPosePositionPriorCostFunctor : public AutoDiffCostFunctor { public: explicit AbsoluteRigPosePositionPriorCostFunctor( const Eigen::Vector3d& position_in_world_prior) : position_in_world_prior_(position_in_world_prior) {} template bool operator()(const T* const sensor_from_rig, const T* const rig_from_world, T* residuals_ptr) const { const Eigen::Quaternion sensor_from_world_rotation = EigenQuaternionMap(sensor_from_rig) * EigenQuaternionMap(rig_from_world); const Eigen::Matrix sensor_from_world_translation = EigenVector3Map(sensor_from_rig + 4) + EigenQuaternionMap(sensor_from_rig) * EigenVector3Map(rig_from_world + 4); Eigen::Map> residuals(residuals_ptr); residuals = position_in_world_prior_.cast() + sensor_from_world_rotation.inverse() * sensor_from_world_translation; return true; } private: const Eigen::Vector3d position_in_world_prior_; }; // 6-DoF error between two absolute camera poses based on a prior on their // relative pose, with identical scale for the translation. The residual is // computed in the frame of camera i. Its first and last three components // correspond to the rotation and translation errors, respectively. // // Derivation: // i_T_w = ΔT_i·i_T_j·j_T_w // where ΔT_i = exp(η_i) is the resjdual in SE(3) and η_i in tangent space. // Thus η_i = log(i_T_w·j_T_wâ»Â¹Â·j_T_i) // Rotation term: ΔR = log(i_R_w·j_R_wâ»Â¹Â·j_R_i) // Translation term: Δt = i_t_w + i_R_w·j_R_wâ»Â¹Â·(j_t_i -j_t_w) struct RelativePosePriorCostFunctor : public AutoDiffCostFunctor { public: explicit RelativePosePriorCostFunctor(const Rigid3d& i_from_j_prior) : j_from_i_prior_(Inverse(i_from_j_prior)) {} template bool operator()(const T* const i_from_world, const T* const j_from_world, T* residuals_ptr) const { const Eigen::Quaternion i_from_j_rotation = EigenQuaternionMap(i_from_world) * EigenQuaternionMap(j_from_world).inverse(); const Eigen::Quaternion param_from_prior_rotation = i_from_j_rotation * j_from_i_prior_.rotation().template cast(); AngleAxisFromEigenQuaternion(param_from_prior_rotation.coeffs().data(), residuals_ptr); const Eigen::Matrix j_from_i_prior_translation = j_from_i_prior_.translation().cast() - EigenVector3Map(j_from_world + 4); Eigen::Map> param_from_prior_translation( residuals_ptr + 3); param_from_prior_translation = EigenVector3Map(i_from_world + 4) + i_from_j_rotation * j_from_i_prior_translation; return true; } private: const Rigid3d j_from_i_prior_; }; } // namespace colmap colmap-4.2.0/src/colmap/estimators/cost_functions/pose_prior_test.cc000066400000000000000000000221261524536416500260310ustar00rootroot00000000000000// Copyright (c), ETH Zurich and UNC Chapel Hill. // All rights reserved. // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are met: // // * Redistributions of source code must retain the above copyright // notice, this list of conditions and the following disclaimer. // // * Redistributions in binary form must reproduce the above copyright // notice, this list of conditions and the following disclaimer in the // documentation and/or other materials provided with the distribution. // // * Neither the name of ETH Zurich and UNC Chapel Hill nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE // ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE // LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR // CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF // SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS // INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN // CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) // ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE // POSSIBILITY OF SUCH DAMAGE. #include "colmap/estimators/cost_functions/pose_prior.h" #include "colmap/geometry/rigid3.h" #include "colmap/math/math.h" #include "colmap/math/random_eigen.h" #include "colmap/util/eigen_matchers.h" #include namespace colmap { namespace { TEST(AbsolutePosePositionPriorCostFunctor, Nominal) { std::unique_ptr cost_function( AbsolutePosePositionPriorCostFunctor::Create(Eigen::Vector3d::Zero())); Rigid3d sensor_from_world = Rigid3d(Eigen::Quaterniond::Identity(), Eigen::Vector3d::Zero()); Eigen::Vector3d residuals = Eigen::Vector3d::Constant(std::numeric_limits::quiet_NaN()); const double* parameters[1] = {sensor_from_world.params.data()}; EXPECT_TRUE(cost_function->Evaluate(parameters, residuals.data(), nullptr)); EXPECT_THAT(residuals, EigenMatrixNear(Eigen::Vector3d(0, 0, 0), 1e-6)); sensor_from_world = Rigid3d(RandomEigenQuaterniond(), RandomEigenVectord<3>()); const Eigen::Vector3d position_in_world = Inverse(sensor_from_world).translation(); residuals = Eigen::Vector3d::Constant(std::numeric_limits::quiet_NaN()); EXPECT_TRUE(cost_function->Evaluate(parameters, residuals.data(), nullptr)); EXPECT_THAT(residuals, EigenMatrixNear(Eigen::Vector3d(-position_in_world), 1e-6)); cost_function.reset( AbsolutePosePositionPriorCostFunctor::Create(position_in_world)); residuals = Eigen::Vector3d::Constant(std::numeric_limits::quiet_NaN()); EXPECT_TRUE(cost_function->Evaluate(parameters, residuals.data(), nullptr)); EXPECT_THAT(residuals, EigenMatrixNear(Eigen::Vector3d(0, 0, 0), 1e-6)); } TEST(AbsoluteRigPosePositionPriorCostFunctor, Nominal) { std::unique_ptr cost_function( AbsoluteRigPosePositionPriorCostFunctor::Create(Eigen::Vector3d::Zero())); Rigid3d sensor_from_rig(Eigen::Quaterniond::Identity(), Eigen::Vector3d::Zero()); Rigid3d rig_from_world(Eigen::Quaterniond::Identity(), Eigen::Vector3d::Zero()); Eigen::Vector3d residuals = Eigen::Vector3d::Constant(std::numeric_limits::quiet_NaN()); const double* parameters[2] = {sensor_from_rig.params.data(), rig_from_world.params.data()}; EXPECT_TRUE(cost_function->Evaluate(parameters, residuals.data(), nullptr)); EXPECT_THAT(residuals, EigenMatrixNear(Eigen::Vector3d(0, 0, 0), 1e-6)); sensor_from_rig = Rigid3d(RandomEigenQuaterniond(), RandomEigenVectord<3>()); rig_from_world = Rigid3d(RandomEigenQuaterniond(), RandomEigenVectord<3>()); const Rigid3d sensor_from_world = sensor_from_rig * rig_from_world; const Eigen::Vector3d position_in_world = Inverse(sensor_from_world).translation(); residuals = Eigen::Vector3d::Constant(std::numeric_limits::quiet_NaN()); EXPECT_TRUE(cost_function->Evaluate(parameters, residuals.data(), nullptr)); EXPECT_THAT(residuals, EigenMatrixNear(Eigen::Vector3d(-position_in_world), 1e-6)); cost_function.reset( AbsoluteRigPosePositionPriorCostFunctor::Create(position_in_world)); residuals = Eigen::Vector3d::Constant(std::numeric_limits::quiet_NaN()); EXPECT_TRUE(cost_function->Evaluate(parameters, residuals.data(), nullptr)); EXPECT_THAT(residuals, EigenMatrixNear(Eigen::Vector3d(0, 0, 0), 1e-6)); } TEST(AbsolutePosePriorCostFunctor, Nominal) { const Rigid3d cam_from_world_prior; std::unique_ptr cost_function( AbsolutePosePriorCostFunctor::Create(cam_from_world_prior)); double cam_from_world[7] = {0, 0, 0, 1, 0, 0, 0}; double residuals[6]; const double* parameters[1] = {cam_from_world}; EXPECT_TRUE(cost_function->Evaluate(parameters, residuals, nullptr)); EXPECT_EQ(residuals[0], 0); EXPECT_EQ(residuals[1], 0); EXPECT_EQ(residuals[2], 0); EXPECT_EQ(residuals[3], 0); EXPECT_EQ(residuals[4], 0); EXPECT_EQ(residuals[5], 0); cam_from_world[4] = 1; EXPECT_TRUE(cost_function->Evaluate(parameters, residuals, nullptr)); EXPECT_EQ(residuals[0], 0); EXPECT_EQ(residuals[1], 0); EXPECT_EQ(residuals[2], 0); EXPECT_EQ(residuals[3], 1); EXPECT_EQ(residuals[4], 0); EXPECT_EQ(residuals[5], 0); // Rotation by 90 degrees around the Y axis. Eigen::Matrix3d rotation_matrix; rotation_matrix << 0, 0, 1, 0, 1, 0, -1, 0, 0; Eigen::Map(static_cast(cam_from_world)) = rotation_matrix; cam_from_world[5] = 2; cam_from_world[6] = 3; EXPECT_TRUE(cost_function->Evaluate(parameters, residuals, nullptr)); EXPECT_NEAR(residuals[0], 0, 1e-6); EXPECT_NEAR(residuals[1], DegToRad(90.0), 1e-6); EXPECT_NEAR(residuals[2], 0, 1e-6); EXPECT_NEAR(residuals[3], 1, 1e-6); EXPECT_NEAR(residuals[4], 2, 1e-6); EXPECT_NEAR(residuals[5], 3, 1e-6); } TEST(RelativePosePriorCostFunctor, Nominal) { Rigid3d i_from_j_prior(Eigen::Quaterniond::Identity(), Eigen::Vector3d(0, 0, -1)); std::unique_ptr cost_function( RelativePosePriorCostFunctor::Create(i_from_j_prior)); double i_from_world[7] = {0, 0, 0, 1, 0, 0, 0}; double j_from_world[7] = {0, 0, 0, 1, 0, 0, 1}; double residuals[6]; const double* parameters[2] = {i_from_world, j_from_world}; EXPECT_TRUE(cost_function->Evaluate(parameters, residuals, nullptr)); EXPECT_EQ(residuals[0], 0); EXPECT_EQ(residuals[1], 0); EXPECT_EQ(residuals[2], 0); EXPECT_EQ(residuals[3], 0); EXPECT_EQ(residuals[4], 0); EXPECT_EQ(residuals[5], 0); i_from_world[6] = 4; EXPECT_TRUE(cost_function->Evaluate(parameters, residuals, nullptr)); EXPECT_EQ(residuals[0], 0); EXPECT_EQ(residuals[1], 0); EXPECT_EQ(residuals[2], 0); EXPECT_EQ(residuals[3], 0); EXPECT_EQ(residuals[4], 0); EXPECT_EQ(residuals[5], 4); j_from_world[4] = 2; EXPECT_TRUE(cost_function->Evaluate(parameters, residuals, nullptr)); EXPECT_EQ(residuals[0], 0); EXPECT_EQ(residuals[1], 0); EXPECT_EQ(residuals[2], 0); EXPECT_EQ(residuals[3], -2); EXPECT_EQ(residuals[4], 0); EXPECT_EQ(residuals[5], 4); // Rotation by 90 degrees around the Y axis. Eigen::Matrix3d rotation_matrix; rotation_matrix << 0, 0, 1, 0, 1, 0, -1, 0, 0; Eigen::Map(static_cast(j_from_world)) = rotation_matrix; EXPECT_TRUE(cost_function->Evaluate(parameters, residuals, nullptr)); EXPECT_NEAR(residuals[0], 0, 1e-6); EXPECT_NEAR(residuals[1], DegToRad(-90.0), 1e-6); EXPECT_NEAR(residuals[2], 0, 1e-6); EXPECT_NEAR(residuals[3], 0, 1e-6); EXPECT_NEAR(residuals[4], 0, 1e-6); EXPECT_NEAR(residuals[5], 2, 1e-6); } TEST(CovarianceWeightedCostFunctor, AbsolutePosePositionPriorCostFunctor) { const Rigid3d cam_from_world(RandomEigenQuaterniond(), RandomEigenVectord<3>()); const Rigid3d world_from_cam = Inverse(cam_from_world); double residuals[3]; const double* parameters[1] = {cam_from_world.params.data()}; std::unique_ptr cost_function( CovarianceWeightedCostFunctor:: Create(2 * Eigen::Matrix3d::Identity(), Eigen::Vector3d::Zero())); EXPECT_TRUE(cost_function->Evaluate(parameters, residuals, nullptr)); EXPECT_NEAR(residuals[0], -0.5 * std::sqrt(2) * world_from_cam.translation()[0], 1e-6); EXPECT_NEAR(residuals[1], -0.5 * std::sqrt(2) * world_from_cam.translation()[1], 1e-6); EXPECT_NEAR(residuals[2], -0.5 * std::sqrt(2) * world_from_cam.translation()[2], 1e-6); } } // namespace } // namespace colmap colmap-4.2.0/src/colmap/estimators/cost_functions/quaternion_utils.h000066400000000000000000000136241524536416500260630ustar00rootroot00000000000000// Copyright (c), ETH Zurich and UNC Chapel Hill. // All rights reserved. // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are met: // // * Redistributions of source code must retain the above copyright // notice, this list of conditions and the following disclaimer. // // * Redistributions in binary form must reproduce the above copyright // notice, this list of conditions and the following disclaimer in the // documentation and/or other materials provided with the distribution. // // * Neither the name of ETH Zurich and UNC Chapel Hill nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE // ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE // LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR // CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF // SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS // INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN // CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) // ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE // POSSIBILITY OF SUCH DAMAGE. #pragma once #include #include #include namespace colmap { template using EigenVector3Map = Eigen::Map>; template using EigenQuaternionMap = Eigen::Map>; template inline void AngleAxisFromEigenQuaternion(const T* eigen_quaternion, T* angle_axis) { const T quaternion[4] = {eigen_quaternion[3], eigen_quaternion[0], eigen_quaternion[1], eigen_quaternion[2]}; ceres::QuaternionToAngleAxis(quaternion, angle_axis); } template inline void EigenQuaternionFromAngleAxis(const T* angle_axis, T* eigen_quaternion) { T quaternion[4]; ceres::AngleAxisToQuaternion(angle_axis, quaternion); eigen_quaternion[0] = quaternion[1]; eigen_quaternion[1] = quaternion[2]; eigen_quaternion[2] = quaternion[3]; eigen_quaternion[3] = quaternion[0]; } // Quaternion utilities for analytical Jacobian computation in cost functions. // // Convention: Eigen quaternion storage order (x, y, z, w), Hamilton product. // A quaternion q = (x, y, z, w) represents the rotation matrix: // R(q) = (w^2 - ||v||^2) I + 2 v v^T + 2 w [v]_x // where v = (x, y, z) is the vector part. // // The 4-vector representation used in matrices is [x, y, z, w] (Eigen order). // Eigen::Quaterniond::coeffs() returns [x, y, z, w]. // Hamilton quaternion left-multiplication matrix (xyzw storage): // QuaternionLeftMultMatrix(q) * p = q * p (as 4-vectors). inline Eigen::Matrix4d QuaternionLeftMultMatrix(const Eigen::Quaterniond& q) { Eigen::Matrix4d Q; const double x = q.x(), y = q.y(), z = q.z(), w = q.w(); // clang-format off Q << w, -z, y, x, z, w, -x, y, -y, x, w, z, -x, -y, -z, w; // clang-format on return Q; } // Hamilton quaternion right-multiplication matrix (xyzw storage): // QuaternionRightMultMatrix(p) * q = q * p (as 4-vectors). inline Eigen::Matrix4d QuaternionRightMultMatrix(const Eigen::Quaterniond& q) { Eigen::Matrix4d Q; const double x = q.x(), y = q.y(), z = q.z(), w = q.w(); // clang-format off Q << w, z, -y, x, -z, w, x, y, y, -x, w, z, -x, -y, -z, w; // clang-format on return Q; } // Rotates the point and optionally computes the Jacobian of R(q) * p // w.r.t. Eigen quaternion q (xyzw storage). J_out is a 3x4 row-major matrix. // Pass nullptr for J_out to skip the Jacobian computation. inline Eigen::Vector3d QuaternionRotatePointWithJac(const double* q, const double* pt, double* J_out) { const double qx = q[0], qy = q[1], qz = q[2], qw = q[3]; const double px = pt[0], py = pt[1], pz = pt[2]; const double qx_py = qx * py, qx_pz = qx * pz; const double qy_px = qy * px, qy_pz = qy * pz; const double qz_px = qz * px, qz_py = qz * py; // R(q) * p = p + 2*w*(v x p) + 2*(v x (v x p)) const double v_x_p0 = qy_pz - qz_py; const double v_x_p1 = qz_px - qx_pz; const double v_x_p2 = qx_py - qy_px; const double v_x_v_x_p0 = qy * v_x_p2 - qz * v_x_p1; const double v_x_v_x_p1 = qz * v_x_p0 - qx * v_x_p2; const double v_x_v_x_p2 = qx * v_x_p1 - qy * v_x_p0; Eigen::Vector3d pt_out(px + 2.0 * (qw * v_x_p0 + v_x_v_x_p0), py + 2.0 * (qw * v_x_p1 + v_x_v_x_p1), pz + 2.0 * (qw * v_x_p2 + v_x_v_x_p2)); if (J_out) { const double qx_px = qx * px; const double qy_py = qy * py; const double qz_pz = qz * pz; const double qw_px = qw * px; const double qw_py = qw * py; const double qw_pz = qw * pz; J_out[0] = 2.0 * (qy_py + qz_pz); J_out[1] = 2.0 * (-2.0 * qy_px + qx_py + qw_pz); J_out[2] = 2.0 * (-2.0 * qz_px - qw_py + qx_pz); J_out[3] = 2.0 * (-qz_py + qy_pz); J_out[4] = 2.0 * (qy_px - 2.0 * qx_py - qw_pz); J_out[5] = 2.0 * (qx_px + qz_pz); J_out[6] = 2.0 * (qw_px - 2.0 * qz_py + qy_pz); J_out[7] = 2.0 * (qz_px - qx_pz); J_out[8] = 2.0 * (qz_px + qw_py - 2.0 * qx_pz); J_out[9] = 2.0 * (-qw_px + qz_py - 2.0 * qy_pz); J_out[10] = 2.0 * (qx_px + qy_py); J_out[11] = 2.0 * (-qy_px + qx_py); } return pt_out; } } // namespace colmap colmap-4.2.0/src/colmap/estimators/cost_functions/quaternion_utils_test.cc000066400000000000000000000140531524536416500272550ustar00rootroot00000000000000// Copyright (c), ETH Zurich and UNC Chapel Hill. // All rights reserved. // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are met: // // * Redistributions of source code must retain the above copyright // notice, this list of conditions and the following disclaimer. // // * Redistributions in binary form must reproduce the above copyright // notice, this list of conditions and the following disclaimer in the // documentation and/or other materials provided with the distribution. // // * Neither the name of ETH Zurich and UNC Chapel Hill nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE // ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE // LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR // CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF // SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS // INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN // CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) // ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE // POSSIBILITY OF SUCH DAMAGE. #include "colmap/estimators/cost_functions/quaternion_utils.h" #include "colmap/math/random_eigen.h" #include "colmap/util/eigen_matchers.h" #include #include #include namespace colmap { namespace { TEST(QuaternionLeftMultMatrix, Nominal) { constexpr double kEps = 1e-7; for (int i = 0; i < 100; ++i) { Eigen::Quaterniond q = RandomEigenQuaterniond(); Eigen::Quaterniond p = RandomEigenQuaterniond(); Eigen::Vector4d p_vec(p.x(), p.y(), p.z(), p.w()); // L(q) * p = q * p. Eigen::Vector4d result = QuaternionLeftMultMatrix(q) * p_vec; Eigen::Quaterniond qp = q * p; Eigen::Vector4d expected(qp.x(), qp.y(), qp.z(), qp.w()); EXPECT_THAT(result, EigenMatrixNear(expected, 1e-12)); // L(q) = d(q*p)/dp (Jacobian w.r.t. second argument). Eigen::Matrix4d J_numeric; for (int k = 0; k < 4; ++k) { Eigen::Vector4d p_plus = p_vec, p_minus = p_vec; p_plus(k) += kEps; p_minus(k) -= kEps; Eigen::Quaterniond pp(p_plus(3), p_plus(0), p_plus(1), p_plus(2)); Eigen::Quaterniond pm(p_minus(3), p_minus(0), p_minus(1), p_minus(2)); Eigen::Quaterniond rp = q * pp, rm = q * pm; J_numeric.col(k) = (Eigen::Vector4d(rp.x(), rp.y(), rp.z(), rp.w()) - Eigen::Vector4d(rm.x(), rm.y(), rm.z(), rm.w())) / (2.0 * kEps); } EXPECT_THAT(QuaternionLeftMultMatrix(q), EigenMatrixNear(J_numeric, 1e-5)); } } TEST(QuaternionRightMultMatrix, Nominal) { constexpr double kEps = 1e-7; for (int i = 0; i < 100; ++i) { Eigen::Quaterniond q = RandomEigenQuaterniond(); Eigen::Quaterniond p = RandomEigenQuaterniond(); Eigen::Vector4d q_vec(q.x(), q.y(), q.z(), q.w()); // R(p) * q = q * p. Eigen::Vector4d result = QuaternionRightMultMatrix(p) * q_vec; Eigen::Quaterniond qp = q * p; Eigen::Vector4d expected(qp.x(), qp.y(), qp.z(), qp.w()); EXPECT_THAT(result, EigenMatrixNear(expected, 1e-12)); // R(p) = d(q*p)/dq (Jacobian w.r.t. first argument). Eigen::Matrix4d J_numeric; for (int k = 0; k < 4; ++k) { Eigen::Vector4d q_plus = q_vec, q_minus = q_vec; q_plus(k) += kEps; q_minus(k) -= kEps; Eigen::Quaterniond qp_p(q_plus(3), q_plus(0), q_plus(1), q_plus(2)); Eigen::Quaterniond qm_p(q_minus(3), q_minus(0), q_minus(1), q_minus(2)); Eigen::Quaterniond rp = qp_p * p, rm = qm_p * p; J_numeric.col(k) = (Eigen::Vector4d(rp.x(), rp.y(), rp.z(), rp.w()) - Eigen::Vector4d(rm.x(), rm.y(), rm.z(), rm.w())) / (2.0 * kEps); } EXPECT_THAT(QuaternionRightMultMatrix(p), EigenMatrixNear(J_numeric, 1e-5)); } } TEST(QuaternionRotatePointWithJac, Nominal) { constexpr double kEps = 1e-7; for (int i = 0; i < 100; ++i) { Eigen::Quaterniond q = RandomEigenQuaterniond(); Eigen::Vector3d pt = RandomEigenVectord<3>(); double q_arr[4] = {q.x(), q.y(), q.z(), q.w()}; // R(q) * pt matches Eigen. Eigen::Matrix J_analytical; Eigen::Vector3d result = QuaternionRotatePointWithJac(q_arr, pt.data(), J_analytical.data()); EXPECT_THAT(result, EigenMatrixNear(Eigen::Vector3d(q * pt), 1e-12)); // Jacobian d(R(q)*pt)/dq matches numeric. Eigen::Matrix J_numeric; for (int k = 0; k < 4; ++k) { double q_plus[4] = {q_arr[0], q_arr[1], q_arr[2], q_arr[3]}; double q_minus[4] = {q_arr[0], q_arr[1], q_arr[2], q_arr[3]}; q_plus[k] += kEps; q_minus[k] -= kEps; J_numeric.col(k) = (QuaternionRotatePointWithJac(q_plus, pt.data(), nullptr) - QuaternionRotatePointWithJac(q_minus, pt.data(), nullptr)) / (2.0 * kEps); } EXPECT_THAT(J_analytical, EigenMatrixNear(J_numeric, 1e-5)); } } TEST(EigenQuaternionAngleAxis, Roundtrip) { for (int i = 0; i < 100; ++i) { const Eigen::Quaterniond q = RandomEigenQuaterniond(); const double q_arr[4] = {q.x(), q.y(), q.z(), q.w()}; // quaternion -> angle-axis -> quaternion recovers the original rotation. double angle_axis[3]; AngleAxisFromEigenQuaternion(q_arr, angle_axis); double q_out[4]; EigenQuaternionFromAngleAxis(angle_axis, q_out); // Compare as rotations to avoid the quaternion double-cover sign ambiguity. const Eigen::Quaterniond q_recovered( q_out[3], q_out[0], q_out[1], q_out[2]); EXPECT_NEAR(q.angularDistance(q_recovered), 0.0, 1e-10); } } } // namespace } // namespace colmap colmap-4.2.0/src/colmap/estimators/cost_functions/reprojection_error.h000066400000000000000000000473431524536416500263770ustar00rootroot00000000000000// Copyright (c), ETH Zurich and UNC Chapel Hill. // All rights reserved. // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are met: // // * Redistributions of source code must retain the above copyright // notice, this list of conditions and the following disclaimer. // // * Redistributions in binary form must reproduce the above copyright // notice, this list of conditions and the following disclaimer in the // documentation and/or other materials provided with the distribution. // // * Neither the name of ETH Zurich and UNC Chapel Hill nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE // ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE // LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR // CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF // SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS // INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN // CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) // ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE // POSSIBILITY OF SUCH DAMAGE. #pragma once #include "colmap/estimators/cost_functions/quaternion_utils.h" #include "colmap/estimators/cost_functions/utils.h" #include "colmap/geometry/rigid3.h" #include "colmap/sensor/models.h" #include #include #include namespace colmap { // Periodic (azimuthal) camera models such as EQUIRECTANGULAR wrap the x image // coordinate at the ±π seam, so a raw pixel residual can jump by ~width across // the seam (e.g. an observation at x ≈ 0 whose 3D point reprojects to // x ≈ width). Wrap the x-residual into [-width/2, width/2) so the // bundle-adjustment cost stays continuous across the seam. The offset is // locally constant, so it does not perturb the residual's derivatives. No-op // for non-periodic camera models. (Elevation has no wrap, so y is untouched.) template inline void WrapEquirectangularHorizontalSeam(const T* camera_params, T* residuals) { if constexpr (CameraModel::model_id == CameraModelId::kEquirectangular) { const T width = camera_params[0]; residuals[0] -= width * ceres::floor(residuals[0] / width + T(0.5)); } } // Full reprojection error cost function with analytical Jacobians. // Requires camera model to implement ImgFromCamWithJac(). template class AnalyticalReprojErrorCostFunction : public ceres::SizedCostFunction<2, 3, 7, CameraModel::num_params> { public: explicit AnalyticalReprojErrorCostFunction(const Eigen::Vector2d& point2D) : point2D_(point2D) {} bool Evaluate(double const* const* parameters, double* residuals, double** jacobians) const override { const double* point3D_in_world = parameters[0]; const double* cam_from_world = parameters[1]; const double* camera_params = parameters[2]; double* J_point = jacobians ? jacobians[0] : nullptr; double* J_pose = jacobians ? jacobians[1] : nullptr; double* J_params = jacobians ? jacobians[2] : nullptr; Eigen::Map residuals_vec(residuals); Eigen::Map> J_point_mat( J_point); Eigen::Map> J_pose_mat(J_pose); Eigen::Map< Eigen::Matrix> J_params_mat(J_params); Eigen::Matrix J_Rp_quat_mat; Eigen::Matrix J_uvw_mat; const Eigen::Vector3d point3D_in_cam = QuaternionRotatePointWithJac(cam_from_world, point3D_in_world, J_pose ? J_Rp_quat_mat.data() : nullptr) + Eigen::Map(cam_from_world + 4); if (!CameraModel::ImgFromCamWithJac( camera_params, point3D_in_cam[0], point3D_in_cam[1], point3D_in_cam[2], &residuals[0], &residuals[1], J_params, (J_point || J_pose) ? J_uvw_mat.data() : nullptr)) { residuals_vec.setZero(); if (J_pose) { J_pose_mat.setZero(); } if (J_point) { J_point_mat.setZero(); } if (J_params) { J_params_mat.setZero(); } return true; } residuals_vec -= point2D_; // No-op for non-periodic models. The offset is locally constant, so the // analytic Jacobians below are unaffected. WrapEquirectangularHorizontalSeam(camera_params, residuals); if (J_point) { J_point_mat = J_uvw_mat * EigenQuaternionMap(cam_from_world).toRotationMatrix(); } if (J_pose) { J_pose_mat.leftCols<4>() = J_uvw_mat * J_Rp_quat_mat; J_pose_mat.rightCols<3>() = J_uvw_mat; } return true; } private: const Eigen::Vector2d point2D_; }; // Reprojection error cost function with analytical Jacobians for a fixed camera // pose (variable point and camera calibration). Analytical counterpart of // ReprojErrorConstantPoseCostFunctor. Requires camera model to implement // ImgFromCamWithJac(). As in that functor, the fixed pose is stored as a // precomputed rotation matrix and translation; besides the faster matrix-vector // transform, the rotation matrix is reused directly for the point Jacobian, // avoiding a quaternion-to-matrix conversion on every evaluation. template class AnalyticalReprojErrorConstantPoseCostFunction : public ceres::SizedCostFunction<2, 3, CameraModel::num_params> { public: AnalyticalReprojErrorConstantPoseCostFunction(const Eigen::Vector2d& point2D, const Rigid3d& cam_from_world) : point2D_(point2D), cam_from_world_rotation_(cam_from_world.rotation().toRotationMatrix()), cam_from_world_translation_(cam_from_world.translation()) {} bool Evaluate(double const* const* parameters, double* residuals, double** jacobians) const override { const Eigen::Map point3D_in_world(parameters[0]); const double* camera_params = parameters[1]; double* J_point = jacobians ? jacobians[0] : nullptr; double* J_params = jacobians ? jacobians[1] : nullptr; Eigen::Map residuals_vec(residuals); Eigen::Map> J_point_mat( J_point); Eigen::Map< Eigen::Matrix> J_params_mat(J_params); Eigen::Matrix J_uvw_mat; const Eigen::Vector3d point3D_in_cam = cam_from_world_rotation_ * point3D_in_world + cam_from_world_translation_; if (!CameraModel::ImgFromCamWithJac(camera_params, point3D_in_cam[0], point3D_in_cam[1], point3D_in_cam[2], &residuals[0], &residuals[1], J_params, J_point ? J_uvw_mat.data() : nullptr)) { residuals_vec.setZero(); if (J_point) { J_point_mat.setZero(); } if (J_params) { J_params_mat.setZero(); } return true; } residuals_vec -= point2D_; // No-op for non-periodic models. The offset is locally constant, so the // analytic Jacobian below is unaffected. WrapEquirectangularHorizontalSeam(camera_params, residuals); if (J_point) { J_point_mat = J_uvw_mat * cam_from_world_rotation_; } return true; } private: const Eigen::Vector2d point2D_; const Eigen::Matrix3d cam_from_world_rotation_; const Eigen::Vector3d cam_from_world_translation_; }; // Standard bundle adjustment cost function for variable // camera pose, calibration, and point parameters. template class ReprojErrorCostFunctor : public AutoDiffCostFunctor, 2, 3, 7, CameraModel::num_params> { public: explicit ReprojErrorCostFunctor(const Eigen::Vector2d& point2D) : point2D_(point2D) {} template bool operator()(const T* const point3D_in_world, const T* const cam_from_world, const T* const camera_params, T* residuals) const { const Eigen::Matrix point3D_in_cam = EigenQuaternionMap(cam_from_world) * EigenVector3Map(point3D_in_world) + EigenVector3Map(cam_from_world + 4); Eigen::Map> residuals_vec(residuals); if (CameraModel::ImgFromCam(camera_params, point3D_in_cam[0], point3D_in_cam[1], point3D_in_cam[2], &residuals[0], &residuals[1])) { residuals_vec -= point2D_.cast(); WrapEquirectangularHorizontalSeam(camera_params, residuals); } else { residuals_vec.setZero(); } return true; } private: const Eigen::Vector2d point2D_; }; // Bundle adjustment cost function for variable camera calibration and point // parameters, and fixed camera pose. Since the pose is constant, it is stored // as a precomputed rotation matrix and translation rather than a quaternion: // applying a fixed rotation as a matrix-vector product is faster than a // quaternion rotation on every evaluation. template class ReprojErrorConstantPoseCostFunctor : public AutoDiffCostFunctor< ReprojErrorConstantPoseCostFunctor, 2, 3, CameraModel::num_params> { public: // The pose is fixed, so precompute and store the rotation as a 3x3 matrix // instead of a quaternion: rotating a point with a matrix is faster than // quaternion rotation, and the conversion is done once here rather than on // every evaluation. (The analytical variant reuses this matrix directly as // the point Jacobian, which additionally avoids a quaternion-to-matrix // conversion there.) ReprojErrorConstantPoseCostFunctor(const Eigen::Vector2d& point2D, const Rigid3d& cam_from_world) : point2D_(point2D), cam_from_world_rotation_(cam_from_world.rotation().toRotationMatrix()), cam_from_world_translation_(cam_from_world.translation()) {} template bool operator()(const T* const point3D_in_world, const T* const camera_params, T* residuals) const { const Eigen::Matrix point3D_in_cam = cam_from_world_rotation_.cast() * EigenVector3Map(point3D_in_world) + cam_from_world_translation_.cast(); Eigen::Map> residuals_vec(residuals); if (CameraModel::ImgFromCam(camera_params, point3D_in_cam[0], point3D_in_cam[1], point3D_in_cam[2], &residuals[0], &residuals[1])) { residuals_vec -= point2D_.cast(); WrapEquirectangularHorizontalSeam(camera_params, residuals); } else { residuals_vec.setZero(); } return true; } private: const Eigen::Vector2d point2D_; const Eigen::Matrix3d cam_from_world_rotation_; const Eigen::Vector3d cam_from_world_translation_; }; // Bundle adjustment cost function for variable // camera pose and calibration parameters, and fixed point. template class ReprojErrorConstantPoint3DCostFunctor : public AutoDiffCostFunctor< ReprojErrorConstantPoint3DCostFunctor, 2, 7, CameraModel::num_params> { public: ReprojErrorConstantPoint3DCostFunctor(const Eigen::Vector2d& point2D, const Eigen::Vector3d& point3D_in_world) : point3D_in_world_(point3D_in_world), reproj_cost_(point2D) {} template bool operator()(const T* const cam_from_world, const T* const camera_params, T* residuals) const { const Eigen::Matrix point3D_in_world = point3D_in_world_.cast(); return reproj_cost_( point3D_in_world.data(), cam_from_world, camera_params, residuals); } private: const Eigen::Vector3d point3D_in_world_; const ReprojErrorCostFunctor reproj_cost_; }; // Rig bundle adjustment cost function for variable camera pose and calibration // and point parameters. Different from the standard bundle adjustment function, // this cost function is suitable for camera rigs with consistent relative poses // of the cameras within the rig. The cost function first projects points into // the local system of the camera rig and then into the local system of the // camera within the rig. template class RigReprojErrorCostFunctor : public AutoDiffCostFunctor, 2, 3, 7, 7, CameraModel::num_params> { public: explicit RigReprojErrorCostFunctor(const Eigen::Vector2d& point2D) : point2D_(point2D) {} template bool operator()(const T* const point3D_in_world, const T* const cam_from_rig, const T* const rig_from_world, const T* const camera_params, T* residuals) const { const Eigen::Matrix point3D_in_cam = EigenQuaternionMap(cam_from_rig) * (EigenQuaternionMap(rig_from_world) * EigenVector3Map(point3D_in_world) + EigenVector3Map(rig_from_world + 4)) + EigenVector3Map(cam_from_rig + 4); Eigen::Map> residuals_vec(residuals); if (CameraModel::ImgFromCam(camera_params, point3D_in_cam[0], point3D_in_cam[1], point3D_in_cam[2], &residuals[0], &residuals[1])) { residuals_vec -= point2D_.cast(); WrapEquirectangularHorizontalSeam(camera_params, residuals); } else { residuals_vec.setZero(); } return true; } private: const Eigen::Vector2d point2D_; }; // Rig bundle adjustment cost function for variable camera pose and camera // calibration and point parameters but fixed rig extrinsic poses. template class RigReprojErrorConstantRigCostFunctor : public AutoDiffCostFunctor< RigReprojErrorConstantRigCostFunctor, 2, 3, 7, CameraModel::num_params> { public: RigReprojErrorConstantRigCostFunctor(const Eigen::Vector2d& point2D, const Rigid3d& cam_from_rig) : cam_from_rig_(cam_from_rig), reproj_cost_(point2D) {} template bool operator()(const T* const point3D_in_world, const T* const rig_from_world, const T* const camera_params, T* residuals) const { const Eigen::Matrix cam_from_rig = cam_from_rig_.params.cast(); return reproj_cost_(point3D_in_world, cam_from_rig.data(), rig_from_world, camera_params, residuals); } private: const Rigid3d cam_from_rig_; const RigReprojErrorCostFunctor reproj_cost_; }; // Creates the analytical reprojection error cost function for camera models // that implement ImgFromCamWithJac(). The overloads are selected via SFINAE so // that AnalyticalReprojErrorCostFunction is only ever named (and // thus instantiated) for qualifying models. This avoids instantiating its // virtual Evaluate() member for models without an analytical Jacobian, which // would reference the SFINAE-disabled ImgFromCamWithJac() overload. template std::enable_if_t CreateAnalyticalReprojErrorCostFunction(Args&&... args) { return new AnalyticalReprojErrorCostFunction( std::forward(args)...); } template std::enable_if_t CreateAnalyticalReprojErrorCostFunction(Args&&... /*args*/) { // Unreachable: callers guard on has_img_from_cam_with_jac. return nullptr; } // Same SFINAE pattern as CreateAnalyticalReprojErrorCostFunction, for the // fixed-pose analytical cost function. template std::enable_if_t CreateAnalyticalReprojErrorConstantPoseCostFunction(Args&&... args) { return new AnalyticalReprojErrorConstantPoseCostFunction( std::forward(args)...); } template std::enable_if_t CreateAnalyticalReprojErrorConstantPoseCostFunction(Args&&... /*args*/) { // Unreachable: callers guard on has_img_from_cam_with_jac. return nullptr; } template