-
Notifications
You must be signed in to change notification settings - Fork 116
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #71 from leggedrobotics/feature/max_layer_filter
Added max layer filter.
- Loading branch information
Showing
5 changed files
with
257 additions
and
5 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
89 changes: 89 additions & 0 deletions
89
elevation_mapping_cupy/script/elevation_mapping_cupy/plugins/erosion.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,89 @@ | ||
# | ||
# Copyright (c) 2024, Takahiro Miki. All rights reserved. | ||
# Licensed under the MIT license. See LICENSE file in the project root for details. | ||
# | ||
import cv2 as cv | ||
import cupy as cp | ||
import numpy as np | ||
|
||
from typing import List | ||
|
||
from .plugin_manager import PluginBase | ||
|
||
|
||
class Erosion(PluginBase): | ||
""" | ||
This class is used for applying erosion to an elevation map or specific layers within it. | ||
Erosion is a morphological operation that is used to remove small-scale details from a binary image. | ||
Args: | ||
kernel_size (int): Size of the erosion kernel. Default is 3, which means a 3x3 square kernel. | ||
iterations (int): Number of times erosion is applied. Default is 1. | ||
**kwargs (): Additional keyword arguments. | ||
""" | ||
|
||
def __init__( | ||
self, | ||
input_layer_name="traversability", | ||
kernel_size: int = 3, | ||
iterations: int = 1, | ||
reverse: bool = False, | ||
**kwargs, | ||
): | ||
super().__init__() | ||
self.input_layer_name = input_layer_name | ||
self.kernel_size = kernel_size | ||
self.iterations = iterations | ||
self.reverse = reverse | ||
|
||
def __call__( | ||
self, | ||
elevation_map: cp.ndarray, | ||
layer_names: List[str], | ||
plugin_layers: cp.ndarray, | ||
plugin_layer_names: List[str], | ||
semantic_map: cp.ndarray, | ||
semantic_layer_names: List[str], | ||
*args, | ||
) -> cp.ndarray: | ||
""" | ||
Applies erosion to the given elevation map. | ||
Args: | ||
elevation_map (cupy._core.core.ndarray): The elevation map to be eroded. | ||
layer_names (List[str]): Names of the layers in the elevation map. | ||
plugin_layers (cupy._core.core.ndarray): Layers provided by other plugins. | ||
plugin_layer_names (List[str]): Names of the layers provided by other plugins. | ||
*args (): Additional arguments. | ||
Returns: | ||
cupy._core.core.ndarray: The eroded elevation map. | ||
""" | ||
# Convert the elevation map to a format suitable for erosion (if necessary) | ||
layer_data = self.get_layer_data( | ||
elevation_map, | ||
layer_names, | ||
plugin_layers, | ||
plugin_layer_names, | ||
semantic_map, | ||
semantic_layer_names, | ||
self.input_layer_name, | ||
) | ||
layer_np = cp.asnumpy(layer_data) | ||
|
||
# Define the erosion kernel | ||
kernel = np.ones((self.kernel_size, self.kernel_size), np.uint8) | ||
|
||
if self.reverse: | ||
layer_np = 1 - layer_np | ||
# Apply erosion | ||
layer_min = float(layer_np.min()) | ||
layer_max = float(layer_np.max()) | ||
layer_np_normalized = ((layer_np - layer_min) * 255 / (layer_max - layer_min)).astype("uint8") | ||
eroded_map_np = cv.erode(layer_np_normalized, kernel, iterations=self.iterations) | ||
eroded_map_np = eroded_map_np.astype(np.float32) * (layer_max - layer_min) / 255 + layer_min | ||
if self.reverse: | ||
eroded_map_np = 1 - eroded_map_np | ||
|
||
# Convert back to cupy array and return | ||
return cp.asarray(eroded_map_np) |
99 changes: 99 additions & 0 deletions
99
elevation_mapping_cupy/script/elevation_mapping_cupy/plugins/max_layer_filter.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,99 @@ | ||
# | ||
# Copyright (c) 2022, Takahiro Miki. All rights reserved. | ||
# Licensed under the MIT license. See LICENSE file in the project root for details. | ||
# | ||
import cupy as cp | ||
import numpy as np | ||
from typing import List | ||
|
||
from elevation_mapping_cupy.plugins.plugin_manager import PluginBase | ||
|
||
|
||
class MaxLayerFilter(PluginBase): | ||
"""Applies a maximum filter to the input layers and updates the traversability map. | ||
This can be used to enhance navigation by identifying traversable areas. | ||
Args: | ||
cell_n (int): The width and height of the elevation map. | ||
reverse (list): A list of boolean values indicating whether to reverse the filter operation for each layer. Default is [True]. | ||
min_or_max (str): A string indicating whether to apply a minimum or maximum filter. Accepts "min" or "max". Default is "max". | ||
layers (list): List of layers for semantic traversability. Default is ["traversability"]. | ||
thresholds (list): List of thresholds for each layer. If the value is bigger than a threshold, assign 1.0 otherwise 0.0. If it is False, it does not apply. Default is [False]. | ||
**kwargs: Additional keyword arguments. | ||
""" | ||
|
||
def __init__( | ||
self, | ||
cell_n: int = 100, | ||
layers: list = ["traversability"], | ||
reverse: list = [True], | ||
min_or_max: str = "max", | ||
thresholds: list = [False], | ||
default_value: float = 0.0, | ||
**kwargs, | ||
): | ||
super().__init__() | ||
self.layers = layers | ||
self.reverse = reverse | ||
self.min_or_max = min_or_max | ||
self.thresholds = thresholds | ||
self.default_value = default_value | ||
|
||
def __call__( | ||
self, | ||
elevation_map: cp.ndarray, | ||
layer_names: List[str], | ||
plugin_layers: cp.ndarray, | ||
plugin_layer_names: List[str], | ||
semantic_map: cp.ndarray, | ||
semantic_layer_names: List[str], | ||
*args, | ||
) -> cp.ndarray: | ||
""" | ||
Args: | ||
elevation_map (cupy._core.core.ndarray): | ||
layer_names (List[str]): | ||
plugin_layers (cupy._core.core.ndarray): | ||
plugin_layer_names (List[str]): | ||
semantic_map (elevation_mapping_cupy.semantic_map.SemanticMap): | ||
*args (): | ||
Returns: | ||
cupy._core.core.ndarray: | ||
""" | ||
layers = [] | ||
for it, name in enumerate(self.layers): | ||
layer = self.get_layer_data( | ||
elevation_map, layer_names, plugin_layers, plugin_layer_names, semantic_map, semantic_layer_names, name | ||
) | ||
if layer is None: | ||
continue | ||
if isinstance(self.default_value, float): | ||
layer = cp.where(layer == 0.0, float(self.default_value), layer) | ||
elif isinstance(self.default_value, str): | ||
default_layer = self.get_layer_data( | ||
elevation_map, | ||
layer_names, | ||
plugin_layers, | ||
plugin_layer_names, | ||
semantic_map, | ||
semantic_layer_names, | ||
self.default_value, | ||
) | ||
layer = cp.where(layer == 0, default_layer, layer) | ||
if self.reverse[it]: | ||
layer = 1.0 - layer | ||
if isinstance(self.thresholds[it], float): | ||
layer = cp.where(layer > float(self.thresholds[it]), 1, 0) | ||
layers.append(layer) | ||
if len(layers) == 0: | ||
print("No layers are found, returning traversability!") | ||
idx = layer_names.index("traversability") | ||
return elevation_map[idx] | ||
result = cp.stack(layers, axis=0) | ||
if self.min_or_max == "min": | ||
result = cp.min(result, axis=0) | ||
else: | ||
result = cp.max(result, axis=0) | ||
return result |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters