Skip to content

Commit

Permalink
add histogram plotting workshop exercise
Browse files Browse the repository at this point in the history
  • Loading branch information
hobu committed May 31, 2017
1 parent fe74721 commit 40fad82
Show file tree
Hide file tree
Showing 8 changed files with 203 additions and 1 deletion.
8 changes: 8 additions & 0 deletions doc/workshop/exercises/index.rst
Expand Up @@ -45,6 +45,14 @@ Analysis
analysis/ground/ground
analysis/dtm/dtm

Python
--------------------------------------------------------------------------------

.. toctree::
:maxdepth: 3

python/histogram


Georeferencing
--------------------------------------------------------------------------------
Expand Down
1 change: 1 addition & 0 deletions doc/workshop/exercises/python/histogram-command.txt
@@ -0,0 +1 @@
pdal pipeline c:/Users/hobu/PDAL/exercises/python/histogram.json
17 changes: 17 additions & 0 deletions doc/workshop/exercises/python/histogram.json
@@ -0,0 +1,17 @@
{
"pipeline":[
{
"filename":"c:/Users/hobu/PDAL/exercies/python/athletic-fields.laz"
},
{
"type":"filters.programmable",
"function":"make_plot",
"module":"anything",
"pdalargs":"{\"filename\":\"histogram.png\"}",
"script":"c:/Users/hobu/PDAL/exercies/python/histogram.py"
},
{
"type":"writers.null"
}
]
}
80 changes: 80 additions & 0 deletions doc/workshop/exercises/python/histogram.py
@@ -0,0 +1,80 @@
# import numpy
import numpy as np

# import matplotlib stuff and make sure to use the
# AGG renderer.
import matplotlib
matplotlib.use('Agg')
import matplotlib.pyplot as plt
import matplotlib.mlab as mlab

# This only works for Python 3. Use
# StringIO for Python 2.
from io import BytesIO

# The make_plot function will do all of our work. The
# filters.programmable filter expects a function name in the
# module that has at least two arguments -- "ins" which
# are numpy arrays for each dimension, and the "outs" which
# the script can alter/set/adjust to have them updated for
# further processing.
def make_plot(ins, outs):

# figure position and row will increment
figure_position = 1
row = 1

fig = plt.figure(figure_position, figsize=(6, 8.5), dpi=300)

for key in ins:
dimension = ins[key]
ax = fig.add_subplot(len(ins.keys()), 1, row)

# histogram the current dimension with 30 bins
n, bins, patches = ax.hist( dimension, 30,
normed=0,
facecolor='grey',
alpha=0.75,
align='mid',
histtype='stepfilled',
linewidth=None)

# Set plot particulars
ax.set_ylabel(key, size=10, rotation='horizontal')
ax.get_xaxis().set_visible(False)
ax.set_yticklabels('')
ax.set_yticks((),)
ax.set_xlim(min(dimension), max(dimension))
ax.set_ylim(min(n), max(n))

# increment plot position
row = row + 1
figure_position = figure_position + 1

# We will save the PNG bytes to a BytesIO instance
# and the nwrite that to a file.
output = BytesIO()
plt.savefig(output,format="PNG")

# a module global variable, called 'pdalargs' is available
# to filters.programmable and filters.predicate modules that contains
# a dictionary of arguments that can be explicitly passed into
# the module by the user. We passed in a filename arg in our `pdal pipeline` call
if 'filename' in pdalargs:
filename = pdalargs['filename']
else:
filename = 'histogram.png'

# open up the filename and write out the
# bytes of the PNG stored in the BytesIO instance
o = open(filename, 'wb')
o.write(output.getvalue())
o.close()


# filters.programmable scripts need to
# return True to tell the filter it was successful.
return True



96 changes: 96 additions & 0 deletions doc/workshop/exercises/python/histogram.rst
@@ -0,0 +1,96 @@
.. _workshop-histogram:

Plotting a histogram
================================================================================

.. include:: ../../includes/substitutions.rst

Exercise
--------------------------------------------------------------------------------

PDAL doesn't provide every possible analysis option, but it strives to make it
convenient to link PDAL to other places with substantial functionality. One of
those is the Python/Numpy universe, which is accessed through PDAL's
:ref:`python` bindings and the :ref:`filters.programmable` and
:ref:`filters.predicate` filters. These tools allow you to manipulate point
cloud data with convenient Python tools rather than constructing substantial
C/C++ software to achieve simple tasks, compute simple statistics, or
investigate data quality issues.

This exercise uses PDAL to create a histogram plot of all of the dimensions of
a file. `matplotlib`_ is a Python package for plotting graphs and figures, and
we can use it in combination with the :ref:`python` bindings for PDAL to create
a nice histogram. These histograms can be useful diagnostics in an analysis
pipeline. We will combine a Python script to make a histogram plot with a
:ref:`pipeline_command`.


.. note::

Python allows you to enhance and build functionality that you can use
in the context of other :ref:`pipeline` operations.


PDAL Pipeline
................................................................................

We're going to create a PDAL :ref:`pipeline` to tell PDAL to run our Python
script in a :ref:`filters.programmable` stage.


.. literalinclude:: ./histogram.json
:linenos:

.. note::

This pipeline is available in your workshop materials in the
``./exercies/python/histogram.json`` file.


Python script
................................................................................

The following Python script will do the actual work of creating the histogram
plot with `matplotlib`_. Store it as ``histogram.py`` next to the
``histogram.json`` :ref:`pipeline` file above. The script is mostly regular
Python except for the ``ins`` and ``outs`` arguments to the function -- those
are special arguments that PDAL expects to be a dictionary of Numpy
dictionaries.

.. note::

This Python file is available in your workshop materials in the
``./exercies/python/histogram.py`` file.

.. literalinclude:: ./histogram.py
:linenos:
:emphasize-lines: 34-40

Run ``pdal pipeline``
................................................................................

.. literalinclude:: ./histogram-command.txt
:linenos:

Output
................................................................................

.. image:: ../../images/python-histogram-command.png
:target: ../../../_images/python-histogram-command.png

.. image:: ../../images/python-histogram.png
:target: ../../../_images/python-histogram.png

Notes
--------------------------------------------------------------------------------

.. index:: histogram, Python, matplotlib, Numpy

1. :ref:`writers.null` simply swallows the output of the pipeline. We
don't need to write any data.

2. The ``pdalargs`` JSON needs to be escaped because a valid Python
dictionary entry isn't always valid JSON.


.. _`matplotlib`: https://matplotlib.org/
Binary file added doc/workshop/images/python-histogram-command.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added doc/workshop/images/python-histogram.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 1 addition & 1 deletion doc/workshop/index.rst
Expand Up @@ -7,7 +7,7 @@ Point Cloud Processing and Analysis with PDAL
:Author: Pete Gadomski
:Author: Dr. Craig Glennie
:Contact: howard@hobu.co
:Date: 03/30/2016
:Date: 05/31/2017


.. include:: ./includes/substitutions.rst
Expand Down

0 comments on commit 40fad82

Please sign in to comment.