Colorize a point cloud corresponding to attribute values

Introduction to Visualization of Point Attributes with Colors

Airborne Laser Scanning delivers not only point positions, but also various point attributes related to the properties of the measured echoes. Additionally, user-generated attributes can also be created, describing texture, structure and radiometric properties of each point and their neighbourhoods. Visual representation of these attributes is essential for effectively using them in point cloud filtering, classification and analysis. Traditional GIS software can visualize rasters and vectors based on their attributes, but point cloud visualization software typically only handle the pre-defined attributes available in the data format they are using (mostly .las). This means other user-generated attributes are not possible to directly visualize through colouring of the points themselves. OpalsColorize exploits the built-in Red, Green, Blue attributes of both the OPALS .odm and the ISPRS .las file for visualization, and allows re-scaling of attribute values to these color attributes, using either pre-defined palettes or RGB visualization.

Description of data

This use case is based on the data of the Radiometric calibration of full waveform ALS data, Horn, Austria use case. The processing starts with the two .odm files output after the Radiometric Calibration use case is complete. We start with the two .odm files created at the end of the RadioCal Use case. These are two overlapping full-waveform airborne LIDAR flight strips with imported trajectory data, beam vectors, and calibrated reflectance.

Get information about 680_110922_095526.odm

First we run OpalsInfo on the two strips to see what attributes they hold

opalsinfo -inf 680_110922_095526.odm

The opalsinfo output is as follows:

