Commit 3330e3e3 authored by Bognár, Á.'s avatar Bognár, Á.
Browse files

Initial commit of genral repository of USM_LiDAR

parents
Pipeline #12807 failed with stages
MIT License
Copyright (c) 2020 Marc Tavenier
Copyright (c) 2020 Ádám Bognár
Copyright (c) 2020 Roel Loonen
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
# USM_LiDAR
*Author: Marc Tavenier*
## What is this repository for?
**Urban surroundings modeling with LiDAR-based point clouds for PV and building energy simulations.**<br>
This repository introduces two models to account for the effects of urban surroundings in irradiance modelling for photovoltaic (PV) performance simulations. The aim of them is to keep them accurate and easy to use for a PV system modeller. Considering urban surroundings is important because uncertainties due to not considering surroundings can lead to wrong design recommendations, thus the failure to achieve sustainability targets and/or unrealistic payback time projections. Besides that, traditional PV models assume no shading in calculating the yield. The proposed models aim to be included into simulation workflows.
## What is content of this repository based on?
The complete explanation of the scripts in this repository can be found [here]() (insert link to thesis/paper). In short, they can be explained as containing three models to model (urban) surroundings based on Digital Surface Models (DSMs) from e.g. [PDOK](https://www.pdok.nl/introductie/-/article/actueel-hoogtebestand-nederland-ahn3-)'s AHN files. The models are as follows:
1. Bar model
2. Pixel model
3. Triangulation model (reference)
The bar and pixel model use LiDAR-based DSMs as input and project it onto a cylindrical shading profile to generate shade-casting geometry. The bar model does so by making geometric alterations to the points based on the assumption that all points in the point cloud are bound to the ground directly underneath it. The pixel model uses the projection on the shadign mask to classify where there is shading geometry using a support-vector machine (SVM) classifier with the radial-basis function (RBF) kernel. The triangulation model is based on the current scientific state-of-the-art in accounting for the effects of urban surroundings in irradiance modelling for PV performance simulations, and can be used as a reference point.<br>
Analyses and validation showed that the bar model performs significantly better than the pixel model when comparing to the triangulation model and measurements, but both are included here for completeness sake. Compared to the triangulation model, the root-mean-squared (RMSE) error of the bar model is 63.1 Wh/m² and 202.3 Wh/m² of the pixel model when performing irradiance simulations using [Daysim](https://daysim.ning.com) on an hourly timestep summed to daily values, applied to 11 locations throughout the [Eindhoven city centre](https://www.google.com/maps/@51.4401651,5.4758376,737m/data=!3m1!1e3), the Netherlands. When summing the irradiances to a yearly value, the bar model and pixel model show a deviation of 0.04% and 3.08%, respectively, compared to the triangulation model.
## How to use the scripts?
In the [/src folder](/src), there is one Python file per model that contains the functions for that model. Also included is a Jupyter notebook for each model. Below the binder links for the Jupyter notebooks. The output of the scripts can be used as input for the included Grasshopper file that calls Daysim to simulate the irradiance.
## How do the scripts work?
To understand these scripts, it's recommended to look through the Binder first to see which script each model calls:
[![Binder](https://mybinder.org/badge_logo.svg)](https://mybinder.org/v2/git/https%3A%2F%2Fgitlab.tue.nl%2Fbp-tue%2Fusm_lidar/39dd0ec60aefe31be85aca2575cb5b793ae49a32)
* Bar model: after launcing binder, navigate to /src/bar.ipynb
* Pixel model: after launcing binder, navigate to /src/pixel.ipynb
* Triangulation model: after launcing binder, navigate to /src/triangulation.ipynb
In the [/doc folder](/doc), you can find documentation for each of the functions the models call to understand the inner workings of the model. They consist of:
* [tiff2xyz](/doc/tiff2xyz.md)
* [xyz2proj](/doc/xyz2proj.md)
* [proj2geom](/doc/proj2geom.md)
* [grasshopper_simulation](/doc/grasshopper_simulation.md)
## I have further questions
You can reach me on [marctavenier@msn.com](mailto:marctavenier@msn.com).<br>
<br>
![](doc/images/xyz2proj_img1.jpg)<br>
![](doc/images/proj2geom_img1.png)
\ No newline at end of file
libspatialindex-dev
\ No newline at end of file
# Shading geometry to hourly irradiance
This appendix presents information about the Grasshopper script that calculates hourly irradiance from the shading geometry. The script relies on the [Ladybug 0.0.67 and Honeybee 0.0.64 legacy](https://www.food4rhino.com/app/ladybug-tools) and [LunchBox 2018.11.16](https://www.food4rhino.com/app/lunchbox) plugins. They might also work with the newest Ladybug/Honeybee legacy versions, though this has been untested. This documentation presents a simplified, compressed version below and a flowchart to clarify the workings of the script. Step by step, the process is as follows:
1. Sets the global variables *North direction* (in degrees) and *cylinder radius* (in meters) and imports the Ladybug and Honeybee libraries;
2. Creates the shading geometry of the four models:
1. The triangulation model:
1. Reads the XYZ-file created using appendix 1;
2. Creates points from the x, y, and z coordinates;
3. Applies the Delaunay algorithm to the points.
2. The bar model:
1. Reads the azimuths and elevations of the points in the projection file created using appendix 2;
2. Retrieves the highest elevation per degree of azimuth by constructing a tree (also known as nested lists) with one branch per degree of azimuth with the associated elevations in the branches as items, sorting each branch and retrieving the last item of the list;
3. Creates a point at the bottom and the top of the bar where the height of the top is calculated using equation 1;
4. Rotates the bottom points half the *w_b* from equation 2 left and right and the top points half of *w_t* left and right;
5. Creates an arc between the three points (left and right points from step iv and original point from step iii) for both the bottom and top of the bar;
6. Creates a curved surface between the two arcs.
3. The pixel model:
1. Reads the azimuths and elevations of the pixels in the classified geometry created using appendix 3;
2. Constructs a tree with one branch per azimuth with the associated elevations in the branches as items and sorting each branch;
3. Uses a custom Python script that outputs the azimuth of highest pixel that has consecutive members in the azimuth direction and the number of pixels that are consecutive;
4. Creates a point at every corner of the consecutive set of pixels;
5. Constructs a surface using the corner points.
3. User selects the appropriate input and output depending on the model that they want to use, supplies an EPW weather file, a sensor point (usually (0, 0, 0) because it was already supplied in appendix 1 and 2), and a vector that indicates what direction the sensor is facing;
4. Creates a preview of the shading geometry in Rhinoceros;
5. Simulates the irradiance at a point using the supplied shading geometry and parameters by:
1. Creating a shading material with 0.35 reflectance, 0 roughness, and 0 specularity;
2. Converting the surfaces to the Radiance format using the MSH2RAD component (note that it accepts both surfaces and meshes);
3. Creating a small “fake” surface to input that prevents errors when using only the created Radiance files;
4. Supplying the simulation component with the Radiance and Daysim parameters that make it return the irradiance in W/m² and the low-quality pre-set;
5. Enabling the custom DDS option which is created by adding a toggle to the simulation component that affects the -dds switch when Honeybee calls the Daysim subprograms *gen_dc* and *ds_illum*;
6. Runs the simulation using the supplied parameters and files.
6. Reads the result files from Daysim and puts it into a Panel for possibility of exporting it to a CSV file.
Note that there is also a SunEye model present in the script, but this was purely meant for the validation of the other models in the thesis these scripts are based on and are not further documented in this repository.
Simplified flowchart of the Grasshopper script:
![](images/grasshopper_simulation_img1.png)
Grasshopper model screenshot:
![](images/grasshopper_simulation_img2.jpg)
\ No newline at end of file
# Classify geometry with machine learning
The Python script to classify geometry with SVM relies on the [Scikit-Learn](https://pypi.python.org/pypi/scikit-learn), [Pandas](https://pypi.python.org/pypi/pandas), [NumPy](https://pypi.python.org/pypi/numpy), [Matplotlib](https://pypi.python.org/pypi/matplotlib) libraries. This documentation presents a simplified, compressed version to clarify the workings of the script. Step by step, the process is as follows:
1. Uses the projection returned from appendix 2 as input;
2. Creates a uniform grid of sky points ranging from -15° to 375° in the azimuth direction and 0° to 90° in the elevation direction. The extra degrees are to train the SVM that the “left” North is connected to the “right” North;
3. Creates a uniform test grid of points at each degree of azimuth and elevation, which equals the number of pixels;
4. Iterates over all possible *C*, *γ*, weight and threshold combinations using nested for-loops;
5. Initializes the figure, with all appropriate styling;
6. Fits (also known as training) the points using the SVM with RBF kernel by:
1. Copying the projected points in the Northern-most 30° to either side like in step 2;
2. Concatenating the projected points and the sky points into one variable *X*;
3. Scaling and transforming the variable *X* using the StandardScalar from Scikit-learn;
4. Creating a list of sample weights, where the projected points get assigned the specified weight, and the sky points get a weight of 1;
5. If desired, plots the original projected points onto the figure;
6. Returns the SVM RBF function, scaler, and figure.
7. Predicts (also known as classifying) the points at each point of the test grid by:
1. Scaling the test grid using the same scaler as before;
2. Using the decision function to predict if each point of the test grid is shaded or not;
3. Binarizes the points to be 0 or 1 with a certain threshold;
4. If desired, plots the test points that are classified as shading onto the figure;
5. Returns the predicted values for the whole test grid and the figure.
8. Appends the predicted values to a list, in case multiple parameters are given in step 4 and returns it;
9. Adds a legend to the figure, shows it and if desired, saves it.
Example of a classified projected point cloud:
![](images/proj2geom_img1.png)
\ No newline at end of file
# DSM to pointcloud file
The Python script to create a point cloud file from a DSM relies on the [Pandas](https://pypi.python.org/pypi/pandas), [NumPy](https://pypi.python.org/pypi/numpy), [Pillow](https://pypi.python.org/pypi/Pillow), and [Matplotlib](https://pypi.python.org/pypi/matplotlib) libraries. This documentation presents a simplified, compressed version to clarify the workings of the script. Step by step, the process is as follows:
1. Reads the TIFF-file (e.g. from [PDOK](https://www.pdok.nl/introductie/-/article/actueel-hoogtebestand-nederland-ahn3-));
2. Converts it to a 2D array;
3. If desired, plots the array;
4. Crops the array around the sensor point *(x,y)* with apothem *a*, while also supplying it with the resolution of the TIFF-file;
5. If desired, removes coordinates from the array based on a file with the *(x,y)* coordinates of the points in case the point is inside an object;
6. If desired, plots the cropped array;
7. Converts the array to a point cloud XYZ-file, returns the xyz data and if desired, saves it.
Note that the script does not have support for *(x,y)* coordinates at the edge of a TIFF-file in its current state. Example of an array created from a TIFF-file:
![](images/tiff2xyz_img1.jpg)
Example of a cropped array:
![](images/tiff2xyz_img2.jpg)
\ No newline at end of file
# Point cloud file to projection
The Python script to create a projection from a point cloud relies on the [Pandas](https://pypi.python.org/pypi/pandas), [NumPy](https://pypi.python.org/pypi/numpy), [Matplotlib](https://pypi.python.org/pypi/matplotlib), and [R-tree](https://pypi.python.org/pypi/Rtree) libraries. This appendix presents a simplified, compressed version to clarify the workings of the script. Step by step, the process is as follows:
1. Uses the xyz data returned from appendix 1 as input;
2. Removes points lower than the z-value of the sensor point;
3. Converts the points from cartesian to spherical coordinates;
4. If desired, culls the points that are close to one another, not considering the radius ρ of the points, with a certain tolerance by:
1. Building an R-tree;
2. Iterating over the points;
3. Retrieving the points close to the point being iterated over within the tolerance;
4. Removing all the points and creating a new one in the centre;
5. Converts the purged points back to cartesian coordinates.
5. If desired, projects the points on a cylinder by:
1. Calculating z using equation 1 for each point;
2. Converts the cartesian coordinates to cylindrical coordinates while changing the z-value to the one calculated;
3. If desired, plots the points in 3D by:
1. Converting the cylindrical to cartesian coordinates;
2. Plotting the points in 3D.
6. If desired, saves the projected points and returns them.
Example of a projected point cloud on a cylinder where culling is applied:
![](images/xyz2proj_img1.jpg)
\ No newline at end of file
certifi==2019.11.28
cycler==0.10.0
joblib==0.14.1
kiwisolver==1.1.0
matplotlib==3.1.2
numpy==1.18.1
pandas==0.25.3
Pillow==7.0.0
pyparsing==2.4.6
python-dateutil==2.8.1
pytz==2019.3
Rtree==0.9.3
scikit-learn==0.22.1
scipy==1.4.1
six==1.13.0
sklearn==0.0
This diff is collapsed.
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Created on Wed Jan 15 16:58:48 2020
@author: Marc Tavenier
"""
from tiff2xyz import tiff2xyz
from xyz2proj import xyz2proj
"""
Get the pointcloud
"""
# location of the TIFF-file
tiffloc = 'insert path to tiff file here'
# x and y coordinates of the sensor point in the TIFF-file (in meters)
x = 1156.5
y = 2307
# apothem (square radius) to crop around the sensor point (in meters)
apothem = 200
# boolean whether to save the XYZ-file
savexyz = False
# boolean whether to plot the complete TIFF-file
plot_tiff = False
# boolean whether to plot the cropped TIFF-file
plot_croppedtiff = True
# boolean wheter to save the complete TIFF-file (as a PNG)
save_tiffplot = False
# boolean whether to save the cropped TIFF-file (as a PNG)
save_croppedtiffplot = False
# name of the XYZ-file you want to save
name = 'point cloud'
# resolution of the TIFF-file (in meters)
resolution = 0.5
# run the function to get the pointcloud
pointcloud = tiff2xyz(tiffloc, x, y, apothem, savexyz, plot_tiff,
plot_croppedtiff, save_tiffplot, save_croppedtiffplot,
name, resolution)
"""
Project the pointcloud
"""
# z-value of the sensor point (in meters)
z = 18.00
# tolerance which to use for the culling of points that are close to one
# another (in degrees)
cull_tol = 1.5
# boolean whether to plot the projected points
plot_proj = True
# boolean whether to save the projected plot (as a PNG)
save_projplot = False
# boolean whether to save the projected points (as a CSV)
save_proj = False
# name of the projection file
proj_name = 'projected points'
# boolean whether you want a cylinder (True) or hemisphere (False). Only
# applies to the plot, not the projected point output
cyl = True
# radius of the projected points in the plot. Only applies to the plot, not the
# projecte point output
radius = 10
# run the function to project the pointcloud
projection = xyz2proj(pointcloud, z, cull_tol, plot_proj, save_projplot,
save_proj, proj_name, cyl, radius)
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Created on Wed Jan 15 16:56:38 2020
@author: Marc Tavenier
"""
import os
def save_figure(fig, figurename='figure', filetype='png',
folder='Figure output'):
"""Function to save a matplotlib figure in a folder with a certain
filetype and figure name. Function is used by other scripts as well when
imported."""
# create folder if it doesn't already exist
if not os.path.exists(folder):
os.makedirs(folder)
# check if plot number already exists, otherwise add 1 to the figurename.
i = 0
while os.path.exists('{folder}/{figurename} {i:d}.{filetype}'.format(
folder=folder, figurename=figurename, i=i, filetype=filetype)):
i += 1
# save the figure with the name
fig.savefig('{folder}/{figurename} {i:d}.{filetype}'.format(
folder=folder, figurename=figurename, i=i, filetype=filetype),
dpi=300, bbox_inches='tight')
print(f"Figure saved in /{folder}/ as '{figurename} {i:d}.{filetype}'")
def save_df(df, filename='filename', filetype='csv', folder='CSV output',
header=True, sep=','):
"""Function to save values of a DataFrame to a certain filetype in a
folder. Function is used by other scripts as well when imported."""
# create folder if it doesn't already exist
if not os.path.exists(folder):
os.makedirs(folder)
# check if save number already exists, otherwise add 1 to the filename
i = 0
while os.path.exists('{folder}/{filename} {i:d}.{filetype}'.format(
folder=folder, filename=filename, i=i, filetype=filetype)):
i += 1
# save the DataFrame with the filename and filetype in the folder
df.to_csv('{folder}/{filename} {i:d}.{filetype}'.format(
folder=folder, filename=filename, i=i, filetype=filetype), index=False,
header=header, sep=sep)
print(f"file saved in /{folder}/ as '{filename} {i:d}.{filetype}'")
This diff is collapsed.
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Created on Wed Jan 15 16:58:48 2020
@author: Marc Tavenier
"""
from tiff2xyz import tiff2xyz
from xyz2proj import xyz2proj
from proj2geom import proj2geom
"""
Get the pointcloud
"""
# location of the TIFF-file
tiffloc = 'insert path to tiff file here'
# x and y coordinates of the sensor point in the TIFF-file (in meters)
x = 1156.5
y = 2307
# apothem (square radius) to crop around the sensor point (in meters)
apothem = 200
# boolean whether to save the XYZ-file
savexyz = False
# boolean whether to plot the complete TIFF-file
plot_tiff = False
# boolean whether to plot the cropped TIFF-file
plot_croppedtiff = True
# boolean wheter to save the complete TIFF-file (as a PNG)
save_tiffplot = False
# boolean whether to save the cropped TIFF-file (as a PNG)
save_croppedtiffplot = False
# name of the XYZ-file you want to save
name = 'point cloud'
# resolution of the TIFF-file (in meters)
resolution = 0.5
# run the function to get the pointcloud
pointcloud = tiff2xyz(tiffloc, x, y, apothem, savexyz, plot_tiff,
plot_croppedtiff, save_tiffplot, save_croppedtiffplot,
name, resolution)
"""
Project the pointcloud
"""
# z-value of the sensor point (in meters)
z = 18.00
# tolerance which to use for the culling of points that are close to one
# another (in degrees)
cull_tol = 1.5
# boolean whether to plot the projected points
plot_proj = True
# boolean whether to save the projected plot (as a PNG)
save_projplot = False
# boolean whether to save the projected points (as a CSV)
save_proj = False
# name of the projection file
proj_name = 'projected points'
# boolean whether you want a cylinder (True) or hemisphere (False). Only
# applies to the plot, not the projected point output
cyl = True
# radius of the projected points in the plot. Only applies to the plot, not the
# projecte point output
radius = 10
# run the function to project the pointcloud
projection = xyz2proj(pointcloud, z, cull_tol, plot_proj, save_projplot,
save_proj, proj_name, cyl, radius)
"""
Classify the projection into geometry and sky
"""
# input list of gamma-values, C-values, weight-values and threshold-values
# to iterate over all possible combinations
gammas = [100]
cs = [2.5]
weights = [2]
thresholds = [.5]
# set the skygrid x and y resolution
skygridres_x = 30
skygridres_y = 30
# construct a title with the relevant parameters
title = f'{name}\nshading weight$ = {weights[0]}:1$, cull tolerance' + \
f'$ = {cull_tol}$, DSM apothem$ = {apothem}m$, skygrid ' + \
f'resolution $ = {skygridres_x}x{skygridres_y}$'
# boolean whether to save the figure (as a PNG)
save_figure = False
# boolean whether to save the shaded values (as a CSV)
save_shadedvalues = False
# run the function to classify the projection into geometry
shaded_values = proj2geom(
projection, gammas, cs, weights, thresholds, title, skygridres_x,
skygridres_y, save_figure, name, save_shadedvalues=False)
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Created on Mon Apr 1 16:01:37 2019
@author: Marc Tavenier
"""
import matplotlib.pyplot as plt
from matplotlib.ticker import MultipleLocator
from matplotlib.patches import Patch
import numpy as np
import pandas as pd
import time
from sklearn.svm import SVC
from sklearn.preprocessing import StandardScaler
# dependencies from other Gitlab functions:
from common import save_figure, save_df
from tiff2xyz import tiff2xyz
from xyz2proj import xyz2proj
def initialize_plot(title='title'):
"""function to initialize script"""
# close created figures from memory
plt.close('all')
# create figure
fig, ax = plt.subplots()
fig.tight_layout()
fig.set_size_inches(12, 3)
ax.set_title(title)
ax.set_xlabel('Azimuth ($^\circ$)')
ax.set_ylabel('Elevation ($^\circ$)')
ax.grid(True, zorder=1)
ax.xaxis.set_major_locator(MultipleLocator(45))
ax.xaxis.set_minor_locator(MultipleLocator(22.5))
ax.yaxis.set_major_locator(MultipleLocator(10))
ax.yaxis.set_minor_locator(MultipleLocator(5))
ax.set_xlim(left=0, right=360)
ax.set_ylim(bottom=0, top=90)
ax.set_xticks(np.arange(0, 361, 45))
ax.set_xticklabels(['0\nNorth', '45', '90\nEast', '135', '180\nSouth',
'225', '270\nWest', '315', '360\nNorth'])
ax.set_yticks(np.arange(0, 91, 10))
return (fig, ax)
def end_timer(start, name=''):
"""End timer and print time"""
duration = round(time.time() - start, 2)
print(f'{name} time = {duration} seconds')
def uniform_grid(x_res=1, y_res=1, extranorths=False, x_endpoint=False):
"""create a grid uniformly in the testspace (x=0-360, y=0-90)
x-res = amount of points in the x-direction of the testspace
y-res = amount of points in the y-direction of the testspace
extranorths = toggle to say if you want the grid to include the extra
norths (used in case it's a skygrid)
x_endpoint = toggle to say if you want the x endpoints to appear.
In other words: if set to False it assures that the northern most
points don't appear twice (at 0 and 360 degrees)"""
if extranorths:
xx, yy = np.meshgrid(np.linspace(-15, 375, x_res, endpoint=x_endpoint),
np.linspace(0, 90, y_res))
else:
xx, yy = np.meshgrid(np.linspace(0, 360, x_res, endpoint=x_endpoint),
np.linspace(0, 90, y_res))
uniformgrid = np.c_[xx.ravel(), yy.ravel()]
return uniformgrid
def SVM_fit(sph, skygrid, ax, gamma=100, c=.1, lidarweight=5,
plot_orig_lidar=True):
"""function to prepare data for use of SVM RBF and train it
sph = dataframe with azimuths and elevations of LiDAR points
skygrid = a list of x's and y's where there is sky. Can be generated using
uniform_grid or grid_reader
gamma = gamma parameter as documented here:
www.scikit-learn.org/stable/auto_examples/svm/plot_rbf_parameters.html
(good starting point = 100)
C = C parameter as documented here:
www.scikit-learn.org/stable/auto_examples/svm/plot_rbf_parameters.html
(good starting point = 0.1)
lidarweight = how many times heavier the LiDAR points should 'weigh'
compared to the skygrid (good starting point = 5)"""
# add the norths together on either side so the model can more accuratly
# predict the continuous line between norths
# first, get the northern lidar points
lidar_first = sph.loc[sph['azimuth'].between(0, 15, inclusive=True)]
lidar_last = sph.loc[sph['azimuth'].between(345, 360, inclusive=True)]
# second, renumber the azimuth-values
lidar_first_az_ren = lidar_first['azimuth'] + 360
lidar_last_az_ren = lidar_last['azimuth'] - 360
# third, combine the renumbered x-values with the original y-values
lidar_first_ren = pd.DataFrame({'azimuth': lidar_first_az_ren,
'elevation': lidar_first['elevation']})
lidar_last_ren = pd.DataFrame({'azimuth': lidar_last_az_ren,
'elevation': lidar_last['elevation']})
# last, make one DataFrame with all the values
sph = pd.concat([sph, lidar_first_ren, lidar_last_ren], ignore_index=True,
copy=False)
# put the lidar and skygrid DataFrame into one list
X = np.concatenate((sph, skygrid))
# make a list the length of both the LiDAR and skygrid with 1's and 0's,
# where 1 = LiDAR and 0 = skygrid
y = [1] * len(sph) + [0] * len(skygrid)
# create the SVM RBF function
clf = SVC(C=c, cache_size=2048, class_weight='balanced',
decision_function_shape=None, gamma=gamma, kernel='rbf',
shrinking=False)
# scale the X data with the preprocessing scaler
scaler = StandardScaler().fit(X)
X_scaled = scaler.transform(X)
# add desired weight to the LiDAR points
sample_weight = [lidarweight] * len(sph) + [1] * len(skygrid)
# start the training timer
start = time.time()
# train the data with desired weights
clf.fit(X_scaled, y, sample_weight=sample_weight)
# end the training timer
end_timer(start, 'SVM fit')
# if you want to plot the original LiDAR points
if plot_orig_lidar:
ax.scatter(sph['azimuth'], sph['elevation'], marker='s', c='dimgray',
zorder=3, label='LiDAR points', s=4.75, edgecolors='none')
# return trained data and scaler for use in SVM_predict
return (clf, scaler, ax)