patty.segmentation.boundary_of_center_object()   B
last analyzed

Complexity

Conditions 4

Size

Total Lines 68

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 1
CRAP Score 17.7179
Metric Value
cc 4
dl 0
loc 68
ccs 1
cts 20
cp 0.05
crap 17.7179
rs 8.7864

How to fix   Long Method   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
#!/usr/bin/env python
2
3 1
"""
4
Segmentation algorithms and utility functions
5
"""
6
7 1
from __future__ import print_function
8 1
import numpy as np
9 1
from pcl.boundaries import estimate_boundaries
10 1
from shapely.geometry.polygon import LinearRing
11 1
from shapely.geometry import asPoint
12
13 1
from patty import utils
14 1
from .dbscan import get_largest_dbscan_clusters
15 1
from .. import extract_mask, BoundingBox, log, save
16
17
18 1
def boundary_of_drivemap(drivemap, footprint, height=1.0, edge_width=0.25):
19
    """
20
    Construct an object boundary using the manually recorded corner points.
21
    Do this by finding all the points in the drivemap along the footprint.
22
    Use the bottom 'height' meters of the drivemap (not trees).
23
    Resulting pointcloud has the same SRS and offset as the input.
24
25
    Arguments:
26
        drivemap   : pcl.PointCloud
27
        footprint  : pcl.PointCloud
28
        height     : Cut-off height, points more than this value above the
29
                     lowest point of the drivemap are considered trees,
30
                     and dropped. default 1 m.
31
        edge_width : Points belong to the boundary when they are within
32
                     this distance from the footprint. default 0.25
33
34
    Returns:
35
        boundary   : pcl.PointCloud
36
    """
37
38
    # construct basemap as the bottom 'height' meters of the drivemap
39
40
    drivemap_array = np.asarray(drivemap)
41
    bb = BoundingBox(points=drivemap_array)
42
    basemap = extract_mask(drivemap, drivemap_array[:, 2] < bb.min[2] + height)
43
44
    # Cut band between +- edge_width around the footprint
45
    edge = LinearRing(np.asarray(footprint)).buffer(edge_width)
46
    boundary = extract_mask(basemap,
47
                            [edge.contains(asPoint(pnt)) for pnt in basemap])
48
49
    utils.force_srs(boundary, same_as=basemap)
50
51
    return boundary
52
53
54 1
def boundary_of_lowest_points(pc, height_fraction=0.01):
55
    '''
56
    Construct an object boundary by taking the lowest (ie. min z coordinate)
57
    fraction of points.
58
    Resulting pointcloud has the same SRS and offset as the input.
59
60
    Arguments:
61
        pc               : pcl.PointCloud
62
        height_fraction  : float
63
64
    Returns:
65
        boundary   : pcl.PointCloud
66
    '''
67
    array = np.asarray(pc)
68
    bb = BoundingBox(points=array)
69
    maxh = bb.min[2] + (bb.max[2] - bb.min[2]) * height_fraction
70
71
    newpc = extract_mask(pc, array[:, 2] < maxh)
72
73
    utils.force_srs(newpc, same_as=pc)
74
75
    return newpc
76
77
78 1
def boundary_of_center_object(pc,
79
                              downsample=None,
80
                              angle_threshold=0.1,
81
                              search_radius=0.1,
82
                              normal_search_radius=0.1):
83
    """
84
    Find the boundary of the main object.
85
    First applies dbscan to find the main object,
86
    then estimates its footprint by taking the pointcloud boundary.
87
    Resulting pointcloud has the same SRS and offset as the input.
88
89
    Arguments:
90
        pointcloud : pcl.PointCloud
91
92
        downsample : If given, reduce the pointcloud to given percentage
93
                     values should be in [0,1]
94
95
        angle_threshold : float defaults to 0.1
96
97
        search_radius : float defaults to 0.1
98
99
        normal_search_radius : float defaults to 0.1
100
101
    Returns:
102
        boundary : pcl.PointCloud
103
    """
104
105
    if downsample is not None:
106
        log(' - Random downsampling factor:', downsample)
107
        pc = utils.downsample_random(pc, downsample)
108
    else:
109
        log(' - Not downsampling')
110
111
    # Main noise supression step
112
    # Find largest clusters, accounting for at least 70% of the pointcloud.
113
    # Presumably, this is the main object.
114
    log(' - Starting dbscan on downsampled pointcloud')
115
    mainobject = get_largest_dbscan_clusters(pc, 0.7, .075, 250)
116
    save(mainobject, 'mainobject.las')
117
118
    boundary = estimate_boundaries(mainobject,
119
                                   angle_threshold=angle_threshold,
120
                                   search_radius=search_radius,
121
                                   normal_search_radius=normal_search_radius)
122
123
    boundary = extract_mask(mainobject, boundary)
124
125
    if len(boundary) == len(mainobject) or len(boundary) == 0:
126
        log(' - Cannot find boundary')
127
        return None
128
129
    # project on the xy plane, take 2th percentile height
130
    points = np.asarray(boundary)
131
    points[:, 2] = np.percentile(points[:, 2], 2)
132
133
    # Secondary noise supression step
134
    # some cases have multiple objects close to eachother, and we need to
135
    # filter some out. Assume the object is a single item; perform another
136
    # dbscan to select the footprint of the main item
137
    log(' - Starting dbscan on boundary')
138
    mainboundary = get_largest_dbscan_clusters(boundary, 0.5, .1, 10)
139
140
    # Evenly space out the points
141
    mainboundary = utils.downsample_voxel(mainboundary, voxel_size=0.1)
142
143
    utils.force_srs(mainboundary, same_as=pc)
144
145
    return mainboundary
146