Package: pyForMetrix
pyForMetrixpyForMetrixis a Python package to extract metrics commonly used in forestry from laser scanning/LiDAR data. Main functionalities include a plot-based and a pixel-based calculation, and handling of large datasets.InstallationpyForMetrixis packaged and delivered via PyPi, and can be installed usingpip:python-mpipinstallpyForMetrixGetting startedNote: You can run thisGetting startedsection on binder:First, we need a point cloud dataset. You can use your own or download a sample dataset, e.g. from the City of Vancouver:https://webtransfer.vancouver.ca/opendata/2018LiDAR/4830E_54560N.zipUnzip this file after download to find a.las-File, which we will use in the following.We need to read in the point cloud into a numpy array. Depending on the metrics we will derive later,
different attributes also have to be loaded in. In this example, the 3D point cloud along with classification and
echo number information is required. For reading in the file, we uselaspy.importnumpyasnpimportlaspyinFile=laspy.read(r"4830E_54560N.las")coords=np.vstack([inFile.x,inFile.y,inFile.z]).transpose()points={'points':coords,'echo_number':inFile.return_number,'classification':inFile.classification}After importing the packagepyForMetrics, we can create aRasterMetricsor aPlotMetricsobject, depending on
the application. Let's first work withRasterMetrics, which will calculate the set of metrics for each cell of a
raster overlaid on the point cloud data.frompyForMetrix.metriximportRasterMetricsrm=RasterMetrics(points,raster_size=25)The code above may take some time to run, as on the creation of theRasterMetricsobject, the point cloud is rasterized
to the final cells. The runtime will increase with more points and a smaller raster size.We then select which metrics we want to calculate.pyForMetrixcomes with a number of predefined metrics, convieniently grouped in two collections:publications, where metrics from different publications in the literature are taken, andtypes, which groups metrics by their type. Later, we will see how to create your own metric calculators. For now, we will use the ones presented by Woods et al. (2009):frompyForMetrix.metricCalculators.publicationsimportMCalc_Woods_et_al_2009mc=MCalc_Woods_et_al_2009()metrics=rm.calc_custom_metrics(metrics=mc)With the last line, we created anxarray.DataArrayobject containing the metrics for each pixel:print(metrics)<xarray.DataArray (y: 115, x: 83, val: 26)>
array([[[ 1.19169000e+03,  1.19212000e+03,  1.19236000e+03, ...,
         -1.26632802e+00,  7.51640760e-01,  0.00000000e+00],
        [ 1.19254700e+03,  1.19255400e+03,  1.19256100e+03, ...,
         -2.00000000e+00,  1.00000000e+00,  0.00000000e+00],
...Usingrioxarray, we can save the values (here: thep90metric, i.e., the 90th height percentile) to a raster file:importrioxarraymetrics.sel(val='p90').rio.to_raster(f"p90.tif","COG")More examplesMultiple metric sets at onceInstead of passing a singlemetricCalculatorclass tocalc_custom_metrics, you can call it with a list ofmetricCalculators:frompyForMetrix.metricCalculators.typesimportMCalc_HeightMetrics,MCalc_DensityMetricsheightMetrics=MCalc_HeightMetrics()densityMetrics=MCalc_DensityMetrics()metrics=rm.calc_custom_metrics(metrics=[heightMetrics,densityMetrics])Override percentiles, custom optionsSomemetricCalculators can be customized, e.g. theMCalc_HeightMetricsaccept an optional keywordpercentiles, which
replaces the percentiles calculated by default:heightMetrics=MCalc_HeightMetrics(percentiles=np.array([15,25,50,75,85,95,99]))Similarly, the cell size for the rumple index (e.g. inMCalc_White_et_al_2015) or the DSM inMCalc_Hollaus_et_al_2009can be set - these variables are set as parameter to the__call__function.calc_custom_metricsaccepts them as a (list of)
additional dictionaries with the settings:frompyForMetrix.metricCalculators.publicationsimportMCalc_White_et_al_2015,MCalc_Hollaus_et_al_2009whiteMetrics=MCalc_White_et_al_2015()metrics=rm.calc_custom_metrics(metrics=whiteMetrics,metric_options={'rumple_pixel_size':0.2})hollausMetrics=MCalc_Hollaus_et_al_2009()metrics=rm.calc_custom_metrics(metrics=[whiteMetrics,hollausMetrics],metric_options=[{'rumple_pixel_size':5},{'CHM_pixel_size':7.5}])Parallelize metric computationOn computers with multiple cores, processing can be sped up significantly by multiprocessing. To this end,
we provide a functioncalc_custom_metrics_parallelwhich takes similar arguments tocalc_custom_metrics,
but runs on multiple cores. Note that the parallelization is carried out over the raster cells, i.e., the multiple
processes treat different subsets of the raster cells. As there is a certain overhead in starting the processes,
speedup is only expected if there is a large enough number of (a) valid raster cells and (b) metrics that are complex
to compute. The parametermultiprocessing_point_thresholdchecks the input point cloud and either spawns multiple processes
(in case the number of points is larger than the threshold) or passes the arguments on tocalc_custom_metrics.The other parameters aren_chunks(default: 16), which is the number of blocks the raster cells are divided into to be processed,
andn_processes(default: 4), which is the number of concurrent processes. A higher number ofn_chunksuses less memory, but takes
longer due to the overhead.On systems with sufficient memory (RAM > (number of processes) x (max. size of a tile)), it is generally better to parallelize over
input tiles rather than pixels.Plotwise metric extractionYou can find an example notebook for plotwise metric extractionhere, ordirectly.Full / API documentationThe full documentation can be found atreadthedocs.DependenciesThis package relies on the following packages (installed automatically when using pip). Thank you to all developers making this project possible!laxpynumpyscipypandastqdmxarraymatplotlibshapelyAcknowledgementThis package has been developed in the course of theUncertainTreeproject, funded by the Austrian Science Fund (FWF) [Grant number J 4672-N].