def crowns_to_polys_raster(self): ''' Converts tree crown raster to individual polygons and stores them in the tree dataframe ''' polys = [] for feature in rioshapes(self.crowns, mask=self.crowns.astype(bool)): # Convert pixel coordinates to lon/lat edges = feature[0]['coordinates'][0].copy() for i in range(len(edges)): edges[i] = self._to_lonlat(*edges[i], self.resolution) # poly_smooth = self.smooth_poly(Polygon(edges), s=None, k=9) polys.append(Polygon(edges)) self.trees.crown_poly_raster = polys
def crowns_to_polys_smooth(self, store_las=True): """ Smooth crown polygons using Dalponte & Coomes (2016) approach: Builds a convex hull around first return points (which lie within the rasterized crowns). Optionally, the trees in the LiDAR point cloud are classified based on the generated convex hull. Parameters ---------- store_las : bool set to True if LiDAR point clouds shopuld be classified and stored externally """ print('Converting LAS point cloud to shapely points') geometry = [Point(xy) for xy in zip(self.las.x, self.las.y)] lidar_geodf = gpd.GeoDataFrame(self.las, crs=f'epsg:{self.epsg}', geometry=geometry) print('Converting raster crowns to shapely polygons') polys = [] for feature in rioshapes(self.crowns, mask=self.crowns.astype(bool)): edges = np.array(list(zip(*feature[0]['coordinates'][0]))) edges = np.array( self._to_lonlat(edges[0], edges[1], self.resolution)).T polys.append(Polygon(edges)) crown_geodf = gpd.GeoDataFrame(pd.DataFrame(np.arange(len( self.trees))), crs=f'epsg:{self.epsg}', geometry=polys) print('Attach LiDAR points to corresponding crowns') lidar_in_crowns = gpd.sjoin(lidar_geodf, crown_geodf, op='within', how="inner") lidar_tree_class = np.zeros(lidar_in_crowns['index_right'].size) lidar_tree_mask = np.zeros(lidar_in_crowns['index_right'].size, dtype=bool) print('Create convex hull around first return points') polys = [] for tidx in range(len(self.trees)): bool_indices = lidar_in_crowns['index_right'] == tidx lidar_tree_class[bool_indices] = tidx points = lidar_in_crowns[bool_indices] # check that not all values are the same if len(points.z) > 1 and not np.allclose(points.z, points.iloc[0].z): points = points[points.z >= threshold_otsu(points.z)] points = points[points.return_num == 1] hull = points.unary_union.convex_hull polys.append(hull) lidar_tree_mask[bool_indices] = \ lidar_in_crowns[bool_indices].within(hull) self.trees.crown_poly_smooth = polys if store_las: print('Classifying point cloud') lidar_in_crowns = lidar_in_crowns[lidar_tree_mask] lidar_tree_class = lidar_tree_class[lidar_tree_mask] header = laspy.header.Header() self.outpath.mkdir(parents=True, exist_ok=True) outfile = laspy.file.File(self.outpath / "trees.las", mode="w", header=header) xmin = np.floor(np.min(lidar_in_crowns.x)) ymin = np.floor(np.min(lidar_in_crowns.y)) zmin = np.floor(np.min(lidar_in_crowns.z)) outfile.header.offset = [xmin, ymin, zmin] outfile.header.scale = [0.001, 0.001, 0.001] outfile.x = lidar_in_crowns.x outfile.y = lidar_in_crowns.y outfile.z = lidar_in_crowns.z outfile.intensity = lidar_tree_class outfile.close() self.lidar_in_crowns = lidar_in_crowns