diff --git a/doc/workshop/exercises/index.rst b/doc/workshop/exercises/index.rst index 1ec253f403..281c7c54f7 100644 --- a/doc/workshop/exercises/index.rst +++ b/doc/workshop/exercises/index.rst @@ -45,6 +45,14 @@ Analysis analysis/ground/ground analysis/dtm/dtm +Python +-------------------------------------------------------------------------------- + +.. toctree:: + :maxdepth: 3 + + python/histogram + Georeferencing -------------------------------------------------------------------------------- diff --git a/doc/workshop/exercises/python/histogram-command.txt b/doc/workshop/exercises/python/histogram-command.txt new file mode 100644 index 0000000000..a994a3cb90 --- /dev/null +++ b/doc/workshop/exercises/python/histogram-command.txt @@ -0,0 +1 @@ +pdal pipeline c:/Users/hobu/PDAL/exercises/python/histogram.json diff --git a/doc/workshop/exercises/python/histogram.json b/doc/workshop/exercises/python/histogram.json new file mode 100644 index 0000000000..46bad86071 --- /dev/null +++ b/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" + } + ] +} diff --git a/doc/workshop/exercises/python/histogram.py b/doc/workshop/exercises/python/histogram.py new file mode 100644 index 0000000000..62301032d9 --- /dev/null +++ b/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 + + + diff --git a/doc/workshop/exercises/python/histogram.rst b/doc/workshop/exercises/python/histogram.rst new file mode 100644 index 0000000000..0aacb13f81 --- /dev/null +++ b/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/ diff --git a/doc/workshop/images/python-histogram-command.png b/doc/workshop/images/python-histogram-command.png new file mode 100644 index 0000000000..57bdd9744b Binary files /dev/null and b/doc/workshop/images/python-histogram-command.png differ diff --git a/doc/workshop/images/python-histogram.png b/doc/workshop/images/python-histogram.png new file mode 100644 index 0000000000..b5be1ba97b Binary files /dev/null and b/doc/workshop/images/python-histogram.png differ diff --git a/doc/workshop/index.rst b/doc/workshop/index.rst index 3a0556b329..0ca66f5fce 100644 --- a/doc/workshop/index.rst +++ b/doc/workshop/index.rst @@ -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