From 83c5362debb9e02305cc6da42499f79d72523a40 Mon Sep 17 00:00:00 2001 From: Ray Douglass Date: Tue, 14 Nov 2023 11:30:35 -0500 Subject: [PATCH 01/21] v24.02 Updates [skip ci] --- .github/workflows/build.yaml | 12 ++++++------ .github/workflows/pr.yaml | 16 ++++++++-------- .github/workflows/test.yaml | 4 ++-- README.md | 2 +- VERSION | 2 +- ci/build_docs.sh | 2 +- cucim.code-workspace | 8 ++++---- docs/source/conf.py | 4 ++-- python/cucim/README.md | 2 +- python/cucim/docs/getting_started/index.md | 10 +++++----- python/cucim/docs/index.md | 2 +- python/cucim/pyproject.toml | 2 +- 12 files changed, 33 insertions(+), 33 deletions(-) diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index 1a028553d..ceb5a1eac 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -28,7 +28,7 @@ concurrency: jobs: cpp-build: secrets: inherit - uses: rapidsai/shared-workflows/.github/workflows/conda-cpp-build.yaml@branch-23.12 + uses: rapidsai/shared-workflows/.github/workflows/conda-cpp-build.yaml@branch-24.02 with: build_type: ${{ inputs.build_type || 'branch' }} branch: ${{ inputs.branch }} @@ -38,7 +38,7 @@ jobs: if: github.ref_type == 'branch' needs: [python-build] secrets: inherit - uses: rapidsai/shared-workflows/.github/workflows/custom-job.yaml@branch-23.12 + uses: rapidsai/shared-workflows/.github/workflows/custom-job.yaml@branch-24.02 with: arch: "amd64" branch: ${{ inputs.branch }} @@ -51,7 +51,7 @@ jobs: python-build: needs: [cpp-build] secrets: inherit - uses: rapidsai/shared-workflows/.github/workflows/conda-python-build.yaml@branch-23.12 + uses: rapidsai/shared-workflows/.github/workflows/conda-python-build.yaml@branch-24.02 with: build_type: ${{ inputs.build_type || 'branch' }} branch: ${{ inputs.branch }} @@ -60,7 +60,7 @@ jobs: upload-conda: needs: [cpp-build, python-build] secrets: inherit - uses: rapidsai/shared-workflows/.github/workflows/conda-upload-packages.yaml@branch-23.12 + uses: rapidsai/shared-workflows/.github/workflows/conda-upload-packages.yaml@branch-24.02 with: build_type: ${{ inputs.build_type || 'branch' }} branch: ${{ inputs.branch }} @@ -68,7 +68,7 @@ jobs: sha: ${{ inputs.sha }} wheel-build: secrets: inherit - uses: rapidsai/shared-workflows/.github/workflows/wheels-build.yaml@branch-23.12 + uses: rapidsai/shared-workflows/.github/workflows/wheels-build.yaml@branch-24.02 with: build_type: ${{ inputs.build_type || 'branch' }} branch: ${{ inputs.branch }} @@ -78,7 +78,7 @@ jobs: wheel-publish: needs: wheel-build secrets: inherit - uses: rapidsai/shared-workflows/.github/workflows/wheels-publish.yaml@branch-23.12 + uses: rapidsai/shared-workflows/.github/workflows/wheels-publish.yaml@branch-24.02 with: build_type: ${{ inputs.build_type || 'branch' }} branch: ${{ inputs.branch }} diff --git a/.github/workflows/pr.yaml b/.github/workflows/pr.yaml index d098ff326..7d22e56a2 100644 --- a/.github/workflows/pr.yaml +++ b/.github/workflows/pr.yaml @@ -20,32 +20,32 @@ jobs: - wheel-build - wheel-tests secrets: inherit - uses: rapidsai/shared-workflows/.github/workflows/pr-builder.yaml@branch-23.12 + uses: rapidsai/shared-workflows/.github/workflows/pr-builder.yaml@branch-24.02 checks: secrets: inherit - uses: rapidsai/shared-workflows/.github/workflows/checks.yaml@branch-23.12 + uses: rapidsai/shared-workflows/.github/workflows/checks.yaml@branch-24.02 conda-cpp-build: needs: checks secrets: inherit - uses: rapidsai/shared-workflows/.github/workflows/conda-cpp-build.yaml@branch-23.12 + uses: rapidsai/shared-workflows/.github/workflows/conda-cpp-build.yaml@branch-24.02 with: build_type: pull-request conda-python-build: needs: conda-cpp-build secrets: inherit - uses: rapidsai/shared-workflows/.github/workflows/conda-python-build.yaml@branch-23.12 + uses: rapidsai/shared-workflows/.github/workflows/conda-python-build.yaml@branch-24.02 with: build_type: pull-request conda-python-tests: needs: conda-python-build secrets: inherit - uses: rapidsai/shared-workflows/.github/workflows/conda-python-tests.yaml@branch-23.12 + uses: rapidsai/shared-workflows/.github/workflows/conda-python-tests.yaml@branch-24.02 with: build_type: pull-request docs-build: needs: conda-python-build secrets: inherit - uses: rapidsai/shared-workflows/.github/workflows/custom-job.yaml@branch-23.12 + uses: rapidsai/shared-workflows/.github/workflows/custom-job.yaml@branch-24.02 with: build_type: pull-request node_type: "gpu-v100-latest-1" @@ -55,14 +55,14 @@ jobs: wheel-build: needs: checks secrets: inherit - uses: rapidsai/shared-workflows/.github/workflows/wheels-build.yaml@branch-23.12 + uses: rapidsai/shared-workflows/.github/workflows/wheels-build.yaml@branch-24.02 with: build_type: pull-request script: ci/build_wheel.sh wheel-tests: needs: wheel-build secrets: inherit - uses: rapidsai/shared-workflows/.github/workflows/wheels-test.yaml@branch-23.12 + uses: rapidsai/shared-workflows/.github/workflows/wheels-test.yaml@branch-24.02 with: build_type: pull-request script: ci/test_wheel.sh diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index 8ac82418e..6f55ce705 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -16,7 +16,7 @@ on: jobs: conda-python-tests: secrets: inherit - uses: rapidsai/shared-workflows/.github/workflows/conda-python-tests.yaml@branch-23.12 + uses: rapidsai/shared-workflows/.github/workflows/conda-python-tests.yaml@branch-24.02 with: build_type: nightly branch: ${{ inputs.branch }} @@ -24,7 +24,7 @@ jobs: sha: ${{ inputs.sha }} wheel-tests: secrets: inherit - uses: rapidsai/shared-workflows/.github/workflows/wheels-test.yaml@branch-23.12 + uses: rapidsai/shared-workflows/.github/workflows/wheels-test.yaml@branch-24.02 with: build_type: nightly branch: ${{ inputs.branch }} diff --git a/README.md b/README.md index 0fcb1bcee..27a5d7fbb 100644 --- a/README.md +++ b/README.md @@ -70,7 +70,7 @@ pip install scipy scikit-image cupy-cuda110 ### Notebooks -Please check out our [Welcome](notebooks/Welcome.ipynb) notebook ([NBViewer](https://nbviewer.jupyter.org/github/rapidsai/cucim/blob/branch-23.12/notebooks/Welcome.ipynb)) +Please check out our [Welcome](notebooks/Welcome.ipynb) notebook ([NBViewer](https://nbviewer.jupyter.org/github/rapidsai/cucim/blob/branch-24.02/notebooks/Welcome.ipynb)) #### Downloading sample images diff --git a/VERSION b/VERSION index bb94af9dd..3c6c5e2b7 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -23.12.00a56 +24.02.00 diff --git a/ci/build_docs.sh b/ci/build_docs.sh index ee296fd5c..0df71c729 100755 --- a/ci/build_docs.sh +++ b/ci/build_docs.sh @@ -24,7 +24,7 @@ rapids-mamba-retry install \ --channel "${PYTHON_CHANNEL}" \ cucim libcucim -export RAPIDS_VERSION_NUMBER="23.12" +export RAPIDS_VERSION_NUMBER="24.02" export RAPIDS_DOCS_DIR="$(mktemp -d)" rapids-logger "Build Python docs" diff --git a/cucim.code-workspace b/cucim.code-workspace index c8be5d588..000fd7483 100644 --- a/cucim.code-workspace +++ b/cucim.code-workspace @@ -33,7 +33,7 @@ "CUCIM_TESTDATA_FOLDER": "${workspaceDirectory}/test_data", // Add cuslide plugin's library path to LD_LIBRARY_PATH "LD_LIBRARY_PATH": "${workspaceDirectory}/build-debug/lib:${workspaceDirectory}/cpp/plugins/cucim.kit.cuslide/build-debug/lib:${workspaceDirectory}/temp/cuda/lib64:${os_env:LD_LIBRARY_PATH}", - "CUCIM_TEST_PLUGIN_PATH": "cucim.kit.cuslide@23.12.00.so" + "CUCIM_TEST_PLUGIN_PATH": "cucim.kit.cuslide@24.02.00.so" }, "cwd": "${workspaceDirectory}", "catch2": { @@ -226,7 +226,7 @@ }, { "name": "CUCIM_TEST_PLUGIN_PATH", - "value": "cucim.kit.cuslide@23.12.00.so" + "value": "cucim.kit.cuslide@24.02.00.so" } ], "console": "externalTerminal", @@ -254,7 +254,7 @@ }, { "name": "CUCIM_TEST_PLUGIN_PATH", - "value": "cucim.kit.cuslide@23.12.00.so" + "value": "cucim.kit.cuslide@24.02.00.so" } ], "console": "externalTerminal", @@ -286,7 +286,7 @@ }, { "name": "CUCIM_TEST_PLUGIN_PATH", - "value": "cucim.kit.cuslide@23.12.00.so" + "value": "cucim.kit.cuslide@24.02.00.so" } ], "console": "externalTerminal", diff --git a/docs/source/conf.py b/docs/source/conf.py index 084d85d70..4fc968e8d 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -72,9 +72,9 @@ # built documents. # # The short X.Y version. -version = "23.12" +version = '24.02' # The full version, including alpha/beta/rc tags. -release = "23.12.00" +release = '24.02.00' # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. diff --git a/python/cucim/README.md b/python/cucim/README.md index 0fcb1bcee..27a5d7fbb 100755 --- a/python/cucim/README.md +++ b/python/cucim/README.md @@ -70,7 +70,7 @@ pip install scipy scikit-image cupy-cuda110 ### Notebooks -Please check out our [Welcome](notebooks/Welcome.ipynb) notebook ([NBViewer](https://nbviewer.jupyter.org/github/rapidsai/cucim/blob/branch-23.12/notebooks/Welcome.ipynb)) +Please check out our [Welcome](notebooks/Welcome.ipynb) notebook ([NBViewer](https://nbviewer.jupyter.org/github/rapidsai/cucim/blob/branch-24.02/notebooks/Welcome.ipynb)) #### Downloading sample images diff --git a/python/cucim/docs/getting_started/index.md b/python/cucim/docs/getting_started/index.md index 5eff46f3f..5aad28dc5 100644 --- a/python/cucim/docs/getting_started/index.md +++ b/python/cucim/docs/getting_started/index.md @@ -14,15 +14,15 @@ ## Installation -Please download the latest SDK package (`cuCIM-v23.12.00-linux.tar.gz`). +Please download the latest SDK package (`cuCIM-v24.02.00-linux.tar.gz`). Untar the downloaded file. ```bash -mkdir -p cuCIM-v23.12.00 -tar -xzvf cuCIM-v23.12.00-linux.tar.gz -C cuCIM-v23.12.00 +mkdir -p cuCIM-v24.02.00 +tar -xzvf cuCIM-v24.02.00-linux.tar.gz -C cuCIM-v24.02.00 -cd cuCIM-v23.12.00 +cd cuCIM-v24.02.00 ``` ## Run command @@ -147,7 +147,7 @@ Its execution would show some metadata information and create two files -- `outp ``` $ ./bin/tiff_image notebooks/input/image.tif . [Plugin: cucim.kit.cuslide] Loading... -[Plugin: cucim.kit.cuslide] Loading the dynamic library from: cucim.kit.cuslide@23.12.00.so +[Plugin: cucim.kit.cuslide] Loading the dynamic library from: cucim.kit.cuslide@24.02.00.so [Plugin: cucim.kit.cuslide] loaded successfully. Version: 0 Initializing plugin: cucim.kit.cuslide (interfaces: [cucim::io::IImageFormat v0.1]) (impl: cucim.kit.cuslide) is_loaded: true diff --git a/python/cucim/docs/index.md b/python/cucim/docs/index.md index d78831377..6789e7961 100644 --- a/python/cucim/docs/index.md +++ b/python/cucim/docs/index.md @@ -18,7 +18,7 @@ development/index --> # cuCIM Documentation -Current latest version is [Version 23.12.00](release_notes/v23.12.00.md). +Current latest version is [Version 24.02.00](release_notes/v24.02.00.md). **cuCIM** a toolkit to provide GPU accelerated I/O, image processing & computer vision primitives for N-Dimensional images with a focus on biomedical imaging. diff --git a/python/cucim/pyproject.toml b/python/cucim/pyproject.toml index 1136ee877..76695d5a9 100644 --- a/python/cucim/pyproject.toml +++ b/python/cucim/pyproject.toml @@ -54,7 +54,7 @@ classifiers = [ [project.urls] Homepage = "https://developer.nvidia.com/multidimensional-image-processing" Documentation = "https://docs.rapids.ai/api/cucim/stable/" -Changelog = "https://github.com/rapidsai/cucim/blob/branch-23.12/CHANGELOG.md" +Changelog = "https://github.com/rapidsai/cucim/blob/branch-24.02/CHANGELOG.md" Source = "https://github.com/rapidsai/cucim" Tracker = "https://github.com/rapidsai/cucim/issues" From 12037d892640a74c70ae27613f5e4ddfb4bf2855 Mon Sep 17 00:00:00 2001 From: jakirkham Date: Mon, 4 Dec 2023 11:36:54 -0800 Subject: [PATCH 02/21] Fix style issue in `docs/source/conf.py` (#648) Currently the version update script is introducing a style error (using single quotes instead of double quotes) in the versions in `docs/source/conf.py`. This fixes that issue Authors: - https://github.com/jakirkham Approvers: - Gregory Lee (https://github.com/grlee77) - Ray Douglass (https://github.com/raydouglass) URL: https://github.com/rapidsai/cucim/pull/648 --- ci/release/update-version.sh | 4 ++-- docs/source/conf.py | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/ci/release/update-version.sh b/ci/release/update-version.sh index e4ca4a8dd..e70df365d 100755 --- a/ci/release/update-version.sh +++ b/ci/release/update-version.sh @@ -31,8 +31,8 @@ function sed_runner() { } # RTD update -sed_runner 's/version = .*/version = '"'${NEXT_SHORT_TAG}'"'/g' docs/source/conf.py -sed_runner 's/release = .*/release = '"'${NEXT_FULL_TAG}'"'/g' docs/source/conf.py +sed_runner 's/version = .*/version = "'"${NEXT_SHORT_TAG}"'"/g' docs/source/conf.py +sed_runner 's/release = .*/release = "'"${NEXT_FULL_TAG}"'"/g' docs/source/conf.py # Centralized version file update echo "${NEXT_FULL_TAG}" > VERSION diff --git a/docs/source/conf.py b/docs/source/conf.py index 4fc968e8d..572312f8e 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -72,9 +72,9 @@ # built documents. # # The short X.Y version. -version = '24.02' +version = "24.02" # The full version, including alpha/beta/rc tags. -release = '24.02.00' +release = "24.02.00" # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. From 69c0099bda6136613400b719c046584f90e7f665 Mon Sep 17 00:00:00 2001 From: Gregory Lee Date: Mon, 4 Dec 2023 12:50:37 -0800 Subject: [PATCH 03/21] install imagecodecs and openslide-python dependencies so additional tests will run (#634) This MR may initially fail during wheel testing due to #626, as a few test cases relying on these packages were not being run at the time #619 was merged. Authors: - Gregory Lee (https://github.com/grlee77) - https://github.com/jakirkham Approvers: - Ray Douglass (https://github.com/raydouglass) - Gigon Bae (https://github.com/gigony) - https://github.com/jakirkham URL: https://github.com/rapidsai/cucim/pull/634 --- ci/test_wheel.sh | 15 +++++++++++++-- dependencies.yaml | 7 +++---- python/cucim/pyproject.toml | 2 ++ python/cucim/tests/unit/clara/test_image_cache.py | 1 + 4 files changed, 19 insertions(+), 6 deletions(-) diff --git a/ci/test_wheel.sh b/ci/test_wheel.sh index 2fe2c1fb9..81f7f53e5 100755 --- a/ci/test_wheel.sh +++ b/ci/test_wheel.sh @@ -9,10 +9,21 @@ RAPIDS_PY_WHEEL_NAME="cucim_${RAPIDS_PY_CUDA_SUFFIX}" rapids-download-wheels-fro # echo to expand wildcard before adding `[extra]` requires for pip python -m pip install $(echo ./dist/cucim*.whl)[test] +CUDA_MAJOR_VERSION=${RAPIDS_CUDA_VERSION:0:2} + # Run smoke tests for aarch64 pull requests if [[ "$(arch)" == "aarch64" && ${RAPIDS_BUILD_TYPE} == "pull-request" ]]; then python ./ci/wheel_smoke_test.py else - # TODO: revisit enabling imagecodecs package during testing - python -m pytest ./python/cucim + + DEBIAN_FRONTEND=noninteractive apt update + DEBIAN_FRONTEND=noninteractive apt install -y --no-install-recommends libopenslide0 + + if [[ ${CUDA_MAJOR_VERSION} == "11" ]]; then + # Omit I/O-related tests in ./python/cucim/tests due to known CUDA bug + # with dynamic loading of libcufile. + python -m pytest ./python/cucim/src/ + else + python -m pytest ./python/cucim + fi fi diff --git a/dependencies.yaml b/dependencies.yaml index b7ceeef02..3e7732126 100644 --- a/dependencies.yaml +++ b/dependencies.yaml @@ -262,10 +262,9 @@ dependencies: - opencv-python-headless>=4.6 - output_types: [requirements, pyproject] packages: - # temporarily remove imagecodecs / openslide-python from wheel tests - # # skip packages on arm64 that don't provide a wheel - # - imagecodecs>=2021.6.8; platform_machine=='x86_64' - # - openslide-python>=1.1.2; platform_machine=='x86_64' + # skip packages on arm64 that don't provide a wheel + - imagecodecs>=2021.6.8; platform_machine=='x86_64' + - openslide-python>=1.1.2; platform_machine=='x86_64' - matplotlib - opencv-python-headless>=4.6 - click diff --git a/python/cucim/pyproject.toml b/python/cucim/pyproject.toml index 76695d5a9..880b1a06c 100644 --- a/python/cucim/pyproject.toml +++ b/python/cucim/pyproject.toml @@ -62,8 +62,10 @@ Tracker = "https://github.com/rapidsai/cucim/issues" test = [ "GPUtil>=1.4.0", "click", + "imagecodecs>=2021.6.8; platform_machine=='x86_64'", "matplotlib", "opencv-python-headless>=4.6", + "openslide-python>=1.1.2; platform_machine=='x86_64'", "pooch>=1.6.0", "psutil>=5.8.0", "pytest-cov>=2.12.1", diff --git a/python/cucim/tests/unit/clara/test_image_cache.py b/python/cucim/tests/unit/clara/test_image_cache.py index 1b8987765..258e01da4 100644 --- a/python/cucim/tests/unit/clara/test_image_cache.py +++ b/python/cucim/tests/unit/clara/test_image_cache.py @@ -196,6 +196,7 @@ def test_reserve_more_cache_memory(): assert cache.miss_count == 0 +@pytest.mark.skip(reason="currently fails (gh-626)") def test_cache_hit_miss(testimg_tiff_stripe_32x24_16_jpeg): from cucim import CuImage from cucim.clara.cache import preferred_memory_capacity From 87410cd6a053482e06695751ac848fffd1444a1a Mon Sep 17 00:00:00 2001 From: jakirkham Date: Mon, 11 Dec 2023 15:29:44 -0800 Subject: [PATCH 04/21] Add 3rd party license file in Conda packages (#654) Previously only cuCIM's own license file was included in Conda packages. However the 3rd party license file was not included in Conda packages. This adds that license file as well. Also goes ahead and adds the 3rd party license file to both `libcucim` and `cucim` for good measure (even though it is a little redundant to add it to `cucim` given it depends on `libcucim`, which already has it) Authors: - https://github.com/jakirkham Approvers: - Ray Douglass (https://github.com/raydouglass) - Gregory Lee (https://github.com/grlee77) URL: https://github.com/rapidsai/cucim/pull/654 --- conda/recipes/cucim/meta.yaml | 4 +++- conda/recipes/libcucim/meta.yaml | 4 +++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/conda/recipes/cucim/meta.yaml b/conda/recipes/cucim/meta.yaml index dbb34a906..faeb7fa9e 100644 --- a/conda/recipes/cucim/meta.yaml +++ b/conda/recipes/cucim/meta.yaml @@ -84,6 +84,8 @@ about: summary: cucim Python package license: Apache-2.0 license_family: Apache - license_file: LICENSE + license_file: + - LICENSE + - LICENSE-3rdparty.md doc_url: https://docs.rapids.ai/api/cucim/stable/ dev_url: https://github.com/rapidsai/cucim diff --git a/conda/recipes/libcucim/meta.yaml b/conda/recipes/libcucim/meta.yaml index 512ae3085..7d4cf5ac8 100644 --- a/conda/recipes/libcucim/meta.yaml +++ b/conda/recipes/libcucim/meta.yaml @@ -92,6 +92,8 @@ about: summary: libcucim C++ library license: Apache-2.0 license_family: Apache - license_file: LICENSE + license_file: + - LICENSE + - LICENSE-3rdparty.md doc_url: https://docs.rapids.ai/api/cucim/stable/ dev_url: https://github.com/rapidsai/cucim From ecdf36a4b00685f111ddf6cb3f9e2147542082a3 Mon Sep 17 00:00:00 2001 From: AJ Schmidt Date: Wed, 13 Dec 2023 14:44:19 -0500 Subject: [PATCH 05/21] Update CODEOWNERS (#669) This PR updates the `CODEOWNERS` file to ensure that the Ops team doesn't get tagged for files that we're not concerned with. This should prevent PRs like the following from soliciting us for approvals: - https://github.com/rapidsai/cucim/pull/665 - https://github.com/rapidsai/cucim/pull/666 [skip ci] Authors: - AJ Schmidt (https://github.com/ajschmidt8) Approvers: - Ray Douglass (https://github.com/raydouglass) --- .github/CODEOWNERS | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 745f4a5e4..8bbb437b7 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -10,10 +10,7 @@ python/ @rapidsai/cucim-python-codeowners **/cmake/ @rapidsai/cucim-cmake-codeowners #build/ops code owners -.github/ @rapidsai/ops-codeowners -ci/ @rapidsai/ops-codeowners -conda/ @rapidsai/ops-codeowners -**/Dockerfile @rapidsai/ops-codeowners -**/.dockerignore @rapidsai/ops-codeowners -docker/ @rapidsai/ops-codeowners +/.github/ @rapidsai/ops-codeowners +/ci/ @rapidsai/ops-codeowners +/conda/ @rapidsai/ops-codeowners dependencies.yaml @rapidsai/ops-codeowners From 59bc8f42ca5284295fb91f0e5cfa830f63009ac1 Mon Sep 17 00:00:00 2001 From: Gregory Lee Date: Wed, 13 Dec 2023 12:26:10 -0800 Subject: [PATCH 06/21] remove .idea folder (CLion IDE configuration) (#667) This folder contains configuration for the CLion IDE. It is not tested or maintained and current devs are not using it, so we will should just remove it from the repository. closes #663 Authors: - Gregory Lee (https://github.com/grlee77) Approvers: - https://github.com/jakirkham - Gigon Bae (https://github.com/gigony) URL: https://github.com/rapidsai/cucim/pull/667 --- .idea/.gitignore | 8 -------- .idea/codeStyles/Project.xml | 7 ------- .idea/codeStyles/codeStyleConfig.xml | 5 ----- .idea/cucim.iml | 2 -- .../includes/NVIDIA_CMAKE_HEADER.cmake | 14 -------------- .idea/fileTemplates/includes/NVIDIA_C_HEADER.h | 15 --------------- .idea/fileTemplates/internal/C Header File.h | 5 ----- .idea/fileTemplates/internal/C Source File.c | 4 ---- .idea/fileTemplates/internal/C++ Class Header.h | 13 ------------- .idea/fileTemplates/internal/C++ Class.cc | 2 -- .idea/fileTemplates/internal/CMakeLists.txt.cmake | 1 - .idea/misc.xml | 7 ------- .idea/vcs.xml | 6 ------ 13 files changed, 89 deletions(-) delete mode 100644 .idea/.gitignore delete mode 100644 .idea/codeStyles/Project.xml delete mode 100644 .idea/codeStyles/codeStyleConfig.xml delete mode 100644 .idea/cucim.iml delete mode 100644 .idea/fileTemplates/includes/NVIDIA_CMAKE_HEADER.cmake delete mode 100644 .idea/fileTemplates/includes/NVIDIA_C_HEADER.h delete mode 100644 .idea/fileTemplates/internal/C Header File.h delete mode 100644 .idea/fileTemplates/internal/C Source File.c delete mode 100644 .idea/fileTemplates/internal/C++ Class Header.h delete mode 100644 .idea/fileTemplates/internal/C++ Class.cc delete mode 100644 .idea/fileTemplates/internal/CMakeLists.txt.cmake delete mode 100644 .idea/misc.xml delete mode 100644 .idea/vcs.xml diff --git a/.idea/.gitignore b/.idea/.gitignore deleted file mode 100644 index 73f69e095..000000000 --- a/.idea/.gitignore +++ /dev/null @@ -1,8 +0,0 @@ -# Default ignored files -/shelf/ -/workspace.xml -# Datasource local storage ignored files -/dataSources/ -/dataSources.local.xml -# Editor-based HTTP Client requests -/httpRequests/ diff --git a/.idea/codeStyles/Project.xml b/.idea/codeStyles/Project.xml deleted file mode 100644 index c8f84c353..000000000 --- a/.idea/codeStyles/Project.xml +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - diff --git a/.idea/codeStyles/codeStyleConfig.xml b/.idea/codeStyles/codeStyleConfig.xml deleted file mode 100644 index 0f7bc519d..000000000 --- a/.idea/codeStyles/codeStyleConfig.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - - diff --git a/.idea/cucim.iml b/.idea/cucim.iml deleted file mode 100644 index 08cda128a..000000000 --- a/.idea/cucim.iml +++ /dev/null @@ -1,2 +0,0 @@ - - diff --git a/.idea/fileTemplates/includes/NVIDIA_CMAKE_HEADER.cmake b/.idea/fileTemplates/includes/NVIDIA_CMAKE_HEADER.cmake deleted file mode 100644 index 7272e0dec..000000000 --- a/.idea/fileTemplates/includes/NVIDIA_CMAKE_HEADER.cmake +++ /dev/null @@ -1,14 +0,0 @@ -# -# Copyright (c) $YEAR, NVIDIA CORPORATION. -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# diff --git a/.idea/fileTemplates/includes/NVIDIA_C_HEADER.h b/.idea/fileTemplates/includes/NVIDIA_C_HEADER.h deleted file mode 100644 index cf0461d4c..000000000 --- a/.idea/fileTemplates/includes/NVIDIA_C_HEADER.h +++ /dev/null @@ -1,15 +0,0 @@ -/* - * Copyright (c) $YEAR, NVIDIA CORPORATION. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ diff --git a/.idea/fileTemplates/internal/C Header File.h b/.idea/fileTemplates/internal/C Header File.h deleted file mode 100644 index 9cb1d09e2..000000000 --- a/.idea/fileTemplates/internal/C Header File.h +++ /dev/null @@ -1,5 +0,0 @@ -#parse("NVIDIA_C_HEADER.h") -#[[#ifndef]]# ${INCLUDE_GUARD} -#[[#define]]# ${INCLUDE_GUARD} - -#[[#endif]]# //${INCLUDE_GUARD} diff --git a/.idea/fileTemplates/internal/C Source File.c b/.idea/fileTemplates/internal/C Source File.c deleted file mode 100644 index b04dd6c62..000000000 --- a/.idea/fileTemplates/internal/C Source File.c +++ /dev/null @@ -1,4 +0,0 @@ -#parse("NVIDIA_C_HEADER.h") -#if (${HEADER_FILENAME}) -#[[#include]]# "${HEADER_FILENAME}" -#end diff --git a/.idea/fileTemplates/internal/C++ Class Header.h b/.idea/fileTemplates/internal/C++ Class Header.h deleted file mode 100644 index f521fa555..000000000 --- a/.idea/fileTemplates/internal/C++ Class Header.h +++ /dev/null @@ -1,13 +0,0 @@ -#parse("NVIDIA_C_HEADER.h") -#[[#ifndef]]# ${INCLUDE_GUARD} -#[[#define]]# ${INCLUDE_GUARD} - -${NAMESPACES_OPEN} - -class ${NAME} { - -}; - -${NAMESPACES_CLOSE} - -#[[#endif]]# //${INCLUDE_GUARD} diff --git a/.idea/fileTemplates/internal/C++ Class.cc b/.idea/fileTemplates/internal/C++ Class.cc deleted file mode 100644 index 42f43ccf4..000000000 --- a/.idea/fileTemplates/internal/C++ Class.cc +++ /dev/null @@ -1,2 +0,0 @@ -#parse("NVIDIA_C_HEADER.h") -#[[#include]]# "${HEADER_FILENAME}" diff --git a/.idea/fileTemplates/internal/CMakeLists.txt.cmake b/.idea/fileTemplates/internal/CMakeLists.txt.cmake deleted file mode 100644 index 846356219..000000000 --- a/.idea/fileTemplates/internal/CMakeLists.txt.cmake +++ /dev/null @@ -1 +0,0 @@ -#parse("NVIDIA_CMAKE_HEADER.cmake") diff --git a/.idea/misc.xml b/.idea/misc.xml deleted file mode 100644 index 2019083a1..000000000 --- a/.idea/misc.xml +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - diff --git a/.idea/vcs.xml b/.idea/vcs.xml deleted file mode 100644 index 5ace414d8..000000000 --- a/.idea/vcs.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - From 4ba0177b9eb7ce6182602e3102e258a436465797 Mon Sep 17 00:00:00 2001 From: Gregory Lee Date: Wed, 13 Dec 2023 12:26:29 -0800 Subject: [PATCH 07/21] remove redundant notebook (#668) closes #656 We are retaining `notebooks/Using_Cache.ipynb` which covers use of the image cache. This top-level notebook is not needed. Authors: - Gregory Lee (https://github.com/grlee77) Approvers: - https://github.com/jakirkham - Gigon Bae (https://github.com/gigony) URL: https://github.com/rapidsai/cucim/pull/668 --- Cache Example.ipynb | 531 -------------------------------------------- 1 file changed, 531 deletions(-) delete mode 100644 Cache Example.ipynb diff --git a/Cache Example.ipynb b/Cache Example.ipynb deleted file mode 100644 index ae686fabd..000000000 --- a/Cache Example.ipynb +++ /dev/null @@ -1,531 +0,0 @@ -{ - "cells": [ - { - "cell_type": "code", - "execution_count": 1, - "id": "69aeee77", - "metadata": {}, - "outputs": [], - "source": [ - "from cucim import CuImage\n", - "from cucim.clara.cache import calc_preferred_cache_memory\n", - "\n", - "img = CuImage(\"notebooks/input/image.tif\")\n", - "memory_capacity = calc_preferred_cache_memory(img, (256, 256))\n", - "CuImage.cache(\"per_process\", memory_capacity=memory_capacity, record_stat=True)\n", - "#CuImage.cache(\"per_process\", memory_capacity=2048, record_stat=True)\n", - "\n", - "region = img.read_region((0,0), (100,100))\n", - "\n", - "cache = CuImage.cache()" - ] - }, - { - "cell_type": "code", - "execution_count": 2, - "id": "f2121d1b", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "{'type': 'per_process',\n", - " 'memory_capacity': 2048,\n", - " 'capacity': 5461,\n", - " 'mutex_pool_capacity': 11117,\n", - " 'list_padding': 10000,\n", - " 'extra_shared_memory_size': 100,\n", - " 'record_stat': True}" - ] - }, - "execution_count": 2, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "cache.config" - ] - }, - { - "cell_type": "code", - "execution_count": 3, - "id": "fbea8f0a", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "cache hit: 0, chche miss: 1\n" - ] - } - ], - "source": [ - "print(f\"cache hit: {cache.hit_count}, chche miss: {cache.miss_count}\")" - ] - }, - { - "cell_type": "code", - "execution_count": 4, - "id": "0f60842a", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "cache hit: 1, chche miss: 1\n", - "items in cache: 1/5461, memory usage in cache: 196608/2147483648\n" - ] - } - ], - "source": [ - "region = img.read_region((0,0), (100,100))\n", - "\n", - "print(f\"cache hit: {cache.hit_count}, chche miss: {cache.miss_count}\")\n", - "print(f\"items in cache: {cache.size}/{cache.capacity}, memory usage in cache: {cache.memory_size}/{cache.memory_capacity}\")" - ] - }, - { - "cell_type": "code", - "execution_count": 1, - "id": "5453dbcc", - "metadata": {}, - "outputs": [], - "source": [ - "from cucim import CuImage\n", - "img = CuImage(\"notebooks/input/image.tif\")" - ] - }, - { - "cell_type": "code", - "execution_count": 3, - "id": "059d8ba3", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "['__bool__',\n", - " '__class__',\n", - " '__delattr__',\n", - " '__dict__',\n", - " '__dir__',\n", - " '__doc__',\n", - " '__eq__',\n", - " '__format__',\n", - " '__ge__',\n", - " '__getattribute__',\n", - " '__gt__',\n", - " '__hash__',\n", - " '__init__',\n", - " '__init_subclass__',\n", - " '__le__',\n", - " '__lt__',\n", - " '__module__',\n", - " '__ne__',\n", - " '__new__',\n", - " '__reduce__',\n", - " '__reduce_ex__',\n", - " '__repr__',\n", - " '__setattr__',\n", - " '__sizeof__',\n", - " '__str__',\n", - " '__subclasshook__',\n", - " '_set_array_interface',\n", - " 'associated_image',\n", - " 'associated_images',\n", - " 'cache',\n", - " 'channel_names',\n", - " 'coord_sys',\n", - " 'device',\n", - " 'dims',\n", - " 'direction',\n", - " 'dtype',\n", - " 'is_loaded',\n", - " 'metadata',\n", - " 'ndim',\n", - " 'origin',\n", - " 'path',\n", - " 'raw_metadata',\n", - " 'read_region',\n", - " 'resolutions',\n", - " 'save',\n", - " 'shape',\n", - " 'size',\n", - " 'spacing',\n", - " 'spacing_units']" - ] - }, - "execution_count": 3, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "dir(img._C)\n" - ] - }, - { - "cell_type": "code", - "execution_count": 1, - "id": "895db3d2", - "metadata": {}, - "outputs": [], - "source": [ - "from cucim import CuImage\n", - "\n", - "img = CuImage(\"notebooks/input/image.tif\")" - ] - }, - { - "cell_type": "code", - "execution_count": 5, - "id": "1800de44", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "430 ns ± 4.64 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)\n" - ] - } - ], - "source": [ - "%%timeit\n", - "\n", - "img.is_loaded" - ] - }, - { - "cell_type": "code", - "execution_count": 6, - "id": "3a85c7ff", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "177 ns ± 1.51 ns per loop (mean ± std. dev. of 7 runs, 10000000 loops each)\n" - ] - } - ], - "source": [ - "%%timeit\n", - "img._C.is_loaded" - ] - }, - { - "cell_type": "code", - "execution_count": 14, - "id": "a3872e46", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Processing ./dist/cucim-0.19.1.dev2-py3-none-manylinux2014_x86_64.whl\n", - "Collecting numpy\n", - " Using cached numpy-1.20.3-cp38-cp38-manylinux_2_12_x86_64.manylinux2010_x86_64.whl (15.4 MB)\n", - "Collecting click\n", - " Using cached click-8.0.1-py3-none-any.whl (97 kB)\n", - "Installing collected packages: numpy, click, cucim\n", - " Attempting uninstall: numpy\n", - " Found existing installation: numpy 1.20.3\n", - " Uninstalling numpy-1.20.3:\n", - " Successfully uninstalled numpy-1.20.3\n", - " Attempting uninstall: click\n", - " Found existing installation: click 8.0.1\n", - " Uninstalling click-8.0.1:\n", - " Successfully uninstalled click-8.0.1\n", - " Attempting uninstall: cucim\n", - " Found existing installation: cucim 0.19.1.dev2\n", - " Uninstalling cucim-0.19.1.dev2:\n", - " Successfully uninstalled cucim-0.19.1.dev2\n", - "\u001b[31mERROR: pip's dependency resolver does not currently take into account all the packages that are installed. This behaviour is the source of the following dependency conflicts.\n", - "rasterio 1.2.3 requires click<8,>=4.0, but you have click 8.0.1 which is incompatible.\n", - "cligj 0.7.1 requires click<8,>=4.0, but you have click 8.0.1 which is incompatible.\u001b[0m\n", - "Successfully installed click-8.0.1 cucim-0.19.1.dev2 numpy-1.20.3\n", - "\u001b[33mWARNING: You are using pip version 21.0.1; however, version 21.1.2 is available.\n", - "You should consider upgrading via the '/home/gbae/.virtualenvs/cucim/bin/python -m pip install --upgrade pip' command.\u001b[0m\n" - ] - }, - { - "data": { - "text/plain": [ - "['__bool__',\n", - " '__class__',\n", - " '__delattr__',\n", - " '__dict__',\n", - " '__dir__',\n", - " '__doc__',\n", - " '__eq__',\n", - " '__format__',\n", - " '__ge__',\n", - " '__getattribute__',\n", - " '__gt__',\n", - " '__hash__',\n", - " '__init__',\n", - " '__init_subclass__',\n", - " '__le__',\n", - " '__lt__',\n", - " '__module__',\n", - " '__ne__',\n", - " '__new__',\n", - " '__reduce__',\n", - " '__reduce_ex__',\n", - " '__repr__',\n", - " '__setattr__',\n", - " '__sizeof__',\n", - " '__str__',\n", - " '__subclasshook__',\n", - " '_set_array_interface',\n", - " 'associated_image',\n", - " 'associated_images',\n", - " 'cache',\n", - " 'channel_names',\n", - " 'coord_sys',\n", - " 'device',\n", - " 'dims',\n", - " 'direction',\n", - " 'dtype',\n", - " 'is_loaded',\n", - " 'metadata',\n", - " 'ndim',\n", - " 'origin',\n", - " 'path',\n", - " 'raw_metadata',\n", - " 'read_region',\n", - " 'resolutions',\n", - " 'save',\n", - " 'shape',\n", - " 'size',\n", - " 'spacing',\n", - " 'spacing_units']" - ] - }, - "execution_count": 14, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "!pip install --force-reinstall dist/*.whl\n", - "\n", - "dir(img)" - ] - }, - { - "cell_type": "code", - "execution_count": 3, - "id": "5ec0b0d6", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "161 ns ± 0.967 ns per loop (mean ± std. dev. of 7 runs, 10000000 loops each)\n" - ] - } - ], - "source": [ - "%%timeit\n", - "\n", - "img.is_loaded" - ] - }, - { - "cell_type": "code", - "execution_count": 9, - "id": "fcc68767", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Help on method read_region in module cucim.clara._cucim:\n", - "\n", - "read_region(...) method of cucim.clara.CuImage instance\n", - " read_region(self: cucim.clara._cucim.CuImage, location: List[int] = [], size: List[int] = [], level: int = 0, device: cucim.clara._cucim.io.Device = cpu, buf: object = None, shm_name: str = '', **kwargs) -> object\n", - " \n", - " Returns a subresolution image.\n", - " \n", - " - `location` and `size`'s dimension order is reverse of image's dimension order.\n", - " - Need to specify (X,Y) and (Width, Height) instead of (Y,X) and (Height, Width).\n", - " - If location is not specified, location would be (0, 0) if Z=0. Otherwise, location would be (0, 0, 0)\n", - " - Like OpenSlide, location is level-0 based coordinates (using the level-0 reference frame)\n", - " - If `size` is not specified, size would be (width, height) of the image at the specified `level`.\n", - " - `` Additional parameters (S,T,C,Z) are similar to\n", - " \n", - " - We may not want to support indices/ranges for (S,T,C,Z) for the first release.\n", - " - Default value for level, S, T, Z are zero.\n", - " - Default value for C is -1 (whole channels)\n", - " - `` `device` could be one of the following strings or Device object: e.g., `'cpu'`, `'cuda'`, `'cuda:0'` (use index 0), `cucim.clara.io.Device(cucim.clara.io.CUDA,0)`.\n", - " - `` If `buf` is specified (buf's type can be either numpy object that implements `__array_interface__`, or cupy-compatible object that implements `__cuda_array_interface__`), the read image would be saved into buf object without creating CPU/GPU memory.\n", - " - `` If `shm_name` is specified, shared memory would be created and data would be read in the shared memory.\n", - "\n" - ] - } - ], - "source": [ - "help(img.read_region)" - ] - }, - { - "cell_type": "code", - "execution_count": 6, - "id": "3397aa75", - "metadata": {}, - "outputs": [], - "source": [ - "from cucim import CuImage\n", - "img = CuImage(\"notebooks/input/image.tif\")\n", - "region = img.read_region((1000,1000), (100, 100))" - ] - }, - { - "cell_type": "code", - "execution_count": 7, - "id": "e9595d38", - "metadata": {}, - "outputs": [ - { - "data": { - "image/png": "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\n", - "text/plain": [ - "" - ] - }, - "execution_count": 7, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "from PIL import Image\n", - "import numpy as np\n", - "Image.fromarray(np.asarray(region))" - ] - }, - { - "cell_type": "code", - "execution_count": 8, - "id": "de9a4771", - "metadata": {}, - "outputs": [], - "source": [ - "import cupy as cp\n", - "\n", - "region_cupy = img.read_region((1000,1000), (100, 100), device='cuda')\n", - "vis = cp.asarray(region_cupy)" - ] - }, - { - "cell_type": "code", - "execution_count": 15, - "id": "5a932ac8", - "metadata": {}, - "outputs": [ - { - "data": { - "image/png": "iVBORw0KGgoAAAANSUhEUgAAAGQAAABkCAIAAAD/gAIDAAAXz0lEQVR4nM1d7ZIcqXI9J6mekXav/P4v6bC9kne1M10c/0hIkqKqpyXda5tQTFRTFCRJfpMg/se3fwcAgKT/9YdDkWT9gSR10sbMJAEwMNeYWa3VzPZ93/cde5W0Q9u2+Vsf0R+q6KPE3wxDG50EUJHfWjz5W2k/fJi7qgl8UiQLxrj5bZ+oAGzrnNeyAg3hgFNJtdYE64Bv33dJ9/sdgJkZTUarNeORpPdAGyB5ZUaB17TKk/Ua0E4t1wbkTBb1tGUqBLQdQDlFUKOp6vUDHbl99GBmAPb7Xms1qJRSa6Xw/v5O8vX1tYJG2zb78/tft9utQoQBBCiI89Ak1WgNZANkgC+dkTgA7KIjrcHGNilJIAj6v1YJ1TN+AkABqCKkM8p6jOMrhEZ945Fa7/e7I67Wiqpaq6TPnz+/vd0lifWvv/7yT7bCtXN06ggaiZUIJl1BjZraGdaLk230cxhO1SSA9UIEUQKozReL/uXVMq2cCEPDeisVFQBhLs6qWHeoygly3ytg9/0u2H3fTdr3/e/v71t52baNpLOwmUkMBptIA0Za/9neZz5F+qYzmvWVE0Cfc60y8+laF7+COd4N9US8wKck24Dg3wblR9z7QQmR4SXkkZf7/R7Sbds2R1B8KGkVRZ3BrdaaxeJjAA50R7KUEuQZusi5sq9QvejRwApgIwWIbFhzIpUUXzaUn4nTmRAbg9TaidkooELFzLZyv99Ztu9v76TRjKiff/+NJItVUDRzsdXYxFLPXf3Ver/fgxnbi64QOmrkQk2qQaROB5L2vTreQAMaYwIxOXOYW+fyPgXAZAA2oNEqnbO6mKj1UhwARwnlM8kKKEhp3/dt21zMm9n9fv/06RMAoLy+bvsuCYHfgeg+YpY76AqklOJvzWwBc4IwJFSt9f39/e3tzemrbFsMRxIoksDaiKiTUieC9ryhod81BKXa9MO0thPKQspmgMTiEsZxVMW76vv7u2kgWtpLMWk320hKMJaGHTlENDqDKPcPwMBCA7WVrdw2kvvdSePIOxUEYJJqvd1uku7v91KKKv7rP//4/fffIUI0s6YTO0sSBSBk/S+APePL8rpdGSaxVtk2CS0TKua0wbdv397e3gDcbrdSisvyWu8kicJF+hwMq6gMaZVB9crcrK0KBqkGbLfb7cuXL6+vry8vL7fbLYxhVy1ougGdJ9nRlDDw9fsfp+P52qwoiwVv9tS+O385a+RpuL0eArV2K9Sh3HhuD9uFgHcHwMfqpE1JtQOZ2L8xbK33jhHrNVXCtm0sxdXFYY7TCjk1OeV2AX/pXuRXh8q8wmGCY5EyWRUG1vLcDvCdFm8ZtkWCSqThBPIKQGqNHRBHzWCFRKd57pot7UPZukF8FN4hC7pyjDI00VTvcPhjb2Bm7+/vQU2BtZhSNA8QlSFJ8m4fVOnQuRlhrX0iCBKQKS0hUY26112CxH2XyUgWKz7zAXkDwxGXJu3acKapC5N/tf0SEcUCrm1Ivr6+otNFVmqc8DzwdehkLLgxGCdRZQ0umwcONTc67LbVia/2mKCibLsyZfk3BAbe2gCjL4eeNWSDWXrbPdj+MyzJUsrkMM92MvpK5hlWqPl0EEFA9/vd3fIuJekOY9Bg9EorEXjotpSRbJQoUo17JJFNVk5L1RomZPU1H4AeCrtFN02uL0X8DbWILuODGV32M/k027ax7tFn7vxqhb2N69OMU0nA3jX96MGHc/rKOjF3qDqcDSxLtZZgQ9dulHAGrciJh2kCqwArbP4/dloDoVHDwh0FHay9lq28vb2FvK+1enjrhA0p9zBJFmPZNmisjZp3FRKWnUto5rJN3fCuzWIDHHhUC14dDuep7eLIwvVi/lwhiRYqmOqdnQMUj9jEaodRfgiKrf1LwvnCz4GmJxzcH5q4pI0y8ziRANVEqYu9E7A2jJw16kpFkstw6+JVfZZdvqCUQlFVLs6q7kqqIE3bA08C2FV0YnZH6wqx05p8Qk6AlujOKbqS7G2i55m0+6wlbbPWsFCjD7D+WKwc2jDVN5av4+e+73///fenT59KKbRmymaKG/0sbPIMVKGjh+J6YiJXs9uI0iV++EExTaxRi2yJnIHuflmGQyRV25oNU0C21yrsf3//Xkq53+8vLy/7XagEKTsumKVxTjW9TsUtm6eYZtSAGnNoaOwzOs5nmBpbso+MCaJe+QGV/XRR6HOb/S+nKf4waR8qZ8X3MTAfSEkAwLZxu+93YTczg0vZPbXs4gyY7cjzwbL0CP6RZB426+YySTUu2z69/mYwDzaMQBWMTbjxMiZ3Vh7iMb9qfnIgYkdoysuehxeSR3qelFZl/2FhKpJeXl4cTQHDKrCe6fOHYDgA87hNALYRFdrrvheaERJqbRZqgmNoFrECSEEk1qb1hnlCCBKYNhd6R4PrWfog9KiTQIBmQ1b2RTAAlUkRprlWp8dhgkJSKaVHI6yP3L1dNTsLmekYo+Te+4hGage5edwjE1dXxufce2nkpDZcXYfui4SAB+pDqn+2ZM5o27S1eiAEXVo9UKDPs4WkhqyGrxYsvZBzF9TaDZrByLXx/+RboFvQSd62WEqPfFe4UTXPx2mKGEbAwUZxZDkjRzgwK6g5UjLN/xF60hCoJmlz0ya7DldFfQvvwzYZlINEUPJ158mfSHGS8z77Cb7yKD4Rj4ImEp4GOkTnHswlz8gJdnvf7+7cV7TdELHtUOYO06jN9r3omxE8aHpRE9wImxh7xL3aWyNQnYW72Ox4DzsogmscIDVtYK6CycK97mbFMSP3LiVUl18e0V0kQGLYDtcIw3v95ht5eacXCzkcMJ2mPOOJR74LEviQ4EmqL8AZpuaWqbfgvlCv+77/+eefnz//lqdzasGfzetRzXaPOAaw65hqgDaCCXLh1I2thb4aTWXWMwlKFDEZatZZItlvwXckptaza32Kr3hba317e7vdbm2ciJdeyRAddxtOPE2PLAUoPySPHpegvscO3ZXNTd9SPysrkLXWvFfiQUEvksJuQ9rp8bYZ3AdzyUBu+76/vLw8bpqladN0jSY+0P3HFbtGNdnsNZ8hLvCYu41K14C1VoAS9r1++fJvuWXdvZNleTLGcn0zkrqyBgoJwNzvX8PbMdiPGui/WB6LyzXmyYvyfLetwRMgbQbU+718+kT5Vix8/2RfsNY9KddWIxj9c8VpM1kGzZRtJQlDstHEfd+d6TwAJ4yFZEuSKM3/HKFwAmhOgcYeYjNi+xTrvIOVZ15AqBmT9vr6amZ5h/bnqOkZJ+tx+WhQrpSVRzzo4qOamsE7NGaVXQyuVLbX24v2um1b7EF6o4M0Cnsh/CUfdAys/nuEXyb/64DKeibvJByAJmk0wlMwfATOGaT5+1Sfqg0elprMI7ToKwCPRpoJB/uRpDhiMJuTVYf1HL0PdNmkwh6aKgdlelAdV816G1L1MeGHJZXfNiDT8wGwsEI9Q+kxb2wAbrcbqqwbPGt7pX6zfZUxhRVfMkgpd+ekTLTczJuEUAESiEqR9G1kiqzueI2IwoASAMroIc3mal4tk9UtvN6JeyBS27j0UTaSwYOnQsc/WEXA1fMVeR6afWjWjdEBCLJk02uYjbmf3mB07py2zuo4etUazlzJbLuVDULhiIiiG0TiQO3oNsC8Hn4CpcsRTYH5lErcf6xFLTAGANxBtkwHV3mtsyrOcrPvZBsAA8UpZ2KEj4RpK6cJrrEeROXwLio/tCr/FeUnjKAPy0qkVA9L/5SZuIIk6YNDAyuzTKnov1KyLBO4cHoa7RywQ81BMzTmyJCmLxpJ5QEtd3guZy+hWX39q5YB61q5irYH/TxpeR+aZeX4r/M3vNvtgb8WwHUIJrP6+Df1nP6mh/PGJ8PNQIpBIyRbxr7LnSPVZ3PkwThqsUZPjO8WwCWWR+j1B+j8VyTLT3+7WicrZeW/Byo79XYf9P8Y2u1qz1ktc2YiE/bE78ki81TwC2w8MDLyDJbvMlT+1kiKoLVoP/a2zS2BLJo3Splk4tq92AJvYSJIu3owTikvLH/1lDb8EOvPSIpTAzpePelaroLvQFm5/pSyrqB6BoCt9syWUwHcBVYN4pqVzmhs/Zzg5B7mIvT4NwBmjd6psq72dszVVMNvaTLGwwGN2Gv3dRDHIMbIbQ/cn3cHJl7lh2z3rribjtA9Ux60fHINf6iMlf+RQU+nGh0eEBTUF37FlWbvR3ZGXTwFz3dO1mTkkURi6eDz/rZZH/OK5WNKP7Y8FdUlI+m5Xi6tqsdrPLgVo0hyD5FjN/PUya/qYR8/Wev5+lVtp5JkSZ7mRLH/FPpaW/7cEB+OeCqnHrw9/VbpfEOMkg8TZPinM7bXkDb9c2DsrLN4YoVkQI/cdMWxV0jpryRp75ECNNo5ZgJ0UjqVs6FDJ/Ev+TGgkc7X+3FuMMQJiyfLLzpxH35+2uAp52FJsFplfGiDtX8zYx0s6buoJwLeNdFVUBU85in3snpPueaYaP2MhOq68irXMHOWFLa3i2TCiqHKd/BJ+snPSXirRcfWnklutolwZoxjLAdI/g+iDqfliqYek1UWTKHL4pjO2nId0ckKhTmvxDcfTyjLF2a1v4vvjrj/lVejDbrsSHNkP3XtuuYTpJrUZ9tBWSYz39wwmIgVdLFFou1IAwCL1XftqrftdvgEQMUe86/JBSFplFmpf9937USxUsxQd0jTqfzLEM2pNuEFT/5/KJm+DpUnPzNxdMfIiSvJvhOj1E5jSVNmaT+lA015WCd+uEcTJ78vk+XVTL1kOk3knGg2BnHJlWCTtAOF87k9tMDFGLzzUHM0SLYMRRAsVowkZGoh2khyasj6sUJYtksTls/E34cBoPMxniLevmB9KEmaTj4iYSpq8kKSls2LlbJiglpSu9McJVjBTMZudJgmFERC3mEi/aEfzclImyTXurMdK1FtCnl4lmnXgmdng9RvtlHPyQhAKhswjJz3liJPJL/YIekmqd9EMEaZKOvKGk7FwjV66An+spJlvWLa7jmnrLk5JXFd/vPwkUwpsIfFRkGLC2QB77t7jcMTAacBWnaoX8sSexxd0rnNO+UzXuTVnUIcPediTWSt1pzaCDGW+fkWp4rk2V4IgJrxqAk7ZCD6JACDK5mV/e4gUeDSlU9f2jyXnyjsLKZ8p8JJu4kQFAbEg5a44J71Yf0Qcd4wk+4uv8NgSLsKqdbNilmZ04GTlr1kSsOZoKdyJDNuCanWVXYHrCcaN0klYAQwG2mbuT3Innn/wJi1ftBWWfiD0p6Z8UiojqzQsoEal5F+3RXTkXlVxcVF07R/JJs8gz2oRqFhfa+33YcBQKqrKPnloU8+b3MnfbFOnQf+8ed/xw93jtSvFYhIhX9pSZWeRBiOAQnUSzF/YK6GDvOPvAa4lnMT1ubrVcaIVzILF5zYvhcqUeCBs2NkaZgOZHMgvOb79+9+mDmUMS4OE+Tuni4ZX55DV/uzTyIMa3UpNo31iwSFtLpTEIEt1uca5oCyJuD9/IYjy6/XcU/SU2sIEKyqkkrb+0kue7aDUj6AtRu1CkZqXaa1OPXQGXAgKCVesFJnYr4HtKwR4fFuDVvuqLkqp6ssgA66XwfmFnzkz5iZ1GKG3759+/Lli1/BBMCTf93A1eLQXwMxMVNYRks5CK9McfH5w5MK66BANoOuil1PpHlDc+XWdQfd9fOrk75+/fr+99vtdvM7bpqCULuVJIzAk3tjPHrtkSmN8fz+qUnWdI8/3raY5LCY23EzAGjWvE05qKn/7uktc867NSevkn8yM3q4k/1GGiPZRJIHvfY64tCe8u0E5eS2hfD6XyozxQErZUknWVQdQQeDs8Wk5zZAR9PMLrHfMmoAbOwxWaeUfa/7+/0fv/0eVwlkq4Kk9nZCn4zjOSkSj3a7y1JqsvKdv5obTMaJRRvuBWd57/c2uVzrll2/d632zvp2QxrFpvycIUCYsJbTZ/Y200GvnR8rxiUYfaRSit9YWEp5eXmJ4GwH+VmBdSgP6DGCLY/Fy7Q5whFzkXCIK+RuHrOBLWbnwCZPQh89UN1bhLmwbZs/tAYRC+zSDbGeyS/PAYW2A+xXc0xAJb3Z3PskXNrpwtFDg17Hmj7jAgmokcuuZHrVNH/fM/dMCU/NqZqGPuANUwqXIe6imaM8DEskxL+Z5dN6V/SltOkUS3DaMn+S4HvY8gRTLXJb+/V5Y1E7nAmkk+8Bv4DoyDF1kSWV2KSd7m6b+64CK1hVIamdaPCIWleFj+eQ7KmPGbai564MH8P1o3cwsV6Q24DBMQV47EQ9fuK+3sBUyvNhumG1W2fZrAuWnqa2Q1CXWTGxMCPydtCQ7rFKv6ASY521bPY9+kQnlDXlJXW5nOHMf+di80OYxNYPIys6DBnSE0OqoCprXZtZs28omuSXieQ7W2ZpEtQUkx46IZl37DEMtp3e2PsTIDmBY2g0w/gqMFVxEOGRuAOONLEeEWxtLJ53AUDJO5sd12yRD892ZgzneFQI+D5wA+5UJD2ggoN1/qQtlqTbx1uEA/SDb9jFlmMqdFhOxOYiELJsTaVRGXm8ONF/tBvZgR4rBZRu7sAy80V+N86f8+XznSMriJFL1Rb+Ck1XOfetNz4C72CUpnL0IuX3j6Lu++5HOvfdb4a4HwBrF8H2YQYbNk8wqRhPxDmcpv7pQtLTV+Kgl2uSg0WeDZ+fGPdx+1it+JtzadYl3MZeBmi1IYbEVkxAFXsd1HZ0syl1HrGasxaUniG/jV2q3CV31ful+m5Xb+MOBn8oTdKNs/CtQddfktA8uKOs7OGX4XUafELJysMOtGk6cd1uRdonudyRFTOk+q3aYa1E+nQ2vk4RdFWyfJkcaTWCcieBfj31Eoa96o1zkDMESAzURXtrsHbVLI300ykdwO1WYojuLVB+2ViGI+7EmEHsptbo+9GsZiNwZ+z5c9ww42aV5QsPNYYIR4R9566PZwD2TF9z0BX55E6AICNpOZexGQZODg3ien+PG3Wlpqmt9QlGmuSwBs0OFxYvNPVoj2d9lRXCgRzWz8ciXfSTV5FnIRCpZ2XPA6lvr1wVD66U2xbCetWhW772IKZEcisjl1LDfu5wnOwdZFob6G6ShjVtmsxzm/ssR/o9mWGMpHZSQknyxj7QGKN7AoRLLrfwW7qz/P8jsIKq++fbq8suQKjDUvPF2zx8HCOFc8Mx1mxxuMhYZ/BcOdVuBy9ibZxrDhR6KkOvKh/whBOU31u/Dt1o6M/vXyUJ+TYQSDrsmqThXY9YMwK7fsy95/h316FLgAXZ00w3L/fzqd7zBXL3RhhdD3p9lmWZDfOWRLubuRIeWJj5esKf/0z3ef1wFs3zpcGRFXALTyOA6DSS9i7T5x63axf54hEp/Wg51ar5lTMg56hWd6TT7YpdFmYGEWYvD9PZa6eaIeP2lMFSGjRLfsscRxxqJKeFebQgB/MHhD9TDiNexgYZGjIo2jCCf7PF8RMAnYo2hHrmqO9L2iB2dkup54syuRhOH7U5lJj8KSl9+K2k6Trj/HB2+11yGFnh0upIX/7YJAuAu9x/YiVy5oZf3urqJVsnVzJ4Ws6UF6gU/Dt8ga6jXbaO3aCI9CZkhDd+OrqXbaWjf4pQiG6lkd8S9nSTx/ve/2uYjZFUkXe7NHU16p8Ipx05bqnEYLFLNK0ya4iixykGXbtkKvP4wZKB5+e1spSprBLYUhHdqbrfd98BMatxldNmN8w0jklQ9PhSxJs+sGLSvhSh8BbTVTgdZQyh3jDCejBmTrThMzz8TMmdtBseS/Oo7vf7169fAdxut9fXV/+PU3CxvA8sqewe/ASE83DHSMMKTCArR3kudskzfFMQYzkT4eZ4zl6VrMeCAJRSvvz+D/8vZgrtVtzJuJy5wmfrgsdtNw2GHLuHWYE4EtazjTZMgBgjob7Zj0cwtnVL7lqfHjF9Ko/DVlrtPY48xIk0Opc9G9VY1z9/FGP9aHls4qNTlkU8CABQumioM/J9GyXFqmRYB+hwxw0QTWs2nceqaoDxvpVCsph5fARk7mq2Z9DzmpP2dRL3lxw/TtBtnsDPWKpOX0P++n4lBl+3bjMGH1nwByoL+/sgU69oIUayHtcYrzSc0BR6PTGps9dyOsR15dRJVnxreVwfb/8H9xU9HOYLhZsAAAAASUVORK5CYII=\n", - "text/plain": [ - "" - ] - }, - "execution_count": 15, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "Image.fromarray(vis.get())" - ] - }, - { - "cell_type": "code", - "execution_count": 16, - "id": "4098b771", - "metadata": {}, - "outputs": [], - "source": [ - "del vis" - ] - }, - { - "cell_type": "code", - "execution_count": 12, - "id": "aac6a6fe", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "{'data': (46733280, False),\n", - " 'strides': None,\n", - " 'descr': [('', '|u1')],\n", - " 'typestr': '|u1',\n", - " 'shape': (10, 10, 3),\n", - " 'version': 3}" - ] - }, - "execution_count": 12, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "b.__array_interface__" - ] - }, - { - "cell_type": "code", - "execution_count": 13, - "id": "a836258a", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Requirement already satisfied: cupy-cuda112 in /home/gbae/.virtualenvs/cucim/lib/python3.8/site-packages (9.0.0)\n", - "Requirement already satisfied: numpy>=1.17 in /home/gbae/.virtualenvs/cucim/lib/python3.8/site-packages (from cupy-cuda112) (1.20.3)\n", - "Requirement already satisfied: fastrlock>=0.5 in /home/gbae/.virtualenvs/cucim/lib/python3.8/site-packages (from cupy-cuda112) (0.6)\n", - "\u001b[33mWARNING: You are using pip version 21.0.1; however, version 21.1.2 is available.\n", - "You should consider upgrading via the '/home/gbae/.virtualenvs/cucim/bin/python -m pip install --upgrade pip' command.\u001b[0m\n" - ] - } - ], - "source": [ - "!pip install cupy-cuda112" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.8.5" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} From 45cc4256f91f1fc2aaa4ed1334df7315975365a6 Mon Sep 17 00:00:00 2001 From: Gregory Lee Date: Wed, 13 Dec 2023 13:28:35 -0800 Subject: [PATCH 08/21] remove various files related to old wheel building mechanism (#665) closes #655 Authors: - Gregory Lee (https://github.com/grlee77) Approvers: - Gigon Bae (https://github.com/gigony) - AJ Schmidt (https://github.com/ajschmidt8) URL: https://github.com/rapidsai/cucim/pull/665 --- .dockerignore | 6 - Dockerfile-cuda110 | 39 --- Dockerfile-cuda111 | 39 --- LICENSE-3rdparty.md | 6 - build.sh | 245 -------------- build_docker.sh | 98 ------ dockcross-manylinux2014-x64 | 270 --------------- docker/Dockerfile-claratrain | 28 -- docker/Dockerfile-cmake | 76 ----- docker/Dockerfile-jupyter | 91 ----- docker/Dockerfile-jupyter-dev | 112 ------- docker/Dockerfile-jupyter-gds | 142 -------- docker/Dockerfile-jupyter-gds-dev | 160 --------- docker/cufile.json | 81 ----- docker/requirements-claratrain.txt | 10 - docker/requirements-jupyter-dev.txt | 20 -- docker/requirements-jupyter.txt | 20 -- run | 18 - run_gds.sh | 51 --- scripts/auditwheel_repair.py | 104 ------ scripts/run-dist | 500 ---------------------------- 21 files changed, 2116 deletions(-) delete mode 100644 .dockerignore delete mode 100644 Dockerfile-cuda110 delete mode 100644 Dockerfile-cuda111 delete mode 100755 build.sh delete mode 100755 build_docker.sh delete mode 100755 dockcross-manylinux2014-x64 delete mode 100644 docker/Dockerfile-claratrain delete mode 100644 docker/Dockerfile-cmake delete mode 100644 docker/Dockerfile-jupyter delete mode 100644 docker/Dockerfile-jupyter-dev delete mode 100644 docker/Dockerfile-jupyter-gds delete mode 100644 docker/Dockerfile-jupyter-gds-dev delete mode 100644 docker/cufile.json delete mode 100644 docker/requirements-claratrain.txt delete mode 100644 docker/requirements-jupyter-dev.txt delete mode 100644 docker/requirements-jupyter.txt delete mode 100644 run_gds.sh delete mode 100644 scripts/auditwheel_repair.py delete mode 100755 scripts/run-dist diff --git a/.dockerignore b/.dockerignore deleted file mode 100644 index 26a3e77ec..000000000 --- a/.dockerignore +++ /dev/null @@ -1,6 +0,0 @@ -* -!docker -#cmake-build-* -#build -#install -!temp/gds diff --git a/Dockerfile-cuda110 b/Dockerfile-cuda110 deleted file mode 100644 index 725ddf2b6..000000000 --- a/Dockerfile-cuda110 +++ /dev/null @@ -1,39 +0,0 @@ -# -# Copyright (c) 2020, NVIDIA CORPORATION. -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# - -FROM dockcross/manylinux2014-x64:20230215-2dd9a1c - -ENV DEFAULT_DOCKCROSS_IMAGE gigony/manylinux2014-x64:cuda110 -ENV PATH=/usr/local/cuda/bin/:$PATH - -ENV LD_LIBRARY_PATH=/usr/local/cuda/lib64/:/usr/local/cuda/nvvm/lib64:${LD_LIBRARY_PATH} - -RUN curl -LO https://developer.download.nvidia.com/compute/cuda/11.0.3/local_installers/cuda_11.0.3_450.51.06_linux.run && \ - chmod +x cuda_*.run && \ - ./cuda_*.run --silent --no-opengl-libs --toolkit --override && \ - rm -f cuda_*.run; - -RUN curl -LO https://developer.download.nvidia.com/compute/cuda/repos/rhel7/x86_64/libnvjpeg2k0-0.0.1.17-1.x86_64.rpm && \ - curl -LO https://developer.download.nvidia.com/compute/cuda/repos/rhel7/x86_64/libnvjpeg2k-devel-0.0.1.17-1.x86_64.rpm && \ - rpm -i libnvjpeg2k*.rpm - -# TODO: Currently we don't install dependencies from libtiff here. -RUN yum install -y openslide-python openslide-devel python-devel python3-devel - -# Copy stub libcuda file -RUN cp /usr/local/cuda-11.0/targets/x86_64-linux/lib/stubs/libcuda.so /usr/lib64/libcuda.so.1 - -RUN cp -P /usr/include/nvjpeg2k* /usr/local/cuda/include/ && \ - cp -P /usr/lib64/libnvjpeg2k* /usr/local/cuda/lib64/ diff --git a/Dockerfile-cuda111 b/Dockerfile-cuda111 deleted file mode 100644 index 428c154d6..000000000 --- a/Dockerfile-cuda111 +++ /dev/null @@ -1,39 +0,0 @@ -# -# Copyright (c) 2020, NVIDIA CORPORATION. -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# - -FROM dockcross/manylinux2014-x64:20230215-2dd9a1c - -ENV DEFAULT_DOCKCROSS_IMAGE gigony/manylinux2014-x64:cuda111 -ENV PATH=/usr/local/cuda/bin/:$PATH - -ENV LD_LIBRARY_PATH=/usr/local/cuda/lib64/:/usr/local/cuda/nvvm/lib64:${LD_LIBRARY_PATH} - -RUN curl -LO https://developer.download.nvidia.com/compute/cuda/11.1.0/local_installers/cuda_11.1.0_455.23.05_linux.run && \ - chmod +x cuda_*.run && \ - ./cuda_*.run --silent --no-opengl-libs --toolkit --override && \ - rm -f cuda_*.run; - -RUN curl -LO https://developer.download.nvidia.com/compute/cuda/repos/rhel7/x86_64/libnvjpeg2k0-0.0.1.17-1.x86_64.rpm && \ - curl -LO https://developer.download.nvidia.com/compute/cuda/repos/rhel7/x86_64/libnvjpeg2k-devel-0.0.1.17-1.x86_64.rpm && \ - rpm -i libnvjpeg2k*.rpm - -# TODO: Currently we don't install dependencies from libtiff here. -RUN yum install -y openslide-python openslide-devel python-devel python3-devel - -# Copy stub libcuda file -RUN cp /usr/local/cuda-11.1/targets/x86_64-linux/lib/stubs/libcuda.so /usr/lib64/libcuda.so.1 - -RUN cp -P /usr/include/nvjpeg2k* /usr/local/cuda/include/ && \ - cp -P /usr/lib64/libnvjpeg2k* /usr/local/cuda/lib64/ diff --git a/LICENSE-3rdparty.md b/LICENSE-3rdparty.md index 4c2a045b1..984d0e952 100644 --- a/LICENSE-3rdparty.md +++ b/LICENSE-3rdparty.md @@ -287,9 +287,3 @@ PBA+ - https://github.com/orzzzjq/Parallel-Banding-Algorithm-plus/blob/master/LICENSE - Copyright: School of Computing, National University of Singapore - Usage: PBA+ is used to implement the Euclidean distance transform. - -dockcross -- License: MIT License - - https://github.com/dockcross/dockcross/blob/master/LICENSE -- Copyright: Steeve Morin, Rob Burns, Matthew McCormick, Jean-Christophe-Fillion-Robin, Bensuperpc -- Usage: Building Python wheels for Linux diff --git a/build.sh b/build.sh deleted file mode 100755 index 2f96597d6..000000000 --- a/build.sh +++ /dev/null @@ -1,245 +0,0 @@ -#!/bin/bash -# -# Copyright (c) 2020, NVIDIA CORPORATION. -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# - -if [ "$0" != "/bin/bash" ]; then - SCRIPT_DIR=$(dirname "$(readlink -f "$0")") -fi - -################################################################################ -# Utility functions -################################################################################ - -c_str() { - local old_color=39 - local old_attr=0 - local color=39 - local attr=0 - local text="" - local no_change=0 - for i in "$@"; do - case "$i" in - r|R) - color=31 - ;; - g|G) - color=32 - ;; - y|Y) - color=33 - ;; - b|B) - color=34 - ;; - p|P) - color=35 - ;; - c|C) - color=36 - ;; - w|W) - color=37 - ;; - - z|Z) - color=0 - ;; - esac - case "$i" in - l|L|R|G|Y|B|P|C|W) - attr=1 - ;; - n|N|r|g|y|b|p|c|w) - attr=0 - ;; - z|Z) - attr=0 - ;; - *) - text="${text}$i" - esac - if [ ${old_color} -ne ${color} ] || [ ${old_attr} -ne ${attr} ]; then - text="${text}\033[${attr};${color}m" - old_color=$color - old_attr=$attr - fi - done - /bin/echo -en "$text" -} - -c_echo() { - local old_opt="$(shopt -op xtrace)" # save old xtrace option - set +x # unset xtrace - local text="$(c_str "$@")" - /bin/echo -e "$text\033[0m" - eval "${old_opt}" # restore old xtrace option -} - - -echo_err() { - >&2 echo "$@" -} - -c_echo_err() { - >&2 c_echo "$@" -} - -printf_err() { - >&2 printf "$@" -} - -get_item_ranges() { - local indexes="$1" - local list="$2" - echo -n "$(echo "${list}" | xargs | cut -d " " -f "${indexes}")" - return $? -} - -get_unused_ports() { - local num_of_ports=${1:-1} - comm -23 \ - <(seq 49152 61000 | sort) \ - <(ss -tan | awk '{print $4}' | while read line; do echo ${line##*\:}; done | grep '[0-9]\{1,5\}' | sort -u) \ - | shuf | tail -n ${num_of_ports} # use tail instead head to avoid broken pipe in VSCode terminal -} - -newline() { - echo -} - -info() { - c_echo W "$(date -u '+%Y-%m-%d %H:%M:%S') [INFO] " Z "$@" -} - -error() { - echo R "$(date -u '+%Y-%m-%d %H:%M:%S') [ERROR] " Z "$@" -} - -fatal() { - echo R "$(date -u '+%Y-%m-%d %H:%M:%S') [FATAL] " Z "$@" - echo - if [ -n "${SCRIPT_DIR}" ]; then - exit 1 - fi -} - -run_command() { - local status=0 - local cmd="$@" - - c_echo B "$(date -u '+%Y-%m-%d %H:%M:%S') \$ " G "${cmd}" - - [ "$(echo -n "$@")" = "" ] && return 1 # return 1 if there is no command available - - eval "$@" - status=$? - - if [ -n "${cmd_result}" ]; then - echo "${cmd_result}" - fi - unset IFS - - return $status -} - -retry() { - local retries=$1 - shift - - local count=0 - until run_command "$@"; do - exit=$? - wait=$((2 ** $count)) - count=$(($count + 1)) - if [ $count -lt $retries ]; then - info "Retry $count/$retries. Exit code=$exit, Retrying in $wait seconds..." - sleep $wait - else - fatal "Retry $count/$retries. Exit code=$exit, no more retries left." - return 1 - fi - done - return 0 -} - -parse_args() { - local OPTIND - while getopts 'yh' option; - do - case "${option}" in - # a) - # VALUE=${OPTARG} - # ;; - y) - ALWAYS_YES=true; - ;; - h) - print_usage - exit 1 - ;; - esac - done - shift $((OPTIND-1)) - - CMD="$1" - shift - - ARGS=("$@") -} - -print_usage() { - set +x - echo_err - echo_err "USAGE: $0 [command] [arguments]..." - echo_err "" - echo_err "Global Arguments" - echo_err - echo_err "Command List" - - echo_err - echo_err "Examples" -} - -init_script() { - TOP=$(git rev-parse --show-toplevel || pwd) -} - -main() { - parse_args "$@" - local file_type - case "$CMD" in - list|ls) - # list_testdata "${ARGS[@]}" - ;; - ''|main) - print_usage - ;; - *) - if type ${CMD} > /dev/null 2>&1; then - init_script - run_command "$CMD" "${ARGS[@]}" - else - print_usage - exit 1 - fi - ;; - esac -} - -if [ -n "${SCRIPT_DIR}" ]; then - main "$@" -fi - -# CLARA_VERSION=0.7.1-2008.4 ./serverctl get_latest_version_of recipes clara_bootstrap 2> /dev/null diff --git a/build_docker.sh b/build_docker.sh deleted file mode 100755 index f7845c594..000000000 --- a/build_docker.sh +++ /dev/null @@ -1,98 +0,0 @@ -#!/bin/bash -# -# Copyright (c) 2020, NVIDIA CORPORATION. -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# - -set -eu - -CMAKE_CMD=cmake -CMAKE_BUILD_TYPE=Release -NUM_THREADS=$(nproc) - -SRC_ROOT=/work -BUILD_ROOT=/work/temp - -CUCIM_SDK_PATH=${BUILD_ROOT}/libcucim - -# Build libcucim -${CMAKE_CMD} -S ${SRC_ROOT} -B ${BUILD_ROOT}/libcucim \ - -DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE} \ - -DCMAKE_INSTALL_PREFIX=${CUCIM_SDK_PATH} -${CMAKE_CMD} --build ${BUILD_ROOT}/libcucim --target cucim -- -j ${NUM_THREADS} -${CMAKE_CMD} --build ${BUILD_ROOT}/libcucim --target install -- -j ${NUM_THREADS} - -# Build cuslide plugin -${CMAKE_CMD} -S ${SRC_ROOT}/cpp/plugins/cucim.kit.cuslide -B ${BUILD_ROOT}/cuslide \ - -DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE} \ - -DCMAKE_INSTALL_PREFIX=${BUILD_ROOT}/cuslide/install \ - -DCUCIM_SDK_PATH=${CUCIM_SDK_PATH} -${CMAKE_CMD} --build ${BUILD_ROOT}/cuslide --target cucim.kit.cuslide -- -j ${NUM_THREADS} -${CMAKE_CMD} --build ${BUILD_ROOT}/cuslide --target install -- -j ${NUM_THREADS} - -# Build Python bind - -for PYBIN in /opt/python/*/bin; do - ${CMAKE_CMD} -S ${SRC_ROOT}/python -B ${BUILD_ROOT}/cucim \ - -DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE} \ - -DCMAKE_INSTALL_PREFIX=${BUILD_ROOT}/cucim/install \ - -DCUCIM_SDK_PATH=${CUCIM_SDK_PATH} \ - -DPYTHON_EXECUTABLE=${PYBIN}/python - ${CMAKE_CMD} --build ${BUILD_ROOT}/cucim --target cucim -- -j ${NUM_THREADS} - ${CMAKE_CMD} --build ${BUILD_ROOT}/cucim --target install -- -j ${NUM_THREADS} -done - -# Copy .so files to pybind's build folder -# (it uses -P to copy symbolic links as they are) -cp -P ${BUILD_ROOT}/libcucim/install/lib/lib* ${BUILD_ROOT}/cucim/lib/cucim/ -cp -P ${BUILD_ROOT}/cuslide/install/lib/cucim* ${BUILD_ROOT}/cucim/lib/cucim/ - -# Copy .so files from pybind's build folder to cucim Python source folder -cp -P ${BUILD_ROOT}/cucim/lib/cucim/* ${SRC_ROOT}/python/cucim/src/cucim/clara/ - - - - -set -e -u -x - -function repair_wheel { - wheel="$1" - if ! auditwheel show "$wheel"; then - echo "Skipping non-platform wheel $wheel" - else - auditwheel repair --plat "$PLAT" -w wheelhouse/ "$wheel" - fi -} - -PLAT=manylinux2014_x86_64 - -cd /work/python/cucim -# Compile wheels (one python binary is enough) -for PYBIN in /opt/python/cp36-cp36m/bin; do # /opt/python/*/bin - "${PYBIN}/python" setup.py bdist_wheel -p $PLAT -done - -mkdir -p /work/python/cucim/wheelhouse - -# Bundle external shared libraries into the wheels -for whl in dist/*.whl; do - repair_wheel "$whl" -done - -# # Install packages and test -# for PYBIN in /opt/python/*/bin/; do -# "${PYBIN}/pip" install python-manylinux-demo --no-index -f /io/wheelhouse -# (cd "$HOME"; "${PYBIN}/nosetests" pymanylinuxdemo) -# done - -# python setup.py bdist_wheel -p manylinux2014-x86_64 diff --git a/dockcross-manylinux2014-x64 b/dockcross-manylinux2014-x64 deleted file mode 100755 index e6edf726e..000000000 --- a/dockcross-manylinux2014-x64 +++ /dev/null @@ -1,270 +0,0 @@ -#!/usr/bin/env bash -# -# Copyright (c) 2020, NVIDIA CORPORATION. -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# - -DEFAULT_DOCKCROSS_IMAGE=gigony/manylinux2014-x64:cuda110 - -#------------------------------------------------------------------------------ -# Helpers -# -err() { - echo -e >&2 ERROR: $@\\n -} - -die() { - err $@ - exit 1 -} - -has() { - # eg. has command update - local kind=$1 - local name=$2 - - type -t $kind:$name | grep -q function -} - -#------------------------------------------------------------------------------ -# Command handlers -# -command:update-image() { - docker pull $FINAL_IMAGE -} - -help:update-image() { - echo Pull the latest $FINAL_IMAGE . -} - -command:update-script() { - if cmp -s <( docker run --rm $FINAL_IMAGE ) $0; then - echo $0 is up to date - else - echo -n Updating $0 '... ' - docker run --rm $FINAL_IMAGE > $0 && echo ok - fi -} - -help:update-image() { - echo Update $0 from $FINAL_IMAGE . -} - -command:update() { - command:update-image - command:update-script -} - -help:update() { - echo Pull the latest $FINAL_IMAGE, and then update $0 from that. -} - -command:help() { - if [[ $# != 0 ]]; then - if ! has command $1; then - err \"$1\" is not an dockcross command - command:help - elif ! has help $1; then - err No help found for \"$1\" - else - help:$1 - fi - else - cat >&2 < -ENDHELP - exit 1 - fi -} - -#------------------------------------------------------------------------------ -# Option processing -# -special_update_command='' -while [[ $# != 0 ]]; do - case $1 in - - --) - shift - break - ;; - - --args|-a) - ARG_ARGS="$2" - shift 2 - ;; - - --config|-c) - ARG_CONFIG="$2" - shift 2 - ;; - - --image|-i) - ARG_IMAGE="$2" - shift 2 - ;; - update|update-image|update-script) - special_update_command=$1 - break - ;; - -*) - err Unknown option \"$1\" - command:help - exit - ;; - - *) - break - ;; - - esac -done - -# The precedence for options is: -# 1. command-line arguments -# 2. environment variables -# 3. defaults - -# Source the config file if it exists -DEFAULT_DOCKCROSS_CONFIG=~/.dockcross -FINAL_CONFIG=${ARG_CONFIG-${DOCKCROSS_CONFIG-$DEFAULT_DOCKCROSS_CONFIG}} - -[[ -f "$FINAL_CONFIG" ]] && source "$FINAL_CONFIG" - -# Set the docker image -FINAL_IMAGE=${ARG_IMAGE-${DOCKCROSS_IMAGE-$DEFAULT_DOCKCROSS_IMAGE}} - -# Handle special update command -if [ "$special_update_command" != "" ]; then - case $special_update_command in - - update) - command:update - exit $? - ;; - - update-image) - command:update-image - exit $? - ;; - - update-script) - command:update-script - exit $? - ;; - - esac -fi - -# Set the docker run extra args (if any) -FINAL_ARGS=${ARG_ARGS-${DOCKCROSS_ARGS}} - -# Bash on Ubuntu on Windows -UBUNTU_ON_WINDOWS=$([ -e /proc/version ] && grep -l Microsoft /proc/version || echo "") -# MSYS, Git Bash, etc. -MSYS=$([ -e /proc/version ] && grep -l MINGW /proc/version || echo "") - -if [ -z "$UBUNTU_ON_WINDOWS" -a -z "$MSYS" ]; then - USER_IDS=(-e BUILDER_UID="$( id -u )" -e BUILDER_GID="$( id -g )" -e BUILDER_USER="$( id -un )" -e BUILDER_GROUP="$( id -gn )") -fi - -# Change the PWD when working in Docker on Windows -if [ -n "$UBUNTU_ON_WINDOWS" ]; then - WSL_ROOT="/mnt/" - CFG_FILE=/etc/wsl.conf - if [ -f "$CFG_FILE" ]; then - CFG_CONTENT=$(cat $CFG_FILE | sed -r '/[^=]+=[^=]+/!d' | sed -r 's/\s+=\s/=/g') - eval "$CFG_CONTENT" - if [ -n "$root" ]; then - WSL_ROOT=$root - fi - fi - HOST_PWD=`pwd -P` - HOST_PWD=${HOST_PWD/$WSL_ROOT//} -elif [ -n "$MSYS" ]; then - HOST_PWD=$PWD - HOST_PWD=${HOST_PWD/\//} - HOST_PWD=${HOST_PWD/\//:\/} -else - HOST_PWD=$PWD - [ -L $HOST_PWD ] && HOST_PWD=$(readlink $HOST_PWD) -fi - -# Mount Additional Volumes -if [ -z "$SSH_DIR" ]; then - SSH_DIR="$HOME/.ssh" -fi - -HOST_VOLUMES= -if [ -e "$SSH_DIR" -a -z "$MSYS" ]; then - HOST_VOLUMES+="-v $SSH_DIR:/home/$(id -un)/.ssh" -fi - -#------------------------------------------------------------------------------ -# Now, finally, run the command in a container -# -TTY_ARGS= -tty -s && [ -z "$MSYS" ] && TTY_ARGS=-ti -CONTAINER_NAME=dockcross_$RANDOM -docker run --runtime nvidia $TTY_ARGS --name $CONTAINER_NAME \ - -v "$HOST_PWD":/work \ - $HOST_VOLUMES \ - "${USER_IDS[@]}" \ - $FINAL_ARGS \ - $FINAL_IMAGE "$@" -run_exit_code=$? - -# Attempt to delete container -rm_output=$(docker rm -f $CONTAINER_NAME 2>&1) -rm_exit_code=$? -if [[ $rm_exit_code != 0 ]]; then - if [[ "$CIRCLECI" == "true" ]] && [[ $rm_output == *"Driver btrfs failed to remove"* ]]; then - : # Ignore error because of https://circleci.com/docs/docker-btrfs-error/ - else - echo "$rm_output" - exit $rm_exit_code - fi -fi - -exit $run_exit_code - -################################################################################ -# -# This image is not intended to be run manually. -# -# To create a dockcross helper script for the -# gigony/manylinux2014-x64:cuda110 image, run: -# -# docker run --rm gigony/manylinux2014-x64:cuda110 > gigony-manylinux2014-x64-cuda111 -# chmod +x gigony-manylinux2014-x64-cuda111 -# -# You may then wish to move the dockcross script to your PATH. -# -################################################################################ diff --git a/docker/Dockerfile-claratrain b/docker/Dockerfile-claratrain deleted file mode 100644 index 4526ebbdd..000000000 --- a/docker/Dockerfile-claratrain +++ /dev/null @@ -1,28 +0,0 @@ -# -# Copyright (c) 2020, NVIDIA CORPORATION. -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# -FROM nvcr.io/nvidian/dlmed/clara-train-sdk:v3.1-ga-qa-5 - -RUN apt-get update \ - && apt-get install --yes --fix-missing --no-install-recommends \ - libopenslide0 \ - && rm -rf /var/lib/apt/lists/* - -COPY ./docker/requirements-claratrain.txt ./ -COPY ./*.whl ./ - -# Use `python -m pip` to avoid using an old script wrapper. -RUN python -m pip install --no-cache-dir --upgrade pip setuptools wheel \ - && python -m pip install --no-cache-dir -r requirements-claratrain.txt \ - && python -m pip install cu*.whl diff --git a/docker/Dockerfile-cmake b/docker/Dockerfile-cmake deleted file mode 100644 index edf32abca..000000000 --- a/docker/Dockerfile-cmake +++ /dev/null @@ -1,76 +0,0 @@ -# -# Copyright (c) 2020-2021, NVIDIA CORPORATION. -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# -FROM nvidia/cuda:11.0-devel-ubuntu18.04 - -ENV LC_ALL=C.UTF-8 -ENV LANG=C.UTF-8 -ENV DEBIAN_FRONTEND=noninteractive - -# Download and install Python3 PIP. -RUN apt-get update --yes \ - && apt-get upgrade --yes \ - && apt-get install --yes --fix-missing --no-install-recommends \ - software-properties-common \ - ca-certificates \ - python3-minimal \ - python3-pip \ - && add-apt-repository ppa:ubuntu-toolchain-r/test \ - && rm -rf /var/lib/apt/lists/* - -RUN python3 --version - -# Set additional environment values that make usage more pleasant. -ENV TERM=xterm-256color - -# Make /usr/bin/python point to the ${VERSION_PYTHON3} version of python -RUN VERSION_PYTHON3=$(python3 --version | cut -c8-) && VERSION_PYTHON3=${VERSION_PYTHON3%.*} \ - && rm -f /usr/bin/python \ - && rm -f /usr/bin/python`echo ${VERSION_PYTHON3} | cut -c1-1` \ - && ln -s /usr/bin/python${VERSION_PYTHON3} /usr/bin/python \ - && ln -s /usr/bin/python${VERSION_PYTHON3} /usr/bin/python`echo ${VERSION_PYTHON3} | cut -c1-1` - -# Make /usr/bin/pip point to the ${VERSION_PIP3} version of python -RUN rm -f /usr/bin/pip \ - && ln -s /usr/bin/pip3 /usr/bin/pip - -RUN apt-get update \ - && apt-get install -y --no-install-recommends \ - python3-dev \ - gcc-9 \ - g++-9 \ - libopenslide-dev \ - wget \ - git \ - curl \ - && apt-get autoremove -y \ - && rm -rf /var/lib/apt/lists/* \ - && rm -rf /var/cache/apt/archives/partial/* - -WORKDIR /workspace -ENV HOME=/workspace - -# Use `python -m pip` to avoid using an old script wrapper. -RUN python -m pip install --no-cache-dir --upgrade pip setuptools wheel \ - && python -m pip install cmake - -# Setup gcc-9 -RUN update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-7 10 \ - && update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-9 20 \ - && update-alternatives --install /usr/bin/g++ g++ /usr/bin/g++-7 10 \ - && update-alternatives --install /usr/bin/g++ g++ /usr/bin/g++-9 20 - -ENV LD_LIBRARY_PATH=/usr/local/cuda/lib64:/usr/local/cuda/nvvm/lib64 - -ENTRYPOINT ["/bin/bash"] diff --git a/docker/Dockerfile-jupyter b/docker/Dockerfile-jupyter deleted file mode 100644 index cc6a2403b..000000000 --- a/docker/Dockerfile-jupyter +++ /dev/null @@ -1,91 +0,0 @@ -# -# Copyright (c) 2020-2021, NVIDIA CORPORATION. -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# -FROM nvidia/cuda:11.0-devel-ubuntu18.04 - -ARG NODE_VERSION=v14.13.1 -ARG NODE_DISTRO=linux-x64 - -ENV LC_ALL=C.UTF-8 -ENV LANG=C.UTF-8 -ENV DEBIAN_FRONTEND=noninteractive - -# Download and install Python3 PIP. -RUN apt-get update --yes \ - && apt-get upgrade --yes \ - && apt-get install --yes --fix-missing --no-install-recommends \ - ca-certificates \ - python3-minimal \ - python3-pip \ - && rm -rf /var/lib/apt/lists/* - -RUN python3 --version - -# Set additional environment values that make usage more pleasant. -ENV TERM=xterm-256color - -# Make /usr/bin/python point to the ${VERSION_PYTHON3} version of python -RUN VERSION_PYTHON3=$(python3 --version | cut -c8-) && VERSION_PYTHON3=${VERSION_PYTHON3%.*} \ - && rm -f /usr/bin/python \ - && rm -f /usr/bin/python`echo ${VERSION_PYTHON3} | cut -c1-1` \ - && ln -s /usr/bin/python${VERSION_PYTHON3} /usr/bin/python \ - && ln -s /usr/bin/python${VERSION_PYTHON3} /usr/bin/python`echo ${VERSION_PYTHON3} | cut -c1-1` - -# Make /usr/bin/pip point to the ${VERSION_PIP3} version of python -RUN rm -f /usr/bin/pip \ - && ln -s /usr/bin/pip3 /usr/bin/pip - -# libgl1 is needed for opencv at `cucim convert` CLI command. -RUN apt-get update \ - && apt-get install -y --no-install-recommends \ - python3-dev \ - gcc \ - g++ \ - libopenslide-dev \ - libsm6 \ - libxext6 \ - libxrender-dev \ - libglib2.0-0 \ - libgl1 \ - wget \ - git \ - xz-utils \ - curl \ - && apt-get autoremove -y \ - && rm -rf /var/lib/apt/lists/* \ - && rm -rf /var/cache/apt/archives/partial/* - -WORKDIR /workspace -ENV HOME=/workspace - -# Install nodejs -RUN mkdir -p /usr/local/lib/nodejs \ - && wget https://nodejs.org/dist/$NODE_VERSION/node-${NODE_VERSION}-${NODE_DISTRO}.tar.xz \ - && tar -xJvf node-${NODE_VERSION}-${NODE_DISTRO}.tar.xz -C /usr/local/lib/nodejs \ - && rm node-${NODE_VERSION}-${NODE_DISTRO}.tar.xz -ENV PATH=/usr/local/lib/nodejs/node-$NODE_VERSION-$NODE_DISTRO/bin:$PATH - -COPY ./docker/requirements-jupyter.txt ./ - -# Use `python -m pip` to avoid using an old script wrapper. -RUN python -m pip install --no-cache-dir --upgrade pip setuptools wheel \ - && python -m pip install --no-cache-dir -r requirements-jupyter.txt - -# Install Jupyter Extensions -RUN jupyter labextension install dask-labextension \ - && jupyter serverextension enable dask_labextension - -ENV LD_LIBRARY_PATH=/usr/local/cuda/lib64:/usr/local/cuda/nvvm/lib64 - -ENTRYPOINT ["/bin/bash"] diff --git a/docker/Dockerfile-jupyter-dev b/docker/Dockerfile-jupyter-dev deleted file mode 100644 index 5c7e7c2b1..000000000 --- a/docker/Dockerfile-jupyter-dev +++ /dev/null @@ -1,112 +0,0 @@ -# -# Copyright (c) 2020-2021, NVIDIA CORPORATION. -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# -FROM nvidia/cuda:11.0-devel-ubuntu18.04 - -ARG NODE_VERSION=v14.13.1 -ARG NODE_DISTRO=linux-x64 - -ENV LC_ALL=C.UTF-8 -ENV LANG=C.UTF-8 -ENV DEBIAN_FRONTEND=noninteractive - -RUN apt-get update \ - && apt-get install --yes --fix-missing --no-install-recommends \ - ca-certificates \ - && rm -rf /var/lib/apt/lists/* - -# Download and install Python3 PIP. -RUN apt-get update \ - && apt-get install --yes --fix-missing --no-install-recommends \ - python3-minimal \ - python3-pip \ - && rm -rf /var/lib/apt/lists/* - -RUN python3 --version - -# Set additional environment values that make usage more pleasant. -ENV TERM=xterm-256color - -# Make /usr/bin/python point to the ${VERSION_PYTHON3} version of python -RUN VERSION_PYTHON3=$(python3 --version | cut -c8-) && VERSION_PYTHON3=${VERSION_PYTHON3%.*} \ - && rm -f /usr/bin/python \ - && rm -f /usr/bin/python`echo ${VERSION_PYTHON3} | cut -c1-1` \ - && ln -s /usr/bin/python${VERSION_PYTHON3} /usr/bin/python \ - && ln -s /usr/bin/python${VERSION_PYTHON3} /usr/bin/python`echo ${VERSION_PYTHON3} | cut -c1-1` - -# Make /usr/bin/pip point to the ${VERSION_PIP3} version of python -RUN rm -f /usr/bin/pip \ - && ln -s /usr/bin/pip3 /usr/bin/pip - -# libgl1 is needed for opencv at `cucim convert` CLI command. -RUN apt-get update \ - && apt-get install -y --no-install-recommends \ - python3-dev \ - gcc \ - g++ \ - libopenslide-dev \ - libsm6 \ - libxext6 \ - libxrender-dev \ - libglib2.0-0 \ - libgl1 \ - wget \ - git \ - xz-utils \ - && apt-get autoremove -y \ - && rm -rf /var/lib/apt/lists/* \ - && rm -rf /var/cache/apt/archives/partial/* - -WORKDIR /workspace -ENV HOME=/workspace - -# Install nodejs -RUN mkdir -p /usr/local/lib/nodejs \ - && wget https://nodejs.org/dist/$NODE_VERSION/node-${NODE_VERSION}-${NODE_DISTRO}.tar.xz \ - && tar -xJvf node-${NODE_VERSION}-${NODE_DISTRO}.tar.xz -C /usr/local/lib/nodejs \ - && rm node-${NODE_VERSION}-${NODE_DISTRO}.tar.xz -ENV PATH=/usr/local/lib/nodejs/node-$NODE_VERSION-$NODE_DISTRO/bin:$PATH - -COPY ./docker/requirements-jupyter-dev.txt ./ - -# Use `python -m pip` to avoid using an old script wrapper. -RUN python -m pip install --no-cache-dir --upgrade pip setuptools wheel \ - && python -m pip install --no-cache-dir -r requirements-jupyter-dev.txt - -# Install Jupyter Extensions -RUN jupyter labextension install dask-labextension \ - && jupyter serverextension enable dask_labextension - -RUN apt-get update \ - && apt-get install -y --no-install-recommends \ - curl \ -# build-essential \ -# cmake \ -# git \ -# zlib1g-dev \ -# libssl-dev \ - && apt-get autoremove -y \ - && rm -rf /var/lib/apt/lists/* - -# Download TRITON Client -ARG TRITON_CLIENTS_URL=https://github.com/triton-inference-server/server/releases/download/v2.5.0/v2.5.0_ubuntu1804.clients.tar.gz -RUN mkdir -p /opt/nvidia/triton-clients \ - && curl -L ${TRITON_CLIENTS_URL} | tar xvz -C /opt/nvidia/triton-clients - -RUN pip install --no-cache-dir \ - /opt/nvidia/triton-clients/python/*manylinux1_x86_64.whl - -ENV LD_LIBRARY_PATH=/usr/local/cuda/lib64:/usr/local/cuda/nvvm/lib64 - -ENTRYPOINT ["/bin/bash"] diff --git a/docker/Dockerfile-jupyter-gds b/docker/Dockerfile-jupyter-gds deleted file mode 100644 index 1238c7265..000000000 --- a/docker/Dockerfile-jupyter-gds +++ /dev/null @@ -1,142 +0,0 @@ -# -# Copyright (c) 2020-2021, NVIDIA CORPORATION. -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# -ARG UBUNTU_VER=18.04 -FROM nvidia/cuda:11.0-devel-ubuntu${UBUNTU_VER} - -ARG UBUNTU_VER=18.04 -ARG NODE_VERSION=v14.13.1 -ARG NODE_DISTRO=linux-x64 -ENV UBUNTU_VER=${UBUNTU_VER} - -ENV LC_ALL=C.UTF-8 -ENV LANG=C.UTF-8 -ENV DEBIAN_FRONTEND=noninteractive - -# Download and install Python3 PIP. -RUN apt-get update --yes \ - && apt-get upgrade --yes \ - && apt-get install --yes --fix-missing --no-install-recommends \ - ca-certificates \ - python3-minimal \ - python3-pip \ - && rm -rf /var/lib/apt/lists/* - -RUN python3 --version - -# Set additional environment values that make usage more pleasant. -ENV TERM=xterm-256color - -# Make /usr/bin/python point to the ${VERSION_PYTHON3} version of python -RUN VERSION_PYTHON3=$(python3 --version | cut -c8-) && VERSION_PYTHON3=${VERSION_PYTHON3%.*} \ - && rm -f /usr/bin/python \ - && rm -f /usr/bin/python`echo ${VERSION_PYTHON3} | cut -c1-1` \ - && ln -s /usr/bin/python${VERSION_PYTHON3} /usr/bin/python \ - && ln -s /usr/bin/python${VERSION_PYTHON3} /usr/bin/python`echo ${VERSION_PYTHON3} | cut -c1-1` - -# Make /usr/bin/pip point to the ${VERSION_PIP3} version of python -RUN rm -f /usr/bin/pip \ - && ln -s /usr/bin/pip3 /usr/bin/pip - -# libgl1 is needed for opencv at `cucim convert` CLI command. -RUN apt-get update \ - && apt-get install -y --no-install-recommends \ - python3-dev \ - gcc \ - g++ \ - libopenslide-dev \ - libsm6 \ - libxext6 \ - libxrender-dev \ - libglib2.0-0 \ - libgl1 \ - wget \ - git \ - xz-utils \ - curl \ - && apt-get autoremove -y \ - && rm -rf /var/lib/apt/lists/* \ - && rm -rf /var/cache/apt/archives/partial/* - -WORKDIR /workspace -ENV HOME=/workspace - -# Install nodejs -RUN mkdir -p /usr/local/lib/nodejs \ - && wget https://nodejs.org/dist/$NODE_VERSION/node-${NODE_VERSION}-${NODE_DISTRO}.tar.xz \ - && tar -xJvf node-${NODE_VERSION}-${NODE_DISTRO}.tar.xz -C /usr/local/lib/nodejs \ - && rm node-${NODE_VERSION}-${NODE_DISTRO}.tar.xz -ENV PATH=/usr/local/lib/nodejs/node-$NODE_VERSION-$NODE_DISTRO/bin:$PATH - -COPY ./docker/requirements-jupyter.txt ./ - -# Use `python -m pip` to avoid using an old script wrapper. -RUN python -m pip install --no-cache-dir --upgrade pip setuptools wheel \ - && python -m pip install --no-cache-dir -r requirements-jupyter.txt - -# Install Jupyter Extensions -RUN jupyter labextension install dask-labextension \ - && jupyter serverextension enable dask_labextension - -# Supporting GDS - -ARG GDS_VER=0.9.0 -ARG MLNX_OFED_VER=5.1-2.5.8.0 - -COPY ./temp/gds/tools/README /usr/local/cuda/gds/ -COPY ./temp/gds/samples/ /usr/local/cuda/gds/samples/ -COPY ./temp/gds/tools/ /usr/local/cuda/gds/tools/ -COPY ./temp/gds/lib64/cufile.h /usr/local/cuda/lib64/cufile.h -COPY ./temp/gds/lib64/libcufile.so.${GDS_VER} /usr/local/cuda/lib64/libcufile.so.${GDS_VER} -COPY ./temp/gds/lib64/libcufile_rdma.so.${GDS_VER} /usr/local/cuda/lib64/libcufile_rdma.so.${GDS_VER} - -# Somehow libcufile.so.0 and libcufile_rdma.so.0 are auto-generated during the copy - #&& ln -s libcufile.so.${GDS_VER} /usr/local/cuda/lib64/libcufile.so.0 \ - #&& ln -s libcufile_rdma.so.${GDS_VER} /usr/local/cuda/lib64/libcufile_rdma.so.0 -RUN ln -sfn /usr/local/cuda/gds /usr/local/gds \ - && ln -s libcufile.so.${GDS_VER} /usr/local/cuda/lib64/libcufile.so \ - && ln -s libcufile_rdma.so.${GDS_VER} /usr/local/cuda/lib64/libcufile_rdma.so - -# dpkg: dependency problems prevent configuration of mlnx-iproute2: -# mlnx-iproute2 depends on libcap2 (>= 1:2.10); however: -# Package libcap2 is not installed. -# -# liburcu-bp.so.6 => not found -# liburcu-cds.so.6 => not found -# libjsoncpp.so.1 => not found -RUN apt-get update \ - && apt-get install --yes --fix-missing --no-install-recommends \ - libcap2 \ - liburcu-dev \ - libjsoncpp-dev \ - && wget http://content.mellanox.com/ofed/MLNX_OFED-${MLNX_OFED_VER}/MLNX_OFED_LINUX-${MLNX_OFED_VER}-ubuntu${UBUNTU_VER}-x86_64.tgz \ - && tar -xzvf MLNX_OFED_LINUX-${MLNX_OFED_VER}-ubuntu${UBUNTU_VER}-x86_64.tgz \ - && MLNX_OFED_LINUX-${MLNX_OFED_VER}-ubuntu${UBUNTU_VER}-x86_64/mlnxofedinstall --user-space-only --without-fw-update --all -q --force \ - && rm -rf MLNX_OFED_LINUX* \ - && apt-get autoremove -y \ - && rm -rf /var/lib/apt/lists/* \ - && rm -rf /var/cache/apt/archives/partial/* - -# Installation of MLNX_OFED would install python2, overwriting /usr/bin/python -RUN ln -sf python3 /usr/bin/python \ - && ln -sf pip3 /usr/bin/pip - -COPY ./docker/cufile.json /etc/cufile.json -RUN sed -i 's/"allow_compat_mode": false,/"allow_compat_mode": true,/' /etc/cufile.json \ - && echo "/usr/local/gds/lib/" > /etc/ld.so.conf.d/cufile.conf \ - && ldconfig - -ENV LD_LIBRARY_PATH=/usr/local/cuda/lib64:/usr/local/cuda/nvvm/lib64 - -ENTRYPOINT ["/bin/bash"] diff --git a/docker/Dockerfile-jupyter-gds-dev b/docker/Dockerfile-jupyter-gds-dev deleted file mode 100644 index cc7e2ef2c..000000000 --- a/docker/Dockerfile-jupyter-gds-dev +++ /dev/null @@ -1,160 +0,0 @@ -# -# Copyright (c) 2020-2021, NVIDIA CORPORATION. -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# -ARG UBUNTU_VER=18.04 -FROM nvidia/cuda:11.0-devel-ubuntu${UBUNTU_VER} - -ARG UBUNTU_VER=18.04 -ARG NODE_VERSION=v14.13.1 -ARG NODE_DISTRO=linux-x64 -ENV UBUNTU_VER=${UBUNTU_VER} - -ENV LC_ALL=C.UTF-8 -ENV LANG=C.UTF-8 -ENV DEBIAN_FRONTEND=noninteractive - -# Download and install Python3 PIP. -RUN apt-get update --yes \ - && apt-get upgrade --yes \ - && apt-get install --yes --fix-missing --no-install-recommends \ - ca-certificates \ - python3-minimal \ - python3-pip \ - && rm -rf /var/lib/apt/lists/* - -RUN python3 --version - -# Set additional environment values that make usage more pleasant. -ENV TERM=xterm-256color - -# Make /usr/bin/python point to the ${VERSION_PYTHON3} version of python -RUN VERSION_PYTHON3=$(python3 --version | cut -c8-) && VERSION_PYTHON3=${VERSION_PYTHON3%.*} \ - && rm -f /usr/bin/python \ - && rm -f /usr/bin/python`echo ${VERSION_PYTHON3} | cut -c1-1` \ - && ln -s /usr/bin/python${VERSION_PYTHON3} /usr/bin/python \ - && ln -s /usr/bin/python${VERSION_PYTHON3} /usr/bin/python`echo ${VERSION_PYTHON3} | cut -c1-1` - -# Make /usr/bin/pip point to the ${VERSION_PIP3} version of python -RUN rm -f /usr/bin/pip \ - && ln -s /usr/bin/pip3 /usr/bin/pip - -# libgl1 is needed for opencv at `cucim convert` CLI command. -RUN apt-get update \ - && apt-get install -y --no-install-recommends \ - python3-dev \ - gcc \ - g++ \ - libopenslide-dev \ - libsm6 \ - libxext6 \ - libxrender-dev \ - libglib2.0-0 \ - libgl1 \ - wget \ - git \ - xz-utils \ - && apt-get autoremove -y \ - && rm -rf /var/lib/apt/lists/* \ - && rm -rf /var/cache/apt/archives/partial/* - -WORKDIR /workspace -ENV HOME=/workspace - -# Install nodejs -RUN mkdir -p /usr/local/lib/nodejs \ - && wget https://nodejs.org/dist/$NODE_VERSION/node-${NODE_VERSION}-${NODE_DISTRO}.tar.xz \ - && tar -xJvf node-${NODE_VERSION}-${NODE_DISTRO}.tar.xz -C /usr/local/lib/nodejs \ - && rm node-${NODE_VERSION}-${NODE_DISTRO}.tar.xz -ENV PATH=/usr/local/lib/nodejs/node-$NODE_VERSION-$NODE_DISTRO/bin:$PATH - -COPY docker/requirements-jupyter-dev.txt ./ - -# Use `python -m pip` to avoid using an old script wrapper. -RUN python -m pip install --no-cache-dir --upgrade pip setuptools wheel \ - && python -m pip install --no-cache-dir -r requirements-jupyter-dev.txt - -# Install Jupyter Extensions -RUN jupyter labextension install dask-labextension \ - && jupyter serverextension enable dask_labextension - -RUN apt-get update \ - && apt-get install -y --no-install-recommends \ - curl \ -# build-essential \ -# cmake \ -# git \ -# zlib1g-dev \ -# libssl-dev \ - && apt-get autoremove -y \ - && rm -rf /var/lib/apt/lists/* - -# Download TRITON Client -ARG TRITON_CLIENTS_URL=https://github.com/triton-inference-server/server/releases/download/v2.5.0/v2.5.0_ubuntu1804.clients.tar.gz -RUN mkdir -p /opt/nvidia/triton-clients \ - && curl -L ${TRITON_CLIENTS_URL} | tar xvz -C /opt/nvidia/triton-clients - -RUN pip install --no-cache-dir \ - /opt/nvidia/triton-clients/python/*manylinux1_x86_64.whl - -# Supporting GDS - -ARG GDS_VER=0.9.0 -ARG MLNX_OFED_VER=5.1-2.5.8.0 - -COPY ./temp/gds/tools/README /usr/local/cuda/gds/ -COPY ./temp/gds/samples/ /usr/local/cuda/gds/samples/ -COPY ./temp/gds/tools/ /usr/local/cuda/gds/tools/ -COPY ./temp/gds/lib64/cufile.h /usr/local/cuda/lib64/cufile.h -COPY ./temp/gds/lib64/libcufile.so.${GDS_VER} /usr/local/cuda/lib64/libcufile.so.${GDS_VER} -COPY ./temp/gds/lib64/libcufile_rdma.so.${GDS_VER} /usr/local/cuda/lib64/libcufile_rdma.so.${GDS_VER} - -# Somehow libcufile.so.0 and libcufile_rdma.so.0 are auto-generated during the copy - #&& ln -s libcufile.so.${GDS_VER} /usr/local/cuda/lib64/libcufile.so.0 \ - #&& ln -s libcufile_rdma.so.${GDS_VER} /usr/local/cuda/lib64/libcufile_rdma.so.0 -RUN ln -sfn /usr/local/cuda/gds /usr/local/gds \ - && ln -s libcufile.so.${GDS_VER} /usr/local/cuda/lib64/libcufile.so \ - && ln -s libcufile_rdma.so.${GDS_VER} /usr/local/cuda/lib64/libcufile_rdma.so - -# dpkg: dependency problems prevent configuration of mlnx-iproute2: -# mlnx-iproute2 depends on libcap2 (>= 1:2.10); however: -# Package libcap2 is not installed. -# -# liburcu-bp.so.6 => not found -# liburcu-cds.so.6 => not found -# libjsoncpp.so.1 => not found -RUN apt-get update \ - && apt-get install --yes --fix-missing --no-install-recommends \ - libcap2 \ - liburcu-dev \ - libjsoncpp-dev \ - && wget http://content.mellanox.com/ofed/MLNX_OFED-${MLNX_OFED_VER}/MLNX_OFED_LINUX-${MLNX_OFED_VER}-ubuntu${UBUNTU_VER}-x86_64.tgz \ - && tar -xzvf MLNX_OFED_LINUX-${MLNX_OFED_VER}-ubuntu${UBUNTU_VER}-x86_64.tgz \ - && MLNX_OFED_LINUX-${MLNX_OFED_VER}-ubuntu${UBUNTU_VER}-x86_64/mlnxofedinstall --user-space-only --without-fw-update --all -q --force \ - && rm -rf MLNX_OFED_LINUX* \ - && apt-get autoremove -y \ - && rm -rf /var/lib/apt/lists/* \ - && rm -rf /var/cache/apt/archives/partial/* - -# Installation of MLNX_OFED would install python2, overwriting /usr/bin/python -RUN ln -sf python3 /usr/bin/python \ - && ln -sf pip3 /usr/bin/pip - -COPY ./docker/cufile.json /etc/cufile.json -RUN sed -i 's/"allow_compat_mode": false,/"allow_compat_mode": true,/' /etc/cufile.json \ - && echo "/usr/local/gds/lib/" > /etc/ld.so.conf.d/cufile.conf \ - && ldconfig - -ENV LD_LIBRARY_PATH=/usr/local/cuda/lib64:/usr/local/cuda/nvvm/lib64 - -ENTRYPOINT ["/bin/bash"] diff --git a/docker/cufile.json b/docker/cufile.json deleted file mode 100644 index 55930426c..000000000 --- a/docker/cufile.json +++ /dev/null @@ -1,81 +0,0 @@ -{ - // NOTE : Application can override custom configuration via export CUFILE_ENV_PATH_JSON= - // e.g : export CUFILE_ENV_PATH_JSON="/home//cufile.json" - - "logging": { - // log directory, if not enabled will create log file under current working directory - //"dir": "/home/", - - // ERROR|WARN|INFO|DEBUG|TRACE (in decreasing order of priority) - "level": "ERROR" - }, - - "profile": { - // nvtx profiling on/off - "nvtx": false, - // cufile stats level(0-3) - "cufile_stats": 0 - }, - - "properties": { - // max IO chunk size (parameter should be 4K aligned) used by cuFileRead/Write internally per IO request - "max_direct_io_size_kb" : 16384, - // device memory size (parameter should be 4K aligned) for reserving bounce buffers for the entire GPU - "max_device_cache_size_kb" : 131072, - // limit on maximum device memory size (parameter should be 4K aligned) that can be pinned for a given process - "max_device_pinned_mem_size_kb" : 33554432, - // true or false (true will enable asynchronous io submission to nvidia-fs driver) - // Note : currently the overall IO will still be synchronous - "use_poll_mode" : false, - // maximum IO request size (parameter should be 4K aligned) within or equal to which library will use polling for IO completion - "poll_mode_max_size_kb": 4, - // allow compat mode, this will enable use of cuFile posix read/writes - "allow_compat_mode": false, - // client-side rdma addr list for user-space file-systems(e.g ["10.0.1.0", "10.0.2.0"]) - "rdma_dev_addr_list": [ ], - // load balancing policy for RDMA memory registration(MR), (RoundRobin, RoundRobinMaxMin) - // In RoundRobin, MRs will be distributed uniformly across NICS closest to a GPU - // In RoundRobinMaxMin, MRs will be distributed across NICS closest to a GPU - // with minimal sharing of NICS acros GPUS - "rdma_load_balancing_policy": "RoundRobin" - }, - - "fs": { - "generic": { - - // for unaligned writes, setting it to true will, cuFileWrite use posix write internally instead of regular GDS write - "posix_unaligned_writes" : false - }, - - "lustre": { - - // IO threshold for read/write (param should be 4K aligned)) equal to or below which cuFile will use posix read/write - "posix_gds_min_kb" : 0 - }, - - "weka": { - - // enable/disable RDMA write - "rdma_write_support" : false - } - }, - - "denylist": { - // specify list of vendor driver modules to deny for nvidia-fs (e.g. ["nvme" , "nvme_rdma"]) - "drivers": [ ], - - // specify list of block devices to prevent IO using cuFile (e.g. [ "/dev/nvme0n1" ]) - "devices": [ ], - - // specify list of mount points to prevent IO using cuFile (e.g. ["/mnt/test"]) - "mounts": [ ], - - // specify list of file-systems to prevent IO using cuFile (e.g ["lustre", "wekafs"]) - "filesystems": [ ] - }, - - "miscellaneous": { - // enable only for enforcing strict checks at API level for debugging - "api_check_aggressive": false - } -} diff --git a/docker/requirements-claratrain.txt b/docker/requirements-claratrain.txt deleted file mode 100644 index 485192268..000000000 --- a/docker/requirements-claratrain.txt +++ /dev/null @@ -1,10 +0,0 @@ -openslide-python==1.1.2 -tifffile>=2022.7.28 -itk==5.1.2 -dask[array,delayed,distributed]==2021.2.0 -dask-cuda==0.17.0 -zarr==2.6.1 -fsspec==0.8.5 -numpy # 1.17.3 already exists in the image -opencv-contrib-python==4.5.1.48 -imagecodecs>=2021.6.8 diff --git a/docker/requirements-jupyter-dev.txt b/docker/requirements-jupyter-dev.txt deleted file mode 100644 index 57a46ed8b..000000000 --- a/docker/requirements-jupyter-dev.txt +++ /dev/null @@ -1,20 +0,0 @@ -openslide-python==1.1.2 -tifffile>=2022.7.28 -itk==5.1.2 -dask[array,delayed,distributed]==2021.2.0 -dask-cuda==0.17.0 -zarr==2.6.1 -fsspec==0.8.5 -numpy==1.19.5 -opencv-contrib-python==4.5.1.48 -imagecodecs>=2021.6.8 -cupy-cuda110==8.4.0 -jupyterlab==3.0.7 -dask_labextension==5.0.0 -cmake>=3.18 ---extra-index-url https://developer.download.nvidia.com/compute/redist -nvidia-dali-cuda110 ---find-links https://download.pytorch.org/whl/torch_stable.html -torch==1.7.1+cu110 -torchvision==0.8.2+cu110 -torchaudio===0.7.2 diff --git a/docker/requirements-jupyter.txt b/docker/requirements-jupyter.txt deleted file mode 100644 index 57a46ed8b..000000000 --- a/docker/requirements-jupyter.txt +++ /dev/null @@ -1,20 +0,0 @@ -openslide-python==1.1.2 -tifffile>=2022.7.28 -itk==5.1.2 -dask[array,delayed,distributed]==2021.2.0 -dask-cuda==0.17.0 -zarr==2.6.1 -fsspec==0.8.5 -numpy==1.19.5 -opencv-contrib-python==4.5.1.48 -imagecodecs>=2021.6.8 -cupy-cuda110==8.4.0 -jupyterlab==3.0.7 -dask_labextension==5.0.0 -cmake>=3.18 ---extra-index-url https://developer.download.nvidia.com/compute/redist -nvidia-dali-cuda110 ---find-links https://download.pytorch.org/whl/torch_stable.html -torch==1.7.1+cu110 -torchvision==0.8.2+cu110 -torchaudio===0.7.2 diff --git a/run b/run index b66c2f75a..d0a7df295 100755 --- a/run +++ b/run @@ -262,24 +262,6 @@ is_x86_64() { # Section: Build #================================================================================== -build_manylinux2014_desc() { echo 'Build manylinux2014 image - -Arguments: - $1 - cuda version (e.g., 110, 111) -' -} -build_manylinux2014() { - local cuda_version="${1:-110}" - run_command docker build -f ${TOP}/Dockerfile-cuda${cuda_version} -t gigony/manylinux2014-x64:cuda${cuda_version} ${TOP} - - read -n 1 -r -p "$(c_str R "Do you want to update dockcross-manylinux2014-x64 with " G "cuda${cuda_version}" R " (y/n)?")" - echo - if [[ $REPLY =~ ^[Yy]$ ]]; then - sed -i -e "s/manylinux2014-x64:cuda.../manylinux2014-x64:cuda${cuda_version}/g" ${TOP}/dockcross-manylinux2014-x64 - c_echo W "Done" - fi -} - build_local_libcucim_() { local source_folder=${1:-${TOP}} local build_type=${2:-debug} diff --git a/run_gds.sh b/run_gds.sh deleted file mode 100644 index 74b0d235a..000000000 --- a/run_gds.sh +++ /dev/null @@ -1,51 +0,0 @@ -#!/bin/bash -# -# Copyright (c) 2020-2021, NVIDIA CORPORATION. -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# - -MNT_PATH=/nvme -GDS_IMAGE=cucim-gds - -BUILD_VER=`uname -r` -NV_DRIVER=`nvidia-smi -q -i 0 | sed -n 's/Driver Version.*: *\(.*\) *$/\1/p'` -echo "using nvidia driver version $NV_DRIVER on kernel $BUILD_VER" - - -ofed_version=$(ofed_info -s | grep MLNX) -if [ $? -eq 0 ]; then - rdma_core=$(dpkg -s libibverbs-dev | grep "Source: rdma-core") - if [ $? -eq 0 ]; then - CONFIG_MOFED_VERSION=$(echo $ofed_version | cut -d '-' -f 2) - echo "Found MOFED version $CONFIG_MOFED_VERSION" - fi - MLNX_SRCS="--volume /usr/src/mlnx-ofed-kernel-${CONFIG_MOFED_VERSION}:/usr/src/mlnx-ofed-kernel-${CONFIG_MOFED_VERSION}:ro" - MOFED_DEVS="--net=host --volume /sys/class/infiniband_verbs:/sys/class/infiniband_verbs/ " -fi - -docker run \ - --ipc host \ - -it - --rm - --gpus all \ - --volume /run/udev:/run/udev:ro \ - --volume /sys/kernel/config:/sys/kernel/config/ \ - --volume /usr/src/nvidia-$NV_DRIVER:/usr/src/nvidia-$NV_DRIVER:ro ${MLNX_SRCS}\ - --volume /dev:/dev:ro \ - --privileged \ - --env NV_DRIVER=${NV_DRIVER} \ - --volume /lib/modules/$BUILD_VER/:/lib/modules/$BUILD_VER \ - --volume "${MNT_PATH}/data:/data:rw" \ - --volume "${MNT_PATH}/results:/results:rw" ${MOFED_DEVS} \ - -itd ${REPO_URI}/${GDS_IMAGE} \ - /bin/bash diff --git a/scripts/auditwheel_repair.py b/scripts/auditwheel_repair.py deleted file mode 100644 index 2581ea977..000000000 --- a/scripts/auditwheel_repair.py +++ /dev/null @@ -1,104 +0,0 @@ -# Apache License, Version 2.0 -# Copyright 2020 NVIDIA Corporation -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import functools -import re -from unittest.mock import patch - -import auditwheel.elfutils -from auditwheel.main import main -from auditwheel.wheeltools import InWheelCtx - -# How auditwheel repair works? -# -# From https://github.com/pypa/auditwheel/blob/3.2.0/auditwheel/wheel_abi.py#L38 -# 1) Find a Python extension libraries(.so) -# if so ==> A, else ==> B -# 2) `needed_libs` <== external libraries needed by A and B -# 3) From b in B, -# if b is not in `needed_libs`, b is added to A -# 4) Only external libraries in A are patched to use locally copied .so files -# - external libraries that exists under wheel path are discarded -# - https://github.com/pypa/auditwheel/blob/3.2.0/auditwheel/policy/external_references.py#L61 -# - https://github.com/pypa/auditwheel/blob/3.2.0/auditwheel/repair.py#L62 -# -# With current implementation, -# - `cucim/_cucim.cpython-XX-x86_64-linux-gnu.so` files are in A by 1) -# - `cucim/libcucim.so.??` is in B by 1) -# - `cucim/libcucim.so.??` and `libcudart.so.11.0` are in `needed_libs` by 2) -# - `cucim/cucim.kit.cuslide@??.??.??.so` is in A by 3) -# -# And only libz and libcudart are considered as external libraries. - -# To work with cuCIM, we need to -# 1) make `cucim/libcucim.so.??` as Python extension library -# - Patch elf_is_python_extension : https://github.com/pypa/auditwheel/blob/3.2.0/auditwheel/elfutils.py#L81 -# 2) control how to copy external libraries -# - Patch copylib: https://github.com/pypa/auditwheel/blob/3.2.0/auditwheel/repair.py#L108 -# - Need for libnvjpeg library -# 3) preprocess wheel metadata -# - patch InWheelCtx.__enter__ : https://github.com/pypa/auditwheel/blob/3.2.0/auditwheel/wheeltools.py#L158 -# - `Root-Is-Purelib: true` -> `Root-Is-Purelib: false` from WHEEL file - - -# Parameters -PYTHON_EXTENSION_LIBRARIES = [r"cucim/libcucim\.so\.\d{1,2}"] - -# 1) auditwheel.elfutils.elf_is_python_extension replacement -orig_elf_is_python_extension = auditwheel.elfutils.elf_is_python_extension - - -@functools.wraps(orig_elf_is_python_extension) -def elf_is_python_extension(fn, elf): - if any(map(lambda x: re.fullmatch(x, fn), PYTHON_EXTENSION_LIBRARIES)): - print("[cuCIM] Consider {} as a python extension.".format(fn)) - return True, 3 - return orig_elf_is_python_extension(fn, elf) - - -# 3) auditwheel.wheeltools.InWheelCtx.__enter__ replacement -orig_inwheelctx_enter = InWheelCtx.__enter__ - - -@functools.wraps(orig_inwheelctx_enter) -def inwheelctx_enter(self): - rtn = orig_inwheelctx_enter(self) - - # `self.path` is a path that extracted files from the wheel file exists - - # base_dir = glob.glob(join(self.path, '*.dist-info')) - # wheel_path = join(base_dir[0], 'WHEEL') - # with open(wheel_path, 'r') as f: - # wheel_text = f.read() - - # wheel_text = wheel_text.replace('Root-Is-Purelib: true', 'Root-Is-Purelib: false') # noqa: E501 - - # with open(wheel_path, 'w') as f: - # f.write(wheel_text) - - return rtn - - -# # sys.argv replacement -# testargs = ["auditwheel_repair.py", "repair", "--plat", "manylinux2014_x86_64", "-w", "wherehouse", "cuclara_image-0.1.1-py3-none-manylinux2014_x86_64.whl"] # noqa: E501 -# with patch.object(sys, 'argv', testargs): - -if __name__ == "__main__": - # Patch - with patch.object( - auditwheel.elfutils, "elf_is_python_extension", elf_is_python_extension - ): - with patch.object(InWheelCtx, "__enter__", inwheelctx_enter): - main() diff --git a/scripts/run-dist b/scripts/run-dist deleted file mode 100755 index 60ed771e0..000000000 --- a/scripts/run-dist +++ /dev/null @@ -1,500 +0,0 @@ -#!/bin/bash -# -# Copyright (c) 2020, NVIDIA CORPORATION. -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# - -init_globals() { - if [ "$0" != "/bin/bash" ]; then - SCRIPT_DIR=$(dirname "$(readlink -f "$0")") - export RUN_SCRIPT_FILE="$(readlink -f "$0")" - else - export RUN_SCRIPT_FILE="$(readlink -f "${BASH_SOURCE[0]}")" - fi - - export TOP=$(dirname "${RUN_SCRIPT_FILE}") -} - -################################################################################ -# Utility functions -################################################################################ - -####################################### -# Get list of available commands from a given input file. -# -# Available commands and command summary are extracted by checking a pattern -# "_desc() { echo '". -# Section title is extracted by checking a pattern "# Section: ". -# This command is used for listing available commands in CLI. -# -# e.g.) -# "# Section: String/IO functions" -# => "# String/IO functions" -# "to_lower_desc() { echo 'Convert to lower case" -# => "to_lower ----------------- Convert to lower case" -# -# Arguments: -# $1 - input file that defines commands -# Returns: -# Print list of available commands from $1 -####################################### -get_list_of_available_commands() { - local file_name="$1" - if [ ! -e "$1" ]; then - echo "$1 doesn't exist!" - fi - - local line_str='--------------------------------' - local IFS= cmd_lines="$(IFS= cat "$1" | grep -E -e "^(([[:alpha:]_[:digit:]]+)_desc\(\)|# Section: )" | sed "s/_desc() *{ *echo '/ : /")" - local line - while IFS= read -r line; do - local cmd=$(echo "$line" | cut -d":" -f1) - local desc=$(echo "$line" | cut -d":" -f2-) - if [ "$cmd" = "# Section" ]; then - c_echo B "${desc}" - else - # there is no substring operation in 'sh' so use 'cut' - local dash_line="$(echo "${line_str}" | cut -c ${#cmd}-)" # = "${line_str:${#cmd}}" - c_echo Y " ${cmd}" w " ${dash_line} ${desc}" - fi - # use <&2 echo "$@" -} - -c_echo_err() { - >&2 c_echo "$@" -} - -printf_err() { - >&2 printf "$@" -} - -get_item_ranges() { - local indexes="$1" - local list="$2" - echo -n "$(echo "${list}" | xargs | cut -d " " -f "${indexes}")" - return $? -} - -get_unused_ports() { - local num_of_ports=${1:-1} - local start=${2:-49152} - local end=${3:-61000} - comm -23 \ - <(seq ${start} ${end} | sort) \ - <(ss -tan | awk '{print $4}' | while read line; do echo ${line##*\:}; done | grep '[0-9]\{1,5\}' | sort -u) \ - | shuf | tail -n ${num_of_ports} # use tail instead head to avoid broken pipe in VSCode terminal -} - -newline() { - echo -} - -info() { - c_echo W "$(date -u '+%Y-%m-%d %H:%M:%S') [INFO] " Z "$@" -} - -error() { - echo R "$(date -u '+%Y-%m-%d %H:%M:%S') [ERROR] " Z "$@" -} - -fatal() { - echo R "$(date -u '+%Y-%m-%d %H:%M:%S') [FATAL] " Z "$@" - echo - if [ -n "${SCRIPT_DIR}" ]; then - exit 1 - fi -} - -run_command() { - local status=0 - local cmd="$*" - - c_echo B "$(date -u '+%Y-%m-%d %H:%M:%S') " W "\$ " G "${cmd}" - - [ "$(echo -n "$@")" = "" ] && return 1 # return 1 if there is no command available - - "$@" - status=$? - - unset IFS - - return $status -} - -retry() { - local retries=$1 - shift - - local count=0 - until run_command "$@"; do - exit=$? - wait=$((2 ** count)) - count=$((count + 1)) - if [ $count -lt $retries ]; then - info "Retry $count/$retries. Exit code=$exit, Retrying in $wait seconds..." - sleep $wait - else - fatal "Retry $count/$retries. Exit code=$exit, no more retries left." - return 1 - fi - done - return 0 -} - -#================================================================================== -# Section: Example -#================================================================================== - -download_testdata_desc() { echo 'Download test data from Docker Hub -' -} -download_testdata() { - c_echo W "Downloading test data..." - run_command mkdir -p ${TOP}/notebooks/input - if [ ! -e ${TOP}/notebooks/input/README.md ]; then - run_command rm -rf ${TOP}/notebooks/input - id=$(docker create gigony/svs-testdata:little-big) - run_command docker cp $id:/input ${TOP}/notebooks - run_command docker rm -v $id - c_echo G "Test data is downloaded to ${TOP}/notebooks/input!" - else - c_echo G "Test data already exists at ${TOP}/notebooks/input!" - fi -} - -copy_gds_files_() { - [ ! -d /usr/local/cuda/gds ] && c_echo_err R "GDS is not available at /usr/local/cuda/gds !" && return 1 - - rm -rf ${TOP}/temp/gds - mkdir -p ${TOP}/temp/gds/lib64 - cp -P -r /usr/local/cuda/gds/* ${TOP}/temp/gds/ - cp -P /usr/local/cuda/lib64/cufile.h /usr/local/cuda/lib64/libcufile* ${TOP}/temp/gds/lib64/ -} - -launch_notebooks_desc() { echo 'Launch jupyter notebooks - -Arguments: - -p - port number - -h - hostname to serve documentation on (default: 0.0.0.0) - -g - launch GDS-enabled container -' -} -launch_notebooks() { - local OPTIND - local port=$(get_unused_ports 1 10000 10030) - local host='0.0.0.0' - local gds_postfix='' - local gds_nvme_path='' - - while getopts 'p:h:g:' option; - do - case "${option}" in - p) - port="$OPTARG" - ;; - h) - host="$OPTARG" - ;; - g) - gds_postfix='-gds' - echo "# OPTARG:$OPTARG" - [ -z "$OPTARG" ] && c_echo_err R "Please specify NVMe path!" && return 1 - gds_nvme_path=$(readlink -f "$OPTARG") - [ ! -d "$gds_nvme_path" ] && c_echo_err R "Folder $gds_nvme_path doesn't exist!" && return 1 - - # Copy cufile SDK from host system to temp/gds - copy_gds_files_ - ;; - *) - return 1 - esac - done - - download_testdata - - run_command cp ${TOP}/*.whl ${TOP}/notebooks - - run_command docker build --runtime nvidia -t cucim-jupyter${gds_postfix} -f ${TOP}/docker/Dockerfile-jupyter${gds_postfix} ${TOP} - - [ $? -ne 0 ] && return 1 - - c_echo W "Port " G "$port" W " would be used...(" B "http://$(hostname -I | cut -d' ' -f 1):${port}" W ")" - - if [ -z "${gds_postfix}" ]; then - run_command docker run --runtime nvidia --gpus all -it --rm \ - -v ${TOP}/notebooks:/notebooks \ - -p ${port}:${port} \ - cucim-jupyter \ - -c "echo -n 'Enter New Password: '; jupyter lab --ServerApp.password=\"\$(python3 -u -c \"from jupyter_server.auth import passwd;pw=input();print(passwd(pw));\" | egrep 'sha|argon')\" --ServerApp.root_dir=/notebooks --allow-root --port=${port} --ip=${host} --no-browser" - else - local MNT_PATH=/nvme - local GDS_IMAGE=cucim-jupyter${gds_postfix} - - local BUILD_VER=`uname -r` - local NV_DRIVER=`nvidia-smi -q -i 0 | sed -n 's/Driver Version.*: *\(.*\) *$/\1/p'` - echo "using nvidia driver version $NV_DRIVER on kernel $BUILD_VER" - - local ofed_version=$(ofed_info -s | grep MLNX) - if [ $? -eq 0 ]; then - local rdma_core=$(dpkg -s libibverbs-dev | grep "Source: rdma-core") - if [ $? -eq 0 ]; then - local CONFIG_MOFED_VERSION=$(echo $ofed_version | cut -d '-' -f 2) - echo "Found MOFED version $CONFIG_MOFED_VERSION" - fi - local MLNX_SRCS="--volume /usr/src/mlnx-ofed-kernel-${CONFIG_MOFED_VERSION}:/usr/src/mlnx-ofed-kernel-${CONFIG_MOFED_VERSION}:ro" - local MOFED_DEVS="--net=host --volume /sys/class/infiniband_verbs:/sys/class/infiniband_verbs/ " - fi - - docker run \ - --ipc host \ - -it \ - --rm \ - --gpus all \ - -v ${TOP}/notebooks:/notebooks \ - -p ${port}:${port} \ - --volume /run/udev:/run/udev:ro \ - --volume /sys/kernel/config:/sys/kernel/config/ \ - --volume /usr/src/nvidia-$NV_DRIVER:/usr/src/nvidia-$NV_DRIVER:ro ${MLNX_SRCS}\ - --volume /dev:/dev:ro \ - --privileged \ - --env NV_DRIVER=${NV_DRIVER} \ - --volume /lib/modules/$BUILD_VER/:/lib/modules/$BUILD_VER \ - --volume "${MNT_PATH}:/notebooks/nvme:rw" \ - ${MOFED_DEVS} \ - ${GDS_IMAGE} \ - -c "echo -n 'Enter New Password: '; jupyter lab --ServerApp.password=\"\$(python3 -u -c \"from jupyter_server.auth import passwd;pw=input();print(passwd(pw));\" | egrep 'sha|argon')\" --ServerApp.root_dir=/notebooks --allow-root --port=${port} --ip=${host} --no-browser" - fi -} - -#================================================================================== -# Section: Build -#================================================================================== - -build_train() { - local image_name=${1:-cucim-train} - run_command docker build -t ${image_name} -f ${TOP}/docker/Dockerfile-claratrain ${TOP}/docker -} - -build_train_desc() { echo 'Build Clara Train Docker image with cuCIM (& OpenSlide) - -Build image from docker/Dockerfile-claratrain - -Arguments: - $1 - docker image name (default:cucim-train) -' -} -build_train() { - local image_name=${1:-cucim-train} - run_command docker build -t ${image_name} -f ${TOP}/docker/Dockerfile-claratrain ${TOP} -} - -build_examples_desc() { echo 'Build cuCIM C++ examples -' -} -build_examples() { - local image_name=cucim-cmake - run_command docker build -t ${image_name} -f ${TOP}/docker/Dockerfile-cmake ${TOP}/docker - run_command docker run -it --rm \ - -v ${TOP}:/workspace \ - ${image_name} \ - -c " - mkdir -p /workspace/examples/cpp/build; - rm -rf /workspace/examples/cpp/build/*; - cd /workspace/examples/cpp/build; - cmake .. && make" - c_echo W "Copying binary files to ${TOP}/bin folder..." - run_command mkdir -p ${TOP}/bin - run_command cp ${TOP}/examples/cpp/build/bin/* ${TOP}/bin - - download_testdata - - c_echo W "Execute the binary with the following commands:" - c_echo " # Set library path" - c_echo B " export LD_LIBRARY_PATH=${TOP}/install/lib:\$LD_LIBRARY_PATH" - c_echo " # Execute" - c_echo B " ./bin/tiff_image notebooks/input/image.tif ." -} - -parse_args() { - local OPTIND - while getopts 'yh' option; - do - case "${option}" in - y) - ALWAYS_YES=true; - ;; - h) - print_usage - exit 1 - ;; - *) - ;; - esac - done - shift $((OPTIND-1)) - - CMD="$1" - shift - - ARGS=("$@") -} - -print_usage() { - set +x - echo_err - echo_err "USAGE: $0 [command] [arguments]..." - echo_err "" - c_echo_err W "Global Arguments" - echo_err - c_echo_err W "Command List" - c_echo_err Y " help " w "---------------------------- Print detailed description for a given argument (command name)" - echo_err "$(get_list_of_available_commands "${RUN_SCRIPT_FILE}" | my_cat_prefix " ")" - echo_err -} - -print_cmd_help_messages() { - local cmd="$1" - if [ -n "${cmd}" ]; then - if type ${cmd}_desc > /dev/null 2>&1; then - ${cmd}_desc - exit 0 - else - c_echo_err R "Command '${cmd}' doesn't exist!" - exit 1 - fi - fi - print_usage - return 0 -} - -main() { - local ret=0 - parse_args "$@" - - case "$CMD" in - help) - print_cmd_help_messages "${ARGS[@]}" - exit 0 - ;; - build) - build_examples "${ARGS[@]}" - ;; - notebooks) - launch_notebooks "${ARGS[@]}" - ;; - ''|main) - print_usage - ;; - *) - if type ${CMD} > /dev/null 2>&1; then - "$CMD" "${ARGS[@]}" - else - print_usage - exit 1 - fi - ;; - esac - ret=$? - if [ -n "${SCRIPT_DIR}" ]; then - exit $ret - fi -} - -init_globals - -if [ -n "${SCRIPT_DIR}" ]; then - main "$@" -fi From b47c623aeecfc982e37f0816ab605cf0f5a9f2c2 Mon Sep 17 00:00:00 2001 From: jakirkham Date: Thu, 14 Dec 2023 12:21:20 -0800 Subject: [PATCH 09/21] Relax `openslide` pin (#653) This was pinned to avoid `openslide` version `4` ( https://github.com/rapidsai/cucim/pull/650 ) due to some solver issues that cropped up with it. Namely it was missing `libdicom` (and an associated version constraint). However this has been fixed upstream by adding... * A repodata patch ( https://github.com/conda-forge/conda-forge-repodata-patches-feedstock/pull/613 ) * A `run_exports` to `libdicom` (was missing) ( https://github.com/conda-forge/libdicom-feedstock/pull/5 ) * A `libdicom` associated test in `openslide` ( https://github.com/conda-forge/openslide-feedstock/pull/25 ) Given this we should be able to relax the pin here, which this does. Authors: - https://github.com/jakirkham Approvers: - AJ Schmidt (https://github.com/ajschmidt8) - Gigon Bae (https://github.com/gigony) URL: https://github.com/rapidsai/cucim/pull/653 --- conda/environments/all_cuda-118_arch-x86_64.yaml | 2 +- conda/environments/all_cuda-120_arch-x86_64.yaml | 2 +- conda/recipes/cucim/meta.yaml | 3 ++- conda/recipes/libcucim/meta.yaml | 6 ++++-- dependencies.yaml | 4 ++-- python/cucim/pyproject.toml | 2 +- python/cucim/requirements-test.txt | 2 +- 7 files changed, 12 insertions(+), 9 deletions(-) diff --git a/conda/environments/all_cuda-118_arch-x86_64.yaml b/conda/environments/all_cuda-118_arch-x86_64.yaml index c038513af..53e8ad163 100644 --- a/conda/environments/all_cuda-118_arch-x86_64.yaml +++ b/conda/environments/all_cuda-118_arch-x86_64.yaml @@ -30,7 +30,7 @@ dependencies: - numpy>=1.21.3 - numpydoc - nvcc_linux-64=11.8 -- openslide-python>=1.1.2 +- openslide-python>=1.3.0 - pip - pooch>=1.6.0 - pre-commit diff --git a/conda/environments/all_cuda-120_arch-x86_64.yaml b/conda/environments/all_cuda-120_arch-x86_64.yaml index 18f61bf9f..1084ea629 100644 --- a/conda/environments/all_cuda-120_arch-x86_64.yaml +++ b/conda/environments/all_cuda-120_arch-x86_64.yaml @@ -29,7 +29,7 @@ dependencies: - ninja - numpy>=1.21.3 - numpydoc -- openslide-python>=1.1.2 +- openslide-python>=1.3.0 - pip - pooch>=1.6.0 - pre-commit diff --git a/conda/recipes/cucim/meta.yaml b/conda/recipes/cucim/meta.yaml index faeb7fa9e..516cbee60 100644 --- a/conda/recipes/cucim/meta.yaml +++ b/conda/recipes/cucim/meta.yaml @@ -68,10 +68,11 @@ requirements: - cupy >=12.0.0 - lazy_loader >=0.1 - libcucim ={{ version }} - # - openslide # skipping here but benchmark binary would need openslide library - python - scikit-image >=0.19.0,<0.22.0a0 - scipy + run_constrained: + - openslide-python >=1.3.0 tests: requirements: diff --git a/conda/recipes/libcucim/meta.yaml b/conda/recipes/libcucim/meta.yaml index 7d4cf5ac8..bd591c9e2 100644 --- a/conda/recipes/libcucim/meta.yaml +++ b/conda/recipes/libcucim/meta.yaml @@ -18,6 +18,7 @@ build: string: cuda{{ cuda_major }}_{{ date_string }}_{{ GIT_DESCRIBE_HASH }}_{{ GIT_DESCRIBE_NUMBER }} ignore_run_exports: - libwebp-base + - openslide ignore_run_exports_from: {% if cuda_major == "11" %} - {{ compiler('cuda11') }} @@ -70,7 +71,7 @@ requirements: - jbig - libwebp-base - nvtx-c >=3.1.0 - - openslide <4 + - openslide - xz - zlib - zstd @@ -82,10 +83,11 @@ requirements: {% endif %} - {{ pin_compatible('libwebp-base', max_pin='x.x') }} - jbig - # - openslide # skipping here but benchmark binary would need openslide library - xz - zlib - zstd + run_constrained: + - {{ pin_compatible('openslide') }} about: home: https://developer.nvidia.com/multidimensional-image-processing diff --git a/dependencies.yaml b/dependencies.yaml index 3e7732126..1f1a3dfd3 100644 --- a/dependencies.yaml +++ b/dependencies.yaml @@ -256,7 +256,7 @@ dependencies: packages: - imagecodecs>=2021.6.8 - matplotlib-base - - openslide-python>=1.1.2 + - openslide-python>=1.3.0 - pip - pip: - opencv-python-headless>=4.6 @@ -264,7 +264,7 @@ dependencies: packages: # skip packages on arm64 that don't provide a wheel - imagecodecs>=2021.6.8; platform_machine=='x86_64' - - openslide-python>=1.1.2; platform_machine=='x86_64' + - openslide-python>=1.3.0; platform_machine=='x86_64' - matplotlib - opencv-python-headless>=4.6 - click diff --git a/python/cucim/pyproject.toml b/python/cucim/pyproject.toml index 8b4e867ca..7af428bd4 100644 --- a/python/cucim/pyproject.toml +++ b/python/cucim/pyproject.toml @@ -65,7 +65,7 @@ test = [ "imagecodecs>=2021.6.8; platform_machine=='x86_64'", "matplotlib", "opencv-python-headless>=4.6", - "openslide-python>=1.1.2; platform_machine=='x86_64'", + "openslide-python>=1.3.0; platform_machine=='x86_64'", "pooch>=1.6.0", "psutil>=5.8.0", "pytest-cov>=2.12.1", diff --git a/python/cucim/requirements-test.txt b/python/cucim/requirements-test.txt index e8324ced4..036edf6c5 100644 --- a/python/cucim/requirements-test.txt +++ b/python/cucim/requirements-test.txt @@ -1,6 +1,6 @@ GPUtil>=1.4.0 imagecodecs>=2021.6.8 -openslide-python>=1.1.2 +openslide-python>=1.3.0 opencv-python-headless>=4.6 psutil>=5.8.0 pytest>=6.2.4 From ee30868ed481463b1e1171f6ff4d2552f41de8b7 Mon Sep 17 00:00:00 2001 From: Gregory Lee Date: Thu, 14 Dec 2023 16:14:59 -0800 Subject: [PATCH 10/21] Cleanup old ci and docs subfolders and related files under python/cucim (#666) closes #659 closes #660 closes #661 Authors: - Gregory Lee (https://github.com/grlee77) - https://github.com/jakirkham Approvers: - AJ Schmidt (https://github.com/ajschmidt8) - https://github.com/jakirkham - Gigon Bae (https://github.com/gigony) URL: https://github.com/rapidsai/cucim/pull/666 --- python/cucim/.appveyor.yml | 78 ---- python/cucim/.cookiecutterrc | 70 ---- python/cucim/.gitignore | 71 ---- python/cucim/.readthedocs.yml | 10 - python/cucim/.travis.yml | 57 --- python/cucim/AUTHORS.md | 7 - python/cucim/CONTRIBUTING.md | 97 +---- python/cucim/MANIFEST.in | 17 +- python/cucim/README.md | 114 +----- python/cucim/VERSION | 1 + python/cucim/ci/appveyor-with-compiler.cmd | 23 -- python/cucim/ci/bootstrap.py | 91 ----- python/cucim/ci/requirements.txt | 5 - python/cucim/ci/templates/.appveyor.yml | 49 --- python/cucim/ci/templates/.travis.yml | 47 --- python/cucim/docs/_static/css/custom.css | 20 - .../docs/_static/images/RAPIDS_cuCIM.png | Bin 152115 -> 0 bytes .../docs/api_reference/cucim.CuImage.rst | 5 - .../cucim.clara.filesystem.CuFileDriver.rst | 5 - .../api_reference/cucim.clara.filesystem.rst | 5 - .../api_reference/cucim.clara.io.Device.rst | 5 - .../docs/api_reference/cucim.clara.io.rst | 4 - python/cucim/docs/api_reference/cucim.rst | 9 - python/cucim/docs/api_reference/index.md | 28 -- python/cucim/docs/conf.py | 131 ------- python/cucim/docs/development/index.md | 1 - python/cucim/docs/getting_started/index.md | 172 -------- python/cucim/docs/index.md | 86 ---- python/cucim/docs/release_notes/index.md | 60 --- python/cucim/docs/release_notes/v0.1.0.md | 24 -- python/cucim/docs/release_notes/v0.1.1.md | 18 - python/cucim/docs/release_notes/v0.18.0.md | 7 - python/cucim/docs/release_notes/v0.18.1.md | 7 - python/cucim/docs/release_notes/v0.18.2.md | 13 - python/cucim/docs/release_notes/v0.18.3.md | 5 - python/cucim/docs/release_notes/v0.19.0.md | 7 - python/cucim/docs/release_notes/v0.2.0.md | 32 -- python/cucim/docs/release_notes/v0.3.0.md | 53 --- python/cucim/docs/requirements.txt | 23 -- python/cucim/docs/roadmap/index.md | 370 ------------------ python/cucim/docs/spelling_wordlist.txt | 11 - python/cucim/docs/user_guide/index.md | 1 - python/cucim/tox.ini | 111 ------ 43 files changed, 6 insertions(+), 1944 deletions(-) delete mode 100644 python/cucim/.appveyor.yml delete mode 100644 python/cucim/.cookiecutterrc delete mode 100644 python/cucim/.gitignore delete mode 100644 python/cucim/.readthedocs.yml delete mode 100644 python/cucim/.travis.yml delete mode 100644 python/cucim/AUTHORS.md mode change 100644 => 120000 python/cucim/CONTRIBUTING.md mode change 100755 => 120000 python/cucim/README.md create mode 120000 python/cucim/VERSION delete mode 100644 python/cucim/ci/appveyor-with-compiler.cmd delete mode 100755 python/cucim/ci/bootstrap.py delete mode 100644 python/cucim/ci/requirements.txt delete mode 100644 python/cucim/ci/templates/.appveyor.yml delete mode 100644 python/cucim/ci/templates/.travis.yml delete mode 100644 python/cucim/docs/_static/css/custom.css delete mode 100644 python/cucim/docs/_static/images/RAPIDS_cuCIM.png delete mode 100644 python/cucim/docs/api_reference/cucim.CuImage.rst delete mode 100644 python/cucim/docs/api_reference/cucim.clara.filesystem.CuFileDriver.rst delete mode 100644 python/cucim/docs/api_reference/cucim.clara.filesystem.rst delete mode 100644 python/cucim/docs/api_reference/cucim.clara.io.Device.rst delete mode 100644 python/cucim/docs/api_reference/cucim.clara.io.rst delete mode 100644 python/cucim/docs/api_reference/cucim.rst delete mode 100644 python/cucim/docs/api_reference/index.md delete mode 100644 python/cucim/docs/conf.py delete mode 100644 python/cucim/docs/development/index.md delete mode 100644 python/cucim/docs/getting_started/index.md delete mode 100644 python/cucim/docs/index.md delete mode 100644 python/cucim/docs/release_notes/index.md delete mode 100644 python/cucim/docs/release_notes/v0.1.0.md delete mode 100644 python/cucim/docs/release_notes/v0.1.1.md delete mode 100644 python/cucim/docs/release_notes/v0.18.0.md delete mode 100644 python/cucim/docs/release_notes/v0.18.1.md delete mode 100644 python/cucim/docs/release_notes/v0.18.2.md delete mode 100644 python/cucim/docs/release_notes/v0.18.3.md delete mode 100644 python/cucim/docs/release_notes/v0.19.0.md delete mode 100644 python/cucim/docs/release_notes/v0.2.0.md delete mode 100644 python/cucim/docs/release_notes/v0.3.0.md delete mode 100644 python/cucim/docs/requirements.txt delete mode 100644 python/cucim/docs/roadmap/index.md delete mode 100644 python/cucim/docs/spelling_wordlist.txt delete mode 100644 python/cucim/docs/user_guide/index.md delete mode 100644 python/cucim/tox.ini diff --git a/python/cucim/.appveyor.yml b/python/cucim/.appveyor.yml deleted file mode 100644 index 71d045008..000000000 --- a/python/cucim/.appveyor.yml +++ /dev/null @@ -1,78 +0,0 @@ -version: '{branch}-{build}' -build: off -environment: - matrix: - - TOXENV: check - TOXPYTHON: C:\Python36\python.exe - PYTHON_HOME: C:\Python36 - PYTHON_VERSION: '3.6' - PYTHON_ARCH: '32' - - TOXENV: py27,codecov - TOXPYTHON: C:\Python27\python.exe - PYTHON_HOME: C:\Python27 - PYTHON_VERSION: '2.7' - PYTHON_ARCH: '32' - - TOXENV: py27,codecov - TOXPYTHON: C:\Python27-x64\python.exe - PYTHON_HOME: C:\Python27-x64 - PYTHON_VERSION: '2.7' - PYTHON_ARCH: '64' - WINDOWS_SDK_VERSION: v7.0 - - TOXENV: py35,codecov - TOXPYTHON: C:\Python35\python.exe - PYTHON_HOME: C:\Python35 - PYTHON_VERSION: '3.5' - PYTHON_ARCH: '32' - - TOXENV: py35,codecov - TOXPYTHON: C:\Python35-x64\python.exe - PYTHON_HOME: C:\Python35-x64 - PYTHON_VERSION: '3.5' - PYTHON_ARCH: '64' - - TOXENV: py36,codecov - TOXPYTHON: C:\Python36\python.exe - PYTHON_HOME: C:\Python36 - PYTHON_VERSION: '3.6' - PYTHON_ARCH: '32' - - TOXENV: py36,codecov - TOXPYTHON: C:\Python36-x64\python.exe - PYTHON_HOME: C:\Python36-x64 - PYTHON_VERSION: '3.6' - PYTHON_ARCH: '64' - - TOXENV: py37,codecov - TOXPYTHON: C:\Python37\python.exe - PYTHON_HOME: C:\Python37 - PYTHON_VERSION: '3.7' - PYTHON_ARCH: '32' - - TOXENV: py37,codecov - TOXPYTHON: C:\Python37-x64\python.exe - PYTHON_HOME: C:\Python37-x64 - PYTHON_VERSION: '3.7' - PYTHON_ARCH: '64' - - TOXENV: py38,codecov - TOXPYTHON: C:\Python38\python.exe - PYTHON_HOME: C:\Python38 - PYTHON_VERSION: '3.8' - PYTHON_ARCH: '32' - - TOXENV: py38,codecov - TOXPYTHON: C:\Python38-x64\python.exe - PYTHON_HOME: C:\Python38-x64 - PYTHON_VERSION: '3.8' - PYTHON_ARCH: '64' -init: - - ps: echo $env:TOXENV - - ps: ls C:\Python* -install: - - '%PYTHON_HOME%\python -mpip install --progress-bar=off tox -rci/requirements.txt' - - '%PYTHON_HOME%\Scripts\virtualenv --version' - - '%PYTHON_HOME%\Scripts\easy_install --version' - - '%PYTHON_HOME%\Scripts\pip --version' - - '%PYTHON_HOME%\Scripts\tox --version' -test_script: - - cmd /E:ON /V:ON /C .\ci\appveyor-with-compiler.cmd %PYTHON_HOME%\Scripts\tox -on_failure: - - ps: dir "env:" - - ps: get-content .tox\*\log\* - -### To enable remote debugging uncomment this (also, see: http://www.appveyor.com/docs/how-to/rdp-to-build-worker): -# on_finish: -# - ps: $blockRdp = $true; iex ((new-object net.webclient).DownloadString('https://raw.githubusercontent.com/appveyor/ci/master/scripts/enable-rdp.ps1')) diff --git a/python/cucim/.cookiecutterrc b/python/cucim/.cookiecutterrc deleted file mode 100644 index 437fca81c..000000000 --- a/python/cucim/.cookiecutterrc +++ /dev/null @@ -1,70 +0,0 @@ -# This file exists so you can easily regenerate your project. -# -# `cookiepatcher` is a convenient shim around `cookiecutter` -# for regenerating projects (it will generate a .cookiecutterrc -# automatically for any template). To use it: -# -# pip install cookiepatcher -# cookiepatcher gh:ionelmc/cookiecutter-pylibrary cucim -# -# See: -# https://pypi.org/project/cookiepatcher -# -# Alternatively, you can run: -# -# cookiecutter --overwrite-if-exists --config-file=cucim/.cookiecutterrc gh:ionelmc/cookiecutter-pylibrary - -default_context: - - _extensions: ['jinja2_time.TimeExtension'] - _template: 'https://github.com/ionelmc/cookiecutter-pylibrary' - allow_tests_inside_package: 'no' - appveyor: 'yes' - c_extension_function: 'longest' - c_extension_module: '_cucim' - c_extension_optional: 'no' - c_extension_support: 'no' - c_extension_test_pypi: 'no' - c_extension_test_pypi_username: '' - codacy: 'no' - codacy_projectid: '[Get ID from https://app.codacy.com/app/rapidsai/cucim/settings]' - codeclimate: 'no' - codecov: 'yes' - command_line_interface: 'click' - command_line_interface_bin_name: 'cucim' - coveralls: 'no' - coveralls_token: '[Required for Appveyor, take it from https://coveralls.io/github/rapidsai/cucim]' - distribution_name: 'cucim' - email: '' - full_name: 'cuCIM Developers' - landscape: 'no' - license: 'Apache Software License 2.0' - linter: 'flake8' - package_name: 'cucim' - pre_commit: 'yes' - project_name: 'cucim' - project_short_description: 'A CUDA-accerlated image processing library' - pypi_badge: 'yes' - pypi_disable_upload: 'no' - release_date: 'today' - repo_hosting: 'github.com' - repo_hosting_domain: 'github.com' - repo_name: 'cucim' - repo_username: '' - requiresio: 'yes' - scrutinizer: 'no' - setup_py_uses_setuptools_scm: 'no' - setup_py_uses_test_runner: 'no' - sphinx_docs: 'yes' - sphinx_docs_hosting: 'https://cucim.readthedocs.io/' - sphinx_doctest: 'yes' - sphinx_theme: 'sphinx-rtd-theme' - test_matrix_configurator: 'no' - test_matrix_separate_coverage: 'no' - test_runner: 'pytest' - travis: 'yes' - travis_osx: 'no' - version: '0.0.0' - website: 'https://rapids.ai/' - year_from: '2021' - year_to: '2021' diff --git a/python/cucim/.gitignore b/python/cucim/.gitignore deleted file mode 100644 index dfe58380d..000000000 --- a/python/cucim/.gitignore +++ /dev/null @@ -1,71 +0,0 @@ -*.py[cod] -__pycache__ - -# C extensions -*.so - -# Packages -*.egg -*.egg-info -dist -build -eggs -.eggs -parts -bin -var -sdist -wheelhouse -develop-eggs -.installed.cfg -lib -lib64 -venv*/ -pyvenv*/ -pip-wheel-metadata/ - -# Installer logs -pip-log.txt - -# Unit test / coverage reports -.coverage -.tox -.coverage.* -.pytest_cache/ -nosetests.xml -coverage.xml -htmlcov - -# Translations -*.mo - -# Mr Developer -.mr.developer.cfg -.project -.pydevproject -.idea -*.iml -*.komodoproject - -# Complexity -output/*.html -output/*/index.html - -# Sphinx -docs/_build - -.DS_Store -*~ -.*.sw[po] -.build -.ve -.env -.cache -.pytest -.benchmarks -.bootstrap -.appveyor.token -*.bak - -# Mypy Cache -.mypy_cache/ diff --git a/python/cucim/.readthedocs.yml b/python/cucim/.readthedocs.yml deleted file mode 100644 index 59ff5c04f..000000000 --- a/python/cucim/.readthedocs.yml +++ /dev/null @@ -1,10 +0,0 @@ -# See https://docs.readthedocs.io/en/stable/config-file/v2.html for details -version: 2 -sphinx: - configuration: docs/conf.py -formats: all -python: - install: - - requirements: docs/requirements.txt - - method: pip - path: . diff --git a/python/cucim/.travis.yml b/python/cucim/.travis.yml deleted file mode 100644 index 09858acea..000000000 --- a/python/cucim/.travis.yml +++ /dev/null @@ -1,57 +0,0 @@ -language: python -dist: xenial -cache: false -env: - global: - - LD_PRELOAD=/lib/x86_64-linux-gnu/libSegFault.so - - SEGFAULT_SIGNALS=all - - LANG=en_US.UTF-8 -matrix: - include: - - python: '3.6' - env: - - TOXENV=check - - python: '3.6' - env: - - TOXENV=docs - - env: - - TOXENV=py27,codecov - python: '2.7' - - env: - - TOXENV=py35,codecov - python: '3.5' - - env: - - TOXENV=py36,codecov - python: '3.6' - - env: - - TOXENV=py37,codecov - python: '3.7' - - env: - - TOXENV=py38,codecov - python: '3.8' - - env: - - TOXENV=pypy,codecov - python: 'pypy' - - env: - - TOXENV=pypy3,codecov - - TOXPYTHON=pypy3 - python: 'pypy3' -before_install: - - python --version - - uname -a - - lsb_release -a || true -install: - - python -mpip install --progress-bar=off tox -rci/requirements.txt - - virtualenv --version - - easy_install --version - - pip --version - - tox --version -script: - - tox -v -after_failure: - - cat .tox/log/* - - cat .tox/*/log/* -notifications: - email: - on_success: never - on_failure: always diff --git a/python/cucim/AUTHORS.md b/python/cucim/AUTHORS.md deleted file mode 100644 index c16398d00..000000000 --- a/python/cucim/AUTHORS.md +++ /dev/null @@ -1,7 +0,0 @@ -# Authors - -* Gigon Bae (gigony) -* Gregory R. Lee (grlee77) -* Benjamin Zaitlen (quasiben) -* John Kirkham (jakirkham) -* Ray Douglass (raydouglass) diff --git a/python/cucim/CONTRIBUTING.md b/python/cucim/CONTRIBUTING.md deleted file mode 100644 index 77f80ae8a..000000000 --- a/python/cucim/CONTRIBUTING.md +++ /dev/null @@ -1,96 +0,0 @@ -# Contributing - -Contributions are welcome, and they are greatly appreciated! Every -little bit helps, and credit will always be given. - -# Bug reports - -When [reporting a bug](https://github.com/rapidsai/cucim/issues) please include: - - * Your operating system name and version. - * Any details about your local setup that might be helpful in troubleshooting. - * Detailed steps to reproduce the bug. - -# Documentation improvements - -cucim could always use more documentation, whether as part of the -official cucim docs, in docstrings, or even on the web in blog posts, -articles, and such. - -# Feature requests and feedback - -The best way to send feedback is to file an issue at https://github.com/rapidsai/cucim/issues. - -If you are proposing a feature: - -* Explain in detail how it would work. -* Keep the scope as narrow as possible, to make it easier to implement. -* Remember that this is a volunteer-driven project, and that code contributions are welcome :) - -# Development - -To set up `cucim` for local development: - -1. Fork [cucim](https://github.com/rapidsai/cucim) - (look for the "Fork" button). - -2. Clone your fork locally: - - ```bash - git clone git@github.com:YOURGITHUBNAME/cucim.git - ``` - -3. Create a branch for local development:: - - ```bash - git checkout -b name-of-your-bugfix-or-feature - ``` - - Now you can make your changes locally. - -4. When you're done making changes run all the checks and docs builder with [tox](https://tox.readthedocs.io/en/latest/install.html) one command:: - - ```bash - tox - ``` - -5. Commit your changes and push your branch to GitHub: - - ```bash - git add . - git commit -m "Your detailed description of your changes." - git push origin name-of-your-bugfix-or-feature - ``` - -6. Submit a pull request through the GitHub website. - -## Pull Request Guidelines - -If you need some code review or feedback while you're developing the code just make the pull request. - -For merging, you should: - -1. Include passing tests (run `tox`). - - If you don't have all the necessary python versions available locally you can rely on Travis - it will [run the tests](https://travis-ci.org/rapidsai/cucim/pull_requests) for each change you add in the pull request. - - It will be slower though ... -2. Update documentation when there's new API, functionality etc. -3. Add a note to `CHANGELOG.md`` about the changes. -4. Add yourself to `AUTHORS.md`. - - -## Tips - - -To run a subset of tests:: - -```bash -tox -e envname -- pytest -k test_myfeature -``` - -To run all the test environments in *parallel*:: - -```bash -tox -p auto -``` diff --git a/python/cucim/CONTRIBUTING.md b/python/cucim/CONTRIBUTING.md new file mode 120000 index 000000000..f939e75f2 --- /dev/null +++ b/python/cucim/CONTRIBUTING.md @@ -0,0 +1 @@ +../../CONTRIBUTING.md \ No newline at end of file diff --git a/python/cucim/MANIFEST.in b/python/cucim/MANIFEST.in index 756e96d3c..61881e2c1 100644 --- a/python/cucim/MANIFEST.in +++ b/python/cucim/MANIFEST.in @@ -1,24 +1,13 @@ # https://packaging.python.org/guides/using-manifest-in/ -graft docs -graft src -graft ci -graft tests - -include .bumpversion.cfg -include .coveragerc -include .cookiecutterrc include .editorconfig -include VERSION -include AUTHORS.md +include src/cucim/VERSION include CHANGELOG.md include CONTRIBUTING.md -include ../../LICENSE -include ../../LICENSE-3rdparty.md +include LICENSE +include LICENSE-3rdparty.md include README.md -include tox.ini .travis.yml .appveyor.yml .readthedocs.yml - include src/cucim/clara/*.so* recursive-include src/cucim *.py *.pyi *.cu *.h *.npy *.txt *.md diff --git a/python/cucim/README.md b/python/cucim/README.md deleted file mode 100755 index 27a5d7fbb..000000000 --- a/python/cucim/README.md +++ /dev/null @@ -1,113 +0,0 @@ -#
 cuCIM
