diff --git a/.github/workflows/python.yml b/.github/workflows/python.yml new file mode 100644 index 0000000..5ba924d --- /dev/null +++ b/.github/workflows/python.yml @@ -0,0 +1,27 @@ +name: Python API +on: + push: + branches: ["main"] + pull_request: + branches: ["main"] + +jobs: + python_package: + runs-on: ${{ matrix.os }} + strategy: + matrix: + os: [ubuntu-22.04, ubuntu-20.04] + + steps: + - uses: actions/checkout@v3 + - name: Setup Python3 + uses: actions/setup-python@v3 + - name: Install dependencies + run: | + python -m pip install --upgrade pip + - name: Build pip package + run: | + python -m pip install --verbose . + - name: Test installation + run: | + mapmos_pipeline --help diff --git a/Makefile b/Makefile index e7b6123..94f9315 100644 --- a/Makefile +++ b/Makefile @@ -1,9 +1,14 @@ install: @pip install -v . +install-all: + @pip install -v ".[all]" + uninstall: @pip -v uninstall mapmos -editable: +build-system: @pip install scikit-build-core pyproject_metadata pathspec pybind11 ninja cmake + +editable: build-system @pip install --no-build-isolation -ve . diff --git a/README.md b/README.md index d27052f..cf5a58e 100644 --- a/README.md +++ b/README.md @@ -30,7 +30,6 @@ First, make sure the [MinkowskiEngine](https://github.com/NVIDIA/MinkowskiEngine) is installed on your system, see [here](https://github.com/NVIDIA/MinkowskiEngine#installation) for more details. Next, clone our repository - ```bash git clone git@github.com:PRBonn/MapMOS && cd MapMOS ``` @@ -40,7 +39,11 @@ and install with make install ``` -**or** in editable mode: +**or** +```bash +make install-all +``` +if you want to install the project with all optional dependencies (needed for the visualizer). In case you want to edit the Python code, install in editable mode: ```bash make editable ``` diff --git a/pyproject.toml b/pyproject.toml index 43e6a06..a20dcad 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -10,7 +10,6 @@ license_files = "LICENSE" dependencies = [ "kiss-icp>=0.2.10", "diskcache>=5.3.0", - "open3d>=0.13", "pytorch_lightning>=1.6.4", ] @@ -20,6 +19,7 @@ all = [ "ouster-sdk>=0.7.1", "pyntcloud", "trimesh", + "open3d>=0.13", ] [project.scripts] diff --git a/scripts/cache_to_ply.py b/scripts/cache_to_ply.py index 7ff281c..1137c68 100755 --- a/scripts/cache_to_ply.py +++ b/scripts/cache_to_ply.py @@ -23,13 +23,13 @@ import os import typer +import importlib import numpy as np import torch from typing import List, Optional from torch.utils.data import DataLoader from tqdm import tqdm -import open3d as o3d from pathlib import Path from mapmos.datasets.mapmos_dataset import MapMOSDataset, collate_fn @@ -68,6 +68,12 @@ def cache_to_ply( help="[Optional] Path to the configuration file", ), ): + try: + o3d = importlib.import_module("open3d") + except ModuleNotFoundError as err: + print(f'open3d is not installed on your system, run "pip install open3d"') + exit(1) + for seq in sequence: # Run cfg = load_config(config) diff --git a/src/mapmos/pipeline.py b/src/mapmos/pipeline.py index 5d723ff..a5e5a2b 100644 --- a/src/mapmos/pipeline.py +++ b/src/mapmos/pipeline.py @@ -37,7 +37,7 @@ from mapmos.metrics import get_confusion_matrix from mapmos.utils.visualizer import MapMOSVisualizer, StubVisualizer from mapmos.utils.pipeline_results import MOSPipelineResults -from mapmos.utils.save import save_to_ply, save_to_kitti +from mapmos.utils.save import PlyWriter, KITTIWriter, StubWriter from mapmos.config import load_config @@ -99,8 +99,8 @@ def __init__( self.visualize = visualize self.visualizer = MapMOSVisualizer() if visualize else StubVisualizer() - self.save_ply = save_ply - self.save_kitti = save_kitti + self.ply_writer = PlyWriter() if save_ply else StubWriter() + self.kitti_writer = KITTIWriter() if save_kitti else StubWriter() # Public interface ------ def run(self): @@ -203,19 +203,16 @@ def process_final_prediction(self, query_index, query_points, query_labels): torch.tensor(belief_labels_query, dtype=torch.int32), torch.tensor(query_labels, dtype=torch.int32), ) - if self.save_ply: - save_to_ply( - query_points, - belief_labels_query, - query_labels, - filename=f"{self.results_dir}/ply/{query_index:06}.ply", - ) - - if self.save_kitti: - save_to_kitti( - belief_labels_query, - filename=f"{self.results_dir}/bin/sequences/{self.dataset_sequence}/predictions/{query_index:06}.label", - ) + self.ply_writer.write( + query_points, + belief_labels_query, + query_labels, + filename=f"{self.results_dir}/ply/{query_index:06}.ply", + ) + self.kitti_writer.write( + belief_labels_query, + filename=f"{self.results_dir}/bin/sequences/{self.dataset_sequence}/predictions/{query_index:06}.label", + ) def _next(self, idx): dataframe = self._dataset[idx] diff --git a/src/mapmos/utils/save.py b/src/mapmos/utils/save.py index 623c7d9..de18775 100644 --- a/src/mapmos/utils/save.py +++ b/src/mapmos/utils/save.py @@ -20,38 +20,58 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. -import open3d as o3d import numpy as np import os +import importlib from pathlib import Path -def save_to_ply( - scan_points: np.ndarray, pred_labels: np.ndarray, gt_labels: np.ndarray, filename: str -): - os.makedirs(Path(filename).parent, exist_ok=True) - pcd_current_scan = o3d.geometry.PointCloud( - o3d.utility.Vector3dVector(scan_points) - ).paint_uniform_color([0, 0, 0]) +class StubWriter: + def __init__(self) -> None: + pass - scan_colors = np.array(pcd_current_scan.colors) + def write(self, *_, **__): + pass - tp = (pred_labels == 1) & (gt_labels == 1) - fp = (pred_labels == 1) & (gt_labels != 1) - fn = (pred_labels != 1) & (gt_labels == 1) - scan_colors[tp] = [0, 1, 0] - scan_colors[fp] = [1, 0, 0] - scan_colors[fn] = [0, 0, 1] +class PlyWriter: + def __init__(self) -> None: + try: + self.o3d = importlib.import_module("open3d") + except ModuleNotFoundError as err: + print(f'open3d is not installed on your system, run "pip install open3d"') + exit(1) - pcd_current_scan.colors = o3d.utility.Vector3dVector(scan_colors) - o3d.io.write_point_cloud(filename, pcd_current_scan) + def write( + self, scan_points: np.ndarray, pred_labels: np.ndarray, gt_labels: np.ndarray, filename: str + ): + os.makedirs(Path(filename).parent, exist_ok=True) + pcd_current_scan = self.o3d.geometry.PointCloud( + self.o3d.utility.Vector3dVector(scan_points) + ).paint_uniform_color([0, 0, 0]) + scan_colors = np.array(pcd_current_scan.colors) -def save_to_kitti(pred_labels: np.ndarray, filename: str): - os.makedirs(Path(filename).parent, exist_ok=True) - kitti_labels = np.copy(pred_labels) - kitti_labels[pred_labels == 0] = 9 - kitti_labels[pred_labels == 1] = 251 - kitti_labels = kitti_labels.reshape(-1).astype(np.int32) - kitti_labels.tofile(filename) + tp = (pred_labels == 1) & (gt_labels == 1) + fp = (pred_labels == 1) & (gt_labels != 1) + fn = (pred_labels != 1) & (gt_labels == 1) + + scan_colors[tp] = [0, 1, 0] + scan_colors[fp] = [1, 0, 0] + scan_colors[fn] = [0, 0, 1] + + pcd_current_scan.colors = self.o3d.utility.Vector3dVector(scan_colors) + self.o3d.io.write_point_cloud(filename, pcd_current_scan) + + +class KITTIWriter: + def __init__(self) -> None: + pass + + def write(self, pred_labels: np.ndarray, filename: str): + os.makedirs(Path(filename).parent, exist_ok=True) + kitti_labels = np.copy(pred_labels) + kitti_labels[pred_labels == 0] = 9 + kitti_labels[pred_labels == 1] = 251 + kitti_labels = kitti_labels.reshape(-1).astype(np.int32) + kitti_labels.tofile(filename)