def down_sample(
    dense_pcd_path, dense_label_path, sparse_pcd_path, sparse_label_path, voxel_size
):
    # Skip if done
    if os.path.isfile(sparse_pcd_path) and (
        not os.path.isfile(dense_label_path) or os.path.isfile(sparse_label_path)
    ):
        print("Skipped:", file_prefix)
        return
    else:
        print("Processing:", file_prefix)

    # Inputs
    dense_pcd = open3d.io.read_point_cloud(dense_pcd_path)
    try:
        dense_labels = load_labels(dense_label_path)
    except:
        dense_labels = None

    # Skip label 0, we use explicit frees to reduce memory usage
    print("Num points:", np.asarray(dense_pcd.points).shape[0])
    if dense_labels is not None:
        non_zero_indexes = dense_labels != 0

        dense_points = np.asarray(dense_pcd.points)[non_zero_indexes]
        dense_pcd.points = open3d.utility.Vector3dVector()
        dense_pcd.points = open3d.utility.Vector3dVector(dense_points)
        del dense_points

        dense_colors = np.asarray(dense_pcd.colors)[non_zero_indexes]
        dense_pcd.colors = open3d.utility.Vector3dVector()
        dense_pcd.colors = open3d.utility.Vector3dVector(dense_colors)
        del dense_colors

        dense_labels = dense_labels[non_zero_indexes]
        print("Num points after 0-skip:", np.asarray(dense_pcd.points).shape[0])

    # Downsample points
    min_bound = dense_pcd.get_min_bound() - voxel_size * 0.5
    max_bound = dense_pcd.get_max_bound() + voxel_size * 0.5

    sparse_pcd, cubics_ids = open3d.geometry.PointCloud.voxel_down_sample_and_trace(
        dense_pcd, voxel_size, min_bound, max_bound, False
    )
    print("Num points after down sampling:", np.asarray(sparse_pcd.points).shape[0])

    open3d.io.write_point_cloud(sparse_pcd_path, sparse_pcd)
    print("Point cloud written to:", sparse_pcd_path)

    # Downsample labels
    if dense_labels is not None:
        sparse_labels = []
        for cubic_ids in cubics_ids:
            cubic_ids = cubic_ids[cubic_ids != -1]
            cubic_labels = dense_labels[cubic_ids]
            sparse_labels.append(np.bincount(cubic_labels).argmax())
        sparse_labels = np.array(sparse_labels)

        write_labels(sparse_label_path, sparse_labels)
        print("Labels written to:", sparse_label_path)
        try:
            dense_gt_labels = load_labels(os.path.join(gt_dir, file_prefix + ".labels"))
            print("dense_gt_labels loaded", flush=True)
        except:
            print("dense_gt_labels not found, treat as test set")
            dense_gt_labels = None

        # Assign labels
        start = time.time()
        dense_labels, dense_colors = interpolator.interpolate_labels(
            sparse_points, sparse_labels, dense_points
        )
        print("KNN interpolation time: ", time.time() - start, "seconds", flush=True)

        # Write dense labels
        write_labels(dense_labels_path, dense_labels)
        print("Dense labels written to:", dense_labels_path, flush=True)

        # Write dense point cloud with color
        dense_pcd.colors = open3d.utility.Vector3dVector(dense_colors)
        open3d.io.write_point_cloud(dense_points_colored_path, dense_pcd)
        print("Dense pcd with color written to:", dense_points_colored_path, flush=True)

        # Eval
        if dense_gt_labels is not None:
            cm = ConfusionMatrix(9)
            cm.increment_from_list(dense_gt_labels, dense_labels)
            cm.print_metrics()
            cm_global.increment_from_list(dense_gt_labels, dense_labels)

    pprint("Global results")