{
 "cells": [
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "nbsphinx": "hidden"
   },
   "outputs": [],
   "source": [
    "import open3d as o3d\n",
    "import numpy as np\n",
    "import os\n",
    "import sys\n",
    "\n",
    "# monkey patches visualization and provides helpers to load geometries\n",
    "sys.path.append('..')\n",
    "import open3d_tutorial as o3dtut\n",
    "# change to True if you want to interact with the visualization windows\n",
    "o3dtut.interactive = not \"CI\" in os.environ"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Point cloud outlier removal\n",
    "When collecting data from scanning devices, the resulting point cloud tends to contain noise and artifacts that one would like to remove. This tutorial addresses the outlier removal features of Open3D."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Prepare input data\n",
    "A point cloud is loaded and downsampled using `voxel_downsample`."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "print(\"Load a ply point cloud, print it, and render it\")\n",
    "sample_pcd_data = o3d.data.PCDPointCloud()\n",
    "pcd = o3d.io.read_point_cloud(sample_pcd_data.path)\n",
    "o3d.visualization.draw_geometries([pcd],\n",
    "                                  zoom=0.3412,\n",
    "                                  front=[0.4257, -0.2125, -0.8795],\n",
    "                                  lookat=[2.6172, 2.0475, 1.532],\n",
    "                                  up=[-0.0694, -0.9768, 0.2024])\n",
    "\n",
    "print(\"Downsample the point cloud with a voxel of 0.02\")\n",
    "voxel_down_pcd = pcd.voxel_down_sample(voxel_size=0.02)\n",
    "o3d.visualization.draw_geometries([voxel_down_pcd],\n",
    "                                  zoom=0.3412,\n",
    "                                  front=[0.4257, -0.2125, -0.8795],\n",
    "                                  lookat=[2.6172, 2.0475, 1.532],\n",
    "                                  up=[-0.0694, -0.9768, 0.2024])"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Alternatively, use `uniform_down_sample` to downsample the point cloud by collecting every n-th points."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "print(\"Every 5th points are selected\")\n",
    "uni_down_pcd = pcd.uniform_down_sample(every_k_points=5)\n",
    "o3d.visualization.draw_geometries([uni_down_pcd],\n",
    "                                  zoom=0.3412,\n",
    "                                  front=[0.4257, -0.2125, -0.8795],\n",
    "                                  lookat=[2.6172, 2.0475, 1.532],\n",
    "                                  up=[-0.0694, -0.9768, 0.2024])"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Select down sample\n",
    "The following helper function uses `select_by_index`, which takes a binary mask to output only the selected points. The selected points and the non-selected points are visualized."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "def display_inlier_outlier(cloud, ind):\n",
    "    inlier_cloud = cloud.select_by_index(ind)\n",
    "    outlier_cloud = cloud.select_by_index(ind, invert=True)\n",
    "\n",
    "    print(\"Showing outliers (red) and inliers (gray): \")\n",
    "    outlier_cloud.paint_uniform_color([1, 0, 0])\n",
    "    inlier_cloud.paint_uniform_color([0.8, 0.8, 0.8])\n",
    "    o3d.visualization.draw_geometries([inlier_cloud, outlier_cloud],\n",
    "                                      zoom=0.3412,\n",
    "                                      front=[0.4257, -0.2125, -0.8795],\n",
    "                                      lookat=[2.6172, 2.0475, 1.532],\n",
    "                                      up=[-0.0694, -0.9768, 0.2024])"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Statistical outlier removal\n",
    "`statistical_outlier_removal` removes points that are further away from their neighbors compared to the average for the point cloud. It takes two input parameters:\n",
    "\n",
    "- `nb_neighbors`, which specifies how many neighbors are taken into account in order to calculate the average distance for a given point.\n",
    "- `std_ratio`, which allows setting the threshold level based on the standard deviation of the average distances across the point cloud. The lower this number the more aggressive the filter will be."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "print(\"Statistical oulier removal\")\n",
    "cl, ind = voxel_down_pcd.remove_statistical_outlier(nb_neighbors=20,\n",
    "                                                    std_ratio=2.0)\n",
    "display_inlier_outlier(voxel_down_pcd, ind)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Radius outlier removal\n",
    "`radius_outlier_removal` removes points that have few neighbors in a given sphere around them. Two parameters can be used to tune the filter to your data:\n",
    "\n",
    "- `nb_points`, which lets you pick the minimum amount of points that the sphere should contain.\n",
    "- `radius`, which defines the radius of the sphere that will be used for counting the neighbors."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "print(\"Radius oulier removal\")\n",
    "cl, ind = voxel_down_pcd.remove_radius_outlier(nb_points=16, radius=0.05)\n",
    "display_inlier_outlier(voxel_down_pcd, ind)"
   ]
  }
 ],
 "metadata": {
  "celltoolbar": "Edit 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.7.7"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