- -[RAPIDS](https://rapids.ai) cuCIM is an open-source, accelerated computer vision and image processing software library for multidimensional images used in biomedical, geospatial, material and life science, and remote sensing use cases. - -cuCIM offers: - -- Enhanced Image Processing Capabilities for large and n-dimensional tag image file format (TIFF) files -- Accelerated performance through Graphics Processing Unit (GPU)-based image processing and computer vision primitives -- A Straightforward Pythonic Interface with Matching Application Programming Interface (API) for Openslide - -cuCIM supports the following formats: - -- Aperio ScanScope Virtual Slide (SVS) -- Philips TIFF -- Generic Tiled, Multi-resolution RGB TIFF files with the following compression schemes: - - No Compression - - JPEG - - JPEG2000 - - Lempel-Ziv-Welch (LZW) - - Deflate - -**NOTE:** For the latest stable [README.md](https://github.com/rapidsai/cucim/blob/main/README.md) ensure you are on the `main` branch. - -- [GTC 2022 Accelerating Storage IO to GPUs with Magnum IO [S41347]](https://events.rainfocus.com/widget/nvidia/gtcspring2022/sessioncatalog/session/1634960000577001Etxp) - - cuCIM's GDS API examples: -- [SciPy 2021 cuCIM - A GPU image I/O and processing library](https://www.scipy2021.scipy.org/) - - [video](https://youtu.be/G46kOOM9xbQ) -- [GTC 2021 cuCIM: A GPU Image I/O and Processing Toolkit [S32194]](https://www.nvidia.com/en-us/on-demand/search/?facet.mimetype[]=event%20session&layout=list&page=1&q=cucim&sort=date) - - [video](https://www.nvidia.com/en-us/on-demand/session/gtcspring21-s32194/) - -**[Developer Page](https://developer.nvidia.com/multidimensional-image-processing)** - -**Blogs** -- [Enhanced Image Analysis with Multidimensional Image Processing](https://developer.nvidia.com/blog/enhanced-image-analysis-with-multidimensional-image-processing/) -- [Accelerating Scikit-Image API with cuCIM: n-Dimensional Image Processing and IO on GPUs](https://developer.nvidia.com/blog/cucim-rapid-n-dimensional-image-processing-and-i-o-on-gpus/) -- [Accelerating Digital Pathology Pipelines with NVIDIA Clara™ Deploy](https://developer.nvidia.com/blog/accelerating-digital-pathology-pipelines-with-nvidia-clara-deploy-2/) - -**Webinars** - -- [cuCIM: a GPU Image IO and Processing Library](https://www.youtube.com/watch?v=G46kOOM9xbQ) - -**[Documentation](https://docs.rapids.ai/api/cucim/stable)** - -**Release notes** are available on our [wiki page](https://github.com/rapidsai/cucim/wiki/Release-Notes). - -## Install cuCIM - -### Conda - -#### [Conda (stable)](https://anaconda.org/rapidsai/cucim) - -> conda create -n cucim -c rapidsai -c conda-forge cucim cudatoolkit=`` - -`` should be 11.0+ (e.g., `11.0`, `11.2`, etc.) - -#### [Conda (nightlies)](https://anaconda.org/rapidsai-nightly/cucim) - -> conda create -n cucim -c rapidsai-nightly -c conda-forge cucim cudatoolkit=`` - -`` should be 11.0+ (e.g., `11.0`, `11.2`, etc) - -### [PyPI](https://pypi.org/project/cucim/) - -```bash -pip install cucim - -# Install dependencies for `cucim.skimage` (assuming that CUDA 11.0 is used for CuPy) -pip install scipy scikit-image cupy-cuda110 -``` - -### Notebooks - -Please check out our [Welcome](notebooks/Welcome.ipynb) notebook ([NBViewer](https://nbviewer.jupyter.org/github/rapidsai/cucim/blob/branch-24.02/notebooks/Welcome.ipynb)) - -#### Downloading sample images - -To download images used in the notebooks, please execute the following commands from the repository root folder to copy sample input images into `notebooks/input` folder: - -(You will need [Docker](https://www.docker.com/) installed in your system) - -```bash -./run download_testdata -``` -or - -```bash -mkdir -p notebooks/input -tmp_id=$(docker create gigony/svs-testdata:little-big) -docker cp $tmp_id:/input notebooks -docker rm -v ${tmp_id} -``` - -## Build/Install from Source - -See build [instructions](CONTRIBUTING.md#setting-up-your-build-environment). - -## Contributing Guide - -Contributions to cuCIM are more than welcome! -Please review the [CONTRIBUTING.md](https://github.com/rapidsai/cucim/blob/main/CONTRIBUTING.md) file for information on how to contribute code and issues to the project. - -## Acknowledgments - -Without awesome third-party open source software, this project wouldn't exist. - -Please find [LICENSE-3rdparty.md](LICENSE-3rdparty.md) to see which third-party open source software -is used in this project. - -## License - -Apache-2.0 License (see [LICENSE](LICENSE) file). - -Copyright (c) 2020-2022, NVIDIA CORPORATION. diff --git a/python/cucim/README.md b/python/cucim/README.md new file mode 120000 index 000000000..fe8400541 --- /dev/null +++ b/python/cucim/README.md @@ -0,0 +1 @@ +../../README.md \ No newline at end of file diff --git a/python/cucim/VERSION b/python/cucim/VERSION new file mode 120000 index 000000000..558194c5a --- /dev/null +++ b/python/cucim/VERSION @@ -0,0 +1 @@ +../../VERSION \ No newline at end of file diff --git a/python/cucim/ci/appveyor-with-compiler.cmd b/python/cucim/ci/appveyor-with-compiler.cmd deleted file mode 100644 index 289585fc1..000000000 --- a/python/cucim/ci/appveyor-with-compiler.cmd +++ /dev/null @@ -1,23 +0,0 @@ -:: Very simple setup: -:: - if WINDOWS_SDK_VERSION is set then activate the SDK. -:: - disable the WDK if it's around. - -SET COMMAND_TO_RUN=%* -SET WIN_SDK_ROOT=C:\Program Files\Microsoft SDKs\Windows -SET WIN_WDK="c:\Program Files (x86)\Windows Kits\10\Include\wdf" -ECHO SDK: %WINDOWS_SDK_VERSION% ARCH: %PYTHON_ARCH% - -IF EXIST %WIN_WDK% ( - REM See: https://connect.microsoft.com/VisualStudio/feedback/details/1610302/ - REN %WIN_WDK% 0wdf -) -IF "%WINDOWS_SDK_VERSION%"=="" GOTO main - -SET DISTUTILS_USE_SDK=1 -SET MSSdk=1 -"%WIN_SDK_ROOT%\%WINDOWS_SDK_VERSION%\Setup\WindowsSdkVer.exe" -q -version:%WINDOWS_SDK_VERSION% -CALL "%WIN_SDK_ROOT%\%WINDOWS_SDK_VERSION%\Bin\SetEnv.cmd" /x64 /release - -:main -ECHO Executing: %COMMAND_TO_RUN% -CALL %COMMAND_TO_RUN% || EXIT 1 diff --git a/python/cucim/ci/bootstrap.py b/python/cucim/ci/bootstrap.py deleted file mode 100755 index 05dbf0b3f..000000000 --- a/python/cucim/ci/bootstrap.py +++ /dev/null @@ -1,91 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- - -import os -import subprocess -import sys -from os.path import abspath, dirname, exists, join - -base_path = dirname(dirname(abspath(__file__))) - - -def check_call(args): - print("+", *args) - subprocess.check_call(args) - - -def exec_in_env(): - env_path = join(base_path, ".tox", "bootstrap") - if sys.platform == "win32": - bin_path = join(env_path, "Scripts") - else: - bin_path = join(env_path, "bin") - if not exists(env_path): - import subprocess - - print("Making bootstrap env in: {0} ...".format(env_path)) - try: - check_call([sys.executable, "-m", "venv", env_path]) - except subprocess.CalledProcessError: - try: - check_call([sys.executable, "-m", "virtualenv", env_path]) - except subprocess.CalledProcessError: - check_call(["virtualenv", env_path]) - print("Installing `jinja2` into bootstrap environment...") - check_call([join(bin_path, "pip"), "install", "jinja2", "tox"]) - python_executable = join(bin_path, "python") - if not os.path.exists(python_executable): - python_executable += ".exe" - - print("Re-executing with: {0}".format(python_executable)) - print("+ exec", python_executable, __file__, "--no-env") - os.execv(python_executable, [python_executable, __file__, "--no-env"]) - - -def main(): - import jinja2 - - print("Project path: {0}".format(base_path)) - - jinja = jinja2.Environment( - loader=jinja2.FileSystemLoader(join(base_path, "ci", "templates")), - trim_blocks=True, - lstrip_blocks=True, - keep_trailing_newline=True, - ) - - tox_environments = [ - line.strip() - # 'tox' need not be installed globally, but must be importable - # by the Python that is running this script. - # This uses sys.executable the same way that the call in - # cookiecutter-pylibrary/hooks/post_gen_project.py - # invokes this bootstrap.py itself. - for line in subprocess.check_output( - [sys.executable, "-m", "tox", "--listenvs"], universal_newlines=True - ).splitlines() - ] - tox_environments = [ - line for line in tox_environments if line.startswith("py") - ] - - for name in os.listdir(join("ci", "templates")): - with open(join(base_path, name), "w") as fh: - fh.write( - jinja.get_template(name).render( - tox_environments=tox_environments - ) - ) - print("Wrote {}".format(name)) - print("DONE.") - - -if __name__ == "__main__": - args = sys.argv[1:] - if args == ["--no-env"]: - main() - elif not args: - exec_in_env() - else: - print("Unexpected arguments {0}".format(args), file=sys.stderr) - sys.exit(1) diff --git a/python/cucim/ci/requirements.txt b/python/cucim/ci/requirements.txt deleted file mode 100644 index a0f53e12a..000000000 --- a/python/cucim/ci/requirements.txt +++ /dev/null @@ -1,5 +0,0 @@ -virtualenv>=16.6.0 -pip>=19.1.1 -setuptools>=18.0.1 -six>=1.14.0 -lazy_loader>=0.1 diff --git a/python/cucim/ci/templates/.appveyor.yml b/python/cucim/ci/templates/.appveyor.yml deleted file mode 100644 index bb4a05599..000000000 --- a/python/cucim/ci/templates/.appveyor.yml +++ /dev/null @@ -1,49 +0,0 @@ -version: '{branch}-{build}' -build: off -environment: - matrix: - - TOXENV: check - TOXPYTHON: C:\Python36\python.exe - PYTHON_HOME: C:\Python36 - PYTHON_VERSION: '3.6' - PYTHON_ARCH: '32' -{% for env in tox_environments %} -{% if env.startswith(('py2', 'py3')) %} - - TOXENV: {{ env }},codecov{{ "" }} - TOXPYTHON: C:\Python{{ env[2:4] }}\python.exe - PYTHON_HOME: C:\Python{{ env[2:4] }} - PYTHON_VERSION: '{{ env[2] }}.{{ env[3] }}' - PYTHON_ARCH: '32' -{% if 'nocov' in env %} - WHEEL_PATH: .tox/dist -{% endif %} - - TOXENV: {{ env }},codecov{{ "" }} - TOXPYTHON: C:\Python{{ env[2:4] }}-x64\python.exe - PYTHON_HOME: C:\Python{{ env[2:4] }}-x64 - PYTHON_VERSION: '{{ env[2] }}.{{ env[3] }}' - PYTHON_ARCH: '64' -{% if 'nocov' in env %} - WHEEL_PATH: .tox/dist -{% endif %} -{% if env.startswith('py2') %} - WINDOWS_SDK_VERSION: v7.0 -{% endif %} -{% endif %}{% endfor %} -init: - - ps: echo $env:TOXENV - - ps: ls C:\Python* -install: - - '%PYTHON_HOME%\python -mpip install --progress-bar=off tox -rci/requirements.txt' - - '%PYTHON_HOME%\Scripts\virtualenv --version' - - '%PYTHON_HOME%\Scripts\easy_install --version' - - '%PYTHON_HOME%\Scripts\pip --version' - - '%PYTHON_HOME%\Scripts\tox --version' -test_script: - - cmd /E:ON /V:ON /C .\ci\appveyor-with-compiler.cmd %PYTHON_HOME%\Scripts\tox -on_failure: - - ps: dir "env:" - - ps: get-content .tox\*\log\* - -### To enable remote debugging uncomment this (also, see: http://www.appveyor.com/docs/how-to/rdp-to-build-worker): -# on_finish: -# - ps: $blockRdp = $true; iex ((new-object net.webclient).DownloadString('https://raw.githubusercontent.com/appveyor/ci/master/scripts/enable-rdp.ps1')) diff --git a/python/cucim/ci/templates/.travis.yml b/python/cucim/ci/templates/.travis.yml deleted file mode 100644 index cb15bd09c..000000000 --- a/python/cucim/ci/templates/.travis.yml +++ /dev/null @@ -1,47 +0,0 @@ -language: python -dist: xenial -cache: false -env: - global: - - LD_PRELOAD=/lib/x86_64-linux-gnu/libSegFault.so - - SEGFAULT_SIGNALS=all - - LANG=en_US.UTF-8 -matrix: - include: - - python: '3.6' - env: - - TOXENV=check - - python: '3.6' - env: - - TOXENV=docs -{%- for env in tox_environments %}{{ '' }} - - env: - - TOXENV={{ env }},codecov -{%- if env.startswith('pypy3') %}{{ '' }} - - TOXPYTHON=pypy3 - python: 'pypy3' -{%- elif env.startswith('pypy') %}{{ '' }} - python: 'pypy' -{%- else %}{{ '' }} - python: '{{ '{0[2]}.{0[3]}'.format(env) }}' -{%- endif %}{{ '' }} -{%- endfor %}{{ '' }} -before_install: - - python --version - - uname -a - - lsb_release -a || true -install: - - python -mpip install --progress-bar=off tox -rci/requirements.txt - - virtualenv --version - - easy_install --version - - pip --version - - tox --version -script: - - tox -v -after_failure: - - cat .tox/log/* - - cat .tox/*/log/* -notifications: - email: - on_success: never - on_failure: always diff --git a/python/cucim/docs/_static/css/custom.css b/python/cucim/docs/_static/css/custom.css deleted file mode 100644 index 87f612eaf..000000000 --- a/python/cucim/docs/_static/css/custom.css +++ /dev/null @@ -1,20 +0,0 @@ -/* -https: //github.com/pandas-dev/pydata-sphinx-theme/blob/master/pydata_sphinx_theme/static/css/theme.css -https: //pydata-sphinx-theme.readthedocs.io/en/latest/user_guide/customizing.html#customizing-the-css -*/ - -/* To fix a bug: `.col-xl-2` and `.bd-toc` conflicts in the following HTML of right navbar in the theme. - - `.col-xl-2` has `position: relative` - - `.bd-toc` has `position: sticky` - -
- -*/ -div.bd-toc { - position: sticky; -} - -/* Set max width of container */ -div.container-xl { - max-width: 1400px; -} diff --git a/python/cucim/docs/_static/images/RAPIDS_cuCIM.png b/python/cucim/docs/_static/images/RAPIDS_cuCIM.png deleted file mode 100644 index aa7ed8cdc17811f736e0c2fa31fa4980373abe0c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 152115 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 would show you detailed information about the command. - -```bash -./run help build_train -``` -``` -Build Clara Train Docker image with cuCIM (& OpenSlide) - -Build image from docker/Dockerfile-claratrain - -Arguments: - $1 - docker image name (default:cucim-train) -``` - -### download_testdata - -It downloads test data from DockerHub (`gigony/svs-testdata:little-big`) and make it available at `notebooks/input` folder. - -The folder has the following four files. - -- `TUPAC-TR-488.svs` -- `TUPAC-TR-467.svs` -- `image.tif` -- `image2.tif` - -#### Test Dataset - -`TUPAC-TR-488.svs` and `TUPAC-TR-467.svs` are from the dataset -of Tumor Proliferation Assessment Challenge 2016 (TUPAC16 | MICCAI Grand Challenge). - -- Website: -- Data link: - -#### Converted files - -- `image.tif` : 256x256 multi-resolution/tiled TIF conversion of TUPAC-TR-467.svs -- `image2.tif` : 256x256 multi-resolution/tiled TIF conversion of TUPAC-TR-488.svs - - -### launch_notebooks - -It launches a **Jupyter Lab** instance so that the user can experiment with cuCIM. - -After executing the command, type a password for the instance and open a web browser to access the instance. - -```bash -./run launch_notebooks -``` - -```bash -... -Port 10001 would be used...(http://172.26.120.129:10001) -2021-02-13 01:12:44 $ docker run --runtime nvidia --gpus all -it --rm -v /home/repo/cucim/notebooks:/notebooks -p 10001:10001 cucim-jupyter -c echo -n 'Enter New Password: '; jupyter lab --ServerApp.password="$(python3 -u -c "from jupyter_server.auth import passwd;pw=input();print(passwd(pw));" | egrep 'sha|argon')" --ServerApp.root_dir=/notebooks --allow-root --port=10001 --ip=0.0.0.0 --no-browser -Enter New Password: -[I 2021-02-13 01:12:47.981 ServerApp] dask_labextension | extension was successfully linked. -[I 2021-02-13 01:12:47.981 ServerApp] jupyter_server_proxy | extension was successfully linked. -... -``` - -### build_train - -It builds an image from the Clara Deploy SDK image. The image would install other useful python package as well as cu -CIM wheel file. - -`nvcr.io/nvidian/dlmed/clara-train-sdk:v3.1-ga-qa-5` is used and `docker/Dockerfile-claratrain` has the recipe of the image. - -You will need to have a permission to access `nvidian/dlmed` group in NGC. - -```bash -./run build_train - -docker run -it --rm cucim-train /bin/bash -``` - -### build_examples - -It builds C++ examples at `examples/cpp` folder by using `cmake` in `cucim-cmake` image that is built in runtime. - -After the execution, it would copy built file into `bin` folder and show how to execute it. - -```bash -./run build_examples -``` - -```bash -... - -Execute the binary with the following commands: - # Set library path - export LD_LIBRARY_PATH=/ssd/repo/cucim/dist/install/lib:$LD_LIBRARY_PATH - # Execute - ./bin/tiff_image notebooks/input/image.tif . -``` - -Its execution would show some metadata information and create two files -- `output.ppm` and `output2.ppm`. - -`.ppm` file can be viewed by `eog` in Ubuntu. -``` -$ ./bin/tiff_image notebooks/input/image.tif . -[Plugin: cucim.kit.cuslide] Loading... -[Plugin: cucim.kit.cuslide] Loading the dynamic library from: cucim.kit.cuslide@24.02.00.so -[Plugin: cucim.kit.cuslide] loaded successfully. Version: 0 -Initializing plugin: cucim.kit.cuslide (interfaces: [cucim::io::IImageFormat v0.1]) (impl: cucim.kit.cuslide) -is_loaded: true -device: cpu -metadata: {"key": "value"} -dims: YXC -shape: (26420, 19920, 3) -size('XY'): (19920, 26420) -channel_names: (R, G, B) - -is_loaded: true -device: cpu -metadata: {"key": "value"} -dims: YXC -shape: (1024, 1024, 3) -size('XY'): (1024, 1024) -channel_names: (R, G, B) -[Plugin: cucim.kit.cuslide] Unloaded. - -$ eog output.ppm -$ eog output2.ppm -``` diff --git a/python/cucim/docs/index.md b/python/cucim/docs/index.md deleted file mode 100644 index 6789e7961..000000000 --- a/python/cucim/docs/index.md +++ /dev/null @@ -1,86 +0,0 @@ - - -```{toctree} -:maxdepth: 3 -:hidden: - -getting_started/index -api_reference/index -release_notes/index -roadmap/index -``` - - - -# cuCIM Documentation - -Current latest version is [Version 24.02.00](release_notes/v24.02.00.md). - -**cuCIM** a toolkit to provide GPU accelerated I/O, image processing & computer vision primitives for N-Dimensional images with a focus on biomedical imaging. - -:::{figure-md} fig-cucim-architecture -:class: myclass - -cuCIM Architecture - -RAPIDS cuCIM Architecture -::: - - - - - - diff --git a/python/cucim/docs/release_notes/index.md b/python/cucim/docs/release_notes/index.md deleted file mode 100644 index 84183cc4d..000000000 --- a/python/cucim/docs/release_notes/index.md +++ /dev/null @@ -1,60 +0,0 @@ -# Release Notes - -```{toctree} -:glob: -:hidden: -:maxdepth: 2 - -v0.19.0 -v0.18.3 -v0.18.2 -v0.18.1 -v0.18.0 -v0.3.0 -v0.2.0 -v0.1.1 -v0.1.0 -``` - -## Version 0.19 - -```{toctree} -:maxdepth: 2 - -v0.19.0 -``` - -## Version 0.18 - -```{toctree} -:maxdepth: 2 - -v0.18.3 -v0.18.2 -v0.18.1 -v0.18.0 -``` - -## Version 0.3 - -```{toctree} -:maxdepth: 2 - -v0.3.0 -``` - -## Version 0.2 - -```{toctree} -:maxdepth: 2 - -v0.2.0 -``` -## Version 0.1 - -```{toctree} -:maxdepth: 2 - -v0.1.1 -v0.1.0 -``` diff --git a/python/cucim/docs/release_notes/v0.1.0.md b/python/cucim/docs/release_notes/v0.1.0.md deleted file mode 100644 index 0d2f40150..000000000 --- a/python/cucim/docs/release_notes/v0.1.0.md +++ /dev/null @@ -1,24 +0,0 @@ -# Version 0.1.0 (October 28, 2020) - -## What are provided in the package? - -- API Documents -- C++ & Python library packages -- Example project (using CMake. For C++) -- Example code in a Jupyter notebook (with a docker image) - -## Features - -For v1, we have a limited feature focusing on generic tiled/multi-resolution TIFF file format (Jpeg-compressed RGB image). - -- Loading part of the image using read_region() API -- Saving the loaded image in .ppm format (loadable by 'eog' viewer in Ubuntu or PIL library in Python) - -## Limitations - -- The following feature is not implemented yet - - Accessing image data through container() API (in C++) or as a numpy array (using `__array_interface__` in Python) -- Errors are not handled properly yet (e.g., loading non-existing file would cause a crash) -- Some metadata (e.g., physical size) is hard-coded for now -- C++ library is forced to set `_GLIBCXX_USE_CXX11_ABI` to 0 due to [Dual ABI](https://gcc.gnu.org/onlinedocs/libstdc++/manual/using_dual_abi.html) problem - - Will package CXX11 ABI library separately later diff --git a/python/cucim/docs/release_notes/v0.1.1.md b/python/cucim/docs/release_notes/v0.1.1.md deleted file mode 100644 index 7ed9b5832..000000000 --- a/python/cucim/docs/release_notes/v0.1.1.md +++ /dev/null @@ -1,18 +0,0 @@ -# Version 0.1.1 (November 3, 2020) - -## What's new? - -The following features are implemented. -- Access image data through `container()` API (in C++) or as a numpy array (using `__array_interface__` in Python) - - [Example](../notebooks/Basic_Usage.html#array-interface-support) -- Remove hard-coded metadata for `resolutions` - - [Example](../notebooks/Basic_Usage.html#see-metadata) -- Sort resolution levels (level 0: the largest resolution) for `CuImage::read_region()` method - - Add `TIFF::level_ifd(size_t level_index)` method -- Pass SWIPAT - -## Fixes - -- Fix a crash that occurs when opening a non-existing file -- Fix an error that occurs when loading a TIFF image that has `TIFFTAG_JPEGTABLES` tag - - `Quantization table 0x00 was not defined` message can be shown diff --git a/python/cucim/docs/release_notes/v0.18.0.md b/python/cucim/docs/release_notes/v0.18.0.md deleted file mode 100644 index f4d16f682..000000000 --- a/python/cucim/docs/release_notes/v0.18.0.md +++ /dev/null @@ -1,7 +0,0 @@ -# Version 0.18.0 (March 16, 2021) - -## What's new? - -- The namespace of the project is changed from `cuimage` to `cucim` and project name is now `cuCIM` -- Support Deflate(zlib) compression in Generic TIFF Format. - - [libdeflate](https://github.com/ebiggers/libdeflate) library is used to decode the deflate-compressed data. diff --git a/python/cucim/docs/release_notes/v0.18.1.md b/python/cucim/docs/release_notes/v0.18.1.md deleted file mode 100644 index 0a1d15001..000000000 --- a/python/cucim/docs/release_notes/v0.18.1.md +++ /dev/null @@ -1,7 +0,0 @@ -# Version 0.18.1 (March 17, 2021) - -## What's new? - -- Disable using cuFile - - Remove warning messages when libcufile.so is not available. - - `[warning] CuFileDriver cannot be open. Falling back to use POSIX file IO APIs.` diff --git a/python/cucim/docs/release_notes/v0.18.2.md b/python/cucim/docs/release_notes/v0.18.2.md deleted file mode 100644 index 44b81fba3..000000000 --- a/python/cucim/docs/release_notes/v0.18.2.md +++ /dev/null @@ -1,13 +0,0 @@ -# Version 0.18.2 (March 29, 2021) - -## What's new? - -- Use the white background only for Philips TIFF file. - - Generic TIFF file would have the black background by default. - -## Fixes - -- Fix upside-downed image for TIFF file if the image is not RGB & tiled image with JPEG/Deflate-compressed tiles. - - Use slow path if the image is not RGB & tiled image with JPEG/Deflate-compressed tiles. - - Show an error message if the out-of-boundary cases are requested with the slow path. - - `ValueError: Cannot handle the out-of-boundary cases for a non-RGB image or a non-Jpeg/Deflate-compressed image.` diff --git a/python/cucim/docs/release_notes/v0.18.3.md b/python/cucim/docs/release_notes/v0.18.3.md deleted file mode 100644 index c3dead3c1..000000000 --- a/python/cucim/docs/release_notes/v0.18.3.md +++ /dev/null @@ -1,5 +0,0 @@ -# Version 0.18.3 (April 16, 2021) - -## Fixes - -- Fix memory leaks that occur when reading completely out-of-boundary regions. diff --git a/python/cucim/docs/release_notes/v0.19.0.md b/python/cucim/docs/release_notes/v0.19.0.md deleted file mode 100644 index 00b2b5083..000000000 --- a/python/cucim/docs/release_notes/v0.19.0.md +++ /dev/null @@ -1,7 +0,0 @@ -# Version 0.19.0 (April 19, 2021) - -## What's new? - -- The first release of cuClaraImage + [cupyimg](https://github.com/mritools/cupyimg) as a single project `cuCIM`. - - `cucim.skimage` package is added from `cupyimg`. - - CuPy (>=9.0.0b3), scipy, scikit-image is required to use cuCIM's scikit-image-compatible API. diff --git a/python/cucim/docs/release_notes/v0.2.0.md b/python/cucim/docs/release_notes/v0.2.0.md deleted file mode 100644 index e5817c379..000000000 --- a/python/cucim/docs/release_notes/v0.2.0.md +++ /dev/null @@ -1,32 +0,0 @@ -# Version 0.2.0 (December 18, 2020) - -## What's new? - -The following features are implemented. -- Make it work without CUDA runtime installed - - CUDA 11.0 runtime is embedded in the .whl file -- Develop a wrapper for cufile API - - Refer to `Accessing File with GDS` (/notebooks/Accessing_File_with_GDS.html) notebook - - Did some experiments on accessing TIFF files (see `File-access Experiments on TIFF File` (/notebooks/File-access_Experiments_on_TIFF.html) notebook) -- Support loading [Philips TIFF](https://openslide.org/formats/philips/) files - - Loading multi-resolution images and associated images (such as 'macro' and 'label') from TIFF Image File Directory (IFD) are available - - Please see `Basic Usage` (/notebooks/Basic_Usage.html#associated-images) notebook to know how to access the associated images. - - ```{admonition} Characteristic of Philips TIFF format - As specified in [Philips format](https://openslide.org/formats/philips/), - - "slides may omit pixel data for TIFF tiles not in an ROI; this is represented as a TileOffset of 0 and a TileByteCount of 0. When such tiles are downsampled into a tile that does contain pixel data, their contents are rendered as white pixels." - - For the above reason, some Philips TIFF images can actually hold important information (‘tiles that are not ROIs or tissues’) which can expedite pre-processing by discarding unnecessarily tiles. Due to feature parity with Openslide, cuClaraImage also renders such tiles as white pixels. Please let us know and suggest APIs for getting the information if such non-ROI region information is useful to you. - ``` - - The following tasks remain for feature-parity with OpenSlide - - Support Philips TIFF associated image from metadata - - Expose XML metadata of the Philips TIFF file as JSON -- Provide an example/plan for the interoperability with DALI - - Created a notebook for the feasibility and plan (see `Working with DALI` (/notebooks/Working_with_DALI.html) notebook) - -## Fixes/Improvements - -- Fix again for the error that occurs when loading a TIFF image that has `TIFFTAG_JPEGTABLES` tag - - `ERROR in line 126 while reading JPEG header tables: Not a JPEG file: starts with 0x01 0x00` message can be shown -- Force-reinstall cucim Python package in the Tox environment whenever `gen_docs` or `gen_docs_dev` command is executed diff --git a/python/cucim/docs/release_notes/v0.3.0.md b/python/cucim/docs/release_notes/v0.3.0.md deleted file mode 100644 index bac97083b..000000000 --- a/python/cucim/docs/release_notes/v0.3.0.md +++ /dev/null @@ -1,53 +0,0 @@ -# Version 0.3.0 (February 16, 2021) - -## What's new? - -- A new name and namespace (currently `cuClaraImage` and `cucim`) will be picked in `v0.4.0` once it's finalized -- Add metadata and associated images for Philips TIFF Format - - Support Philips TIFF associated image from XML metadata -- Expose metadata of the image as JSON - - `raw_metadata` property returns the image description of the first IFD in the TIFF image - - `resolution_dim_start` property of `CuImage` is removed - - `physical_pixel_size` property is renamed to `spacing` - - `ndim`/`origin`/`direction`/`coord_sys`/`spacing_units` properties are added - - Please see `Basic Usage` (/notebooks/Basic_Usage.html#see-metadata) notebook to know how to access metadata. -- Support reading out of boundary region - - `read_region()` method now accepts a region that is out of the image boundary - - `size` parameter accepts values that are up to the size of the highest-resolution image - - The out of the boundary area would be filled with the white color -- Showcase the interoperability with DALI - - Please see `Working with DALI` (/notebooks/Working_with_DALI.html) notebook - -## Fixes/Improvements - -- Fix wrong parameter interpretation (`size` in `read_region()` method). Now only `location` is level-0 based coordinates (using the level-0 reference frame). `size` is output image size. (Thanks `@Behrooz Hashemian`!) -- Static link with cufile when [libcufile.a is available](https://docs.google.com/document/d/1DQ_T805dlTcDU9bGW32E2ak5InX8iUcNI7Tq_lXAtLc/edit?ts=5f90bc5f) -- Implemented but disabled for now -- Fix a memory leak for cuslide::tiff::TIFF object (248 bytes) in CuImage class. -- Fix incorrect method visibility in a plugin file (.so) -- Replace malloc with better allocator for small-sized memory - - Use a custom allocator(pmr) for metadata data -- Copy data using `std::vector::insert()` instead of `std::vector::push_back()` - - Small improvement (when opening TIFF file), but benchmark result showed that time for assigning 50000 tile offset/size (uint64_t) is reduced from 118 us to 8 us -- Parameterize input library/image for testing -- Update test input path - - Add test data under `test_data/private` : See `test_data/README.md` file. -- Setup development environment with VSCode (in addition to CLion) -- Use a VSCode plugin for local test execution - - Now it uses `matepek.vscode-catch2-test-adapter` extension - - -- Prevent relative path problem of .so with no DT_SONAME -- Refactoring - - Add Development environment for VSCode - - Update run script - - Add settings for VSCode - - Refactor CMakeLists.txt - - Add definition `_GLIBCXX_USE_CXX11_ABI=0` to all sub directories - - Compile multiple architectures for CUDA Kernels - - Parameterize input files for tests - - Add `test_data` folder for test data - - plugin folder is from `CUCIM_TEST_PLUGIN_PATH` environment variable now (with static plugin name (cucim.kit.cuslide@0.3.0.so)) - - Move cucim_malloc() to memory_manager.cu - -## Limitations - -- Some metadata (`origin`/`direction`/`coord_sys`/`spacing`/`spacing_units`) doesn't have correct values for now. diff --git a/python/cucim/docs/requirements.txt b/python/cucim/docs/requirements.txt deleted file mode 100644 index f211153ad..000000000 --- a/python/cucim/docs/requirements.txt +++ /dev/null @@ -1,23 +0,0 @@ -Sphinx==4.5.0 -sphinx-autobuild -myst-parser -sphinx-book-theme -numpy -matplotlib -ipywidgets -pandas -nbclient -myst-nb -sphinx-togglebutton -sphinx-copybutton -plotly<5 -sphinxcontrib-bibtex<2.0.0 # https://github.com/executablebooks/jupyter-book/issues/1137 -sphinx-thebe -sphinx-panels -ablog -docutils==0.16 # 0.17 causes error. https://github.com/executablebooks/MyST-Parser/issues/343 -pydata_sphinx_theme -sphinxemoji -cupy-cuda110 -scipy -scikit-image diff --git a/python/cucim/docs/roadmap/index.md b/python/cucim/docs/roadmap/index.md deleted file mode 100644 index 7ac8596a0..000000000 --- a/python/cucim/docs/roadmap/index.md +++ /dev/null @@ -1,370 +0,0 @@ -# Roadmap - - - - - - -```{eval-rst} -The following list is on the road |:smile:| -``` - -## cuCIM - -### {fa}`calendar-alt,text-info mr-1` `v0.1` - -- {fa}`check,text-success mr-1` Abstract C++ API -- [v0.1.0](../release_notes/v0.1.0.md) -- {fa}`check,text-success mr-1` Benchmark with openslide (for generic tiff file) : link -- [v0.1.0](../release_notes/v0.1.0.md) -- {fa}`check,text-success mr-1` Usage with C++ API -- [v0.1.0](../release_notes/v0.1.0.md) -- {fa}`check,text-success mr-1` Implement Python API -- [v0.1.0](../release_notes/v0.1.0.md) -- {fa}`check,text-success mr-1` Usage with Python API -- [v0.1.0](../release_notes/v0.1.0.md) - 1. Setup document/build system - 1. Package it -- {fa}`check,text-success mr-1` Sort resolution levels (level 0: the largest resolution) for `CuImage::read_region()` method -- [v0.1.1](../release_notes/v0.1.1.md) -- {fa}`check,text-success mr-1` Fix a crash that occurs when opening a non-existing file -- [v0.1.1](../release_notes/v0.1.1.md) -- {fa}`check,text-success mr-1` Fix an error that occurs when loading a TIFF image that has `TIFFTAG_JPEGTABLES` tag -- [v0.1.1](../release_notes/v0.1.1.md) - - `Quantization table 0x00 was not defined` message can be shown -- {fa}`check,text-success mr-1` Sort resolution levels (level 0: the largest resolution) for `CuImage::read_region()` method -- [v0.1.1](../release_notes/v0.1.1.md) -- {fa}`check,text-success mr-1` Pass SWIPAT -- [v0.1.1](../release_notes/v0.1.1.md) -- {fa}`check,text-success mr-1` Ignore link check for relative link with header that starts with `/` or `..` -- [v0.1.1](../release_notes/v0.1.1.md) - -### {fa}`calendar-alt,text-info mr-1` `v0.2` - -- {fa}`check,text-success mr-1` Make it work with various CUDA versions -- [v0.2.0](../release_notes/v0.2.0.md) -- {fa}`check,text-success mr-1` Develop a wrapper for cufile API -- [v0.2.0](../release_notes/v0.2.0.md) -- {fa}`check,text-success mr-1` Support loading [Philips TIFF](https://openslide.org/formats/philips/) files - - {fa}`check,text-success mr-1` Support Philips TIFF multi-resolution images -- [v0.2.0](../release_notes/v0.2.0.md) - - {fa}`check,text-success mr-1` Support Philips TIFF associated image from IFD -- [v0.2.0](../release_notes/v0.2.0.md) -- {fa}`check,text-success mr-1` Provide an example/plan for the interoperability with DALI -- [v0.2.0](../release_notes/v0.2.0.md) -- {fa}`check,text-success mr-1` Fix again for the error that occurs when loading a TIFF image that has `TIFFTAG_JPEGTABLES` tag -- [v0.2.0](../release_notes/v0.2.0.md) -- {fa}`check,text-success mr-1` Force-reinstall cucim Python package in the Tox environment whenever `gen_docs` or `gen_docs_dev` command is executed -- [v0.2.0](../release_notes/v0.2.0.md) - -### {fa}`calendar-alt,text-info mr-1` `v0.3` - -- {fa}`check,text-success mr-1` Add metadata and associated images for Philips TIFF Format - - {fa}`check,text-success mr-1` Support Philips TIFF associated image from XML metadata -- [v0.3.0](../release_notes/v0.3.0.md) -- {fa}`check,text-success mr-1` Expose metadata of the image as JSON -- [v0.3.0](../release_notes/v0.3.0.md) -- {fa}`check,text-success mr-1` Support reading out of boundary region -- [v0.3.0](../release_notes/v0.3.0.md) -- {fa}`check,text-success mr-1` Showcase the interoperability with DALI -- [v0.3.0](../release_notes/v0.3.0.md) - - -### {fa}`calendar-alt,text-info mr-1` `v0.18` - -- {fa}`check,text-success mr-1` Support Deflate(zlib)-compressed RGB Tiff Image -- [v0.18.0](../release_notes/v0.18.0.md) -- {fa}`check,text-success mr-1` Change the namespaces (`cuimage` to `cucim`) -- [v0.18.0](../release_notes/v0.18.0.md) - -### {fa}`calendar-alt,text-info mr-1` `v0.19` - -- Refactor the cupyimg package to incorporate it in the adaption layer of cuCIM. Change the namespaces -- Support `__cuda_array_interface__` and DLPack object in Python API -- Support loading data to CUDA memory -- Implement cache mechanism for tile-based image formats - -### {fa}`calendar-alt,text-info mr-1` `v0.20` - -- Make use of nvJPEG to decode TIFF Files -- Support .svs format with nvJPEG2000 -- Design a plug-in mechanism for developing CUDA based 2D/3D imaging filters -- Implement a filter (example: Otsu Thresholding) -- Support loading MHD files - -### {fa}`calendar-alt,text-info mr-1` `v0.21` - -- Support JPEG, Jpeg 2000, PNG, BMP formats -- Support MIRAX/3DHISTECH (.mrxs) format -- Support LEICA (.scn) format - -### {fa}`calendar-alt,text-info mr-1` `v0.22` - -- Design a CT bone segmentation filter -- Provide a robust CI/CD system -- Define KPIs and publish report -- Update project to use the latest [Carbonite SDK](https://docs.omniverse.nvidia.com/kit/docs/carbonite/latest/docs/carb/Framework.html) for supporting plug-in architecture - -## TODOs - -### Image Format - -#### Generic TIFF(.tif) - -- {fa}`check,text-success mr-1` Access image data through container() API (in C++) or as a numpy array (using `__array_interface__` in Python) -- [v0.1.1](../release_notes/v0.1.1.md) -- {fa}`check,text-success mr-1` Fix a crash that occurs when opening a non-existing file -- [v0.1.1](../release_notes/v0.1.1.md) -- {fa}`check,text-success mr-1` Fix an error that occurs when loading a TIFF image that has `TIFFTAG_JPEGTABLES` tag -- [v0.1.1](../release_notes/v0.1.1.md) - - `Quantization table 0x00 was not defined` message can be shown -- {fa}`check,text-success mr-1` Fix again for the error that occurs when loading a TIFF image that has `TIFFTAG_JPEGTABLES` tag -- [v0.2.0](../release_notes/v0.2.0.md) - - `ERROR in line 126 while reading JPEG header tables: Not a JPEG file: starts with 0x01 0x00` message can be shown -- {fa}`check,text-success mr-1` Expose metadata of the TIFF file as JSON -- [v0.3.0](../release_notes/v0.3.0.md) -- {fa}`check,text-success mr-1` Support reading out of boundary region -- [v0.3.0](../release_notes/v0.3.0.md) -- {fa}`check,text-success mr-1` Support Deflate(zlib)-compressed RGB Tiff Image -- [v0.18.0](../release_notes/v0.18.0.md) -- Implement cache mechanism for tile-based image formats -- [v0.19.1](../release_notes/v0.19.1.md) -- Use CuFileDriver class for reading files -- Make use of nvJPEG to decode TIFF Files -- [v0.20.0](../release_notes/v0.20.0.md) - -- Remove hard-coded metadata (Fill correct values for `cucim::io::format::ImageMetadataDesc`) - - {fa}`check,text-success mr-1` `resolutions` -- [v0.1.1](../release_notes/v0.1.1.md) - - `metadata` -- Check if the `tile_rester` memory is freed by jpeg-turbo or not - - {fa}`check,text-success mr-1` `cpp/plugins/cucim.kit.cuslide/src/cuslide/tiff/ifd.cpp:365` in `IFD::read_region_tiles_libjpeg()` -- [v0.3.0](../release_notes/v0.3.0.md) - - `cpp/plugins/cucim.kit.cuslide/src/cuslide/cuslide.cpp:123` in `parser_parse` -- [v0.19.1](../release_notes/v0.19.1.md) -- Fill correct metadata information for `CuImage::read_region()` - - `cpp/src/cucim.cpp:417` -- [v0.19.1](../release_notes/v0.19.1.md) -- Check and use `ifd->samples_per_pixel()` once we can get RGB data instead of RGBA - - `cpp/plugins/cucim.kit.cuslide/src/cuslide/tiff/ifd.cpp:280` in `IFD::read_region_tiles_libjpeg()` -- [v0.19.1](../release_notes/v0.19.1.md) -- Consider endianness of the .tif file - - `cpp/plugins/cucim.kit.cuslide/src/cuslide/tiff/ifd.cpp:329` in `IFD::read_region_tiles_libjpeg()` -- Consider tile's depth tag - - `cpp/plugins/cucim.kit.cuslide/src/cuslide/tiff/ifd.cpp:329` in `IFD::read_region_tiles_libjpeg()` -- Make `file_handle_` object to pointer - - `cpp/plugins/cucim.kit.cuslide/src/cuslide/tiff/tiff.cpp:50` in `TIFF::TIFF()` -- Remove assumption of sub-resolution dims to 2 - - `cpp/plugins/cucim.kit.cuslide/src/cuslide/tiff/tiff.cpp:140` in `TIFF::read()` - -#### [Philips TIFF](https://openslide.org/formats/philips/) (.tif) - -- {fa}`check,text-success mr-1` Support Philips TIFF multi-resolution images -- [v0.2.0](../release_notes/v0.2.0.md) -- {fa}`check,text-success mr-1` Support Philips TIFF associated image from IFD -- [v0.2.0](../release_notes/v0.2.0.md) -- {fa}`check,text-success mr-1` Support Philips TIFF associated image from XML metadata -- [v0.3.0](../release_notes/v0.3.0.md) -- {fa}`check,text-success mr-1` Expose XML metadata of the Philips TIFF file as JSON -- [v0.3.0](../release_notes/v0.3.0.md) - -#### .mhd - -- Support loading MHD files -- [v0.20.0](../release_notes/v0.20.0.md) - -#### .svs - -- Support .svs format with nvJPEG2000 -- [v0.20.0](../release_notes/v0.20.0.md) - -#### .png - -- Support .png with [libspng](https://github.com/randy408/libspng/) -- [v0.21.0](../release_notes/v0.21.0.md) - - **libspng** is faster than **libpng** (but doesn't support encoding) - -#### .jpg - -- Support .jpg with libjpeg-turbo and nvJPEG -- [v0.21.0](../release_notes/v0.21.0.md) - -#### .jp2/.j2k - -- Support .jp2/.j2k files with OpenJpeg and nvJPEG2000 -- [v0.21.0](../release_notes/v0.21.0.md) - -#### .bmp - -- Support .bmp file natively -- [v0.21.0](../release_notes/v0.21.0.md) - -#### .mrxs - -- Support MIRAX/3DHISTECH (.mrxs) format -- [v0.21.0](../release_notes/v0.21.0.md) - -#### .scn - -- Support LEICA (.scn) format -- [v0.21.0](../release_notes/v0.21.0.md) - -#### .dcm - -- Support DICOM format -- Support reading segmentation image instead of main pixel array - - `examples/cpp/dicom_image/main.cpp:37` - -#### .iSyntax - -- Support Philips iSyntax format - - - - - -### Image Filter - -#### Basic Filter - -- Design a plug-in mechanism for developing CUDA based 2D/3D imaging filters -- [v0.20.0](../release_notes/v0.20.0.md) -- Implement a filter (example: Otsu Thresholding) -- [v0.20.0](../release_notes/v0.20.0.md) - -#### Medical-specific Filter - -- Design a CT bone segmentation filter -- [v0.22.0](../release_notes/v0.22.0.md) - -### Performance Improvements - -- {fa}`check,text-success mr-1` Copy data using `std::vector::insert()` instead of `std::vector::push_back()` -- [v0.3.0](../release_notes/v0.3.0.md) - - `cpp/plugins/cucim.kit.cuslide/src/cuslide/tiff/ifd.cpp:78` in `IFD::IFD()` - - `cpp/plugins/cucim.kit.cuslide/src/cuslide/tiff/ifd.cpp:98` in `IFD::IFD()` - - Benchmark result showed that time for assigning 50000 tile offset/size (uint64_t) is reduced from 118 us to 8 us. -- {fa}`check,text-success mr-1` Replace malloc with better allocator for small-sized memory -- [v0.3.0](../release_notes/v0.3.0.md) - - Use a custom allocator(pmr) for metadata data. -- Try to use `__array_struct__`. Access to array interface could be faster - - - - Check the performance difference between python int vs python long later - - `python/pybind11/cucim_py.cpp:234` in `get_array_interface()` -- Check performance - - `cpp/plugins/cucim.kit.cuslide/src/cuslide/tiff/ifd.cpp:122` in `IFD::read()` : string concatenation - -### GPUDirect-Storage (GDS) Support - -- {fa}`check,text-success mr-1` Develop a wrapper for cufile API -- [v0.2.0](../release_notes/v0.2.0.md) -- {fa}`check,text-success mr-1` Static link with cufile when [libcufile.a is available](https://docs.google.com/document/d/1DQ_T805dlTcDU9bGW32E2ak5InX8iUcNI7Tq_lXAtLc/edit?ts=5f90bc5f) -- [v0.3.0](../release_notes/v0.3.0.md) - -### Interoperability - -- {fa}`check,text-success mr-1` Provide an example/plan for the interoperability with DALI -- [v0.2.0](../release_notes/v0.2.0.md) -- {fa}`check,text-success mr-1` Showcase the interoperability with DALI -- [v0.3.0](../release_notes/v0.3.0.md) -- Support `__cuda_array_interface__` and DLPack object in Python API -- [v0.19.1](../release_notes/v0.19.1.md) - - https://docs.cupy.dev/en/stable/reference/interoperability.html#dlpack - - https://github.com/pytorch/pytorch/pull/11984 -- Refactor the cupyimg package to incorporate it in the adaption layer of cuCIM. Change the namespaces -- [v0.19.0](../release_notes/v0.19.0.md) - - Implement/expose `scikit-image`-like image loading APIs (such as `imread`) and filtering APIs for cuCIM library by using cuCIM's APIs -- Support DALI's CPU/GPU Tensor: -- Support loading data to CUDA memory -- [v0.19.1](../release_notes/v0.19.1.md) -- Consider adding `to_xxx()` methods in Python API - - `examples/python/tiff_image/main.py:125` -- Support byte-like object for CuImage object so that the following method works -- [v0.19.1](../release_notes/v0.19.1.md) - ```python - from PIL import Image - ... - #np_img_arr = np.asarray(region) - #Image.fromarray(np_img_arr) - - Image.fromarray(region) - # /usr/local/lib/python3.6/dist-packages/PIL/Image.py in frombytes(self, data, decoder_name, *args) - # 792 d = _getdecoder(self.mode, decoder_name, args) - # 793 d.setimage(self.im) - # --> 794 s = d.decode(data) - # 795 - # 796 if s[0] >= 0: - # TypeError: a bytes-like object is required, not 'cucim._cucim.CuImage' - ``` -- Provide universal cucim adaptors for DALI (for cucim::io::format::IImageFormat and cucim::filter::IImageFilter interfaces) -- Support pretty display for IPython(Jupyter Notebook) - - https://ipython.readthedocs.io/en/stable/config/integrating.html#integrating-your-objects-with-ipython - -### Pipeline - -- Use app_dp_sample pipeline to convert input image(.svs) of Nuclei segmentation pipeline(app_dp_nuclei) to .tif image - - Load .tif file using cuCIM for Nuclei segmentation pipeline - -### Python API - -- Feature parity with OpenSlide -- Add context manager for CuImage class (for `close()` method) -- [v0.19.1](../release_notes/v0.19.1.md) - -### C++ API - -- Design filtering API (which can embrace CuPy/CVCore/CuPyImg/OpenCV/scikit-image/dask-image) -- Feature parity with OpenSlide - -- {fa}`check,text-success mr-1` Sort resolution levels (level 0: the largest resolution) for `CuImage::read_region()` method -- [v0.1.1](../release_notes/v0.1.1.md) - - Add `TIFF::level_ifd(size_t level_index)` method -- {fa}`check,text-success mr-1` Support `metadata` and `raw_metadata` properties/methods -- [v0.3.0](../release_notes/v0.3.0.md) - - Implement `CuImage::metadata()` with JSON library (Folly or Modern JSON) - - `cpp/src/cucim.cpp:238` -- `ImageMetadataDesc` struct - - {fa}`check,text-success mr-1` `resolution_dim_start` field: Reconsider its use (may not be needed) -- [v0.3.0](../release_notes/v0.3.0.md) - - `cpp/include/cucim/io/format/image_format.h:53` - - `channel_names` field : `S`, `T`, and other dimension can have names so need to be generalized - - `cpp/include/cucim/io/format/image_format.h:51` -- `numpy_dtype()` method - - Consider bfloat16: - - Consider other byte-order (currently, we assume `little-endian`) - - `cpp/plugins/cucim.kit.cuslide/src/cuslide/tiff/tiff.cpp:53) - - `cpp/include/cucim/memory/dlpack.h:41` -- `checker_is_valid()` method - - Add `buf_size` parameter and implement the method - - `cpp/plugins/cucim.kit.cuslide/src/cuslide/cuslide.cpp:68` - -- Consider default case (how to handle -1 index?) - - `cpp/src/io/device.cpp` in `Device::Device()` -- Implement `Device::parse_type()` - - `cpp/src/io/device.cpp:81` -- Implement `Device::validate_device()` - - `cpp/src/io/device.cpp:116` - -- Check illegal characters for `DimIndices::DimIndices()` - - `cpp/src/cucim.cpp:35` - - `cpp/src/cucim.cpp:46` - -- Implement `detect_format()` method - - `cpp/src/cucim.cpp:103` -- Detect available format for the file path - - Also consider if the given file path is folder path (DICOM case) - - `cpp/src/cucim.cpp:117` in `CuImage::CuImage()` -- Implement `CuImage::CuImage(const filesystem::Path& path, const std::string& plugin_name)` - - `cpp/src/cucim.cpp:128` -- Implement `CuImage::dtype()` - - Support string conversion like Device class - - `cpp/src/cucim.cpp:283` - -### Build - -- Check if `CMAKE_EXPORT_PACKAGE_REGISTRY` is duplicate and remove it - - `cucim/cpp/plugins/cucim.kit.cuslide/CMakeLists.txt:255` -- Install other dependencies for libtiff so that other compression methods are available - - `cucim/Dockerfile:32` -- {fa}`check,text-success mr-1` Setup development environment with VSCode (in addition to CLion) -- [v0.3.0](../release_notes/v0.3.0.md) -- Use prebuilt libraries for dependencies - -### Test - -- {fa}`check,text-success mr-1` Parameterize input library/image -- [v0.3.0](../release_notes/v0.3.0.md) - - `/ssd/repo/cucim/cpp/tests/test_read_region.cpp:69` in `Verify read_region` - - `/ssd/repo/cucim/cpp/tests/test_cufile.cpp:79` in `Verify libcufile usage` -- {fa}`check,text-success mr-1` Use a VSCode plugin for local test execution -- [v0.3.0](../release_notes/v0.3.0.md) - - `matepek.vscode-catch2-test-adapter` extension - - - -### Platform - -- Support Windows (currently only Linux package is available) - -### Package & CI/CD - -- {fa}`check,text-success mr-1` Make it work with various CUDA versions -- [v0.2.0](../release_notes/v0.2.0.md) - - Currently, it is linked to CUDA 11.0 library - - Refer to PyTorch's PyPi package - - The PyPi package embeds CUDA runtime library. - - https://github.com/pytorch/pytorch/issues/47268#issuecomment-721996861 -- Move to Github Project -- Move `tox` setup from python folder to the project root folder -- Setup Conda recipe -- Setup automated test cases -- Provide a robust CI/CD system -- [v0.22.0](../release_notes/v0.22.0.md) -- Define KPIs and publish report -- [v0.22.0](../release_notes/v0.22.0.md) - -- Add license files to the package -- Package a separate CXX11 ABI library - - Currently, C++ library is forced to set `_GLIBCXX_USE_CXX11_ABI` to 0 due to [Dual ABI](https://gcc.gnu.org/onlinedocs/libstdc++/manual/using_dual_abi.html) problem - - `cpp/CMakeLists.txt:98` -- Support CPack - - `CMakeLists.txt:177` - -### Documentation - -- {fa}`check,text-success mr-1` Pass SWIPAT -- [v0.1.1](../release_notes/v0.1.1.md) -- Refine README.md and relevant documents for the project -- Move Sphinx docs to the project root folder -- Add C++ API document -- Add C++ examples to Jupyter Notebook - - Can install C++ Kernel: -- {fa}`check,text-success mr-1` Ignore link check for relative link with header that starts with `/` or `..` -- [v0.1.1](../release_notes/v0.1.1.md) - - `python/cucim/docs/conf.py:71` - - -- {fa}`check,text-success mr-1` Force-reinstall cucim Python package in the Tox environment whenever `gen_docs` or `gen_docs_dev` command is executed -- [v0.2.0](../release_notes/v0.2.0.md) - - -- Simplify method signatures in Python API Docs - - `cucim._cucim.CuImage` -> `cucim.CuImage` -- Use new feature to reference a cross-link with header (from v0.13.0 of [myst-parser](https://pypi.org/project/myst-parser/)) - - - - - - - -### Plugin-system (Carbonite) - -- Update project to use the latest [Carbonite SDK](https://docs.omniverse.nvidia.com/kit/docs/carbonite/latest/docs/carb/Framework.html) for supporting plug-in architecture -- [v0.22.0](../release_notes/v0.22.0.md) - - Migrate to use Carbonite SDK as it is - - Update to use Minimal Carbonite SDK - -- Handle errors and log error message once switched to use Carbonite SDK's built-in error routine - - `cpp/plugins/cucim.kit.cuslide/src/cuslide/tiff/ifd.cpp` : when reading field info - - `cpp/plugins/cucim.kit.cuslide/src/cuslide/tiff/ifd.cpp` in `IFD::read()` : memory size check if `out_buf->data` has high-enough memory -- Get plugin name from file_path - - `cpp/src/core/cucim_plugin.cpp:53` in `Plugin::Plugin()` -- Generalize `CuImage::ensure_init()` - - 'LINUX' path separator is used. Need to make it generalize once filesystem library is available - - `cucim/cpp/src/cucim.cpp:520` diff --git a/python/cucim/docs/spelling_wordlist.txt b/python/cucim/docs/spelling_wordlist.txt deleted file mode 100644 index f95eb78d8..000000000 --- a/python/cucim/docs/spelling_wordlist.txt +++ /dev/null @@ -1,11 +0,0 @@ -builtin -builtins -classmethod -staticmethod -classmethods -staticmethods -args -kwargs -callstack -Changelog -Indices diff --git a/python/cucim/docs/user_guide/index.md b/python/cucim/docs/user_guide/index.md deleted file mode 100644 index cd3d45227..000000000 --- a/python/cucim/docs/user_guide/index.md +++ /dev/null @@ -1 +0,0 @@ -# User Guide diff --git a/python/cucim/tox.ini b/python/cucim/tox.ini deleted file mode 100644 index 04fe6475b..000000000 --- a/python/cucim/tox.ini +++ /dev/null @@ -1,111 +0,0 @@ -[testenv:bootstrap] -deps = - jinja2 - matrix - tox -skip_install = true -commands = - python ci/bootstrap.py --no-env -passenv = - * -; a generative tox configuration, see: https://tox.readthedocs.io/en/latest/config.html#generative-envlist - -[tox] -envlist = - clean, - check, - docs, - docs-dev, - release, - {py35,py36,py37,py38,py39,pypy,pypy3}, - report -ignore_basepython_conflict = true - -[testenv] -basepython = - pypy: {env:TOXPYTHON:pypy} - pypy3: {env:TOXPYTHON:pypy3} - py35: {env:TOXPYTHON:python3.5} - py36: {env:TOXPYTHON:python3.6} - py37: {env:TOXPYTHON:python3.7} - py38: {env:TOXPYTHON:python3.8} - py39: {env:TOXPYTHON:python3.9} - {bootstrap,clean,check,report,docs,docs-dev,release,codecov}: {env:TOXPYTHON:python3} -setenv = - PYTHONPATH={toxinidir}/tests - PYTHONUNBUFFERED=yes -passenv = - * -usedevelop = false -deps = - pytest - pytest-travis-fold - pytest-cov -commands = - {posargs:pytest --cov --cov-report=term-missing -vv tests} - -[testenv:check] -deps = - docutils - check-manifest - black - ruff - readme-renderer - pygments - isort - twine -skip_install = true - -; https://packaging.python.org/guides/making-a-pypi-friendly-readme/#validating-restructuredtext-markup -commands = - twine check dist/*.whl - check-manifest {toxinidir} - ruff . - black --check . - isort --verbose --check-only --diff --filter-files . - -[testenv:docs] -; Installing from `sdist` package instead of `setup.py develop` (https://tox.readthedocs.io/en/latest/config.html#conf-usedevelop) -usedevelop = false -deps = - -r{toxinidir}/docs/requirements.txt -commands = - sphinx-build -E -b doctest docs {posargs:-dist/docs} - sphinx-build -E -b html docs {posargs:-dist/docs} - sphinx-build -b linkcheck docs {posargs:-dist/docs} - -[testenv:docs-dev] -; Installing from `sdist` package instead of `setup.py develop` (https://tox.readthedocs.io/en/latest/config.html#conf-usedevelop) -usedevelop = false -deps = - -r{toxinidir}/docs/requirements.txt -commands = - ; https://pypi.org/project/sphinx-autobuild/ - sphinx-autobuild {posargs:---host 0.0.0.0 --port 9999 docs dist/docs} - -[testenv:release] -usedevelop = false -allowlist_externals = /bin/bash -commands = - /bin/bash -c "{posargs}" - -[testenv:codecov] -deps = - codecov -skip_install = true -commands = - codecov [] - -[testenv:report] -deps = - coverage -skip_install = true -commands = - coverage report - coverage html - -[testenv:clean] -commands = coverage erase -skip_install = true -deps = - coverage From a3c0f13551d9d7545381774b87a1a80dbd5d27c1 Mon Sep 17 00:00:00 2001 From: James Lamb Date: Thu, 11 Jan 2024 10:59:33 -0600 Subject: [PATCH 11/21] refactor CUDA versions in dependencies.yaml (#671) Contributes to https://github.com/rapidsai/build-planning/issues/7. Proposes splitting the `cuda-version` dependency in `dependencies.yaml` out to its own thing, separate from the bits of the CUDA Toolkit this project needs. ### Benefits of this change * prevents accidental inclusion of multiple `cuda-version` version in environments * reduces update effort (via enabling more use of globs like `"12.*"`) * improves the chance that errors like "`conda` recipe is missing a dependency" are caught in CI Authors: - James Lamb (https://github.com/jameslamb) Approvers: - https://github.com/jakirkham - Jake Awe (https://github.com/AyodeAwe) URL: https://github.com/rapidsai/cucim/pull/671 --- dependencies.yaml | 24 ++++++++++++++++-------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/dependencies.yaml b/dependencies.yaml index 1f1a3dfd3..d95d62cd2 100644 --- a/dependencies.yaml +++ b/dependencies.yaml @@ -7,7 +7,8 @@ files: arch: [x86_64] includes: - build - - cudatoolkit + - cuda + - cuda_version - checks - docs - py_version @@ -16,7 +17,7 @@ files: test_python: output: none includes: - - cudatoolkit + - cuda_version - py_version - test_python checks: @@ -27,7 +28,7 @@ files: docs: output: none includes: - - cudatoolkit + - cuda_version - docs - py_version py_build: @@ -140,7 +141,7 @@ dependencies: - output_types: [conda, requirements] packages: - pre-commit - cudatoolkit: + cuda_version: specific: - output_types: conda matrices: @@ -148,26 +149,33 @@ dependencies: cuda: "11.2" packages: - cuda-version=11.2 - - cudatoolkit - matrix: cuda: "11.4" packages: - cuda-version=11.4 - - cudatoolkit - matrix: cuda: "11.5" packages: - cuda-version=11.5 - - cudatoolkit - matrix: cuda: "11.8" packages: - cuda-version=11.8 - - cudatoolkit - matrix: cuda: "12.0" packages: - cuda-version=12.0 + cuda: + specific: + - output_types: conda + matrices: + - matrix: + cuda: "11.*" + packages: + - cudatoolkit + - matrix: + cuda: "12.*" + packages: - cuda-cudart-dev - libnvjpeg-dev - libcufile-dev From 0bb1dd66e207d62fa542358e8ee6b2e8a2480274 Mon Sep 17 00:00:00 2001 From: Kyle Edwards Date: Fri, 12 Jan 2024 12:24:53 -0500 Subject: [PATCH 12/21] Remove usages of rapids-env-update (#673) Reference: https://github.com/rapidsai/ops/issues/2766 Replace rapids-env-update with rapids-configure-conda-channels, rapids-configure-sccache, and rapids-date-string. Authors: - Kyle Edwards (https://github.com/KyleFromNVIDIA) Approvers: - AJ Schmidt (https://github.com/ajschmidt8) URL: https://github.com/rapidsai/cucim/pull/673 --- ci/build_cpp.sh | 6 +++++- ci/build_python.sh | 6 +++++- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/ci/build_cpp.sh b/ci/build_cpp.sh index b0ef4a446..0ecf3d5bb 100755 --- a/ci/build_cpp.sh +++ b/ci/build_cpp.sh @@ -3,7 +3,11 @@ set -euo pipefail -source rapids-env-update +rapids-configure-conda-channels + +source rapids-configure-sccache + +source rapids-date-string export CMAKE_GENERATOR=Ninja diff --git a/ci/build_python.sh b/ci/build_python.sh index 58d8a8e00..35d3ed08c 100755 --- a/ci/build_python.sh +++ b/ci/build_python.sh @@ -3,7 +3,11 @@ set -euo pipefail -source rapids-env-update +rapids-configure-conda-channels + +source rapids-configure-sccache + +source rapids-date-string export CMAKE_GENERATOR=Ninja From 5aae7cd587f7ad0d01310d0cb5128d44af5e9db9 Mon Sep 17 00:00:00 2001 From: Ray Douglass <3107146+raydouglass@users.noreply.github.com> Date: Mon, 22 Jan 2024 11:35:08 -0500 Subject: [PATCH 13/21] Remove update to symlink (#674) Since `python/cucim/README.md` is a symlink to `README.md`, it should not be modified by the `update-version` script. Authors: - Ray Douglass (https://github.com/raydouglass) Approvers: - AJ Schmidt (https://github.com/ajschmidt8) - https://github.com/jakirkham --- ci/release/update-version.sh | 1 - 1 file changed, 1 deletion(-) diff --git a/ci/release/update-version.sh b/ci/release/update-version.sh index e70df365d..3ccc1b71d 100755 --- a/ci/release/update-version.sh +++ b/ci/release/update-version.sh @@ -42,7 +42,6 @@ sed_runner "s/v${CURRENT_LONG_TAG}/v${NEXT_FULL_TAG}/g" python/cucim/docs/gettin sed_runner "s#cucim.kit.cuslide@${CURRENT_LONG_TAG}.so#cucim.kit.cuslide@${NEXT_FULL_TAG}.so#g" python/cucim/docs/getting_started/index.md sed_runner "s#cucim.kit.cuslide@${CURRENT_LONG_TAG}.so#cucim.kit.cuslide@${NEXT_FULL_TAG}.so#g" cucim.code-workspace sed_runner "s#branch-${CURRENT_MAJOR}.${CURRENT_MINOR}#branch-${NEXT_MAJOR}.${NEXT_MINOR}#g" README.md -sed_runner "s#branch-${CURRENT_MAJOR}.${CURRENT_MINOR}#branch-${NEXT_MAJOR}.${NEXT_MINOR}#g" python/cucim/README.md sed_runner "s#branch-${CURRENT_MAJOR}.${CURRENT_MINOR}#branch-${NEXT_MAJOR}.${NEXT_MINOR}#g" python/cucim/pyproject.toml for FILE in .github/workflows/*.yaml; do From b4827f78b1f955cc4580c621e24a59d19f32590a Mon Sep 17 00:00:00 2001 From: jakirkham Date: Mon, 22 Jan 2024 10:49:52 -0800 Subject: [PATCH 14/21] Fix CI issues (#676) This definition causes problems with newer CCCL versions. As we don't actually use it ourselves, go ahead and drop it to fix CI Authors: - https://github.com/jakirkham Approvers: - Gregory Lee (https://github.com/grlee77) URL: https://github.com/rapidsai/cucim/pull/676 --- .../cucim/src/cucim/skimage/_vendored/_ndimage_filters_core.py | 1 - 1 file changed, 1 deletion(-) diff --git a/python/cucim/src/cucim/skimage/_vendored/_ndimage_filters_core.py b/python/cucim/src/cucim/skimage/_vendored/_ndimage_filters_core.py index e1c8ade57..13cccd6b7 100644 --- a/python/cucim/src/cucim/skimage/_vendored/_ndimage_filters_core.py +++ b/python/cucim/src/cucim/skimage/_vendored/_ndimage_filters_core.py @@ -190,7 +190,6 @@ def _call_kernel( template<> struct std::is_floating_point : std::true_type {}; template<> struct std::is_signed : std::true_type {}; -template struct std::is_signed> : std::is_signed {}; """ From bf5a6f765de64b522ac060f92e24c52f69834c56 Mon Sep 17 00:00:00 2001 From: jakirkham Date: Wed, 24 Jan 2024 11:59:45 -0800 Subject: [PATCH 15/21] Fix CI (pt. 2) (#680) Fixes https://github.com/rapidsai/cucim/issues/664 * Pin RAPIDS packages to intended version (this helped find the `openslide` / `openslide-python` issue) * Validate wheels have the expected name (no `none` in them like with pure wheels) Authors: - https://github.com/jakirkham Approvers: - Gregory Lee (https://github.com/grlee77) - Jake Awe (https://github.com/AyodeAwe) URL: https://github.com/rapidsai/cucim/pull/680 --- ci/build_wheel.sh | 1 + ci/release/update-version.sh | 1 + ci/test_python.sh | 5 ++++- 3 files changed, 6 insertions(+), 1 deletion(-) diff --git a/ci/build_wheel.sh b/ci/build_wheel.sh index a39ebf502..8d3fcf941 100755 --- a/ci/build_wheel.sh +++ b/ci/build_wheel.sh @@ -47,5 +47,6 @@ python -m pip wheel . -w dist -vvv --no-deps --disable-pip-version-check mkdir -p final_dist python -m auditwheel repair -w final_dist dist/* +ls -1 final_dist | grep -vqz 'none' RAPIDS_PY_WHEEL_NAME="${package_name}_${RAPIDS_PY_CUDA_SUFFIX}" rapids-upload-wheels-to-s3 final_dist diff --git a/ci/release/update-version.sh b/ci/release/update-version.sh index 3ccc1b71d..d16562bf7 100755 --- a/ci/release/update-version.sh +++ b/ci/release/update-version.sh @@ -48,3 +48,4 @@ for FILE in .github/workflows/*.yaml; do sed_runner "/shared-workflows/ s/@.*/@branch-${NEXT_SHORT_TAG}/g" "${FILE}" done sed_runner "s/RAPIDS_VERSION_NUMBER=\".*/RAPIDS_VERSION_NUMBER=\"${NEXT_SHORT_TAG}\"/g" ci/build_docs.sh +sed_runner "s/RAPIDS_VERSION_NUMBER=\".*/RAPIDS_VERSION_NUMBER=\"${NEXT_SHORT_TAG}\"/g" ci/test_python.sh diff --git a/ci/test_python.sh b/ci/test_python.sh index 476671b19..3739acf2e 100755 --- a/ci/test_python.sh +++ b/ci/test_python.sh @@ -7,6 +7,8 @@ set -euo pipefail . /opt/conda/etc/profile.d/conda.sh +export RAPIDS_VERSION_NUMBER="24.02" + rapids-logger "Generate Python testing dependencies" rapids-dependency-file-generator \ --output conda \ @@ -33,7 +35,8 @@ rapids-print-env rapids-mamba-retry install \ --channel "${CPP_CHANNEL}" \ --channel "${PYTHON_CHANNEL}" \ - libcucim cucim + "libcucim=${RAPIDS_VERSION_NUMBER}" \ + "cucim=${RAPIDS_VERSION_NUMBER}" rapids-logger "Check GPU usage" nvidia-smi From d04887109cd12aebafad8cad214e43d39cbd1906 Mon Sep 17 00:00:00 2001 From: jakirkham Date: Fri, 26 Jan 2024 11:08:16 -0800 Subject: [PATCH 16/21] Consolidate test requirements in `dependencies.yaml` (#683) Instead of having test requirements in `requirements-test.txt` and `dependencies.yaml`, consolidate test requirements into `dependencies.yaml` and rely on that (and where it propagates) to handle installing test dependencies Authors: - https://github.com/jakirkham Approvers: - Ray Douglass (https://github.com/raydouglass) - Gregory Lee (https://github.com/grlee77) URL: https://github.com/rapidsai/cucim/pull/683 --- dependencies.yaml | 2 +- python/cucim/requirements-test.txt | 9 --------- run | 6 ++++-- 3 files changed, 5 insertions(+), 12 deletions(-) delete mode 100644 python/cucim/requirements-test.txt diff --git a/dependencies.yaml b/dependencies.yaml index d95d62cd2..24d3ddf90 100644 --- a/dependencies.yaml +++ b/dependencies.yaml @@ -254,10 +254,10 @@ dependencies: packages: - GPUtil>=1.4.0 - psutil>=5.8.0 + - pytest>=6.2.4 - pytest-cov>=2.12.1 - pytest-lazy-fixture>=0.6.3 - pytest-xdist - - pytest>=6.2.4 - tifffile>=2022.7.28 - pooch>=1.6.0 # needed to download scikit-image sample data - output_types: [conda] diff --git a/python/cucim/requirements-test.txt b/python/cucim/requirements-test.txt deleted file mode 100644 index 036edf6c5..000000000 --- a/python/cucim/requirements-test.txt +++ /dev/null @@ -1,9 +0,0 @@ -GPUtil>=1.4.0 -imagecodecs>=2021.6.8 -openslide-python>=1.3.0 -opencv-python-headless>=4.6 -psutil>=5.8.0 -pytest>=6.2.4 -pytest-cov>=2.12.1 -pytest-lazy-fixture>=0.6.3 -tifffile>=2022.7.28 diff --git a/run b/run index d0a7df295..c8bec742e 100755 --- a/run +++ b/run @@ -608,11 +608,13 @@ install_python_test_deps_() { # (https://github.com/rapidsai/cucim/pull/433) run_command pip install -r ${TOP}/python/cucim/requirements-test.txt else + pushd "${TOP}/python/cucim" if [ -n "${VIRTUAL_ENV}" ]; then - run_command pip3 install -r ${TOP}/python/cucim/requirements-test.txt + run_command pip3 install -e .[test] else - run_command pip3 install --user -r ${TOP}/python/cucim/requirements-test.txt + run_command pip3 install --user -e .[test] fi + popd fi hash -r } From 7d4373ba270547267121d1e1ae8f8ce587fdf57f Mon Sep 17 00:00:00 2001 From: jakirkham Date: Fri, 26 Jan 2024 13:56:50 -0800 Subject: [PATCH 17/21] Update OpenJPEG to 2.5.0 (#685) Authors: - https://github.com/jakirkham Approvers: - Gigon Bae (https://github.com/gigony) URL: https://github.com/rapidsai/cucim/pull/685 --- cpp/plugins/cucim.kit.cuslide/cmake/deps/libopenjpeg.cmake | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cpp/plugins/cucim.kit.cuslide/cmake/deps/libopenjpeg.cmake b/cpp/plugins/cucim.kit.cuslide/cmake/deps/libopenjpeg.cmake index 428b04f0f..b080554f9 100644 --- a/cpp/plugins/cucim.kit.cuslide/cmake/deps/libopenjpeg.cmake +++ b/cpp/plugins/cucim.kit.cuslide/cmake/deps/libopenjpeg.cmake @@ -18,7 +18,7 @@ if (NOT TARGET deps::libopenjpeg) FetchContent_Declare( deps-libopenjpeg GIT_REPOSITORY https://github.com/uclouvain/openjpeg.git - GIT_TAG v2.4.0 + GIT_TAG v2.5.0 GIT_SHALLOW TRUE ) FetchContent_GetProperties(deps-libopenjpeg) From caade438d4ce92c8c7c62eaa91359eb660c0dbc5 Mon Sep 17 00:00:00 2001 From: Gregory Lee Date: Mon, 29 Jan 2024 10:07:07 -0800 Subject: [PATCH 18/21] minor updates/fixes for consistency with scikit-image 0.22 (#670) This MR does not add any new features. It just has minor fixes ported from scikit-image 0.22. Most changed lines are from moving additional modules to use `__init__.pyi` files as for upstream scikit-image. Regarding introduction of `py.typed`, see [discussion on the scikit-image repo](https://github.com/scikit-image/scikit-image/pull/7073). The TLDR version is that it enables VS CODE (and potentially other IDEs) to use the `.pyi` files for intellisense capabilities. Those using mypy with strict type checking, may have to add an exclusion like the following to disable warnings about most of the cuCIM API which does not currently use type hints: ```toml [tool.mypy] strict = true untyped_calls_exclude = [ "cucim" ] ``` Authors: - Gregory Lee (https://github.com/grlee77) - https://github.com/jakirkham Approvers: - Gigon Bae (https://github.com/gigony) - https://github.com/jakirkham - Ray Douglass (https://github.com/raydouglass) URL: https://github.com/rapidsai/cucim/pull/670 --- .../all_cuda-118_arch-x86_64.yaml | 5 +- .../all_cuda-120_arch-x86_64.yaml | 5 +- conda/recipes/cucim/meta.yaml | 4 +- dependencies.yaml | 9 +- python/cucim/pyproject.toml | 6 +- python/cucim/src/cucim/py.typed | 0 .../src/cucim/skimage/_shared/filters.py | 47 ++-------- .../src/cucim/skimage/_vendored/_internal.py | 11 ++- .../src/cucim/skimage/color/colorconv.py | 10 +- .../skimage/color/tests/test_colorconv.py | 10 +- .../src/cucim/skimage/feature/__init__.py | 49 +--------- .../src/cucim/skimage/feature/__init__.pyi | 47 ++++++++++ .../cucim/src/cucim/skimage/feature/peak.py | 2 +- .../cucim/src/cucim/skimage/filters/ridges.py | 6 +- .../skimage/filters/tests/test_gaussian.py | 40 +------- .../filters/tests/test_thresholding.py | 28 ++++-- .../src/cucim/skimage/filters/thresholding.py | 27 ++++-- .../src/cucim/skimage/measure/__init__.py | 57 +---------- .../src/cucim/skimage/measure/__init__.pyi | 62 ++++++++++++ .../src/cucim/skimage/measure/_blur_effect.py | 10 +- .../src/cucim/skimage/measure/_regionprops.py | 18 ++-- .../cucim/src/cucim/skimage/measure/block.py | 7 +- .../skimage/measure/tests/test_regionprops.py | 26 ++++- .../src/cucim/skimage/morphology/__init__.py | 8 ++ .../cucim/skimage/registration/__init__.py | 5 +- .../cucim/skimage/registration/__init__.pyi | 8 ++ .../registration/_phase_cross_correlation.py | 94 ++++++++----------- .../tests/test_phase_cross_correlation.py | 11 ++- .../skimage/registration/tests/test_tvl1.py | 4 +- .../src/cucim/skimage/restoration/__init__.py | 13 +-- .../cucim/skimage/restoration/__init__.pyi | 16 ++++ .../skimage/restoration/deconvolution.py | 6 +- .../cucim/skimage/restoration/j_invariant.py | 14 ++- .../restoration/tests/test_j_invariant.py | 7 +- .../cucim/skimage/segmentation/__init__.py | 3 + .../src/cucim/skimage/transform/__init__.py | 59 +----------- .../src/cucim/skimage/transform/__init__.pyi | 62 ++++++++++++ .../src/cucim/skimage/transform/_geometric.py | 11 ++- .../src/cucim/skimage/transform/pyramids.py | 2 +- python/cucim/src/cucim/skimage/util/dtype.py | 4 +- 40 files changed, 444 insertions(+), 369 deletions(-) create mode 100644 python/cucim/src/cucim/py.typed create mode 100644 python/cucim/src/cucim/skimage/feature/__init__.pyi create mode 100644 python/cucim/src/cucim/skimage/measure/__init__.pyi create mode 100644 python/cucim/src/cucim/skimage/registration/__init__.pyi create mode 100644 python/cucim/src/cucim/skimage/restoration/__init__.pyi create mode 100644 python/cucim/src/cucim/skimage/transform/__init__.pyi diff --git a/conda/environments/all_cuda-118_arch-x86_64.yaml b/conda/environments/all_cuda-118_arch-x86_64.yaml index 53e8ad163..0cc4a61e2 100644 --- a/conda/environments/all_cuda-118_arch-x86_64.yaml +++ b/conda/environments/all_cuda-118_arch-x86_64.yaml @@ -28,7 +28,7 @@ dependencies: - nbsphinx - ninja - numpy>=1.21.3 -- numpydoc +- numpydoc>=1.5 - nvcc_linux-64=11.8 - openslide-python>=1.3.0 - pip @@ -41,8 +41,9 @@ dependencies: - pytest-xdist - pytest>=6.2.4 - python>=3.8,<3.11 +- pywavelets>=1.0 - recommonmark -- scikit-image>=0.19.0,<0.22.0a0 +- scikit-image>=0.19.0,<0.23.0a0 - scipy - sphinx<6 - sysroot_linux-64==2.17 diff --git a/conda/environments/all_cuda-120_arch-x86_64.yaml b/conda/environments/all_cuda-120_arch-x86_64.yaml index 1084ea629..221169e0c 100644 --- a/conda/environments/all_cuda-120_arch-x86_64.yaml +++ b/conda/environments/all_cuda-120_arch-x86_64.yaml @@ -28,7 +28,7 @@ dependencies: - nbsphinx - ninja - numpy>=1.21.3 -- numpydoc +- numpydoc>=1.5 - openslide-python>=1.3.0 - pip - pooch>=1.6.0 @@ -40,8 +40,9 @@ dependencies: - pytest-xdist - pytest>=6.2.4 - python>=3.8,<3.11 +- pywavelets>=1.0 - recommonmark -- scikit-image>=0.19.0,<0.22.0a0 +- scikit-image>=0.19.0,<0.23.0a0 - scipy - sphinx<6 - sysroot_linux-64==2.17 diff --git a/conda/recipes/cucim/meta.yaml b/conda/recipes/cucim/meta.yaml index 516cbee60..47606ab77 100644 --- a/conda/recipes/cucim/meta.yaml +++ b/conda/recipes/cucim/meta.yaml @@ -59,7 +59,7 @@ requirements: - libcucim ={{ version }} - numpy 1.21 - python - - scikit-image >=0.19.0,<0.22.0a0 + - scikit-image >=0.19.0,<0.23.0a0 - scipy run: - {{ pin_compatible('cuda-version', max_pin='x', min_pin='x') }} @@ -69,7 +69,7 @@ requirements: - lazy_loader >=0.1 - libcucim ={{ version }} - python - - scikit-image >=0.19.0,<0.22.0a0 + - scikit-image >=0.19.0,<0.23.0a0 - scipy run_constrained: - openslide-python >=1.3.0 diff --git a/dependencies.yaml b/dependencies.yaml index 24d3ddf90..60d482b1d 100644 --- a/dependencies.yaml +++ b/dependencies.yaml @@ -193,7 +193,7 @@ dependencies: packages: - ipython - nbsphinx - - numpydoc + - numpydoc>=1.5 - pydata-sphinx-theme - recommonmark # TODO: sphinx upper version limit can likely be removed after pydata 0.13 @@ -223,7 +223,7 @@ dependencies: - output_types: [conda, requirements, pyproject] packages: - lazy_loader>=0.1 - - scikit-image>=0.19.0,<0.22.0a0 + - scikit-image>=0.19.0,<0.23.0a0 - scipy - output_types: conda packages: @@ -260,6 +260,7 @@ dependencies: - pytest-xdist - tifffile>=2022.7.28 - pooch>=1.6.0 # needed to download scikit-image sample data + - pywavelets>=1.0 - output_types: [conda] packages: - imagecodecs>=2021.6.8 @@ -276,3 +277,7 @@ dependencies: - matplotlib - opencv-python-headless>=4.6 - click + - output_types: [pyproject] + packages: + # Already added to requirements via docs. This is for tests. + - numpydoc>=1.5 diff --git a/python/cucim/pyproject.toml b/python/cucim/pyproject.toml index 7af428bd4..b39c912a7 100644 --- a/python/cucim/pyproject.toml +++ b/python/cucim/pyproject.toml @@ -27,7 +27,7 @@ dependencies = [ "cupy-cuda11x>=12.0.0", "lazy_loader>=0.1", "numpy", - "scikit-image>=0.19.0,<0.22.0a0", + "scikit-image>=0.19.0,<0.23.0a0", "scipy", ] # This list was generated by `rapids-dependency-file-generator`. To make changes, edit ../../dependencies.yaml and run `rapids-dependency-file-generator`. classifiers = [ @@ -64,6 +64,7 @@ test = [ "click", "imagecodecs>=2021.6.8; platform_machine=='x86_64'", "matplotlib", + "numpydoc>=1.5", "opencv-python-headless>=4.6", "openslide-python>=1.3.0; platform_machine=='x86_64'", "pooch>=1.6.0", @@ -72,6 +73,7 @@ test = [ "pytest-lazy-fixture>=0.6.3", "pytest-xdist", "pytest>=6.2.4", + "pywavelets>=1.0", "tifffile>=2022.7.28", ] # This list was generated by `rapids-dependency-file-generator`. To make changes, edit ../../dependencies.yaml and run `rapids-dependency-file-generator`. developer = [ @@ -83,7 +85,7 @@ developer = [ docs = [ "ipython", "nbsphinx", - "numpydoc", + "numpydoc>=1.5", "pydata-sphinx-theme", "recommonmark", "sphinx<6", diff --git a/python/cucim/src/cucim/py.typed b/python/cucim/src/cucim/py.typed new file mode 100644 index 000000000..e69de29bb diff --git a/python/cucim/src/cucim/skimage/_shared/filters.py b/python/cucim/src/cucim/skimage/_shared/filters.py index 2892050d5..fe4c000ec 100644 --- a/python/cucim/src/cucim/skimage/_shared/filters.py +++ b/python/cucim/src/cucim/skimage/_shared/filters.py @@ -10,26 +10,7 @@ import cucim.skimage._vendored.ndimage as ndi -from .._shared.utils import _supported_float_type, convert_to_float, warn - - -class _PatchClassRepr(type): - """Control class representations in rendered signatures.""" - - def __repr__(cls): - return f"<{cls.__name__}>" - - -class ChannelAxisNotSet(metaclass=_PatchClassRepr): - """Signal that the `channel_axis` parameter is not set. - This is a proxy object, used to signal to `skimage.filters.gaussian` that - the `channel_axis` parameter has not been set, in which case the function - will determine whether a color channel is present. We cannot use ``None`` - for this purpose as it has its own meaning which indicates that the given - image is grayscale. - This automatic behavior was broken in v0.19, recovered but deprecated in - v0.20 and will be removed in v0.21. - """ +from .._shared.utils import _supported_float_type, convert_to_float def gaussian( @@ -41,7 +22,7 @@ def gaussian( preserve_range=False, truncate=4.0, *, - channel_axis=ChannelAxisNotSet, + channel_axis=None, ): """Multi-dimensional Gaussian filter. @@ -77,12 +58,10 @@ def gaussian( to channels. .. warning:: - Automatic detection of the color channel based on the old deprecated - ``multichannel=None`` was broken in version 0.19. In 0.20 this - behavior is recovered. The last axis of an `image` with dimensions - (M, N, 3) is interpreted as a color channel if `channel_axis` is - not set. Starting with release 23.04.02, ``channel_axis=None`` will - be used as the new default value. + In versions prior to 24.02, the last axis of an `image` with + dimensions (M, N, 3) was interpreted as a color channel if + `channel_axis` was not set. Starting with release 24.02, + ``channel_axis=None`` will be used as the new default value. Returns ------- @@ -133,21 +112,7 @@ def gaussian( >>> from skimage.data import astronaut >>> image = cp.array(astronaut()) >>> filtered_img = gaussian(image, sigma=1, channel_axis=-1) - """ - if channel_axis is ChannelAxisNotSet: - if image.ndim == 3 and image.shape[-1] == 3: - warn( - "Automatic detection of the color channel was deprecated in " - "v0.19, and `channel_axis=None` will be the new default in " - "v0.21. Set `channel_axis=-1` explicitly to silence this " - "warning.", - FutureWarning, - stacklevel=2, - ) - channel_axis = -1 - else: - channel_axis = None # CuPy Backend: refactor to avoid overhead of cp.any(cp.asarray(sigma)) sigma_msg = "Sigma values less than zero are not valid" diff --git a/python/cucim/src/cucim/skimage/_vendored/_internal.py b/python/cucim/src/cucim/skimage/_vendored/_internal.py index 9ac4cef9a..f87e07744 100644 --- a/python/cucim/src/cucim/skimage/_vendored/_internal.py +++ b/python/cucim/src/cucim/skimage/_vendored/_internal.py @@ -3,7 +3,14 @@ from operator import mul import cupy -import numpy + +# TODO: when minimum numpy dependency is 1.25 use: +# np.exceptions.AxisError instead of AxisError +# and remove this try-except +try: + from numpy import AxisError +except ImportError: + from numpy.exceptions import AxisError try: # try importing Cython-based private axis handling functions from CuPy @@ -37,7 +44,7 @@ def _normalize_axis_index(axis, ndim): # NOQA if axis < 0: axis += ndim if not (0 <= axis < ndim): - raise numpy.AxisError("axis out of bounds") + raise AxisError("axis out of bounds") return axis def _normalize_axis_indices(axes, ndim): # NOQA diff --git a/python/cucim/src/cucim/skimage/color/colorconv.py b/python/cucim/src/cucim/skimage/color/colorconv.py index 0b18aa910..89eeffabb 100644 --- a/python/cucim/src/cucim/skimage/color/colorconv.py +++ b/python/cucim/src/cucim/skimage/color/colorconv.py @@ -63,6 +63,14 @@ ) from ..util import dtype, dtype_limits +# TODO: when minimum numpy dependency is 1.25 use: +# np.exceptions.AxisError instead of AxisError +# and remove this try-except +try: + from numpy import AxisError +except ImportError: + from numpy.exceptions import AxisError + def convert_colorspace(arr, fromspace, tospace, *, channel_axis=-1): """Convert an image array to a new color space. @@ -173,7 +181,7 @@ def _validate_channel_axis(channel_axis, ndim): if not isinstance(channel_axis, int): raise TypeError("channel_axis must be an integer") if channel_axis < -ndim or channel_axis >= ndim: - raise np.AxisError("channel_axis exceeds array dimensions") + raise AxisError("channel_axis exceeds array dimensions") @cp.memoize(for_each_device=True) diff --git a/python/cucim/src/cucim/skimage/color/tests/test_colorconv.py b/python/cucim/src/cucim/skimage/color/tests/test_colorconv.py index 7dfc80564..704e874df 100644 --- a/python/cucim/src/cucim/skimage/color/tests/test_colorconv.py +++ b/python/cucim/src/cucim/skimage/color/tests/test_colorconv.py @@ -63,6 +63,14 @@ ) from cucim.skimage.util import img_as_float, img_as_float32, img_as_ubyte +# TODO: when minimum numpy dependency is 1.25 use: +# np.exceptions.AxisError instead of AxisError +# and remove this try-except +try: + from numpy import AxisError +except ImportError: + from numpy.exceptions import AxisError + data_dir = os.path.join(os.path.dirname(__file__), "data") @@ -133,7 +141,7 @@ def test_rgba2rgb_error_channel_axis_invalid(self, channel_axis): @pytest.mark.parametrize("channel_axis", [-4, 3]) def test_rgba2rgb_error_channel_axis_out_of_range(self, channel_axis): - with pytest.raises(np.AxisError): + with pytest.raises(AxisError): rgba2rgb(self.img_rgba, channel_axis=channel_axis) def test_rgba2rgb_error_rgb(self): diff --git a/python/cucim/src/cucim/skimage/feature/__init__.py b/python/cucim/src/cucim/skimage/feature/__init__.py index 04b53fbfc..62d86f776 100644 --- a/python/cucim/src/cucim/skimage/feature/__init__.py +++ b/python/cucim/src/cucim/skimage/feature/__init__.py @@ -1,48 +1,3 @@ -from ._basic_features import multiscale_basic_features -from ._canny import canny -from ._daisy import daisy -from .blob import blob_dog, blob_doh, blob_log -from .corner import ( - corner_foerstner, - corner_harris, - corner_kitchen_rosenfeld, - corner_peaks, - corner_shi_tomasi, - hessian_matrix, - hessian_matrix_det, - hessian_matrix_eigvals, - shape_index, - structure_tensor, - structure_tensor_eigenvalues, -) -from .match import match_descriptors -from .peak import peak_local_max -from .template import match_template +import lazy_loader as lazy -__all__ = [ - "canny", - "daisy", - "multiscale_basic_features", - "peak_local_max", - "structure_tensor", - "structure_tensor_eigenvalues", - "structure_tensor_eigvals", - "hessian_matrix", - "hessian_matrix_det", - "hessian_matrix_eigvals", - "shape_index", - "corner_kitchen_rosenfeld", - "corner_harris", - "corner_shi_tomasi", - "corner_foerstner", - # 'corner_subpix', - "corner_peaks", - # 'corner_moravec', - # 'corner_fast', - # 'corner_orientations', - "match_template", - "match_descriptors", - "blob_dog", - "blob_log", - "blob_doh", -] +__getattr__, __dir__, __all__ = lazy.attach_stub(__name__, __file__) diff --git a/python/cucim/src/cucim/skimage/feature/__init__.pyi b/python/cucim/src/cucim/skimage/feature/__init__.pyi new file mode 100644 index 000000000..22519324e --- /dev/null +++ b/python/cucim/src/cucim/skimage/feature/__init__.pyi @@ -0,0 +1,47 @@ +# Explicitly setting `__all__` is necessary for type inference engines +# to know which symbols are exported. See +# https://peps.python.org/pep-0484/#stub-files + +__all__ = [ + "canny", + "daisy", + "peak_local_max", + "structure_tensor", + "structure_tensor_eigenvalues", + "hessian_matrix", + "hessian_matrix_det", + "hessian_matrix_eigvals", + "shape_index", + "corner_kitchen_rosenfeld", + "corner_harris", + "corner_shi_tomasi", + "corner_foerstner", + "corner_peaks", + "match_template", + "match_descriptors", + "blob_dog", + "blob_doh", + "blob_log", + "multiscale_basic_features", +] + +from ._basic_features import multiscale_basic_features +from ._canny import canny +from ._daisy import daisy +from .blob import blob_dog, blob_doh, blob_log +from .corner import ( + corner_foerstner, + corner_harris, + corner_kitchen_rosenfeld, + corner_peaks, + corner_shi_tomasi, + hessian_matrix, + hessian_matrix_det, + hessian_matrix_eigvals, + shape_index, + structure_tensor, + structure_tensor_eigenvalues, +) +from .match import match_descriptors +from .peak import peak_local_max +from .template import match_template diff --git a/python/cucim/src/cucim/skimage/feature/peak.py b/python/cucim/src/cucim/skimage/feature/peak.py index 84965b328..58e0f1899 100644 --- a/python/cucim/src/cucim/skimage/feature/peak.py +++ b/python/cucim/src/cucim/skimage/feature/peak.py @@ -23,7 +23,7 @@ def _get_high_intensity_peaks(image, mask, num_peaks, min_distance, p_norm): coord = cp.nonzero(mask) intensities = image[coord] # Highest peak first - idx_maxsort = cp.argsort(-intensities) + idx_maxsort = cp.argsort(-intensities, kind="stable") coord = cp.column_stack(coord)[idx_maxsort] if np.isfinite(num_peaks): diff --git a/python/cucim/src/cucim/skimage/filters/ridges.py b/python/cucim/src/cucim/skimage/filters/ridges.py index 0a929641e..34bd008d3 100644 --- a/python/cucim/src/cucim/skimage/filters/ridges.py +++ b/python/cucim/src/cucim/skimage/filters/ridges.py @@ -58,7 +58,7 @@ def meijering( wrinkles, rivers. It can be used to calculate the fraction of the whole image containing such objects. - Calculates the eigenvectors of the Hessian to compute the similarity of + Calculates the eigenvalues of the Hessian to compute the similarity of an image region to neurites, according to the method described in [1]_. Parameters @@ -151,7 +151,7 @@ def sato( wrinkles, rivers. It can be used to calculate the fraction of the whole image containing such objects. - Defined only for 2-D and 3-D images. Calculates the eigenvectors of the + Defined only for 2-D and 3-D images. Calculates the eigenvalues of the Hessian to compute the similarity of an image region to tubes, according to the method described in [1]_. @@ -334,7 +334,7 @@ def frangi( wrinkles, rivers. It can be used to calculate the fraction of the whole image containing such objects. - Defined only for 2-D and 3-D images. Calculates the eigenvectors of the + Defined only for 2-D and 3-D images. Calculates the eigenvalues of the Hessian to compute the similarity of an image region to vessels, according to the method described in [1]_. diff --git a/python/cucim/src/cucim/skimage/filters/tests/test_gaussian.py b/python/cucim/src/cucim/skimage/filters/tests/test_gaussian.py index 73ffe943d..ae3349f43 100644 --- a/python/cucim/src/cucim/skimage/filters/tests/test_gaussian.py +++ b/python/cucim/src/cucim/skimage/filters/tests/test_gaussian.py @@ -2,8 +2,6 @@ import numpy as np import pytest -from cucim.skimage._shared._warnings import expected_warnings -from cucim.skimage._vendored import pad from cucim.skimage.filters._gaussian import difference_of_gaussians, gaussian @@ -60,17 +58,12 @@ def test_multichannel(channel_axis): ) if channel_axis % a.ndim == 2: - # Test legacy behavior equivalent to old (channel_axis = -1) - with expected_warnings(["Automatic detection of the color channel"]): - gaussian_rgb_a = gaussian( - a, sigma=1, mode="reflect", preserve_range=True - ) - # Check that the mean value is conserved in each channel # (color channels are not mixed together) assert cp.allclose( a.mean(axis=spatial_axes), gaussian_rgb_a.mean(axis=spatial_axes) ) + # Iterable sigma gaussian_rgb_a = gaussian( a, @@ -189,34 +182,3 @@ def test_shared_mem_check_fix(dtype, sigma): # The exact range that fails depends on the shared memory limit # of the GPU, so we test with a range of sigmas here. gaussian(cp.ones((512, 512), dtype=dtype), sigma=sigma) - - -def test_deprecated_automatic_channel_detection(): - rgb = cp.zeros((5, 5, 3)) - rgb[1, 1] = cp.arange(1, 4) - gray = pad(rgb, pad_width=((0, 0), (0, 0), (1, 0))) - - # Warning is raised if channel_axis is not set and shape is (M, N, 3) - with pytest.warns( - FutureWarning, - match="Automatic detection .* was deprecated .* Set `channel_axis=-1`", - ): - filtered_rgb = gaussian(rgb, sigma=1, mode="reflect") - # Check that the mean value is conserved in each channel - # (color channels are not mixed together) - assert cp.allclose(filtered_rgb.mean(axis=(0, 1)), rgb.mean(axis=(0, 1))) - - # No warning if channel_axis is not set and shape is not (M, N, 3) - filtered_gray = gaussian(gray, sigma=1, mode="reflect") - - # No warning is raised if channel_axis is explicitly set - filtered_rgb2 = gaussian(rgb, sigma=1, mode="reflect", channel_axis=-1) - assert cp.array_equal(filtered_rgb, filtered_rgb2) - filtered_gray2 = gaussian(gray, sigma=1, mode="reflect", channel_axis=None) - assert cp.array_equal(filtered_gray, filtered_gray2) - assert not cp.array_equal(filtered_rgb, filtered_gray) - - # Check how the proxy value shows up in the rendered function signature - from cucim.skimage._shared.filters import ChannelAxisNotSet - - assert repr(ChannelAxisNotSet) == "" diff --git a/python/cucim/src/cucim/skimage/filters/tests/test_thresholding.py b/python/cucim/src/cucim/skimage/filters/tests/test_thresholding.py index f08fe60b4..a454c9584 100644 --- a/python/cucim/src/cucim/skimage/filters/tests/test_thresholding.py +++ b/python/cucim/src/cucim/skimage/filters/tests/test_thresholding.py @@ -640,12 +640,28 @@ def test_mean(): assert threshold_mean(img) == 1.0 -def test_triangle_uint_images(): - text = cp.array(data.text()) - assert threshold_triangle(cp.invert(text)) == 151 - assert threshold_triangle(text) == 104 - assert threshold_triangle(coinsd) == 80 - assert threshold_triangle(cp.invert(coinsd)) == 175 +# also run cases with nbins > 100000 to also test CuPy-based code path. +@pytest.mark.parametrize("kwargs", [{}, {"nbins": 300000}]) +@pytest.mark.parametrize("dtype", [cp.uint8, cp.int16, cp.float16, cp.float32]) +def test_triangle_uniform_images(dtype, kwargs): + assert threshold_triangle(cp.zeros((10, 10), dtype=dtype), **kwargs) == 0 + assert threshold_triangle(cp.ones((10, 10), dtype=dtype), **kwargs) == 1 + assert threshold_triangle(cp.full((10, 10), 2, dtype=dtype), **kwargs) == 2 + + +# also run cases with nbins > 100000 to also test CuPy-based code path. +@pytest.mark.parametrize("kwargs", [{}, {"nbins": 300000}]) +@pytest.mark.parametrize( + "data, expected_value", + [ + (cp.invert(cp.array(data.text())), 151), + (cp.array(data.text()), 104), + (coinsd, 80), + (cp.invert(coinsd), 175), + ], +) +def test_triangle_uint_images(data, expected_value, kwargs): + assert threshold_triangle(data, **kwargs) == expected_value def test_triangle_float_images(): diff --git a/python/cucim/src/cucim/skimage/filters/thresholding.py b/python/cucim/src/cucim/skimage/filters/thresholding.py index 53e8b8d55..76127e52a 100644 --- a/python/cucim/src/cucim/skimage/filters/thresholding.py +++ b/python/cucim/src/cucim/skimage/filters/thresholding.py @@ -902,6 +902,8 @@ def threshold_triangle(image, nbins=256): Examples -------- + >>> import cupy as cp + >>> from cucim.skimage.filters import threshold_triangle >>> from skimage.data import camera >>> image = cp.array(camera()) >>> thresh = threshold_triangle(image) @@ -910,12 +912,25 @@ def threshold_triangle(image, nbins=256): # nbins is ignored for integer arrays # so, we recalculate the effective nbins. hist, bin_centers = histogram(image.ravel(), nbins, source_range="image") - nbins = len(hist) + if hist.size == 1: + # integer-valued image with constant intensity will have just 1 bin + return image.ravel()[0] + # In most cases, nbins is small so it is better to process hist on the CPU + if nbins > 100000: + xp = cp + else: + xp = np + hist = cp.asnumpy(hist) + + nbins = len(hist) # Find peak, lowest and highest gray levels. - arg_peak_height = cp.argmax(hist) + arg_peak_height = xp.argmax(hist) + arg_low_level, arg_high_level = xp.flatnonzero(hist)[[0, -1]] + if arg_low_level == arg_high_level: + # Image has constant intensity. + return image.ravel()[0] peak_height = hist[arg_peak_height] - arg_low_level, arg_high_level = cp.flatnonzero(hist)[[0, -1]] # Flip is True if left tail is shorter. flip = arg_peak_height - arg_low_level < arg_high_level - arg_peak_height @@ -930,11 +945,11 @@ def threshold_triangle(image, nbins=256): # Set up the coordinate system. width = arg_peak_height - arg_low_level - x1 = cp.arange(width) + x1 = xp.arange(width) y1 = hist[x1 + arg_low_level] # Normalize. - norm = cp.sqrt(peak_height**2 + width**2) + norm = xp.sqrt(peak_height**2 + width**2) try: peak_height /= norm width /= norm @@ -952,7 +967,7 @@ def threshold_triangle(image, nbins=256): # the length, but here we omit it as it does not affect the location of the # minimum. length = peak_height * x1 - width * y1 - arg_level = cp.argmax(length) + arg_low_level + arg_level = xp.argmax(length) + arg_low_level if flip: arg_level = nbins - arg_level - 1 diff --git a/python/cucim/src/cucim/skimage/measure/__init__.py b/python/cucim/src/cucim/skimage/measure/__init__.py index 4f32c7980..62d86f776 100644 --- a/python/cucim/src/cucim/skimage/measure/__init__.py +++ b/python/cucim/src/cucim/skimage/measure/__init__.py @@ -1,56 +1,3 @@ -from ._blur_effect import blur_effect -from ._colocalization import ( - intersection_coeff, - manders_coloc_coeff, - manders_overlap_coeff, - pearson_corr_coeff, -) -from ._label import label -from ._moments import ( - centroid, - inertia_tensor, - inertia_tensor_eigvals, - moments, - moments_central, - moments_coords, - moments_coords_central, - moments_hu, - moments_normalized, -) -from ._polygon import approximate_polygon, subdivide_polygon -from ._regionprops import ( - euler_number, - perimeter, - perimeter_crofton, - regionprops, - regionprops_table, -) -from .block import block_reduce -from .entropy import shannon_entropy -from .profile import profile_line +import lazy_loader as lazy -__all__ = [ - "blur_effect", - "regionprops", - "regionprops_table", - "perimeter", - "approximate_polygon", - "subdivide_polygon", - "block_reduce", - "centroid", - "moments", - "moments_central", - "moments_coords", - "moments_coords_central", - "moments_normalized", - "moments_hu", - "inertia_tensor", - "inertia_tensor_eigvals", - "profile_line", - "label", - "shannon_entropy", - "intersection_coeff", - "manders_coloc_coeff", - "manders_overlap_coeff", - "pearson_corr_coeff", -] +__getattr__, __dir__, __all__ = lazy.attach_stub(__name__, __file__) diff --git a/python/cucim/src/cucim/skimage/measure/__init__.pyi b/python/cucim/src/cucim/skimage/measure/__init__.pyi new file mode 100644 index 000000000..4ae693f16 --- /dev/null +++ b/python/cucim/src/cucim/skimage/measure/__init__.pyi @@ -0,0 +1,62 @@ +# Explicitly setting `__all__` is necessary for type inference engines +# to know which symbols are exported. See +# https://peps.python.org/pep-0484/#stub-files + +__all__ = [ + "regionprops", + "regionprops_table", + "perimeter", + "perimeter_crofton", + "euler_number", + "approximate_polygon", + "subdivide_polygon", + "block_reduce", + "centroid", + "moments", + "moments_central", + "moments_coords", + "moments_coords_central", + "moments_normalized", + "moments_hu", + "inertia_tensor", + "inertia_tensor_eigvals", + "profile_line", + "label", + "shannon_entropy", + "blur_effect", + "pearson_corr_coeff", + "manders_coloc_coeff", + "manders_overlap_coeff", + "intersection_coeff", +] + +from ._blur_effect import blur_effect +from ._colocalization import ( + intersection_coeff, + manders_coloc_coeff, + manders_overlap_coeff, + pearson_corr_coeff, +) +from ._label import label +from ._moments import ( + centroid, + inertia_tensor, + inertia_tensor_eigvals, + moments, + moments_central, + moments_coords, + moments_coords_central, + moments_hu, + moments_normalized, +) +from ._polygon import approximate_polygon, subdivide_polygon +from ._regionprops import ( + euler_number, + perimeter, + perimeter_crofton, + regionprops, + regionprops_table, +) +from .block import block_reduce +from .entropy import shannon_entropy +from .profile import profile_line diff --git a/python/cucim/src/cucim/skimage/measure/_blur_effect.py b/python/cucim/src/cucim/skimage/measure/_blur_effect.py index e3ba81d1f..6fc77774c 100644 --- a/python/cucim/src/cucim/skimage/measure/_blur_effect.py +++ b/python/cucim/src/cucim/skimage/measure/_blur_effect.py @@ -5,6 +5,14 @@ from ..color import rgb2gray from ..util import img_as_float +# TODO: when minimum numpy dependency is 1.25 use: +# np.exceptions.AxisError instead of AxisError +# and remove this try-except +try: + from numpy import AxisError +except ImportError: + from numpy.exceptions import AxisError + __all__ = ["blur_effect"] @@ -56,7 +64,7 @@ def blur_effect(image, h_size=11, channel_axis=None, reduce_func=max): try: # ensure color channels are in the final dimension image = cp.moveaxis(image, channel_axis, -1) - except cp.AxisError: + except AxisError: print("channel_axis must be one of the image array dimensions") raise except TypeError: diff --git a/python/cucim/src/cucim/skimage/measure/_regionprops.py b/python/cucim/src/cucim/skimage/measure/_regionprops.py index fb1da9dfa..6246798ee 100644 --- a/python/cucim/src/cucim/skimage/measure/_regionprops.py +++ b/python/cucim/src/cucim/skimage/measure/_regionprops.py @@ -34,6 +34,7 @@ "ConvexImage": "image_convex", "convex_image": "image_convex", "Coordinates": "coords", + "coords_scaled": "coords_scaled", "Eccentricity": "eccentricity", "EquivDiameter": "equivalent_diameter_area", "equivalent_diameter": "equivalent_diameter_area", @@ -67,6 +68,7 @@ "minor_axis_length": "axis_minor_length", "Moments": "moments", "NormalizedMoments": "moments_normalized", + "num_pixels": "num_pixels", "Orientation": "orientation", "Perimeter": "perimeter", "CroftonPerimeter": "perimeter_crofton", @@ -102,6 +104,7 @@ "centroid_weighted": float, "centroid_weighted_local": float, "coords": object, + "coords_scaled": object, "eccentricity": float, "equivalent_diameter_area": float, "euler_number": int, @@ -125,6 +128,7 @@ "moments_weighted_central": float, "moments_weighted_hu": float, "moments_weighted_normalized": float, + "num_pixels": int, "orientation": float, "perimeter": float, "perimeter_crofton": float, @@ -192,18 +196,18 @@ def _infer_regionprop_dtype(func, *, intensity, ndim): dtype : NumPy data type The data type of the returned property. """ - labels = [1, 2] - sample = cp.zeros((3,) * ndim, dtype=np.intp) - sample[(0,) * ndim] = labels[0] - sample[(slice(1, None),) * ndim] = labels[1] - propmasks = [(sample == n) for n in labels] + mask_1 = np.ones((1,) * ndim, dtype=bool) + mask_1 = np.pad(mask_1, (0, 1), constant_values=False) + mask_2 = np.ones((2,) * ndim, dtype=bool) + mask_2 = np.pad(mask_2, (1, 0), constant_values=False) + propmasks = [cp.array(mask_1), cp.array(mask_2)] rng = cp.random.default_rng() if intensity and _infer_number_of_required_args(func) == 2: def _func(mask): - return func(mask, rng.random(sample.shape)) + return func(mask, rng.random(mask.shape)) else: _func = func @@ -921,7 +925,7 @@ def _props_to_dict(regions, properties=("label", "bbox"), separator="-"): or prop in OBJECT_COLUMNS or dtype is np.object_ ): - if prop in OBJECT_COLUMNS: + if prop in OBJECT_COLUMNS or dtype is np.object_: # keep objects in a NumPy array column_buffer = np.empty(n, dtype=dtype) for i in range(n): diff --git a/python/cucim/src/cucim/skimage/measure/block.py b/python/cucim/src/cucim/skimage/measure/block.py index db8cbfeac..4c3c8dc8c 100644 --- a/python/cucim/src/cucim/skimage/measure/block.py +++ b/python/cucim/src/cucim/skimage/measure/block.py @@ -89,9 +89,10 @@ def block_reduce(image, block_size=2, func=cp.sum, cval=0, func_kwargs=None): after_width = 0 pad_width.append((0, after_width)) - image = pad( - image, pad_width=pad_width, mode="constant", constant_values=cval - ) + if any(after_width > 0 for _, after_width in pad_width): + image = pad( + image, pad_width=pad_width, mode="constant", constant_values=cval + ) blocked = view_as_blocks(image, block_size) diff --git a/python/cucim/src/cucim/skimage/measure/tests/test_regionprops.py b/python/cucim/src/cucim/skimage/measure/tests/test_regionprops.py index 7ce1351a2..1ce3b841f 100644 --- a/python/cucim/src/cucim/skimage/measure/tests/test_regionprops.py +++ b/python/cucim/src/cucim/skimage/measure/tests/test_regionprops.py @@ -1,4 +1,5 @@ import math +import re import cupy as cp import cupyx.scipy.ndimage as ndi @@ -1240,6 +1241,20 @@ def test_column_dtypes_correct(): assert False, f"{col} dtype {t} {msg} {COL_DTYPES[col]}" +def test_all_documented_items_in_col_dtypes(): + numpydoc = pytest.importorskip("numpydoc") + docstring = numpydoc.docscrape.FunctionDoc(regionprops) + notes_lines = docstring["Notes"] + property_lines = filter(lambda line: line.startswith("**"), notes_lines) + pattern = r"\*\*(?P[a-z_]+)\*\*.*" + property_names = { + re.search(pattern, property_line).group("property_name") + for property_line in property_lines + } + column_keys = set(COL_DTYPES.keys()) + assert column_keys == property_names + + def pixelcount(regionmask): """a short test for an extra property""" return cp.sum(regionmask) @@ -1249,6 +1264,11 @@ def intensity_median(regionmask, image_intensity): return cp.median(image_intensity[regionmask]) +def bbox_list(regionmask): + """Extra property whose output shape is dependent on mask shape.""" + return [1] * regionmask.shape[1] + + def too_many_args(regionmask, image_intensity, superfluous): return 1 @@ -1314,11 +1334,15 @@ def test_extra_properties_table(): SAMPLE_MULTIPLE, intensity_image=INTENSITY_SAMPLE_MULTIPLE, properties=("label",), - extra_properties=(intensity_median, pixelcount), + extra_properties=(intensity_median, pixelcount, bbox_list), ) assert_array_almost_equal(out["intensity_median"], np.array([2.0, 4.0])) assert_array_equal(out["pixelcount"], np.array([10, 2])) + assert out["bbox_list"].dtype == np.object_ + assert out["bbox_list"][0] == [1] * 10 + assert out["bbox_list"][1] == [1] * 1 + def test_multichannel(): """Test that computing multichannel properties works.""" diff --git a/python/cucim/src/cucim/skimage/morphology/__init__.py b/python/cucim/src/cucim/skimage/morphology/__init__.py index 2c3cbf114..eedc0bc77 100644 --- a/python/cucim/src/cucim/skimage/morphology/__init__.py +++ b/python/cucim/src/cucim/skimage/morphology/__init__.py @@ -1,3 +1,11 @@ +"""Utilities that operate on shapes in images. + +These operations are particularly suited for binary images, +although some may be useful for images of other types as well. + +Basic morphological operations include dilation and erosion. +""" + from ._skeletonize import medial_axis, thin from .binary import ( binary_closing, diff --git a/python/cucim/src/cucim/skimage/registration/__init__.py b/python/cucim/src/cucim/skimage/registration/__init__.py index 5734f266f..62d86f776 100644 --- a/python/cucim/src/cucim/skimage/registration/__init__.py +++ b/python/cucim/src/cucim/skimage/registration/__init__.py @@ -1,4 +1,3 @@ -from ._optical_flow import optical_flow_ilk, optical_flow_tvl1 # noqa -from ._phase_cross_correlation import phase_cross_correlation # noqa +import lazy_loader as lazy -__all__ = ["optical_flow_ilk", "optical_flow_tvl1", "phase_cross_correlation"] +__getattr__, __dir__, __all__ = lazy.attach_stub(__name__, __file__) diff --git a/python/cucim/src/cucim/skimage/registration/__init__.pyi b/python/cucim/src/cucim/skimage/registration/__init__.pyi new file mode 100644 index 000000000..e6992c41e --- /dev/null +++ b/python/cucim/src/cucim/skimage/registration/__init__.pyi @@ -0,0 +1,8 @@ +# Explicitly setting `__all__` is necessary for type inference engines +# to know which symbols are exported. See +# https://peps.python.org/pep-0484/#stub-files + +__all__ = ["optical_flow_ilk", "optical_flow_tvl1", "phase_cross_correlation"] + +from ._optical_flow import optical_flow_ilk, optical_flow_tvl1 +from ._phase_cross_correlation import phase_cross_correlation diff --git a/python/cucim/src/cucim/skimage/registration/_phase_cross_correlation.py b/python/cucim/src/cucim/skimage/registration/_phase_cross_correlation.py index 4fcc497a0..ba5b8935e 100644 --- a/python/cucim/src/cucim/skimage/registration/_phase_cross_correlation.py +++ b/python/cucim/src/cucim/skimage/registration/_phase_cross_correlation.py @@ -5,13 +5,13 @@ import itertools import math -import warnings import cupy as cp import cupyx.scipy.ndimage as ndi import numpy as np from .._shared.fft import fftmodule as fft +from .._shared.utils import remove_arg from ._masked_phase_cross_correlation import _masked_phase_cross_correlation @@ -188,6 +188,7 @@ def _disambiguate_shift(reference_image, moving_image, shift): return real_shift +@remove_arg("return_error", changed_version="24.06") def phase_cross_correlation( reference_image, moving_image, @@ -234,10 +235,6 @@ def phase_cross_correlation( this parameter is set to ``True``, the *real* space cross-correlation is computed for each possible shift, and the shift with the highest cross-correlation within the overlapping area is returned. - return_error : bool, {"always"}, optional - Returns error and phase difference if "always" is given. If False, or - either ``reference_mask`` or ``moving_mask`` are given, only the shift - is returned. reference_mask : ndarray Boolean mask for ``reference_image``. The mask should evaluate to ``True`` (or 1) on valid pixels. ``reference_mask`` should @@ -268,13 +265,11 @@ def phase_cross_correlation( error : float Translation invariant normalized RMS error between ``reference_image`` and ``moving_image``. For masked cross-correlation - this error is not available and NaN is returned if ``return_error`` - is "always". + this error is not available and NaN is returned. phasediff : float Global phase difference between the two images (should be zero if images are non-negative). For masked cross-correlation - this phase difference is not available and NaN is returned if - ``return_error`` is "always". + this phase difference is not available and NaN is returned. Notes ----- @@ -312,15 +307,11 @@ def phase_cross_correlation( Pattern Recognition, pp. 2918-2925 (2010). :DOI:`10.1109/CVPR.2010.5540032` """ - - def warn_return_error(): - warnings.warn( - "In scikit-image 0.22, phase_cross_correlation will start " - "returning a tuple or 3 items (shift, error, phasediff) always. " - "To enable the new return behavior and silence this warning, use " - "return_error='always'.", - category=FutureWarning, - stacklevel=3, + if not return_error: + raise ValueError( + "return_error must be True (or 'always'), False is no longer " + "supported as of cuCIM 24.02 and the `return_error` kwarg will be " + "removed in cuCIM 24.06." ) if (reference_mask is not None) or (moving_mask is not None): @@ -331,11 +322,7 @@ def warn_return_error(): moving_mask, overlap_ratio, ) - if return_error == "always": - return shift, np.nan, np.nan - else: - warn_return_error() - return shift + return shift, np.nan, np.nan # images must be the same shape if reference_image.shape != moving_image.shape: @@ -373,14 +360,13 @@ def warn_return_error(): ) if upsample_factor == 1: - if return_error: - sabs = cp.abs(src_freq) - sabs *= sabs - tabs = cp.abs(target_freq) - tabs *= tabs - src_amp = np.sum(sabs) / src_freq.size - target_amp = np.sum(tabs) / target_freq.size - CCmax = cross_correlation[maxima] + sabs = cp.abs(src_freq) + sabs *= sabs + tabs = cp.abs(target_freq) + tabs *= tabs + src_amp = np.sum(sabs) / src_freq.size + target_amp = np.sum(tabs) / target_freq.size + CCmax = cross_correlation[maxima] # If upsampling > 1, then refine estimate with matrix multiply DFT else: # Initial shift estimate in upsampled grid @@ -412,13 +398,12 @@ def warn_return_error(): maxima = tuple(float(m) - dftshift for m in maxima) shift = tuple(s + m / upsample_factor for s, m in zip(shift, maxima)) - if return_error: - src_amp = cp.abs(src_freq) - src_amp *= src_amp - src_amp = cp.sum(src_amp) - target_amp = cp.abs(target_freq) - target_amp *= target_amp - target_amp = cp.sum(target_amp) + src_amp = cp.abs(src_freq) + src_amp *= src_amp + src_amp = cp.sum(src_amp) + target_amp = cp.abs(target_freq) + target_amp *= target_amp + target_amp = cp.sum(target_amp) # If its only one row or column the shift along that dimension has no # effect. We set to zero. @@ -432,22 +417,19 @@ def warn_return_error(): moving_image = fft.ifftn(moving_image) shift = _disambiguate_shift(reference_image, moving_image, shift) - if return_error: - # Redirect user to masked_phase_cross_correlation if NaNs are observed - if cp.isnan(CCmax) or cp.isnan(src_amp) or cp.isnan(target_amp): - raise ValueError( - "NaN values found, please remove NaNs from your " - "input data or use the `reference_mask`/`moving_mask` " - "keywords, eg: " - "phase_cross_correlation(reference_image, moving_image, " - "reference_mask=~np.isnan(reference_image), " - "moving_mask=~np.isnan(moving_image))" - ) - - return ( - shift, - _compute_error(CCmax, src_amp, target_amp), - _compute_phasediff(CCmax), + # Redirect user to masked_phase_cross_correlation if NaNs are observed + if cp.isnan(CCmax) or cp.isnan(src_amp) or cp.isnan(target_amp): + raise ValueError( + "NaN values found, please remove NaNs from your " + "input data or use the `reference_mask`/`moving_mask` " + "keywords, eg: " + "phase_cross_correlation(reference_image, moving_image, " + "reference_mask=~np.isnan(reference_image), " + "moving_mask=~np.isnan(moving_image))" ) - else: - return shift + + return ( + shift, + _compute_error(CCmax, src_amp, target_amp), + _compute_phasediff(CCmax), + ) diff --git a/python/cucim/src/cucim/skimage/registration/tests/test_phase_cross_correlation.py b/python/cucim/src/cucim/skimage/registration/tests/test_phase_cross_correlation.py index da089ed67..7a853fb86 100644 --- a/python/cucim/src/cucim/skimage/registration/tests/test_phase_cross_correlation.py +++ b/python/cucim/src/cucim/skimage/registration/tests/test_phase_cross_correlation.py @@ -141,7 +141,7 @@ def test_wrong_input(): template = cp.ones((5, 5)) with expected_warnings([r"invalid value encountered in true_divide|\A\Z"]): with pytest.raises(ValueError): - phase_cross_correlation(template, image, return_error=True) + phase_cross_correlation(template, image) def test_4d_input_pixel(): @@ -202,7 +202,9 @@ def test_disambiguate_2d(shift0, shift1): reference = image[slice0] moving = image[slice1] computed_shift, _, _ = phase_cross_correlation( - reference, moving, disambiguate=True, return_error="always" + reference, + moving, + disambiguate=True, ) np.testing.assert_equal(shift, computed_shift) @@ -215,6 +217,9 @@ def test_disambiguate_zero_shift(): """ image = cp.array(camera()) computed_shift, _, _ = phase_cross_correlation( - image, image, disambiguate=True, return_error="always" + image, + image, + disambiguate=True, + return_error="always", ) assert computed_shift == (0, 0) diff --git a/python/cucim/src/cucim/skimage/registration/tests/test_tvl1.py b/python/cucim/src/cucim/skimage/registration/tests/test_tvl1.py index fa706a9d3..20a70b039 100644 --- a/python/cucim/src/cucim/skimage/registration/tests/test_tvl1.py +++ b/python/cucim/src/cucim/skimage/registration/tests/test_tvl1.py @@ -11,8 +11,8 @@ def _sin_flow_gen(image0, max_motion=4.5, npics=5): """Generate a synthetic ground truth optical flow with a sinusoid as first component. - Parameters: - ---- + Parameters + ---------- image0: ndarray The base image to be warped. max_motion: float diff --git a/python/cucim/src/cucim/skimage/restoration/__init__.py b/python/cucim/src/cucim/skimage/restoration/__init__.py index 07eaeb6b1..62d86f776 100644 --- a/python/cucim/src/cucim/skimage/restoration/__init__.py +++ b/python/cucim/src/cucim/skimage/restoration/__init__.py @@ -1,12 +1,3 @@ -from ._denoise import denoise_tv_chambolle -from .deconvolution import richardson_lucy, unsupervised_wiener, wiener -from .j_invariant import calibrate_denoiser, denoise_invariant +import lazy_loader as lazy -__all__ = [ - "wiener", - "unsupervised_wiener", - "richardson_lucy", - "denoise_tv_chambolle", - "calibrate_denoiser", - "denoise_invariant", -] +__getattr__, __dir__, __all__ = lazy.attach_stub(__name__, __file__) diff --git a/python/cucim/src/cucim/skimage/restoration/__init__.pyi b/python/cucim/src/cucim/skimage/restoration/__init__.pyi new file mode 100644 index 000000000..89e9ec583 --- /dev/null +++ b/python/cucim/src/cucim/skimage/restoration/__init__.pyi @@ -0,0 +1,16 @@ +# Explicitly setting `__all__` is necessary for type inference engines +# to know which symbols are exported. See +# https://peps.python.org/pep-0484/#stub-files + +__all__ = [ + "wiener", + "unsupervised_wiener", + "richardson_lucy", + "denoise_tv_chambolle", + "denoise_invariant", + "calibrate_denoiser", +] + +from ._denoise import denoise_tv_chambolle +from .deconvolution import richardson_lucy, unsupervised_wiener, wiener +from .j_invariant import calibrate_denoiser, denoise_invariant diff --git a/python/cucim/src/cucim/skimage/restoration/deconvolution.py b/python/cucim/src/cucim/skimage/restoration/deconvolution.py index f30da5045..554232d62 100644 --- a/python/cucim/src/cucim/skimage/restoration/deconvolution.py +++ b/python/cucim/src/cucim/skimage/restoration/deconvolution.py @@ -76,7 +76,7 @@ def wiener(image, psf, balance, reg=None, is_real=True, clip=True): unknown original image, the Wiener filter is .. math:: - \hat x = F^\dagger (|\Lambda_H|^2 + \lambda |\Lambda_D|^2) + \hat x = F^\dagger \left( |\Lambda_H|^2 + \lambda |\Lambda_D|^2 \right)^{-1} \Lambda_H^\dagger F y where :math:`F` and :math:`F^\dagger` are the Fourier and inverse @@ -113,7 +113,7 @@ def wiener(image, psf, balance, reg=None, is_real=True, clip=True): .. [2] B. R. Hunt "A matrix theory proof of the discrete convolution theorem", IEEE Trans. on Audio and Electroacoustics, vol. au-19, no. 4, pp. 285-288, dec. 1971 - """ + """ # noqa: E501 if reg is None: reg, _ = uft.laplacian(image.ndim, image.shape, is_real=is_real) if not cp.iscomplexobj(reg): @@ -307,7 +307,7 @@ def unsupervised_wiener( prev_x_postmean = cp.zeros(trans_fct.shape, dtype=float_type) # Difference between two successive mean - delta = np.NAN + delta = np.nan # Initial state of the chain gn_chain, gx_chain = [1], [1] diff --git a/python/cucim/src/cucim/skimage/restoration/j_invariant.py b/python/cucim/src/cucim/skimage/restoration/j_invariant.py index ffc3cef91..10ec14d2f 100644 --- a/python/cucim/src/cucim/skimage/restoration/j_invariant.py +++ b/python/cucim/src/cucim/skimage/restoration/j_invariant.py @@ -141,7 +141,17 @@ def denoise_invariant( .. [1] J. Batson & L. Royer. Noise2Self: Blind Denoising by Self-Supervision, International Conference on Machine Learning, p. 524-533 (2019). - """ + + Examples + -------- + >>> import cucim.skimage + >>> import cupy as cp + >>> import skimage + >>> from cucim.skimage.restoration import denoise_invariant, denoise_tv_chambolle + >>> image = cucim.skimage.util.img_as_float(cp.asarray(skimage.data.chelsea())) + >>> noisy = cucim.skimage.util.random_noise(image, var=0.2 ** 2) + >>> denoised = denoise_invariant(noisy, denoise_function=denoise_tv_chambolle) + """ # noqa: E501 image = img_as_float(image) # promote float16->float32 if needed @@ -273,7 +283,7 @@ def calibrate_denoiser( ... calibrate_denoiser) >>> img = color.rgb2gray(cp.array(data.astronaut()[:50, :50])) >>> noisy = img + 0.5 * img.std() * cp.random.randn(*img.shape) - >>> parameters = {'weight': cp.arange(0.01, 0.5, 0.05)} + >>> parameters = {'weight': cp.arange(0.01, 0.3, 0.02)} >>> denoising_function = calibrate_denoiser(noisy, denoise_tv_chambolle, ... denoise_parameters=parameters) >>> denoised_img = denoising_function(img) diff --git a/python/cucim/src/cucim/skimage/restoration/tests/test_j_invariant.py b/python/cucim/src/cucim/skimage/restoration/tests/test_j_invariant.py index 584e4a711..6057a92c8 100644 --- a/python/cucim/src/cucim/skimage/restoration/tests/test_j_invariant.py +++ b/python/cucim/src/cucim/skimage/restoration/tests/test_j_invariant.py @@ -3,9 +3,6 @@ import pytest from skimage.data import camera, chelsea -# from cucim.skimage.restoration import denoise_wavelet -from skimage.restoration import denoise_wavelet - from cucim.skimage._shared.utils import _supported_float_type from cucim.skimage.data import binary_blobs from cucim.skimage.metrics import mean_squared_error as mse @@ -20,6 +17,10 @@ noisy_img_color = random_noise(test_img_color, mode="gaussian", var=0.01) noisy_img_3d = random_noise(test_img_3d, mode="gaussian", var=0.1) +# skip tests if skimage.restoration module cannot be imported +skimage_restoration = pytest.importorskip("skimage.restoration") +denoise_wavelet = skimage_restoration.denoise_wavelet + # TODO: replace with CuPy version once completed def _denoise_wavelet(image, rescale_sigma=True, **kwargs): diff --git a/python/cucim/src/cucim/skimage/segmentation/__init__.py b/python/cucim/src/cucim/skimage/segmentation/__init__.py index 6d9544aa8..6e2969ae5 100644 --- a/python/cucim/src/cucim/skimage/segmentation/__init__.py +++ b/python/cucim/src/cucim/skimage/segmentation/__init__.py @@ -1,3 +1,6 @@ +"""Algorithms to partition images into meaningful regions or boundaries. +""" + from ._chan_vese import chan_vese from ._clear_border import clear_border from ._expand_labels import expand_labels diff --git a/python/cucim/src/cucim/skimage/transform/__init__.py b/python/cucim/src/cucim/skimage/transform/__init__.py index 81fe7c6e8..62d86f776 100644 --- a/python/cucim/src/cucim/skimage/transform/__init__.py +++ b/python/cucim/src/cucim/skimage/transform/__init__.py @@ -1,58 +1,3 @@ -from ._geometric import ( - AffineTransform, - EssentialMatrixTransform, - EuclideanTransform, - FundamentalMatrixTransform, - PiecewiseAffineTransform, - PolynomialTransform, - ProjectiveTransform, - SimilarityTransform, - estimate_transform, - matrix_transform, -) -from ._warps import ( - downscale_local_mean, - rescale, - resize, - resize_local_mean, - rotate, - swirl, - warp, - warp_coords, - warp_polar, -) -from .integral import integral_image, integrate -from .pyramids import ( - pyramid_expand, - pyramid_gaussian, - pyramid_laplacian, - pyramid_reduce, -) +import lazy_loader as lazy -__all__ = [ - "integral_image", - "integrate", - "warp", - "warp_coords", - "warp_polar", - "estimate_transform", - "matrix_transform", - "EuclideanTransform", - "SimilarityTransform", - "AffineTransform", - "ProjectiveTransform", - "EssentialMatrixTransform", - "FundamentalMatrixTransform", - "PolynomialTransform", - "PiecewiseAffineTransform", - "swirl", - "resize", - "resize_local_mean", - "rotate", - "rescale", - "downscale_local_mean", - "pyramid_reduce", - "pyramid_expand", - "pyramid_gaussian", - "pyramid_laplacian", -] +__getattr__, __dir__, __all__ = lazy.attach_stub(__name__, __file__) diff --git a/python/cucim/src/cucim/skimage/transform/__init__.pyi b/python/cucim/src/cucim/skimage/transform/__init__.pyi new file mode 100644 index 000000000..8f8dcaadb --- /dev/null +++ b/python/cucim/src/cucim/skimage/transform/__init__.pyi @@ -0,0 +1,62 @@ +# Explicitly setting `__all__` is necessary for type inference engines +# to know which symbols are exported. See +# https://peps.python.org/pep-0484/#stub-files + +__all__ = [ + "integral_image", + "integrate", + "warp", + "warp_coords", + "warp_polar", + "estimate_transform", + "matrix_transform", + "EuclideanTransform", + "SimilarityTransform", + "AffineTransform", + "ProjectiveTransform", + "EssentialMatrixTransform", + "FundamentalMatrixTransform", + "PolynomialTransform", + "PiecewiseAffineTransform", + "swirl", + "resize", + "resize_local_mean", + "rotate", + "rescale", + "downscale_local_mean", + "pyramid_reduce", + "pyramid_expand", + "pyramid_gaussian", + "pyramid_laplacian", +] + +from ._geometric import ( + AffineTransform, + EssentialMatrixTransform, + EuclideanTransform, + FundamentalMatrixTransform, + PiecewiseAffineTransform, + PolynomialTransform, + ProjectiveTransform, + SimilarityTransform, + estimate_transform, + matrix_transform, +) +from ._warps import ( + downscale_local_mean, + rescale, + resize, + resize_local_mean, + rotate, + swirl, + warp, + warp_coords, + warp_polar, +) +from .integral import integral_image, integrate +from .pyramids import ( + pyramid_expand, + pyramid_gaussian, + pyramid_laplacian, + pyramid_reduce, +) diff --git a/python/cucim/src/cucim/skimage/transform/_geometric.py b/python/cucim/src/cucim/skimage/transform/_geometric.py index 9b5644bd7..8f3758b37 100644 --- a/python/cucim/src/cucim/skimage/transform/_geometric.py +++ b/python/cucim/src/cucim/skimage/transform/_geometric.py @@ -1365,6 +1365,13 @@ class EuclideanTransform(ProjectiveTransform): translation parameters. The similarity transformation extends the Euclidean transformation with a single scaling factor. + In 2D and 3D, the transformation parameters may be provided either via + `matrix`, the homogeneous transformation matrix, above, or via the + implicit parameters `rotation` and/or `translation` (where `a1` is the + translation along `x`, `b1` along `y`, etc.). Beyond 3D, if the + transformation is only a translation, you may use the implicit parameter + `translation`; otherwise, you must use `matrix`. + Parameters ---------- matrix : (D+1, D+1) ndarray, optional @@ -1375,7 +1382,7 @@ class EuclideanTransform(ProjectiveTransform): (single rotation) and 3D (Euler rotations) values are supported. For higher dimensions, you must provide or estimate the transformation matrix. - translation : sequence of float, length D, optional + translation : (x, y[, z, ...]) sequence of float, length D, optional Translation parameters for each axis. dimensionality : int, optional The dimensionality of the transform. @@ -1510,7 +1517,7 @@ class SimilarityTransform(EuclideanTransform): where ``s`` is a scale factor and the homogeneous transformation matrix is:: - [[a0 b0 a1] + [[a0 -b0 a1] [b0 a0 b1] [0 0 1]] diff --git a/python/cucim/src/cucim/skimage/transform/pyramids.py b/python/cucim/src/cucim/skimage/transform/pyramids.py index 3608c0de4..88c512691 100644 --- a/python/cucim/src/cucim/skimage/transform/pyramids.py +++ b/python/cucim/src/cucim/skimage/transform/pyramids.py @@ -4,7 +4,7 @@ from .._shared.filters import gaussian from .._shared.utils import convert_to_float -from ..transform import resize +from ._warps import resize def _smooth(image, sigma, mode, cval, channel_axis): diff --git a/python/cucim/src/cucim/skimage/util/dtype.py b/python/cucim/src/cucim/skimage/util/dtype.py index 866d515ee..2ba6dc7bb 100644 --- a/python/cucim/src/cucim/skimage/util/dtype.py +++ b/python/cucim/src/cucim/skimage/util/dtype.py @@ -3,6 +3,7 @@ from warnings import warn import cupy as cp +import numpy as np from .._shared.utils import _supported_float_type @@ -46,7 +47,6 @@ bool: (False, True), cp.bool_: (False, True), float: (-1, 1), - cp.float_: (-1, 1), cp.float16: (-1, 1), cp.float32: (-1, 1), cp.float64: (-1, 1), @@ -279,7 +279,7 @@ def _convert(image, dtype, force_copy=False, uniform=False): # is a subclass of that type (e.g. `cp.floating` will allow # `float32` and `float64` arrays through) - if cp.issubdtype(dtype_in, cp.obj2sctype(dtype)): + if cp.issubdtype(dtype_in, np.core.numerictypes.obj2sctype(dtype)): if force_copy: image = image.copy() return image From e29ebc739400d68ea71e19efac8b55372ed1788e Mon Sep 17 00:00:00 2001 From: jakirkham Date: Mon, 29 Jan 2024 16:16:09 -0800 Subject: [PATCH 19/21] Exclude PyTest 8 (#689) There are some test issues cropping up in CI due to PyTest 8. Go ahead and pin PyTest as a workaround for now. Authors: - https://github.com/jakirkham Approvers: - Ray Douglass (https://github.com/raydouglass) - Gregory Lee (https://github.com/grlee77) URL: https://github.com/rapidsai/cucim/pull/689 --- conda/environments/all_cuda-118_arch-x86_64.yaml | 2 +- conda/environments/all_cuda-120_arch-x86_64.yaml | 2 +- dependencies.yaml | 2 +- python/cucim/pyproject.toml | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/conda/environments/all_cuda-118_arch-x86_64.yaml b/conda/environments/all_cuda-118_arch-x86_64.yaml index 0cc4a61e2..31c5e6077 100644 --- a/conda/environments/all_cuda-118_arch-x86_64.yaml +++ b/conda/environments/all_cuda-118_arch-x86_64.yaml @@ -39,7 +39,7 @@ dependencies: - pytest-cov>=2.12.1 - pytest-lazy-fixture>=0.6.3 - pytest-xdist -- pytest>=6.2.4 +- pytest>=6.2.4,<8.0.0a0 - python>=3.8,<3.11 - pywavelets>=1.0 - recommonmark diff --git a/conda/environments/all_cuda-120_arch-x86_64.yaml b/conda/environments/all_cuda-120_arch-x86_64.yaml index 221169e0c..0f25face9 100644 --- a/conda/environments/all_cuda-120_arch-x86_64.yaml +++ b/conda/environments/all_cuda-120_arch-x86_64.yaml @@ -38,7 +38,7 @@ dependencies: - pytest-cov>=2.12.1 - pytest-lazy-fixture>=0.6.3 - pytest-xdist -- pytest>=6.2.4 +- pytest>=6.2.4,<8.0.0a0 - python>=3.8,<3.11 - pywavelets>=1.0 - recommonmark diff --git a/dependencies.yaml b/dependencies.yaml index 60d482b1d..33f7c6a67 100644 --- a/dependencies.yaml +++ b/dependencies.yaml @@ -254,7 +254,7 @@ dependencies: packages: - GPUtil>=1.4.0 - psutil>=5.8.0 - - pytest>=6.2.4 + - pytest>=6.2.4,<8.0.0a0 - pytest-cov>=2.12.1 - pytest-lazy-fixture>=0.6.3 - pytest-xdist diff --git a/python/cucim/pyproject.toml b/python/cucim/pyproject.toml index b39c912a7..ac1034d5d 100644 --- a/python/cucim/pyproject.toml +++ b/python/cucim/pyproject.toml @@ -72,7 +72,7 @@ test = [ "pytest-cov>=2.12.1", "pytest-lazy-fixture>=0.6.3", "pytest-xdist", - "pytest>=6.2.4", + "pytest>=6.2.4,<8.0.0a0", "pywavelets>=1.0", "tifffile>=2022.7.28", ] # This list was generated by `rapids-dependency-file-generator`. To make changes, edit ../../dependencies.yaml and run `rapids-dependency-file-generator`. From f802bdb8d46b651ad1adeedd4250c6410b37a789 Mon Sep 17 00:00:00 2001 From: jakirkham Date: Wed, 7 Feb 2024 13:39:22 -0800 Subject: [PATCH 20/21] Fix CUDA trove classifiers & update README install instructions (#695) Fix CUDA trove classifiers to match [CUDA 11]( https://github.com/pypa/trove-classifiers/blob/521d53011d28fc9490ef83e88eb2464d42dbd633/src/trove_classifiers/__init__.py#L44 ) and [CUDA 12]( https://github.com/pypa/trove-classifiers/blob/521d53011d28fc9490ef83e88eb2464d42dbd633/src/trove_classifiers/__init__.py#L54 ) Also update the README's install instructions for CUDA 11.2+ and CUDA 12 Authors: - https://github.com/jakirkham Approvers: - Gigon Bae (https://github.com/gigony) - Gregory Lee (https://github.com/grlee77) - Ray Douglass (https://github.com/raydouglass) --- README.md | 23 ++++++++++++++++------- python/cucim/pyproject.toml | 10 +++------- 2 files changed, 19 insertions(+), 14 deletions(-) diff --git a/README.md b/README.md index 27a5d7fbb..1e2fa988b 100644 --- a/README.md +++ b/README.md @@ -49,23 +49,32 @@ cuCIM supports the following formats: #### [Conda (stable)](https://anaconda.org/rapidsai/cucim) -> conda create -n cucim -c rapidsai -c conda-forge cucim cudatoolkit=`` +```bash +conda create -n cucim -c rapidsai -c conda-forge cucim cuda-version=`` +``` -`` should be 11.0+ (e.g., `11.0`, `11.2`, etc.) +`` should be 11.2+ (e.g., `11.2`, `12.0`, etc.) #### [Conda (nightlies)](https://anaconda.org/rapidsai-nightly/cucim) -> conda create -n cucim -c rapidsai-nightly -c conda-forge cucim cudatoolkit=`` +```bash +conda create -n cucim -c rapidsai-nightly -c conda-forge cucim cuda-version=`` +``` -`` should be 11.0+ (e.g., `11.0`, `11.2`, etc) +`` should be 11.2+ (e.g., `11.2`, `12.0`, etc.) ### [PyPI](https://pypi.org/project/cucim/) +Install for CUDA 12: + ```bash -pip install cucim +pip install cucim-cu12 +``` -# Install dependencies for `cucim.skimage` (assuming that CUDA 11.0 is used for CuPy) -pip install scipy scikit-image cupy-cuda110 +Alternatively install for CUDA 11: + +```bash +pip install cucim-cu11 ``` ### Notebooks diff --git a/python/cucim/pyproject.toml b/python/cucim/pyproject.toml index ac1034d5d..3d4aa443c 100644 --- a/python/cucim/pyproject.toml +++ b/python/cucim/pyproject.toml @@ -22,7 +22,7 @@ authors = [ { name = "NVIDIA Corporation" }, ] license = { text = "Apache 2.0" } -requires-python = ">=3.8" +requires-python = ">=3.9" dependencies = [ "cupy-cuda11x>=12.0.0", "lazy_loader>=0.1", @@ -39,16 +39,12 @@ classifiers = [ "Topic :: Scientific/Engineering", "Operating System :: POSIX :: Linux", "Environment :: Console", - "Environment :: GPU :: NVIDIA CUDA :: 11.0", - "Environment :: GPU :: NVIDIA CUDA :: 12.0", + "Environment :: GPU :: NVIDIA CUDA :: 11", + "Environment :: GPU :: NVIDIA CUDA :: 12", "License :: OSI Approved :: Apache Software License", "Programming Language :: C++", "Programming Language :: Python", "Programming Language :: Python :: 3", - "Programming Language :: Python :: 3.8", - "Programming Language :: Python :: 3.9", - "Programming Language :: Python :: 3.10", - "Programming Language :: Python :: 3.11", ] [project.urls] From 207ea610a8a5f5bbfaa150eb8d93e88b55fe4936 Mon Sep 17 00:00:00 2001 From: Ray Douglass Date: Mon, 12 Feb 2024 15:45:02 -0500 Subject: [PATCH 21/21] Update Changelog [skip ci] --- CHANGELOG.md | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index fa1e0b55e..c826766cf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,30 @@ +# cuCIM 24.02.00 (12 Feb 2024) + +## 🐛 Bug Fixes + +- Fix CUDA trove classifiers & update README install instructions ([#695](https://github.com/rapidsai/cucim/pull/695)) [@jakirkham](https://github.com/jakirkham) +- Exclude PyTest 8 ([#689](https://github.com/rapidsai/cucim/pull/689)) [@jakirkham](https://github.com/jakirkham) +- Update OpenJPEG to 2.5.0 ([#685](https://github.com/rapidsai/cucim/pull/685)) [@jakirkham](https://github.com/jakirkham) +- Fix CI (pt. 2) ([#680](https://github.com/rapidsai/cucim/pull/680)) [@jakirkham](https://github.com/jakirkham) +- Fix CI issues ([#676](https://github.com/rapidsai/cucim/pull/676)) [@jakirkham](https://github.com/jakirkham) +- Remove update to symlink ([#674](https://github.com/rapidsai/cucim/pull/674)) [@raydouglass](https://github.com/raydouglass) +- Add 3rd party license file in Conda packages ([#654](https://github.com/rapidsai/cucim/pull/654)) [@jakirkham](https://github.com/jakirkham) +- Fix style issue in `docs/source/conf.py` ([#648](https://github.com/rapidsai/cucim/pull/648)) [@jakirkham](https://github.com/jakirkham) + +## 🛠️ Improvements + +- Consolidate test requirements in `dependencies.yaml` ([#683](https://github.com/rapidsai/cucim/pull/683)) [@jakirkham](https://github.com/jakirkham) +- Remove usages of rapids-env-update ([#673](https://github.com/rapidsai/cucim/pull/673)) [@KyleFromNVIDIA](https://github.com/KyleFromNVIDIA) +- refactor CUDA versions in dependencies.yaml ([#671](https://github.com/rapidsai/cucim/pull/671)) [@jameslamb](https://github.com/jameslamb) +- minor updates/fixes for consistency with scikit-image 0.22 ([#670](https://github.com/rapidsai/cucim/pull/670)) [@grlee77](https://github.com/grlee77) +- Update CODEOWNERS ([#669](https://github.com/rapidsai/cucim/pull/669)) [@ajschmidt8](https://github.com/ajschmidt8) +- remove redundant notebook ([#668](https://github.com/rapidsai/cucim/pull/668)) [@grlee77](https://github.com/grlee77) +- remove .idea folder (CLion IDE configuration) ([#667](https://github.com/rapidsai/cucim/pull/667)) [@grlee77](https://github.com/grlee77) +- Cleanup old ci and docs subfolders and related files under python/cucim ([#666](https://github.com/rapidsai/cucim/pull/666)) [@grlee77](https://github.com/grlee77) +- remove various files related to old wheel building mechanism ([#665](https://github.com/rapidsai/cucim/pull/665)) [@grlee77](https://github.com/grlee77) +- Relax `openslide` pin ([#653](https://github.com/rapidsai/cucim/pull/653)) [@jakirkham](https://github.com/jakirkham) +- install imagecodecs and openslide-python dependencies so additional tests will run ([#634](https://github.com/rapidsai/cucim/pull/634)) [@grlee77](https://github.com/grlee77) + # cuCIM 23.12.00 (6 Dec 2023) ## 🐛 Bug Fixes