Filename 
c:\Data\opalscolorize_use_case\dev_colorize_use_case\680_110922_095526.odm
Type    odm
SpatialReference        PROJCS["WGS 84 / UTM zone 33N",GEOGCS["WGS 
84",DATUM["WGS_1984",SPHEROID["WGS 
84",6378137,298.257223563,AUTHORITY["EPSG","7030"]],AUTHORITY["EPSG","6326"]],PRIMEM["Greenwich",0,AUTHORITY["EPSG","8901"]],UNIT["degree",0.0174532925199433,AUTHORITY["EPSG","9122"]],AUTHORITY["EPSG","4326"]],PROJECTION["Transverse_Mercator"],PARAMETER["latitude_of_origin",0],PARAMETER["central_meridian",15],PARAMETER["scale_factor",0.9996],PARAMETER["false_easting",500000],PARAMETER["false_northing",0],UNIT["metre",1,AUTHORITY["EPSG","9001"]],AXIS["Easting",EAST],AXIS["Northing",NORTH],AUTHORITY["EPSG","32633"]]
Point Count     1095464
Line Count      0
Polygon Count   0
Minimum X-Y-Z     548355.271 5389534.000   321.017
Maximum X-Y-Z     548701.000 5390034.000   415.035
Point density   0.61
Attribute       type    count   min     max     mean    sigma
Amplitude       float   1095464 10.000  23874.000       133.781 81.664
EchoNumber      uint8   1095464 1       7       1.210 0.530
NrOfEchos       uint8   1095464 1       7       1.419 0.765
ClassificationFlags     uint8    1095464 0       0 0.000   0.000
ScanDirection   bool 1095464 0       0       0.000   0.000
EdgeOfFlightLine        bool 1095464 0       0       0.000 0.000
Classification  uint8   1095464 0       0       0.000 0.000
ScanAngle       float   1095464 0.022   0.557   0.328   0.133
UserData        uint8   1095464 0       0       0.000 0.000
PointSourceId   uint16  1095464 0       0       0.000 0.000
GPSTime double  1095464 381375.874      381385.716 381380.482      2.407
EchoWidth       float   1095464 3.000   26.700  4.618   0.650
BeamVectorX     float   1095464 -388.474        7.684 -187.519        86.269
BeamVectorY     float   1095464 -142.926        -23.125 -74.467 24.795
BeamVectorZ     float   1095464 -598.542        -507.674 -559.442        
5.714
_BeamVectorSCSX float   1095464 -1.214  1.076   -0.120  0.525
_BeamVectorSCSY float   1095464 12.581  343.868 195.275 85.006
_BeamVectorSCSZ float   1095464 516.645 595.460 562.435 11.292
Range   float   1095464 526.293 695.386 600.629 31.914
Id      int64   1095464 6442450944      1125919234205058 649683261817435.370
379675618954346.500
FileId  uint16   1095464 1       1       1.000   0.000
LayerId uint16   1095464 0       0       0.000   0.000
WinputCode      uint16   1095464 30      30      30.000 0.000
StructNr        uint16   1095464 0       0       0.000 0.000
NormalX float   918990  -1.000  1.000   0.060   0.403
NormalY float   918990  -1.000  1.000   0.028   0.411
NormalZ float   918990  0.000   1.000   0.698   0.421
NormalSigma0    float   918990  0.000   0.994   0.079   0.100
NormalEstimationMethod  uint8    918990  0       0 0.000   0.000
CrossSection    float   0       ---     ---     ---     ---
_IncidenceAngle float   1095464 0.000   84.999  13.383  15.064
Reflectance     float   1095464 0.012   56.456  0.318   0.260
_Gamma  float   1095464 0.048   225.826 1.129   0.699
_Sigma  float   0       ---     ---     ---     ---
Index statistics
geometries      dim     type    depth   node count      leaf count      
tile size
min #owl        max #owl        mean #owl       stddev #owl
Points  2       tile    1       25      21      107.250 1361 106195  
52164.952
36028.082

So the file has approximately 1000000 points with

  • X,Y and Z coordinates,
  • Amplitude,
  • EchoNumber,
  • NrOfEchs,
  • ClassificationFlags (this attribute is empty!),
  • ScanDirection (this attribute is empty!),
  • EdgeOfFlightLine (this attribute is empty!),
  • Classification (this attribute is empty!),
  • ScanAngle, UserData (this attribute is empty!),
  • PointSourceId (this attribute is empty!),
  • GPSTime,
  • EchoWidth,
  • BeamVectorX/Y/Z,
  • _BeamVectorSCSX/Y/Z,
  • Range,
  • ID,
  • FileId,
  • LayerId (this attribute is empty!),
  • WinputCode,
  • StructNr (this attribute is empty!),
  • NormalX/Y/Z,
  • NormalSigma,
  • NormalEstimationMethod,
  • CrossSection (this attribute is empty!),
  • _IncidenceAngle,
  • Reflectance,
  • _Gamma,
  • _Sigma (this attribute is empty!)

Theoretically, all the numeric attributes can be visualized using OpalsColorize on a point basis.

Get information about 680_110922_095947.odm

We run OpalsInfo on the other file as well:

opalsinfo -inf 680_110922_095947.odm

The output is the same, but with approximately 2000000 points.

Filename 
c:\Data\opalscolorize_use_case\dev_colorize_use_case\680_110922_095947.odm
Type    odm
SpatialReference        PROJCS["WGS 84 / UTM zone 33N",GEOGCS["WGS 
84",DATUM["WGS_1984",SPHEROID["WGS 
84",6378137,298.257223563,AUTHORITY["EPSG","7030"]],AUTHORITY["EPSG","6326"]],PRIMEM["Greenwich",0,AUTHORITY["EPSG","8901"]],UNIT["degree",0.0174532925199433,AUTHORITY["EPSG","9122"]],AUTHORITY["EPSG","4326"]],PROJECTION["Transverse_Mercator"],PARAMETER["latitude_of_origin",0],PARAMETER["central_meridian",15],PARAMETER["scale_factor",0.9996],PARAMETER["false_easting",500000],PARAMETER["false_northing",0],UNIT["metre",1,AUTHORITY["EPSG","9001"]],AXIS["Easting",EAST],AXIS["Northing",NORTH],AUTHORITY["EPSG","32633"]]
Point Count     2000660
Line Count      0
Polygon Count   0
Minimum X-Y-Z     548201.001 5389534.000   319.819
Maximum X-Y-Z     548701.000 5390034.000   420.567
Point density   0.56
Attribute       type    count   min     max     mean    sigma
Amplitude       float   2000660 10.000  65517.000       172.725 119.138
EchoNumber      uint8    2000660 1       7       1.158 0.455
NrOfEchos       uint8    2000660 1       7       1.318 0.664
ClassificationFlags     uint8    2000660 0       0 0.000   0.000
ScanDirection   bool 2000660 0       0       0.000   0.000
EdgeOfFlightLine        bool 2000660 0       0       0.000 0.000
Classification  uint8    2000660 0       0       0.000 0.000
ScanAngle       float   2000660 -0.522  0.558   0.040   0.251
UserData        uint8    2000660 0       0       0.000 0.000
PointSourceId   uint16   2000660 0       0       0.000 0.000
GPSTime double  2000660 381607.216      381618.474 381613.123      2.403
EchoWidth       float   2000660 1.600   28.000  4.642   0.608
BeamVectorX     float   2000660 -281.645        298.890 15.565 127.223
BeamVectorY     float   2000660 -138.602        164.437 17.263 62.536
BeamVectorZ     float   2000660 -583.272        -484.437 -544.106        
4.923
_BeamVectorSCSX float   2000660 -1.136  3.138   0.933   0.904
_BeamVectorSCSY float   2000660 -313.871        342.940 22.844 142.387
_BeamVectorSCSZ float   2000660 484.980 585.881 543.968 5.719
Range   float   2000660 485.176 648.505 562.468 18.485
Id      int64   2000660 2147483648      1125919234260358 636357768932269.250
339547697230284.690
FileId  uint16   2000660 1       1       1.000   0.000
LayerId uint16   2000660 0       0       0.000   0.000
WinputCode      uint16   2000660 30      30      30.000 0.000
StructNr        uint16   2000660 0       0       0.000 0.000
NormalX float   1745996 -1.000  1.000   -0.001  0.376
NormalY float   1745996 -1.000  1.000   0.005   0.370
NormalZ float   1745996 0.000   1.000   0.761   0.378
NormalSigma0    float   1745996 0.000   1.266   0.072   0.096
NormalEstimationMethod  uint8    1745996 0       0 0.000   0.000
CrossSection    float   0       ---     ---     ---     ---
_IncidenceAngle float   2000660 0.000   84.999  9.830   12.065
Reflectance     float   2000660 0.012   146.687 0.334   0.245
_Gamma  float   2000660 0.050   586.747 1.268   0.892
_Sigma  float   0       ---     ---     ---     ---
Index statistics
geometries      dim     type    depth   node count      leaf count      
tile size
min #owl        max #owl        mean #owl       stddev #owl
Points  2       tile    1       25      25      128.000 4851 139210  
80026.400
46672.537

Merging the two datasets for easier handling

We merge the two files to one single data file for easier handling, by importing the two .odms to a single .odm

opalsimport -inf 680_110922_095526.odm 680_110922_095947.odm -outf points.odm

In this case, OpalsImport retains all the attributes of the two source files. We check this in OpalsInfo:

opalsinfo -infile points.odm

The result is as follows:

Filename c:\Data\opalscolorize_use_case\dev_colorize_use_case\points.odm
Type    odm
SpatialReference        PROJCS["WGS 84 / UTM zone 33N",GEOGCS["WGS 
84",DATUM["WGS_1984",SPHEROID["WGS 
84",6378137,298.257223563,AUTHORITY["EPSG","7030"]],AUTHORITY["EPSG","6326"]],PRIMEM["Greenwich",0,AUTHORITY["EPSG","8901"]],UNIT["degree",0.0174532925199433,AUTHORITY["EPSG","9122"]],AUTHORITY["EPSG","4326"]],PROJECTION["Transverse_Mercator"],PARAMETER["latitude_of_origin",0],PARAMETER["central_meridian",15],PARAMETER["scale_factor",0.9996],PARAMETER["false_easting",500000],PARAMETER["false_northing",0],UNIT["metre",1,AUTHORITY["EPSG","9001"]],AXIS["Easting",EAST],AXIS["Northing",NORTH],AUTHORITY["EPSG","32633"]]
Point Count     3096124
Line Count      0
Polygon Count   0
Minimum X-Y-Z     548201.001 5389534.000   319.819
Maximum X-Y-Z     548701.000 5390034.000   420.567
Point density   1.15
Attribute       type    count   min     max     mean    sigma
Amplitude       float   3096124 10.000  65517.000       158.946 108.987
EchoNumber      uint8    3096124 1       7       1.176 0.483
NrOfEchos       uint8    3096124 1       7       1.354 0.703
ClassificationFlags     uint8    3096124 0       0 0.000   0.000
ScanDirection   bool 3096124 0       0       0.000   0.000
EdgeOfFlightLine        bool 3096124 0       0       0.000 0.000
Classification  uint8    3096124 0       0       0.000 0.000
ScanAngle       float   3096124 -0.522  0.558   0.142   0.257
UserData        uint8    3096124 0       0       0.000 0.000
PointSourceId   uint16   3096124 0       0       0.000 0.000
GPSTime double  3096124 381375.874      381618.474 381530.811      111.264
EchoWidth       float   3096124 1.600   28.000  4.634   0.624
BeamVectorX     float   3096124 -388.474        298.890 -56.290 150.072
BeamVectorY     float   3096124 -142.926        164.437 -15.193 68.325
BeamVectorZ     float   3096124 -598.542        -484.437 -549.532        
8.999
_BeamVectorSCSX float   3096124 -1.214  3.138   0.560   0.938
_BeamVectorSCSY float   3096124 -313.871        367.928 83.854 149.850
_BeamVectorSCSZ float   3096124 484.980 595.460 550.502 12.009
Range   float   3096124 485.176 695.386 575.970 30.234
Id      int64   3096124 2147483648      1407372736120332 642031292303116.750
360738370783687.810
FileId  uint16   3096124 1       2       1.646   0.478
LayerId uint16   3096124 0       0       0.000   0.000
WinputCode      uint16   3096124 30      30      30.000 0.000
StructNr        uint16   3096124 0       0       0.000 0.000
NormalX float   2664986 -1.000  1.000   0.020   0.386
NormalY float   2664986 -1.000  1.000   0.013   0.385
NormalZ float   2664986 0.000   1.000   0.739   0.394
NormalSigma0    float   2664986 0.000   1.142   0.074   0.097
NormalEstimationMethod  uint8    2664986 0       0 0.000   0.000
CrossSection    float   0       ---     ---     ---     ---
_IncidenceAngle float   3096124 0.000   84.999  11.087  13.313
Reflectance     float   3096124 0.012   146.687 0.328   0.250
_Gamma  float   3096124 0.048   586.747 1.219   0.832
_Sigma  float   0       ---     ---     ---     ---
Index statistics
geometries      dim     type    depth   node count      leaf count      
tile size
min #owl        max #owl        mean #owl       stddev #owl
Points  2       tile    1       25      25      125.500 13756 287781  
123844.960
79551.495

Processing the data with opalsColorize

Basic usage of opalsColorize

This file now has about 3100000 points, with all the original attributes. The attribute FileId refers to the original source files, and is "1" and "2" respectively. We can check this using a basic OpalsColorize command:

opalscolorize -inf points.odm -attribute FileId -outf points_fileId.las

OpalsColorize edits the Red, Green and Blue attributes of an .odm file to create colour outputs based on the values of other attributes. This means that the selected attribute values are rescaled to match the range of the RGB attribute. The default case is that only an .odm is generated, with new attributes. However, if additionally an output .las file is specified, then such a file is also generated, with the color written to the R,G,B attributes of the .las file. For this command, OpalsColorize gives the following output

16:35:13 info: Running opalsColorize ...
16:35:13 info: Index statistics
geometries      dim     type    depth   node count      leaf count      
tile size
min #owl        max #owl        mean #owl       stddev #owl
Points  2       tile    1       25      25      125.500 13756 287781  
123844.960
79551.495

16:35:13 info: Stage 1/1: Classifying data
100%16:35:24 info: Class No.    FileId : Count
    0            BG : 0
    1       underfl : 0
    2         1.000 : 1095464
    3         1.004 : 0
    4         1.008 : 0
...

...
  253         1.992 : 0
  254         1.996 : 0
  255         2.000 : 2000660


16:35:24 info: Running opalsColorize took: 00:00:11

This means that we are using the default palette with 255 colour steps, which is $OPALS_ROOT$/addons/pal/standardPal.xml . This palette runs from green to brown, in our case this means that points with FileId == 1 will be coloured green, while points with FileId == 2 will be coloured brown. By opening the resulting .las point cloud in a point cloud viewer (eg. FugroViewer, CloudCompare), the output point cloud can be checked visually in planar, profile or 3D perspective view.

Fig. 1: FileID as cross section with standard palette


Fig. 2: FileID in planar view with standard palette

Additionally, a basic .svg graphic file legend is generated with attribute values and their respective colouring.

The Echo width values have the following distribution (from the OpalsInfo output):

EchoWidth       float   3096124 1.600   28.000  4.634   0.624

Using a different color scheme

What if we want a different color scheme? We apply a different palette from the folder $OPALS_ROOT$/addons/pal/ or our own user-defined .xml palette files. E.g.

opalscolorize -inf points.odm -attribute FileId -outf points_fileId_jetpal.las -palfile JetPal.xml

this will result in points with FileId == 1 coloured red, and points with FileId == 2 coloured blue. Please note that applying OpalsColorize on the same output file repeatedly will overwrite the Red, Green and Blue values in the file.

Using attribute EchoWidth

Now we visualize another point attribute: Echo Width.

opalscolorize -inf points.odm -attribute EchoWidth -outf points_EchoWidth.las

In this case the minimum and maximum value range of Echo Width are automatically mapped to the palette nodes with a linear stretch. The minimum is 0, the maximum is 28, and the resulting legend can be viewed as the file points_EchoWidth_EchoWidth_Legend.svg. However, this visualization is not informative as nearly all points have very similar shades of green. In order to get a better overview of the EchoWidth distribution, a linear scaling can be applied, excluding points above and below a certain limit, e.g. 1 Sigma from the mean. This is 4.636 ± 0.624, so 4 and 5.5 are reasonable limits that we can expect to contain most of the data. These can be applied to coloring with the -ScalePal parameter. Here we use another palette in order to get a wider colour range:

opalscolorize -inf points.odm -attribute EchoWidth -outf points_EchoWidth_4_5_5.las -scalepal 4,5.5 -palfile colorbrewer_RdYlGnPal.xml
Fig. 3: EchoWidth with with few classes

This results in low echo width points becoming red, medium echo width points orange, and high echo width points green. A more detailed visualization can be achieved by setting a higher number of classes using the parameter -nclasses

opalscolorize -inf points.odm -attribute EchoWidth -outf points_EchoWidth_4_5_5_nclass_50.las -scalepal 4,5.5 -palfile colorbrewer_RdYlGnPal.xml -nclasses 50

Or even better, steps in the palette can be introduced manually, eg. to enhance specific intervals. For this, we first calculate a histogram of Echo Width, within the already established upper and lower limits, to avoid outliers.

opalshisto -infile points.odm -attribute EchoWidth -samplerange 4 5.5

Based on the histogram, we use the following intervals: 4,4.1,4.2,4.5,4.7,5,5.5,6 . We enter these steps into the Scalepal parameter as before

opalscolorize -inf points.odm -attribute EchoWidth -outf points_EchoWidth_4_6_lin_steps.las -scalepal 4,4.1,4.2,4.5,4.7,5,5.5,6 -palfile colorbrewer_RdYlGnPal.xml

On this .las dataset the colours of the palette correspond exactly to the specified intervals and therefore allow quantitative interpretation of echo width. Alternatively, histogram equalization can be used for generating a visually more appealing output that enhances the heterogeneity of the data

opalscolorize -inf points.odm -attribute EchoWidth -outf points_EchoWidth_4_6_eq.las -scalepal 5%,15%,25%,50%,75%,85%,95% -palfile colorbrewer_RdYlGnPal.xml
Fig 4: EchoWidth with histogram equalization

Using attribute normalized height

Normalized height is another important attribute for point-level visualization. For calculating normalized height, first we generate a very basic Digital Terrain Model (DTM) by taking the second lowest point in every 5 m cell, then smoothing with a minimum filter

opalsCell -inf points.odm -feature nmin:4 -cellsize 5 -outf points_basic_dtm_step_1.tif
opalsStatFilter -inf points_basic_dtm_step_1.tif -outf points_basic_dtm.tif -feature nmin:4 -kernelsize 3 -kernelshape circle

Then we use this grid to calculate normalized height as a new attribute, and check its typical values with opalshisto

opalsAddInfo -infile points.odm -gridfile points_basic_dtm.tif -attribute "NormalizedZ = z - r[0]"
opalsHisto -infile points.odm -attribute NormalizedZ

NormalizedZ varies between 0 and 25 m, with a some rare points having negative values. We visualize NormalizedZ with opalsColorize, choosing a palette that is intuitive for height

opalsColorize -infile points.odm -attribute NormalizedZ -outfile points_NormalizedZ_lin_0_25_invspectralpal.las -scalepal 0,25 -palfile colorBrewer_InvSpectralPal.xml

This output is colorized to intervals, with stepwise colour changes

Fig. 5: NormalizedZ with inverted standard palette

It is possible to use the same colour palette and sample range, but specified quantitative steps

opalsColorize -infile points.odm -attribute NormalizedZ -outfile points_NormalizedZ_lin_0_25_invspectralpal_1m.las -scalepal 0,25 -interval 1 -palfile colorBrewer_InvSpectralPal.xml
Fig 6: NormalizedZ using a modified palette

Using Red, Green, and Blue for different attributes

Finally, OpalsColorize allows comparison of three different attributes simultaneously, rescaling their values to the red, green and blue colour channels of the output respectively. The most popular combination of three attributes for full-waveform LIDAR is Reflectance --> Red, EchoWidth --> Green and NormalizedZ --> Blue, as this gives an intuitive output with green vegetation, red terrain and blue high points. If 3 attributes are specified in OpalsColorize, they are colored according to their order Red, Green, and Blue. Histogram equalization is essential, since the three variables have very different value ranges. Please note that no palette is specified: the colour scheme is produced by the mixing of the RGB channels. Three legend files are produced, one for each attribute

opalsColorize -inf points.odm -attribute Reflectance EchoWidth NormalizedZ -outfile points_R_refl_G_ew_B_nz_eq.las -scalepal 5%,15%,25%,50%,75%,85%,95%

The output dataset can be used for understanding the connections between the different attributes and as a basis for selecting attributes for point cloud classification.

Fig 7: Reflectance, EchoWidth, and NormalizedZ within one image

References

@ NormalizedZ
point height above DTM
@ EchoWidth
Full width at half maximum [ns].
opalsAddInfo is the executable file of Module AddInfo
Definition: ModuleExecutables.hpp:8
opalsStatFilter is the executable file of Module StatFilter
Definition: ModuleExecutables.hpp:213
opalsCell is the executable file of Module Cell
Definition: ModuleExecutables.hpp:23
opalsHisto is the executable file of Module Histo
Definition: ModuleExecutables.hpp:103
@ feature
Use a statistic feature of the boundary gap points for filling.
@ circle
circular kernel (Euclidean metrics)