Repository: rapidsai/cucim (language: python)

Issue:
[gpuCI] Forward-merge branch-22.10 to branch-22.12 [skip gpuci]
Forward-merge triggered by push to `branch-22.10` that creates a PR to keep `branch-22.12` up-to-date. If this PR is unable to be immediately merged due to conflicts, it will remain open for the team to manually merge.
[FEA] gpu enabled match_descriptors 
I've heard from some folks that they would like to see an implementation of [match_descriptors](https://scikit-image.org/docs/dev/api/skimage.feature.html?highlight=match_descriptor#skimage.feature.match_descriptors) in cuCIM.  This might be challenging as I don't think [cdist](https://docs.scipy.org/doc/scipy/reference/generated/scipy.spatial.distance.cdist.html) is implemented in CuPy yet.

xref: https://github.com/scikit-image/scikit-image/blob/main/skimage/feature/match.py#L5


Discussion / hints:

> I don't think [cdist](https://docs.scipy.org/doc/scipy/reference/generated/scipy.spatial.distance.cdist.html) is implemented in CuPy yet.

~~Filed issue ( https://github.com/cupy/cupy/issues/6307 )~~

See issue ( https://github.com/cupy/cupy/issues/5946 )
cc @grlee77 @gigony (as we discussed this today)
PR ( https://github.com/cupy/cupy/pull/6690 ) has added `cdist`. Will be in CuPy 11.0.0
Moved to 22.10

There's not a project board for this yet. So moved to feature planning for now. Can update that later

Fix patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index b48db198c..73a40d732 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2,9 +2,28 @@
 
 Please see https://github.com/rapidsai/cucim/releases/tag/v22.12.00a for the latest changes to this development branch.
 
-# cuCIM 22.10.00 (Date TBD)
+# cuCIM 22.10.00 (12 Oct 2022)
 
-Please see https://github.com/rapidsai/cucim/releases/tag/v22.10.00a for the latest changes to this development branch.
+## 🐛 Bug Fixes
+
+- Correctly use dtype when computing shared memory requirements of separable convolution ([#409](https://github.com/rapidsai/cucim/pull/409)) [@grlee77](https://github.com/grlee77)
+- Forward-merge branch-22.08 to branch-22.10 ([#403](https://github.com/rapidsai/cucim/pull/403)) [@jakirkham](https://github.com/jakirkham)
+- Add missing imports of euler_number and perimeter_crofton ([#386](https://github.com/rapidsai/cucim/pull/386)) [@grlee77](https://github.com/grlee77)
+
+## 📖 Documentation
+
+- update pypi CHANGELOG.md for releases 22.08.00 and 22.08.01 ([#404](https://github.com/rapidsai/cucim/pull/404)) [@grlee77](https://github.com/grlee77)
+- Update README.md ([#396](https://github.com/rapidsai/cucim/pull/396)) [@HesAnEasyCoder](https://github.com/HesAnEasyCoder)
+
+## 🚀 New Features
+
+- Allow cupy 11 ([#399](https://github.com/rapidsai/cucim/pull/399)) [@galipremsagar](https://github.com/galipremsagar)
+- Add cucim.skimage.feature.match_descriptors ([#338](https://github.com/rapidsai/cucim/pull/338)) [@grlee77](https://github.com/grlee77)
+
+## 🛠️ Improvements
+
+- Merge docs and add links ([#415](https://github.com/rapidsai/cucim/pull/415)) [@jakirkham](https://github.com/jakirkham)
+- Add benchmarks for scikit-image functions introduced in 22.08 ([#378](https://github.com/rapidsai/cucim/pull/378)) [@grlee77](https://github.com/grlee77)
 
 # cuCIM 22.08.00 (17 Aug 2022)
 
diff --git a/README.md b/README.md
index 5c2217f12..efaaa560f 100644
--- a/README.md
+++ b/README.md
@@ -110,4 +110,4 @@ is used in this project.
 
 Apache-2.0 License (see [LICENSE](LICENSE) file).
 
-Copyright (c) 2020-2021, NVIDIA CORPORATION.
+Copyright (c) 2020-2022, NVIDIA CORPORATION.
diff --git a/benchmarks/skimage/cucim_morphology_bench.py b/benchmarks/skimage/cucim_morphology_bench.py
index 8fe1d6a95..96124820b 100644
--- a/benchmarks/skimage/cucim_morphology_bench.py
+++ b/benchmarks/skimage/cucim_morphology_bench.py
@@ -138,6 +138,7 @@ def main(args):
         ("white_tophat", dict(), dict(), False, True),
         ("black_tophat", dict(), dict(), False, True),
         # _skeletonize.py
+        ("medial_axis", dict(random_state=123), dict(return_distance=[False, True]), False, False),
         ("thin", dict(), dict(), False, True),
         # grayreconstruct.py
         ("reconstruction", dict(), dict(), False, True),
@@ -149,7 +150,7 @@ def main(args):
             continue
 
         ndim = len(shape)
-        if function_name == 'thin':
+        if function_name in ['thin', 'medial_axis']:
             if ndim != 2:
                 raise ValueError("only 2d benchmark data has been implemented")
 
@@ -263,7 +264,7 @@ def main(args):
 
 if __name__ == '__main__':
     parser = argparse.ArgumentParser(description='Benchmarking cuCIM morphology functions')
-    func_name_choices = ['binary_erosion', 'binary_dilation', 'binary_opening', 'binary_closing', 'remove_small_objects', 'remove_small_holes', 'erosion', 'dilation', 'opening', 'closing', 'white_tophat', 'black_tophat', 'thin', 'reconstruction']
+    func_name_choices = ['binary_erosion', 'binary_dilation', 'binary_opening', 'binary_closing', 'remove_small_objects', 'remove_small_holes', 'erosion', 'dilation', 'opening', 'closing', 'white_tophat', 'black_tophat', 'thin', 'medial_axis', 'reconstruction']
     dtype_choices = ['float16', 'float32', 'float64', 'int8', 'int16', 'int32', 'int64', 'uint8', 'uint16', 'uint32', 'uint64']
     parser.add_argument('-i','--img_size', type=str, help='Size of input image (omit color channel, it will be appended as needed)', required=True)
     parser.add_argument('-d','--dtype', type=str, help='Dtype of input image', choices = dtype_choices, required=True)
diff --git a/benchmarks/skimage/cucim_segmentation_bench.py b/benchmarks/skimage/cucim_segmentation_bench.py
index 382349a9b..4b95b5a58 100644
--- a/benchmarks/skimage/cucim_segmentation_bench.py
+++ b/benchmarks/skimage/cucim_segmentation_bench.py
@@ -4,9 +4,7 @@
 import pickle
 
 import cucim.skimage
-import cucim.skimage.data
-import cucim.skimage.exposure
-import cucim.skimage.segmentation
+from cucim.skimage import data, exposure, measure, segmentation
 import cupy as cp
 import numpy as np
 import pandas as pd
@@ -21,7 +19,6 @@ def __init__(
         self,
         function_name,
         shape,
-        contiguous_labels=True,
         dtypes=np.float32,
         fixed_kwargs={},
         var_kwargs={},
@@ -31,8 +28,6 @@ def __init__(
         run_cpu=True,
     ):
 
-        self.contiguous_labels = contiguous_labels
-
         super().__init__(
             function_name=function_name,
             shape=shape,
@@ -45,22 +40,24 @@ def __init__(
             run_cpu=run_cpu,
         )
 
-    def set_args(self, dtype):
-        a = np.array(
-            [
-                [0, 0, 1, 1, 0, 0, 0, 0],
-                [0, 0, 0, 1, 0, 0, 4, 0],
-                [2, 2, 0, 0, 3, 0, 4, 4],
-                [0, 0, 0, 0, 0, 5, 0, 0],
-            ],
-            dtype=dtype,
+    def _generate_labels(self, dtype):
+        ndim = len(self.shape)
+        blobs_kwargs = dict(blob_size_fraction=0.05,
+                            volume_fraction=0.35,
+                            seed=5)
+        # binary blobs only creates square outputs
+        labels = measure.label(
+            data.binary_blobs(max(self.shape), n_dim=ndim, **blobs_kwargs)
         )
-        tiling = tuple(s // a_s for s, a_s in zip(self.shape, a.shape))
-        if self.contiguous_labels:
-            labels = np.kron(a, np.ones(tiling, dtype=a.dtype))
-        else:
-            labels = np.tile(a, tiling)
-        labels_d = cp.asarray(labels)
+        print(f"# labels generated = {labels.max()}")
+
+        # crop to rectangular
+        labels = labels[tuple(slice(s) for s in self.shape)]
+        return labels.astype(dtype, copy=False)
+
+    def set_args(self, dtype):
+        labels_d = self._genereate_labels(dtype)
+        labels = cp.asnumpy(labels_d)
         self.args_cpu = (labels,)
         self.args_gpu = (labels_d,)
 
@@ -68,21 +65,8 @@ def set_args(self, dtype):
 class LabelAndImageBench(LabelBench):
 
     def set_args(self, dtype):
-        a = np.array(
-            [
-                [0, 0, 1, 1, 0, 0, 0, 0],
-                [0, 0, 0, 1, 0, 0, 4, 0],
-                [2, 2, 0, 0, 3, 0, 4, 4],
-                [0, 0, 0, 0, 0, 5, 0, 0],
-            ],
-            dtype=dtype,
-        )
-        tiling = tuple(s // a_s for s, a_s in zip(self.shape, a.shape))
-        if self.contiguous_labels:
-            labels = np.kron(a, np.ones(tiling, dtype=a.dtype))
-        else:
-            labels = np.tile(a, tiling)
-        labels_d = cp.asarray(labels)
+        labels_d = self._genereate_labels(dtype)
+        labels = cp.asnumpy(labels_d)
         image_d = cp.random.standard_normal(labels.shape).astype(np.float32)
         image = cp.asnumpy(image_d)
         self.args_cpu = (image, labels)
@@ -150,7 +134,6 @@ def set_args(self, dtype):
         self.args_gpu = (data, markers)
 
 
-
 def main(args):
 
     pfile = "cucim_segmentation_results.pickle"
@@ -175,6 +158,14 @@ def main(args):
             False,
             True,
         ),
+        # _expand_labels.py
+        (
+            "expand_labels",
+            dict(distance=1),
+            dict(),
+            False,
+            True,
+        ),
         # _join.py
         (
             "relabel_sequential",
@@ -221,6 +212,16 @@ def main(args):
             False,
             False,
         ),
+        (
+            "chan_vese",
+            dict(),
+            # Reduced number of iterations so scikit-image comparison will not
+            # take minutes to complete. Empirically, approximately the same
+            # acceleration was measured for 10 or 100 iterations.
+            dict(max_num_iter=[10], init_level_set=["checkerboard", "disk"]),
+            False,
+            False,
+        ),
         # omit: disk_level_set (simple array generation function)
         # omit: checkerboard_level_set (simple array generation function)
     ]:
@@ -239,7 +240,7 @@ def main(args):
         if shape[-1] == 3 and not allow_color:
             continue
 
-        if function_name in ["clear_border", "relabel_sequential", "find_boundaries", "mark_boundaries", "random_walker"]:
+        if function_name in ["clear_border", "expand_labels", "relabel_sequential", "find_boundaries", "mark_boundaries", "random_walker"]:
             if function_name == 'random_walker':
                 fixed_kwargs['channel_axis'] = -1 if shape[-1] == 3 else None
 
@@ -263,7 +264,7 @@ def main(args):
             all_results = pd.concat([all_results, results["full"]])
 
 
-        elif function_name in ["inverse_gaussian_gradient", "morphological_geodesic_active_contour", "morphological_chan_vese"]:
+        elif function_name in ["inverse_gaussian_gradient", "morphological_geodesic_active_contour", "morphological_chan_vese", "chan_vese"]:
 
             if function_name == "morphological_geodesic_active_contour":
                 bench_class = MorphGeodesicBench
@@ -296,7 +297,7 @@ def main(args):
 
 if __name__ == '__main__':
     parser = argparse.ArgumentParser(description='Benchmarking cuCIM segmentation functions')
-    func_name_choices = ["clear_border", "relabel_sequential", "find_boundaries", "mark_boundaries", "random_walker", "inverse_gaussian_gradient", "morphological_geodesic_active_contour", "morphological_chan_vese"]
+    func_name_choices = ["clear_border", "expand_labels", "relabel_sequential", "find_boundaries", "mark_boundaries", "random_walker", "inverse_gaussian_gradient", "morphological_geodesic_active_contour", "morphological_chan_vese", "chan_vese"]
     label_dtype_choices = ['int8', 'int16', 'int32', 'int64', 'uint8', 'uint16', 'uint32', 'uint64']
     dtype_choices = ['float16', 'float32', 'float64', 'int8', 'int16', 'int32', 'int64', 'uint8', 'uint16', 'uint32', 'uint64']
     parser.add_argument('-i','--img_size', type=str, help='Size of input image (omit color channel, it will be appended as needed)', required=True)
diff --git a/benchmarks/skimage/run-nv-bench-morphology.sh b/benchmarks/skimage/run-nv-bench-morphology.sh
index 4981a58ad..9cf62df6d 100755
--- a/benchmarks/skimage/run-nv-bench-morphology.sh
+++ b/benchmarks/skimage/run-nv-bench-morphology.sh
@@ -1,8 +1,20 @@
 #!/bin/bash
 param_shape=(512,512 3840,2160 3840,2160,3 192,192,192)
-param_filt=(binary_erosion binary_dilation binary_opening binary_closing remove_small_objects remove_small_holes erosion dilation opening closing white_tophat black_tophat thin reconstruction)
-# Note: user-specified dtype ignored for binary_* functions and thin (these only accept binary input)
-param_dt=(float32 uint8)
+param_filt=(binary_erosion binary_dilation binary_opening binary_closing remove_small_objects remove_small_holes erosion dilation opening closing white_tophat black_tophat medial_axis thin reconstruction)
+
+param_dt=(uint8)
+for shape in "${param_shape[@]}"; do
+    for filt in "${param_filt[@]}"; do
+        for dt in "${param_dt[@]}"; do
+            python cucim_morphology_bench.py -f $filt -i $shape -d $dt -t 10
+        done
+    done
+done
+
+# Note: Omit binary_*, medial_axis and thin from floating point benchmarks.
+#       (these functions only take binary input).
+param_filt_float=(remove_small_objects remove_small_holes erosion dilation opening closing white_tophat black_tophat reconstruction)
+param_dt=(float32)
 for shape in "${param_shape[@]}"; do
     for filt in "${param_filt[@]}"; do
         for dt in "${param_dt[@]}"; do
diff --git a/benchmarks/skimage/run-nv-bench-segmentation.sh b/benchmarks/skimage/run-nv-bench-segmentation.sh
index 4723f0c60..f74d05c9a 100755
--- a/benchmarks/skimage/run-nv-bench-segmentation.sh
+++ b/benchmarks/skimage/run-nv-bench-segmentation.sh
@@ -2,7 +2,7 @@
 param_shape=(512,512 3840,2160 3840,2160,3 192,192,192)
 
 # these require an integer-valued label image
-param_filt=(clear_border relabel_sequential find_boundaries mark_boundaries random_walker)
+param_filt=(clear_border expand_labels relabel_sequential find_boundaries mark_boundaries random_walker)
 param_dt=(float32)
 param_dt_label=(uint8 uint32)
 for shape in "${param_shape[@]}"; do
@@ -16,7 +16,7 @@ for shape in "${param_shape[@]}"; do
 done
 
 # these do not require an integer-valued input image
-param_filt=(inverse_gaussian_gradient morphological_geodesic_active_contour morphological_chan_vese)
+param_filt=(inverse_gaussian_gradient morphological_geodesic_active_contour morphological_chan_vese chan_vese)
 param_dt=(float32)
 for shape in "${param_shape[@]}"; do
     for filt in "${param_filt[@]}"; do
diff --git a/conda/recipes/cucim/meta.yaml b/conda/recipes/cucim/meta.yaml
index 89d9f1461..ef99c96a3 100644
--- a/conda/recipes/cucim/meta.yaml
+++ b/conda/recipes/cucim/meta.yaml
@@ -51,8 +51,10 @@ tests:                                 # [linux64]
     - cucim                            # [linux64]
 
 about:
-  home: http://rapids.ai/
+  home: https://developer.nvidia.com/multidimensional-image-processing
+  summary: cucim Python package
   license: Apache-2.0
   license_family: Apache
   license_file: LICENSE
-  summary: cucim Python package
+  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 554841b83..e98674826 100644
--- a/conda/recipes/libcucim/meta.yaml
+++ b/conda/recipes/libcucim/meta.yaml
@@ -44,8 +44,10 @@ requirements:
     - libwebp-base  # [linux or osx]
 
 about:
-  home: http://rapids.ai/
+  home: https://developer.nvidia.com/multidimensional-image-processing
+  summary: libcucim C++ library
   license: Apache-2.0
   license_family: Apache
   license_file: LICENSE
-  summary: libcucim C++ library
+  doc_url: https://docs.rapids.ai/api/cucim/stable/
+  dev_url: https://github.com/rapidsai/cucim
diff --git a/python/cucim/CHANGELOG.md b/python/cucim/CHANGELOG.md
deleted file mode 100644
index deff519e8..000000000
--- a/python/cucim/CHANGELOG.md
+++ /dev/null
@@ -1,166 +0,0 @@
-
-# Changelog (See [Release Notes](https://github.com/rapidsai/cucim/wiki/Release-Notes))
-
-## [22.08.01](https://github.com/rapidsai/cucim/wiki/release_notes_v22.08.01)
-
-- Euclidean distance transform: fix bad choice of block parameters ([#393](https://github.com/rapidsai/cucim/pull/393)) [@grlee77](https://github.com/grlee77))
-
-## [22.08.00](https://github.com/rapidsai/cucim/wiki/release_notes_v22.08.00)
-
-## 🚨 Breaking Changes
-
-- Stain extraction: use a less strict condition across channels when thresholding ([#316](https://github.com/rapidsai/cucim/pull/278)) [@grlee77](https://github.com/grlee77)
-
-## 🐛 Bug Fixes
-
-- create SimilarityTransform using CuPy 9.x-compatible indexing ([#365](https://github.com/rapidsai/cucim/pull/365)) [@grlee77](https://github.com/grlee77)
-- Add `__init__.py` in `cucim.core` ([#359](https://github.com/rapidsai/cucim/pull/359)) [@jakirkham](https://github.com/jakirkham)
-- Stain extraction: use a less strict condition across channels when thresholding ([#316](https://github.com/rapidsai/cucim/pull/316)) [@grlee77](https://github.com/grlee77)
-- Incorporate bug fixes from skimage 0.19.3 ([#312](https://github.com/rapidsai/cucim/pull/312)) [@grlee77](https://github.com/grlee77)
-- fix RawKernel bug for canny filter when quantiles are used ([#310](https://github.com/rapidsai/cucim/pull/310)) [@grlee77](https://github.com/grlee77)
-
-## 📖 Documentation
-
-- Defer loading of `custom.js` ([#383](https://github.com/rapidsai/cucim/pull/383)) [@galipremsagar](https://github.com/galipremsagar)
-- add cucim.core.morphology to API docs + other docstring fixes ([#367](https://github.com/rapidsai/cucim/pull/367)) [@grlee77](https://github.com/grlee77)
-- Update README.md ([#361](https://github.com/rapidsai/cucim/pull/361)) [@HesAnEasyCoder](https://github.com/HesAnEasyCoder)
-- remove unimplemented functions from See Also and fix version numbers in deprecation warnings ([#356](https://github.com/rapidsai/cucim/pull/356)) [@grlee77](https://github.com/grlee77)
-- Forward-merge branch-22.06 to branch-22.08 ([#344](https://github.com/rapidsai/cucim/pull/344)) [@grlee77](https://github.com/grlee77)
-- Update README.md ([#315](https://github.com/rapidsai/cucim/pull/315)) [@HesAnEasyCoder](https://github.com/HesAnEasyCoder)
-- Update index.rst ([#314](https://github.com/rapidsai/cucim/pull/314)) [@HesAnEasyCoder](https://github.com/HesAnEasyCoder)
-- Update PyPI package documentation for v22.06.00 ([#311](https://github.com/rapidsai/cucim/pull/311)) [@gigony](https://github.com/gigony)
-
-## 🚀 New Features
-
-- Add segmentation with the Chan-Vese active contours method ([#343](https://github.com/rapidsai/cucim/pull/343)) [@grlee77](https://github.com/grlee77)
-- Add cucim.skimage.morphology.medial_axis ([#342](https://github.com/rapidsai/cucim/pull/342)) [@grlee77](https://github.com/grlee77)
-- Add cucim.skimage.segmentation.expand_labels ([#341](https://github.com/rapidsai/cucim/pull/341)) [@grlee77](https://github.com/grlee77)
-- Add Euclidean distance transform for images/volumes ([#318](https://github.com/rapidsai/cucim/pull/318)) [@grlee77](https://github.com/grlee77)
-
-## 🛠️ Improvements
-
-- Revert &quot;Allow CuPy 11&quot; ([#362](https://github.com/rapidsai/cucim/pull/362)) [@galipremsagar](https://github.com/galipremsagar)
-- Fix issues with day &amp; night modes in python docs ([#360](https://github.com/rapidsai/cucim/pull/360)) [@galipremsagar](https://github.com/galipremsagar)
-- Allow CuPy 11 ([#357](https://github.com/rapidsai/cucim/pull/357)) [@jakirkham](https://github.com/grlee77)
-- more efficient separable convolution ([#355](https://github.com/rapidsai/cucim/pull/355)) [@grlee77](https://github.com/grlee77)
-- Support resolution and spacing metadata ([#349](https://github.com/rapidsai/cucim/pull/349)) [@gigony](https://github.com/gigony)
-- Performance optimizations to morphological segmentation functions ([#340](https://github.com/rapidsai/cucim/pull/340)) [@grlee77](https://github.com/grlee77)
-- benchmarks: avoid use of deprecated pandas API ([#339](https://github.com/rapidsai/cucim/pull/339)) [@grlee77](https://github.com/grlee77)
-- Reduce memory overhead and improve performance of normalize_colors_pca ([#328](https://github.com/rapidsai/cucim/pull/328)) [@grlee77](https://github.com/grlee77)
-- Protect against obscure divide by zero error in edge case of `normalize_colors_pca` ([#327](https://github.com/rapidsai/cucim/pull/327)) [@grlee77](https://github.com/grlee77)
-- complete parametrization of cucim.skimage benchmarks ([#324](https://github.com/rapidsai/cucim/pull/324)) [@grlee77](https://github.com/grlee77)
-- parameterization of `filters` and `features` benchmarks (v2) ([#322](https://github.com/rapidsai/cucim/pull/322)) [@grlee77](https://github.com/grlee77)
-- Add a fast histogram-based median filter ([#317](https://github.com/rapidsai/cucim/pull/317)) [@grlee77](https://github.com/grlee77)
-- Remove custom compiler environment variables ([#307](https://github.com/rapidsai/cucim/pull/307)) [@ajschmidt8](https://github.com/ajschmidt8)
-
-## [22.02.06](https://github.com/rapidsai/cucim/wiki/release_notes_v22.06.00)
-
-- [Update/Breaking] Promote small integer types to single rather than double precision ([#278](https://github.com/rapidsai/cucim/pull/278)) [@grlee77](https://github.com/grlee77)
-- [Bug] Populate correct channel names for RGBA image ([#294](https://github.com/rapidsai/cucim/pull/294)) [@gigony](https://github.com/gigony)
-- [Bug] Merge branch-22.04 into branch-22.06 ([#258](https://github.com/rapidsai/cucim/pull/258)) [@jakirkham](https://github.com/jakirkham)
-- [New] add missing `cucim.skimage.segmentation.clear_border` function ([#267](https://github.com/rapidsai/cucim/pull/267)) [@grlee77](https://github.com/grlee77)
-- [New] add `cucim.core.operations.color.stain_extraction_pca` and `cucim.core.operations.color.normalize_colors_pca` for digital pathology H&E stain extraction and normalization ([#273](https://github.com/rapidsai/cucim/pull/273)) [@grlee77](https://github.com/grlee77), [@drbeh](https://github.com/drbeh)
-- [Update] Update to use DLPack v0.6 ([#295](https://github.com/rapidsai/cucim/pull/295)) [@gigony](https://github.com/gigony)
-- [Update] Remove plugin-related messages temporarily ([#291](https://github.com/rapidsai/cucim/pull/291)) [@gigony](https://github.com/gigony)
-- [Update] Simplify recipes ([#286](https://github.com/rapidsai/cucim/pull/286)) [@Ethyling](https://github.com/Ethyling)
-- [Update] Use cupy.fuse to improve efficiency hessian_matrix_eigvals ([#280](https://github.com/rapidsai/cucim/pull/280)) [@grlee77](https://github.com/grlee77)
-- [Update] improve efficiency of histogram-based thresholding functions ([#276](https://github.com/rapidsai/cucim/pull/276)) [@grlee77](https://github.com/grlee77)
-- [Update] Remove unused dependencies in GPU tests job ([#268](https://github.com/rapidsai/cucim/pull/268)) [@Ethyling](https://github.com/Ethyling)
-- [Update] Enable footprint decomposition for morphology ([#274](https://github.com/rapidsai/cucim/pull/274)) [@grlee77](https://github.com/grlee77)
-- [Update] Use conda compilers ([#232](https://github.com/rapidsai/cucim/pull/232)) [@Ethyling](https://github.com/Ethyling)
-- [Update] Build packages using mambabuild ([#216](https://github.com/rapidsai/cucim/pull/216)) [@Ethyling](https://github.com/Ethyling)
-- [Doc] update outdated links to example data ([#289](https://github.com/rapidsai/cucim/pull/289)) [@grlee77](https://github.com/grlee77)
-- [Doc] Add missing API docs ([#275](https://github.com/rapidsai/cucim/pull/275)) [@grlee77](https://github.com/grlee77)
-
-## [22.02.04](https://github.com/rapidsai/cucim/wiki/release_notes_v22.04.00)
-
-- [Bug] Fix ImportError from vendored code ([#252](https://github.com/rapidsai/cucim/pull/252)) [@grlee77](https://github.com/grlee77)
-- [Bug] Fix wrong dimension in metadata ([#248](https://github.com/rapidsai/cucim/pull/248)) [@gigony](https://github.com/gigony)
-
-## [22.02.01](https://github.com/rapidsai/cucim/wiki/release_notes_v22.02.01)
-
-- [Bug] Check nullptr of handler in CuFileDriver::close() ([#229](https://github.com/rapidsai/cucim/pull/229)) [@gigony](https://github.com/gigony)
-- [Bug] Handle file descriptor ownership and update documents for GDS ([#234](https://github.com/rapidsai/cucim/pull/234)) [@gigony](https://github.com/gigony)
-- [Bug] Apply fixes to skimage.transform scheduled for scikit-image 0.19.2 ([#208](https://github.com/rapidsai/cucim/pull/208)) [@grlee7](https://github.com/grlee7)
-- [New] Randomization of transforms per image per batch ([#231](https://github.com/rapidsai/cucim/pull/231)) [@shekhardw](https://github.com/shekhardw)
-- [New] Expose data type of CuImage object for interoperability with NumPy ([#246](https://github.com/rapidsai/cucim/pull/246)) [@gigony](https://github.com/gigony)
-- [Update] Remove verbose plugin messages temporarily. Address [#109](https://github.com/rapidsai/cucim/issues/109) ([BUG] - Info messages appearing as warnings in Jupyter notebooks)
-- [Doc] Fix docs builds ([#218](https://github.com/rapidsai/cucim/pull/218)) [@ajschmidt8](https://github.com/ajschmidt8)
-- [Doc] Update GTC 2021 Spring video links ([#227](https://github.com/rapidsai/cucim/pull/227)) [@gigony](https://github.com/gigony)
-- [Doc] Update documents for v22.02.00 ([#226](https://github.com/rapidsai/cucim/pull/226)) [@gigony](https://github.com/gigony)
-
-## [22.02.00](https://github.com/rapidsai/cucim/wiki/release_notes_v22.02.00)
-
-- [New/Breaking] Update cucim.skimage API to match scikit-image 0.19 ([#190](https://github.com/rapidsai/cucim/pull/190)) [@glee77](https://github.com/glee77)
-- [Bug] Fix a bug in [v21.12.01](https://github.com/rapidsai/cucim/wiki/release_notes_v21.12.01) ([#191](https://github.com/rapidsai/cucim/pull/191)) [@gigony](https://github.com/gigony)
-  - Fix GPU memory leak when using nvJPEG API (when `device='cuda'` parameter is used in `read_region` method).
-- [Bug] Fix segfault for preferred_memory_capacity in Python 3.9+ ([#214](https://github.com/rapidsai/cucim/pull/214)) [@gigony](https://github.com/gigony)
-- [Doc] PyPI v21.12.00 release ([#182](https://github.com/rapidsai/cucim/pull/182)) [@gigony](https://github.com/gigony)
-- [New] Allow CuPy 10 ([#195](https://github.com/rapidsai/cucim/pull/195)) [@jakikham](https://github.com/jakikham)
-- [New] Support multi-threads and batch, and support nvJPEG for JPEG-compressed images ([#191](https://github.com/rapidsai/cucim/pull/191)) [@gigony](https://github.com/gigony)
-- [New] Update cucim.skimage API to match scikit-image 0.19 ([#190](https://github.com/rapidsai/cucim/pull/190)) [@glee77](https://github.com/glee77)
-- [Update] Add missing imports tests ([#183](https://github.com/rapidsai/cucim/pull/183)) [@Ethyling](https://github.com/Ethyling)
-- [Update] Allow installation with CuPy 10 ([#197](https://github.com/rapidsai/cucim/pull/197)) [@glee77](https://github.com/glee77)
-- [Update] Upgrade Numpy to 1.18 for Python 3.9 support ([#196](https://github.com/rapidsai/cucim/pull/196)) [@Ethyling](https://github.com/Ethyling)
-- [Update] Upgrade Numpy to 1.19 for Python 3.9 support ([#203](https://github.com/rapidsai/cucim/pull/203)) [@Ethyling](https://github.com/Ethyling)
-
-## [21.12.01](https://github.com/rapidsai/cucim/wiki/release_notes_v21.12.01)
-
-- [New] Supporting Multithreading and Batch Processing ([#149](https://github.com/rapidsai/cucim/issues/149)) [@gigony](https://github.com/gigony)
-
-## [21.12.00](https://github.com/rapidsai/cucim/wiki/release_notes_v21.12.00)
-
-- [New] Support Aperio SVS with CPU LZW and jpeg2k decoder ([#141](https://github.com/rapidsai/cucim/pull/141)) [@gigony](https://github.com/gigony)
-- [New] Add NVTX support for performance analysis ([#144](https://github.com/rapidsai/cucim/pull/144)) [@gigony](https://github.com/gigony)
-- [New] Normalize operation ([#150](https://github.com/rapidsai/cucim/pull/150)) [@shekhardw](https://github.com/shekhardw)
-- [Bug] Load libcufile.so with RTLD_NODELETE flag ([#177](https://github.com/rapidsai/cucim/pull/177)) [@gigony](https://github.com/gigony)
-- [Bug] Remove rmm/nvcc dependencies to fix cudaErrorUnsupportedPtxVersion error ([#175](https://github.com/rapidsai/cucim/pull/175)) [@gigony](https://github.com/gigony)
-- [Bug] Do not compile code with nvcc if no CUDA kernel exists ([#171](https://github.com/rapidsai/cucim/pull/171)) [@gigony](https://github.com/gigony)
-- [Bug] Fix a segmentation fault due to unloaded libcufile ([#158](https://github.com/rapidsai/cucim/pull/158)) [@gigony](https://github.com/gigony)
-- [Bug] Fix thread contention in Cache ([#145](https://github.com/rapidsai/cucim/pull/145)) [@gigony](https://github.com/gigony)
-- [Bug] Build with NumPy 1.17 ([#148](https://github.com/rapidsai/cucim/pull/148)) [@jakirkham](https://github.com/jakirkham)
-- [Doc] Add Jupyter notebook for SVS Support ([#147](https://github.com/rapidsai/cucim/pull/147)) [@gigony](https://github.com/gigony)
-- [Doc] Update change log for v21.10.01 ([#142](https://github.com/rapidsai/cucim/pull/142)) [@gigony](https://github.com/gigony)
-- [Doc] update docs theme to pydata-sphinx-theme ([#138](https://github.com/rapidsai/cucim/pull/138)) [@quasiben](https://github.com/quasiben)
-- [Doc] Update Github links in README.md through script ([#132](https://github.com/rapidsai/cucim/pull/132)) [@gigony](https://github.com/gigony)
-- [Doc] Fix GDS link in Jupyter notebook ([#131](https://github.com/rapidsai/cucim/pull/131)) [@gigony](https://github.com/gigony)
-- [Doc] Update notebook for the interoperability with DALI ([#127](https://github.com/rapidsai/cucim/pull/127)) [@gigony](https://github.com/gigony)
-- [Update] Update `conda` recipes for Enhanced Compatibility effort by ([#164](https://github.com/rapidsai/cucim/pull/164)) [@ajschmidt8](https://github.com/ajschmidt8)
-- [Update] Fix Changelog Merge Conflicts for `branch-21.12` ([#156](https://github.com/rapidsai/cucim/pull/156)) [@ajschmidt8](https://github.com/ajschmidt8)
-- [Update] Add cucim.kit.cumed plugin with skeleton ([#129](https://github.com/rapidsai/cucim/pull/129)) [@gigony](https://github.com/gigony)
-- [Update] Update initial cpp unittests ([#128](https://github.com/rapidsai/cucim/pull/128)) [@gigony](https://github.com/gigony)
-- [Update] Optimize zoom out implementation with separate padding kernel ([#125](https://github.com/rapidsai/cucim/pull/125)) [@chirayuG-nvidia](https://github.com/chirayuG-nvidia)
-- [Update] Do not force install linux-64 version of openslide-python ([#124](https://github.com/rapidsai/cucim/pull/124)) [@Ethyling](https://github.com/Ethyling)
-
-## [21.10.01](https://github.com/rapidsai/cucim/wiki/release_notes_v21.10.01)
-
-- [New] Support Aperio SVS with CPU LZW and jpeg2k decoder ([#141](https://github.com/rapidsai/cucim/pull/141))
-
-## [21.10.00](https://github.com/rapidsai/cucim/wiki/release_notes_v21.10.00)
-
-- [New] Add transforms for Digital Pathology ([#100](https://github.com/rapidsai/cucim/pull/100)) [@shekhardw](https://github.com/shekhardw) [@chirayuG-nvidia](https://github.com/chirayuG-nvidia)
-- [New] Enable GDS and Support Runtime Context (__enter__, __exit__) for CuFileDriver and CuImage ([#106](https://github.com/rapidsai/cucim/pull/106)) [@gigony](https://github.com/gigony)
-- [New] Add a mechanism for user to know the availability of cucim.CuImage ([#107](https://github.com/rapidsai/cucim/pull/107)) [@gigony](https://github.com/gigony)
-- [New] Support raw RGB tiled TIFF ([#108](https://github.com/rapidsai/cucim/pull/108)) [@gigony](https://github.com/gigony)
-- [Bug] fix failing regionprops test cases ([#110](https://github.com/rapidsai/cucim/pull/110)) [@grlee77](https://github.com/grlee77)
-- [Doc] Forward-merge branch-21.08 to branch-21.10 ([#88](https://github.com/rapidsai/cucim/pull/88)) [@jakirkham](https://github.com/jakirkham)
-- [Doc] Update PyPI cuCIM v21.08.01 README.md and CHANGELOG.md ([#87](https://github.com/rapidsai/cucim/pull/87)) [@gigony](https://github.com/gigony)
-- [Update] ENH Replace gpuci_conda_retry with gpuci_mamba_retry ([#69](https://github.com/rapidsai/cucim/pull/69)) [@dillon-cullinan](https://github.com/dillon-cullinan)
-
-## [21.08.02](https://github.com/rapidsai/cucim/wiki/release_notes_v21.08.02)
-
-- [New] Add transforms for Digital Pathology ([#100](https://github.com/rapidsai/cucim/pull/100)) [@shekhardw](https://github.com/shekhardw) [@chirayuG-nvidia](https://github.com/chirayuG-nvidia)
-
-## [21.08.01](https://github.com/rapidsai/cucim/wiki/release_notes_v21.08.01)
-
-- [New] Add skimage.morphology.thin ([#27](https://github.com/rapidsai/cucim/pull/27))
-- [Bug] Fix missing `__array_interface__` for associated_image(): ([#48](https://github.com/rapidsai/cucim/pull/48), [#65](https://github.com/rapidsai/cucim/pull/65))
-- [Testing] Added unit and performance tests for TIFF loaders ([#62](https://github.com/rapidsai/cucim/pull/62))
-- [Bug] Fix Windows int-type Bug: ([#72](https://github.com/rapidsai/cucim/pull/72))
-- [Update] Use more descriptive ElementwiseKernel names in cucim.skimage: ([#75](https://github.com/rapidsai/cucim/pull/75))
-
-## [21.06.00](https://github.com/rapidsai/cucim/wiki/release_notes_v21.06.00)
-
-- Implement cache mechanism
-- Add `__cuda_array_interface`.
-- Fix a memory leak in Deflate decoder.
diff --git a/python/cucim/CHANGELOG.md b/python/cucim/CHANGELOG.md
new file mode 120000
index 000000000..699cc9e7b
--- /dev/null
+++ b/python/cucim/CHANGELOG.md
@@ -0,0 +1,1 @@
+../../CHANGELOG.md
\ No newline at end of file
diff --git a/python/cucim/README.md b/python/cucim/README.md
deleted file mode 100644
index 580eef051..000000000
--- a/python/cucim/README.md
+++ /dev/null
@@ -1,184 +0,0 @@
-# [cuCIM](https://github.com/rapidsai/cucim)
-
-<!-- start-include-here -->
-
-[RAPIDS](https://rapids.ai) [cuCIM](https://github.com/rapidsai/cucim) is an extensible toolkit designed to provide GPU accelerated I/O, computer vision & image processing primitives for N-Dimensional images with a focus on biomedical imaging.
-
-**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: <https://github.com/NVIDIA/MagnumIO/tree/main/gds/readers/cucim-gds>
-- [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/)
-
-
-## Quick Start
-
-### Install 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
-```
-
-### Jupyter Notebooks
-
-Please check out our [Welcome](https://github.com/rapidsai/cucim/blob/branch-22.12/notebooks/Welcome.ipynb) notebook.
-
-### Open Image
-
-cuCIM's dataloader(`cucim.CuImage` class) is currently supporting Digital-pathology specific image formats (tiled multi-resolution raw/deflate/lzw/JPEG/JPEG2000-compressed TIFF-like RGB images such as Generic TIFF, Philips TIFF, Aperio SVS format).
-
-```python
-from cucim import CuImage
-img = CuImage('image.tif')
-```
-
-### See Metadata
-
-```python
-import json
-print(img.is_loaded)        # True if image data is loaded & available.
-print(img.device)           # A device type.
-print(img.ndim)             # The number of dimensions.
-print(img.dims)             # A string containing a list of dimensions being requested.
-print(img.shape)            # A tuple of dimension sizes (in the order of `dims`).
-print(img.size('XYC'))      # Returns size as a tuple for the given dimension order.
-print(img.dtype)            # The data type of the image.
-print(img.channel_names)    # A channel name list.
-print(img.spacing())        # Returns physical size in tuple.
-print(img.spacing_units())  # Units for each spacing element (size is same with `ndim`).
-print(img.origin)           # Physical location of (0, 0, 0) (size is always 3).
-print(img.direction)        # Direction cosines (size is always 3x3).
-print(img.coord_sys)        # Coordinate frame in which the direction cosines are
-                            # measured. Available Coordinate frame is not finalized yet.
-
-# Returns a set of associated image names.
-print(img.associated_images)
-# Returns a dict that includes resolution information.
-print(json.dumps(img.resolutions, indent=2))
-# A metadata object as `dict`
-print(json.dumps(img.metadata, indent=2))
-# A raw metadata string.
-print(img.raw_metadata)
-```
-
-### Read Region
-
-```python
-# Install matplotlib (`pip install matplotlib`) if not installed before.
-from matplotlib import pyplot as plt
-def visualize(image):
-    dpi = 80.0
-    height, width, _ = image.shape
-    plt.figure(figsize=(width / dpi, height / dpi))
-    plt.axis('off')
-    plt.imshow(image)
-
-```
-
-```python
-import numpy as np
-
-# Read whole slide at the highest resolution
-resolutions = img.resolutions
-level_count = resolutions['level_count']  # level: 0 ~ (level_count - 1)
-
-# Note: ‘level’ is at 3rd parameter (OpenSlide has it at 2nd parameter)
-#   `location` is level-0 based coordinates (using the level-0 reference frame)
-#   If `size` is not specified, size would be (width, height) of the image at the specified `level`.
-region = img.read_region(location=(5000, 5000), size=(512, 512), level=0)
-
-visualize(region)
-#from PIL import Image
-#Image.fromarray(np.asarray(region))
-```
-
-Aperio SVS (.svs) image format is supported since cuCIM [v21.10.01](https://github.com/rapidsai/cucim/wiki/release_notes_v21.10.01#support-aperio-svs-svs).
-
-Please check [this notebook](https://nbviewer.org/github/rapidsai/cucim/blob/branch-22.12/notebooks/Supporting_Aperio_SVS_Format.ipynb) to see how to use the feature.
-
-### Using Cache
-
-Please look at this [notebook](https://nbviewer.jupyter.org/github/rapidsai/cucim/blob/branch-22.12/notebooks/Using_Cache.ipynb).
-
-### Accessing File with GDS
-
-Please look at this [notebook](https://nbviewer.jupyter.org/github/rapidsai/cucim/blob/branch-22.12/notebooks/Accessing_File_with_GDS.ipynb).
-
-### NVTX Support for Performance Analysis
-
-Please look at [this release note](https://github.com/rapidsai/cucim/wiki/release_notes_v21.12.00#2-add-nvtx-support-for-performance-analysis).
-
-### Supporting Multithreading and Batch Processing
-
-Please look at [this release note](https://github.com/rapidsai/cucim/wiki/release_notes_v22.02.00#2-supporting-multithreading-and-batch-processing).
-
-### Using scikit-image API
-
-Import `cucim.skimage` instead of `skimage`.
-
-```python
-# The following code is modified from https://scikit-image.org/docs/dev/auto_examples/color_exposure/plot_ihc_color_separation.html#sphx-glr-auto-examples-color-exposure-plot-ihc-color-separation-py
-#
-import cupy as cp  # modified from: `import numpy as np`
-import matplotlib.pyplot as plt
-
-# from skimage import data
-from cucim.skimage.color import rgb2hed, hed2rgb  # modified from: `from skimage.color import rgb2hed, hed2rgb`
-
-# Load image (You can download sample images from https://openslide.cs.cmu.edu/download/openslide-testdata/Aperio/)
-from cucim import CuImage
-img = CuImage("CMU-1.svs")
-region = img.read_region((30000, 10000), (256, 256))
-
-# Example IHC image
-ihc_rgb = cp.asarray(region)  # modified from: `ihc_rgb = data.immunohistochemistry()`
-
-# Separate the stains from the IHC image
-ihc_hed = rgb2hed(ihc_rgb)
-
-# Create an RGB image for each of the stains
-null = cp.zeros_like(ihc_hed[:, :, 0])  # np -> cp
-ihc_h = hed2rgb(cp.stack((ihc_hed[:, :, 0], null, null), axis=-1))  # np -> cp
-ihc_e = hed2rgb(cp.stack((null, ihc_hed[:, :, 1], null), axis=-1))  # np -> cp
-ihc_d = hed2rgb(cp.stack((null, null, ihc_hed[:, :, 2]), axis=-1))  # np -> cp
-
-# Display
-fig, axes = plt.subplots(2, 2, figsize=(7, 6), sharex=True, sharey=True)
-ax = axes.ravel()
-
-ax[0].imshow(ihc_rgb.get())  # appended `.get()`
-ax[0].set_title("Original image")
-
-ax[1].imshow(ihc_h.get())  # appended `.get()`
-ax[1].set_title("Hematoxylin")
-
-ax[2].imshow(ihc_e.get())  # appended `.get()`
-ax[2].set_title("Eosin")
-
-ax[3].imshow(ihc_d.get())  # appended `.get()`
-ax[3].set_title("DAB")
-
-for a in ax.ravel():
-    a.axis('off')
-
-fig.tight_layout()
-```
-
-## Acknowledgments
-
-Without awesome third-party open source software, this project wouldn't exist.
-
-Please find `LICENSE-3rdparty.md` to see which third-party open source software
-is used in this project.
-
-## License
-
-Apache-2.0 License (see `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,1 @@
+../../README.md
\ No newline at end of file
diff --git a/python/cucim/setup.py b/python/cucim/setup.py
index cdff432e8..57e390ad9 100755
--- a/python/cucim/setup.py
+++ b/python/cucim/setup.py
@@ -36,7 +36,7 @@ def read(*names, **kwargs):
     ),
     long_description_content_type='text/markdown',
     author='NVIDIA Corporation',
-    url='https://github.com/rapidsai/cucim',
+    url='https://developer.nvidia.com/multidimensional-image-processing',
     packages=find_packages('src'),
     package_dir={'cucim': 'src/cucim'},
     include_package_data=True,
@@ -73,8 +73,9 @@ def read(*names, **kwargs):
         'Topic :: Scientific/Engineering :: Image Processing',
     ],
     project_urls={
-        'Documentation': 'https://cucim.readthedocs.io/',
-        'Changelog': 'https://cucim.readthedocs.io/en/latest/changelog.html',
+        'Source': 'https://github.com/rapidsai/cucim',
+        'Documentation': 'https://docs.rapids.ai/api/cucim/stable/',
+        'Changelog': 'https://github.com/rapidsai/cucim/blob/main/CHANGELOG.md',
         'Issue Tracker': 'https://github.com/rapidsai/cucim/issues',
     },
     keywords=[
diff --git a/python/cucim/src/cucim/skimage/_vendored/_ndimage_filters.py b/python/cucim/src/cucim/skimage/_vendored/_ndimage_filters.py
index e4dc1311d..8193c0c63 100644
--- a/python/cucim/src/cucim/skimage/_vendored/_ndimage_filters.py
+++ b/python/cucim/src/cucim/skimage/_vendored/_ndimage_filters.py
@@ -8,6 +8,14 @@
 from cucim.skimage._vendored import _ndimage_filters_core as _filters_core
 from cucim.skimage._vendored import _ndimage_util as _util
 
+from cucim.skimage.filters._separable_filtering import (ResourceLimitError,
+                                                        _shmem_convolve1d)
+try:
+    from cupy.cuda.compiler import CompileException
+    compile_errors = (ResourceLimitError, CompileException)
+except ImportError:
+    compile_errors = (ResourceLimitError,)
+
 
 def correlate(input, weights, output=None, mode='reflect', cval=0.0, origin=0):
     """Multi-dimensional correlate.
@@ -182,7 +190,9 @@ def _correlate_or_convolve1d(input, weights, axis, output, mode, cval, origin,
                              convolution=False, algorithm=None):
     # Calls fast shared-memory convolution when possible, otherwise falls back
     # to the vendored elementwise _correlate_or_convolve
+    default_algorithm = False
     if algorithm is None:
+        default_algorithm = True
         if input.ndim == 2 and weights.size <= 256:
             algorithm = 'shared_memory'
         else:
@@ -194,8 +204,6 @@ def _correlate_or_convolve1d(input, weights, axis, output, mode, cval, origin,
     if mode == 'wrap':
         mode = 'grid-wrap'
     if algorithm == 'shared_memory':
-        from cucim.skimage.filters._separable_filtering import (
-            ResourceLimitError, _shmem_convolve1d)
         if input.ndim not in [2, 3]:
             raise NotImplementedError(
                 f"shared_memory not implemented for ndim={input.ndim}"
@@ -205,12 +213,14 @@ def _correlate_or_convolve1d(input, weights, axis, output, mode, cval, origin,
                                     mode=mode, cval=cval, origin=origin,
                                     convolution=convolution)
             return out
-        except ResourceLimitError:
+        except compile_errors:
             # fallback to elementwise if inadequate shared memory available
-            warnings.warn(
-                "Inadequate resources for algorithm='shared_memory: "
-                "falling back to the elementwise implementation"
-            )
+            if not default_algorithm:
+                # only warn if 'shared_memory' was explicitly requested
+                warnings.warn(
+                    "Inadequate resources for algorithm='shared_memory: "
+                    "falling back to the elementwise implementation"
+                )
             algorithm = 'elementwise'
     if algorithm == 'elementwise':
         weights, origins = _filters_core._convert_1d_args(
diff --git a/python/cucim/src/cucim/skimage/feature/__init__.py b/python/cucim/src/cucim/skimage/feature/__init__.py
index 4de8a014b..301816382 100644
--- a/python/cucim/src/cucim/skimage/feature/__init__.py
+++ b/python/cucim/src/cucim/skimage/feature/__init__.py
@@ -6,40 +6,11 @@
                      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
 
 
-@deprecated(
-    alt_func="cucim.skimage.registration.phase_cross_correlation",
-    removed_version="0.19",
-)
-def masked_register_translation(
-    src_image, target_image, src_mask, target_mask=None, overlap_ratio=0.3
-):
-    from ..registration import phase_cross_correlation
-
-    return phase_cross_correlation(
-        src_image,
-        target_image,
-        reference_mask=src_mask,
-        moving_mask=target_mask,
-        overlap_ratio=overlap_ratio,
-    )
-
-
-@deprecated(
-    alt_func="cucim.skimage.registration.phase_cross_correlation",
-    removed_version="0.19",
-)
-def register_translation(
-    src_image, target_image, upsample_factor=1, space="real", return_error=True
-):
-    from ..registration._phase_cross_correlation import \
-        phase_cross_correlation as func
-    return func(src_image, target_image, upsample_factor, space, return_error)
-
-
 __all__ = ['canny',
            'daisy',
            'multiscale_basic_features',
@@ -61,5 +32,4 @@ def register_translation(
            # 'corner_fast',
            # 'corner_orientations',
            'match_template',
-           'register_translation',
-           'masked_register_translation']
+           'match_descriptors']
diff --git a/python/cucim/src/cucim/skimage/feature/match.py b/python/cucim/src/cucim/skimage/feature/match.py
new file mode 100644
index 000000000..0187e369c
--- /dev/null
+++ b/python/cucim/src/cucim/skimage/feature/match.py
@@ -0,0 +1,120 @@
+import warnings
+
+import cupy as cp
+
+try:
+    # CuPy's cdist will only work if pylibraft is available
+    import pylibraft  # noqa
+    from cupyx.scipy.spatial.distance import cdist
+    have_gpu_cdist = True
+except ImportError:
+    from scipy.spatial.distance import cdist
+    have_gpu_cdist = False
+
+
+def match_descriptors(descriptors1, descriptors2, metric=None, p=2,
+                      max_distance=cp.inf, cross_check=True, max_ratio=1.0):
+    """Brute-force matching of descriptors.
+
+    For each descriptor in the first set this matcher finds the closest
+    descriptor in the second set (and vice-versa in the case of enabled
+    cross-checking).
+
+    Parameters
+    ----------
+    descriptors1 : (M, P) array
+        Descriptors of size P about M keypoints in the first image.
+    descriptors2 : (N, P) array
+        Descriptors of size P about N keypoints in the second image.
+    metric : {'euclidean', 'cityblock', 'minkowski', 'hamming', ...} , optional
+        The metric to compute the distance between two descriptors. See
+        `scipy.spatial.distance.cdist` for all possible types. The hamming
+        distance should be used for binary descriptors. By default the L2-norm
+        is used for all descriptors of dtype float or double and the Hamming
+        distance is used for binary descriptors automatically.
+    p : int, optional
+        The p-norm to apply for ``metric='minkowski'``.
+    max_distance : float, optional
+        Maximum allowed distance between descriptors of two keypoints
+        in separate images to be regarded as a match.
+    cross_check : bool, optional
+        If True, the matched keypoints are returned after cross checking i.e. a
+        matched pair (keypoint1, keypoint2) is returned if keypoint2 is the
+        best match for keypoint1 in second image and keypoint1 is the best
+        match for keypoint2 in first image.
+    max_ratio : float, optional
+        Maximum ratio of distances between first and second closest descriptor
+        in the second set of descriptors. This threshold is useful to filter
+        ambiguous matches between the two descriptor sets. The choice of this
+        value depends on the statistics of the chosen descriptor, e.g.,
+        for SIFT descriptors a value of 0.8 is usually chosen, see
+        D.G. Lowe, "Distinctive Image Features from Scale-Invariant Keypoints",
+        International Journal of Computer Vision, 2004.
+
+    Returns
+    -------
+    matches : (Q, 2) array
+        Indices of corresponding matches in first and second set of
+        descriptors, where ``matches[:, 0]`` denote the indices in the first
+        and ``matches[:, 1]`` the indices in the second set of descriptors.
+
+    """
+
+    if descriptors1.shape[1] != descriptors2.shape[1]:
+        raise ValueError("Descriptor length must equal.")
+
+    if metric is None:
+        if cp.issubdtype(descriptors1.dtype, bool):
+            metric = 'hamming'
+        else:
+            metric = 'euclidean'
+
+    kwargs = {}
+    # Scipy raises an error if p is passed as an extra argument when it isn't
+    # necessary for the chosen metric.
+    if metric == 'minkowski':
+        kwargs['p'] = p
+
+    if not have_gpu_cdist:
+        warnings.warn("pylibraft not found, falling back to SciPy "
+                      "implementation of cdist on the CPU")
+        distances = cp.array(
+            cdist(
+                cp.asnumpy(descriptors1),
+                cp.asnumpy(descriptors2),
+                metric=metric,
+                **kwargs
+            )
+        )
+    else:
+        distances = cdist(descriptors1, descriptors2, metric=metric, **kwargs)
+
+    indices1 = cp.arange(descriptors1.shape[0])
+    indices2 = cp.argmin(distances, axis=1)
+
+    if cross_check:
+        matches1 = cp.argmin(distances, axis=0)
+        mask = indices1 == matches1[indices2]
+        indices1 = indices1[mask]
+        indices2 = indices2[mask]
+
+    if max_distance < cp.inf:
+        mask = distances[indices1, indices2] < max_distance
+        indices1 = indices1[mask]
+        indices2 = indices2[mask]
+
+    if max_ratio < 1.0:
+        best_distances = distances[indices1, indices2]
+        distances[indices1, indices2] = cp.inf
+        second_best_indices2 = cp.argmin(distances[indices1], axis=1)
+        second_best_distances = distances[indices1, second_best_indices2]
+        second_best_distances[second_best_distances == 0] \
+            = cp.finfo(cp.float64).eps
+        ratio = best_distances / second_best_distances
+        mask = ratio < max_ratio
+        indices1 = indices1[mask]
+        indices2 = indices2[mask]
+
+    matches = cp.stack((indices1, indices2), axis=-1)
+
+    return matches
diff --git a/python/cucim/src/cucim/skimage/filters/_separable_filtering.py b/python/cucim/src/cucim/skimage/filters/_separable_filtering.py
index eb8dad28a..e4e0fffa7 100644
--- a/python/cucim/src/cucim/skimage/filters/_separable_filtering.py
+++ b/python/cucim/src/cucim/skimage/filters/_separable_filtering.py
@@ -63,7 +63,7 @@ def _get_constants(ndim, axis, kernel_size, anchor, patch_per_block=None):
     return block, patch_per_block, halo_size
 
 
-def _get_smem_shape(ndim, axis, block, patch_per_block, halo_size, anchor=None,
+def _get_smem_shape(ndim, axis, block, patch_per_block, halo_size,
                     image_dtype=cp.float32):
     bx, by, bz = block
     if ndim == 2:
@@ -128,7 +128,12 @@ def _check_smem_availability(ndim, axis, kernel_size, anchor=None,
         ndim, axis, kernel_size, anchor=anchor, patch_per_block=patch_per_block
     )
     shape, nbytes = _get_smem_shape(
-        ndim, axis, block, patch_per_block, halo_size, image_dtype
+        ndim=ndim,
+        axis=axis,
+        block=block,
+        patch_per_block=patch_per_block,
+        halo_size=halo_size,
+        image_dtype=image_dtype
     )
     props = _get_shmem_limits(device_id=device_id)
     if nbytes > props['shared_block']:


Test patch:
diff --git a/python/cucim/src/cucim/skimage/feature/tests/test_match.py b/python/cucim/src/cucim/skimage/feature/tests/test_match.py
new file mode 100644
index 000000000..8bee76b53
--- /dev/null
+++ b/python/cucim/src/cucim/skimage/feature/tests/test_match.py
@@ -0,0 +1,187 @@
+import math
+
+import cupy as cp
+from cupy.testing import assert_array_equal
+from skimage import data
+# TODO: change to cucim.skimage.feature.BRIEF once implemented
+from skimage.feature import BRIEF
+
+from cucim.skimage import transform
+from cucim.skimage._shared import testing
+from cucim.skimage.color import rgb2gray
+from cucim.skimage.feature import corner_harris, corner_peaks, match_descriptors
+
+
+def test_binary_descriptors_unequal_descriptor_sizes_error():
+    """Sizes of descriptors of keypoints to be matched should be equal."""
+    descs1 = cp.array([[True, True, False, True],
+                       [False, True, False, True]])
+    descs2 = cp.array([[True, False, False, True, False],
+                       [False, True, True, True, False]])
+    with testing.raises(ValueError):
+        match_descriptors(descs1, descs2)
+
+
+def test_binary_descriptors():
+    descs1 = cp.array([[True, True, False, True, True],
+                       [False, True, False, True, True]])
+    descs2 = cp.array([[True, False, False, True, False],
+                       [False, False, True, True, True]])
+    matches = match_descriptors(descs1, descs2)
+    assert_array_equal(matches, [[0, 0], [1, 1]])
+
+
+def test_binary_descriptors_rotation_crosscheck_false():
+    """Verify matched keypoints and their corresponding masks results between
+    image and its rotated version with the expected keypoint pairs with
+    cross_check disabled."""
+    img = cp.array(data.astronaut())
+    img = rgb2gray(img)
+    tform = transform.SimilarityTransform(
+        scale=1, rotation=0.15, translation=(0, 0)
+    )
+    rotated_img = transform.warp(img, tform, clip=False)
+
+    extractor = BRIEF(descriptor_size=512)
+
+    keypoints1 = corner_peaks(corner_harris(img), min_distance=5,
+                              threshold_abs=0, threshold_rel=0.1)
+    extractor.extract(cp.asnumpy(img), cp.asnumpy(keypoints1))
+    descriptors1 = cp.array(extractor.descriptors)
+
+    keypoints2 = corner_peaks(corner_harris(rotated_img), min_distance=5,
+                              threshold_abs=0, threshold_rel=0.1)
+    extractor.extract(cp.asnumpy(rotated_img), cp.asnumpy(keypoints2))
+    descriptors2 = cp.array(extractor.descriptors)
+
+    matches = match_descriptors(descriptors1, descriptors2, cross_check=False)
+
+    exp_matches1 = cp.arange(47)
+    exp_matches2 = cp.array([0, 2, 1, 3, 4, 5, 7, 8, 14, 9, 11, 13,
+                             23, 15, 16, 22, 17, 19, 37, 18, 24, 27,
+                             30, 25, 26, 32, 28, 35, 37, 42, 29, 38,
+                             33, 40, 36, 39, 10, 36, 43, 15, 35, 41,
+                             6, 37, 32, 24, 8])
+
+    assert_array_equal(matches[:, 0], exp_matches1)
+    assert_array_equal(matches[:, 1], exp_matches2)
+
+    # minkowski takes a different code path, therefore we test it explicitly
+    matches = match_descriptors(descriptors1, descriptors2,
+                                metric='minkowski', cross_check=False)
+    assert_array_equal(matches[:, 0], exp_matches1)
+    assert_array_equal(matches[:, 1], exp_matches2)
+
+    # it also has an extra parameter
+    matches = match_descriptors(descriptors1, descriptors2,
+                                metric='minkowski', p=4, cross_check=False)
+    assert_array_equal(matches[:, 0], exp_matches1)
+    assert_array_equal(matches[:, 1], exp_matches2)
+
+
+def test_binary_descriptors_rotation_crosscheck_true():
+    """Verify matched keypoints and their corresponding masks results between
+    image and its rotated version with the expected keypoint pairs with
+    cross_check enabled."""
+    img = cp.array(data.astronaut())
+    img = rgb2gray(img)
+    tform = transform.SimilarityTransform(
+        scale=1, rotation=0.15, translation=(0, 0)
+    )
+    rotated_img = transform.warp(img, tform, clip=False)
+
+    extractor = BRIEF(descriptor_size=512)
+
+    keypoints1 = corner_peaks(corner_harris(img), min_distance=5,
+                              threshold_abs=0, threshold_rel=0.1)
+    extractor.extract(cp.asnumpy(img), cp.asnumpy(keypoints1))
+    descriptors1 = cp.array(extractor.descriptors)
+
+    keypoints2 = corner_peaks(corner_harris(rotated_img), min_distance=5,
+                              threshold_abs=0, threshold_rel=0.1)
+    extractor.extract(cp.asnumpy(rotated_img), cp.asnumpy(keypoints2))
+    descriptors2 = cp.array(extractor.descriptors)
+
+    matches = match_descriptors(descriptors1, descriptors2, cross_check=True)
+
+    exp_matches1 = cp.array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12,
+                             13, 14, 15, 16, 17, 19, 20, 21, 22, 23,
+                             24, 26, 27, 28, 29, 30, 31, 32, 33,
+                             34, 38, 41, 42])
+    exp_matches2 = cp.array([0, 2, 1, 3, 4, 5, 7, 8, 14, 9, 11, 13,
+                             23, 15, 16, 22, 17, 19, 18, 24, 27, 30,
+                             25, 26, 28, 35, 37, 42, 29, 38, 33,
+                             40, 36, 43, 41, 6])
+    assert_array_equal(matches[:, 0], exp_matches1)
+    assert_array_equal(matches[:, 1], exp_matches2)
+
+
+def test_max_distance():
+    descs1 = cp.zeros((10, 128))
+    descs2 = cp.zeros((15, 128))
+
+    descs1[0, :] = 1
+
+    matches = match_descriptors(descs1, descs2, metric='euclidean',
+                                max_distance=0.1, cross_check=False)
+    assert len(matches) == 9
+
+    matches = match_descriptors(descs1, descs2, metric='euclidean',
+                                max_distance=math.sqrt(128.1),
+                                cross_check=False)
+    assert len(matches) == 10
+
+    matches = match_descriptors(descs1, descs2, metric='euclidean',
+                                max_distance=0.1,
+                                cross_check=True)
+    assert_array_equal(matches, [[1, 0]])
+
+    matches = match_descriptors(descs1, descs2, metric='euclidean',
+                                max_distance=math.sqrt(128.1),
+                                cross_check=True)
+    assert_array_equal(matches, [[1, 0]])
+
+
+def test_max_ratio():
+    descs1 = 10 * cp.arange(10)[:, None].astype(cp.float32)
+    descs2 = 10 * cp.arange(15)[:, None].astype(cp.float32)
+
+    descs2[0] = 5.0
+
+    matches = match_descriptors(descs1, descs2, metric='euclidean',
+                                max_ratio=1.0, cross_check=False)
+    assert_array_equal(len(matches), 10)
+
+    matches = match_descriptors(descs1, descs2, metric='euclidean',
+                                max_ratio=0.6, cross_check=False)
+    assert_array_equal(len(matches), 10)
+
+    matches = match_descriptors(descs1, descs2, metric='euclidean',
+                                max_ratio=0.5, cross_check=False)
+    assert_array_equal(len(matches), 9)
+
+    descs1[0] = 7.5
+
+    matches = match_descriptors(descs1, descs2, metric='euclidean',
+                                max_ratio=0.5, cross_check=False)
+    assert_array_equal(len(matches), 9)
+
+    descs2 = 10 * cp.arange(1)[:, None].astype(cp.float32)
+
+    matches = match_descriptors(descs1, descs2, metric='euclidean',
+                                max_ratio=1.0, cross_check=False)
+    assert_array_equal(len(matches), 10)
+
+    matches = match_descriptors(descs1, descs2, metric='euclidean',
+                                max_ratio=0.5, cross_check=False)
+    assert_array_equal(len(matches), 10)
+
+    descs1 = 10 * cp.arange(1)[:, None].astype(cp.float32)
+
+    matches = match_descriptors(descs1, descs2, metric='euclidean',
+                                max_ratio=1.0, cross_check=False)
+    assert_array_equal(len(matches), 1)
+
+    matches = match_descriptors(descs1, descs2, metric='euclidean',
+                                max_ratio=0.5, cross_check=False)
+    assert_array_equal(len(matches), 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 c75092d31..4db78e1b8 100644
--- a/python/cucim/src/cucim/skimage/filters/tests/test_gaussian.py
+++ b/python/cucim/src/cucim/skimage/filters/tests/test_gaussian.py
@@ -184,3 +184,15 @@ def test_dog_invalid_sigma2():
         difference_of_gaussians(image, 3, 2)
     with pytest.raises(ValueError):
         difference_of_gaussians(image, (1, 5), (2, 4))
+
+
+@pytest.mark.parametrize(
+    'dtype', [cp.uint8, cp.float16, cp.float32, cp.float64]
+)
+@pytest.mark.parametrize('sigma', range(1, 40, 5))
+def test_shared_mem_check_fix(dtype, sigma):
+    # Verify fix for gh-408 (no compilation errors occur).
+    # Prior to the fix in gh-409, some float64 cases failed.
+    # 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)