[Portugal] A simple QGIS python plugin tutorial

Hugo Martins hfpmartins at gmail.com
Wed Aug 11 22:05:19 EDT 2010


  Sent to you by Hugo Martins via Google Reader: A simple QGIS python
plugin tutorial via Linfiniti Geo Blog by Tim Sutton on 8/11/10
Introduction to Python Plugins and QGIS
Note: This tutorial requires QGIS 1.6 (not yet released at time of
writing) or QGIS Trunk r14052 (available from osgeo4w as a nightly
build or build it yourself on linux from source).
Generating your plugin
There is an online tool for [creating a plugin
http://pyqgis.org/builder/plugin_builder.py]. Simply fill in the blanks
and it will generate a simple plugin framework for you. We are going to
make a simple
plugin to perform a histogram stretch based on the min max values in
the current view extents. Lets start by filling in some details in the
plugin builder:

Class name (use CamelCase) : RasterScale Short descriptive title :
Raster Local Histogram Stretch Description : Scale the min max of the
raster to the min max within the view. Version number : 0.1 Minimum
required QGIS version : 1.5 Text for the menu item : Raster
Author/Company name : Linfiniti Consulting CC Email address :
tim at linfiniti.com

After clicking the ‘build it’ link you will see a screen like this:

Generation complete for RasterScale. You can download it here. What
Next? Unzip the plugin into your QGIS plugin directory and test it.
Modify it by editing the implementation file RasterScale.py Create your
own custom icon, replacing default icon.png Modify your user interface
by opening RasterScale.ui in Qt Designer (don't forget to compile it
with pyuic4 after changing it) Use the Makefile to compile your Ui and
resource files if you make changes to them (requires gmake)

Click the download link and save it to your local disk.
Testing your plugin
Use your operating system to extract the plugin to your home
directory .qgis folder:

/home/[yourname]/.qgis/python/plugins/

If this directory does not already exist, you should create it.

Now open QGIS.

Next do:

Plugins -> Manage Plugins

In the filter box enter

Stretch

Now tick the box next to the plugin to enable it then click ‘OK’. An
icon will appear in the plugin toolbar and if you click it, your plugin
will run!
Install the plugin reloader plugin in QGIS
Normally when you change a plugin you have to close and reopen QGIS to
see the result of your changes. This can become a little tedious. To
work around this, you can install the plugin reloader plugin like this:

Start QGIS Plugins -> Fetch Python Plugins Repositories tab Click
the 'Add 3rd party repositories button' and click Ok for the message
that appears. Wait a few moments while the repository list is updated.
On the Options tab, check the 'Show all plugins, even those marked
experimental' radio button In the Plugins tab, type reload into the
filter box Select the Plugin Reloader plugin from the list Click
Install, then Ok

Now we want to configure the plugin reloader to reload our raster scale
plugin so do this:

Press Shift+F5 Choose rasterscale from the plugin list Press Ok

Now whenever you press the F5 key, your plugin will be reloaded along
with any changes you might have made to it.
First tweaks to our plugin
Lets make our first little tweaks to our plugin – just to test out the
development process. Look at the name of your plugin:

click Plugins -> Raster

In the raster submenu you will see our plugin is named “Raster“. Lets
rename it to “Raster Scale“. To do this, in Eric, open the
RasterScale.py file from the list of files in your project on the left.
Now look at the initiGui method and change the line that creates the
menu action – it looks like this:

self.action =
QAction(QIcon(":/plugins/rasterscale/icon.png"), \ "Raster",
self.iface.mainWindow())

Now change it so that it looks like this:

self.action =
QAction(QIcon(":/plugins/rasterscale/icon.png"), \ "Raster Scale",
self.iface.mainWindow())

You will see above that I have simply added the characters ‘Scale’ to
the QAction’s name.

Now save that file and go back to QGIS. Hit the “F5“ key to reload your
plugin. Now once again do

click Plugins -> Raster

You should see the submenu is now named ‘Raster Scale’ instead of just
‘Raster’.

This is the general process you should follow when writing a plugin -

Edit code Save source file Reload plugin in QGIS (F5) Test

In the units that follow we will assume that you do this each time we
ask you to modify your plugin sources.
First steps into the QGIS api
Writing python plugins in QGIS requires knowledge of three things:

1 Python
2 PyQt – the Python language bindings for the Qt4 framework
3 The QGIS api

A really good resource for learning PyQt is the command prompt reference

Each of these three things is documented and generally searchable with
google. We will look at the documentation in a little while. but first
lets get our hands dirty and start modifying our plugin.

The first thing I would like to do is outline the functionality that we
are going to give our plugin. Here is the logic flow:


Getting and checking the active layer
As a first step, we are going to replace the run() method in
RasterScale.py with our own logic – the comments in the code explain
step for step what is going on:

# run method that performs all the real work def run(self): # get the
currently active layer (if any) layer =
self.iface.mapCanvas().currentLayer() # test if a valid layer was
returned if layer: # test if the layer is a raster from a local file
(not a wms) if layer.type() == layer.RasterLayer and ( not
layer.usesProvider() ): # Test if the raster is single band greyscale
if layer.rasterType()==QgsRasterLayer.GrayOrUndefined: #Everything
looks fine so show a little message and exit
QMessageBox.information(None,"Raster Scale","Layer is ok") return # One
of our tests above failed - show and error message and exit
QMessageBox.information(None,"Raster Scale", \ "A single band greyscale
raster layer must be selected") return

Now press F5 in QGIS to reload the plugin (since we have changed its
code) and then click the plugin icon with no layers loaded in your
project. You should get a message telling you that there needs to be a
layer available.

Next add a greyscale raster (e.g. a dem) and then click on the plugin
icon again. This time you should get a ‘Layer is Ok’ message.
Computing the min max values within the view extent for the layer
Now we want to answer the question: ‘what is the minimum and maximum
value in my current view extent?’ Fortunately its pretty easy to do –
using the raster layer computeMinimumMaximumFromLastExtent method e.g.:

# compute the min and max for the current extent extentMin, extentMax =
layer.computeMinimumMaximumFromLastExtent( band )

If for example our current view extent contains pixels with values from
20 to 140, extentMin will now be assigned a value of 20 and extentMax
will now be assigned a value of 140. The idea is to then scale the
colour assigments made to each pixel to this range, such that a pixel
of value 20 will be painted black and a pixel of value 140 will be
painted white. The image below tries to explain this better:


Setting the band min and max values
Now what we need to do is tell the raster layer to consider the min max
values from the current extent to be the min max values for the whole
layer, and then to stretch the colour pallette accross the new min/max
value range for the layer.

Ok so to do this we can use the following api calls:

# For greyscale layers there is only ever one band band =
layer.bandNumber( layer.grayBandName() ) # base 1 counting in gdal # We
don't want to create a lookup table generateLookupTableFlag = False #
set the layer min value for this band layer.setMinimumValue( band,
extentMin, generateLookupTableFlag ); # set the layer max value for
this band layer.setMaximumValue( band, extentMax,
generateLookupTableFlag );

This should be self explanatory except maybe the part about a lookup
table. Lookup tables are used for creating custom colour pallettes.
Since in our case we are not interested in creating a custom colour
pallette, we can leave it out of the equation for now by setting it to
false.
Extra house keeping
Just a little extra house keeping is needed. First we have to ensure
that standard deviations are disabled as it will affect the values
given to each pixel. Next we let the raster layer know that we are
using a user defined min and max range rather than the true range of
the data in the raster. Next we clear any cached image for the raster
(used to speed up drawing in some situations). Lastly we tell the layer
to redraw itself!

# ensure that stddev is set to zero layer.setStandardDeviations(
0.0 ); # let the layer know that the min max are user defined
layer.setUserDefinedGrayMinimumMaximum( True ); # ensure any cached
render data for this layer is cleared layer.setCacheImage( None ); #
make sure the layer is redrawn layer.triggerRepaint();
Putting it all together
Lets look at our complete run method now:

# run method that performs all the real work def run(self): # get the
currently active layer (if any) layer =
self.iface.mapCanvas().currentLayer() # test if a valid layer was
returned if layer: # test if the layer is a raster from a local file
(not a wms) if layer.type() == layer.RasterLayer and ( not
layer.usesProvider() ): # Test if the raster is single band greyscale
if layer.rasterType()==QgsRasterLayer.GrayOrUndefined: #Everything
looks fine so set stretch and exit #For greyscale layers there is only
ever one band band = layer.bandNumber( layer.grayBandName() ) extentMin
= 0.0 extentMax = 0.0 generateLookupTableFlag = False # compute the min
and max for the current extent extentMin, extentMax = \
layer.computeMinimumMaximumFromLastExtent( band ) # set the layer min
value for this band layer.setMinimumValue( band, extentMin,
generateLookupTableFlag ) # set the layer max value for this band
layer.setMaximumValue( band, extentMax, generateLookupTableFlag ) #
ensure that stddev is set to zero layer.setStandardDeviations( 0.0 ); #
let the layer know that the min max are user defined
layer.setUserDefinedGrayMinimumMaximum( True ) # ensure any cached
render data for this layer is cleared layer.setCacheImage( None ) #
make sure the layer is redrawn
layer.triggerRepaint() #QMessageBox.information(None, 'Raster
Scale', \ "Min %s : Max %s" % ( extentMin , extentMax )) return # One
of our tests above failed - show and error message and exit
QMessageBox.information(None,"Raster Scale", \ "A single band raster
layer must be selected") return
Testing
Refresh the plugin using the plugin reloader F5 keyboard shortcut. Now
zoom to an area on your greyscale raster and then click the RasterScale
plugin icon. You should see something happen like that shown below:


Before scaling


After scaling
Exercise
See if you can update the plugin so that it works for RGB and palletted
images too!
Exercise Solution
/***************************************************************************
RasterScale A QGIS plugin Scale the min max of the raster to the min
max within the view. ------------------- begin : 2010-08-05 copyright :
(C) 2010 by Linfiniti Consulting CC. email :
tim at linfiniti.com  
***************************************************************************/  
/***************************************************************************  
* * *
This program is free software; you can redistribute it and/or
modify * * it under the terms of the GNU General Public License as
published by * * the Free Software Foundation; either version 2 of the
License, or * * (at your option) any later
version. * * *  
***************************************************************************/ """  
#
Import the PyQt and QGIS libraries from PyQt4.QtCore import * from
PyQt4.QtGui import * from qgis.core import * from qgis.gui import * #
Initialize Qt resources from file resources.py import resources #
Import the code for the dialog from RasterScaleDialog import
RasterScaleDialog class RasterScale: def __init__(self, iface): # Save
reference to the QGIS interface self.iface = iface def initGui(self): #
Create action that will start plugin configuration self.action =
QAction(QIcon(":/plugins/rasterscale/icon.png"), \ "Raster Scale",
self.iface.mainWindow()) # connect the action to the run method
QObject.connect(self.action, SIGNAL("triggered()"), self.run) # Add
toolbar button and menu item self.iface.addToolBarIcon(self.action)
self.iface.addPluginToMenu("&Raster", self.action) def unload(self): #
Remove the plugin menu item and icon
self.iface.removePluginMenu("&Raster",self.action)
self.iface.removeToolBarIcon(self.action) # run method that performs
all the real work def run(self): # Allowed drawing styles that can have
a local histogram stretch: allowedGreyStyles = [
QgsRasterLayer.SingleBandGray,
QgsRasterLayer.MultiBandSingleBandPseudoColor,
QgsRasterLayer.MultiBandSingleBandGray,
QgsRasterLayer.SingleBandPseudoColor ] allowedRgbStyles = [
QgsRasterLayer.MultiBandColor ] # get the currently active layer (if
any) layer = self.iface.mapCanvas().currentLayer() # test if a valid
layer was returned if layer: # test if the layer is a raster from a
local file (not a wms) if layer.type() == layer.RasterLayer and ( not
layer.usesProvider() ): # Test if the raster is single band greyscale
if layer.drawingStyle() in allowedGreyStyles: #Everything looks fine so
set stretch and exit #For greyscale layers there is only ever one band
band = layer.bandNumber( layer.grayBandName() ) # base 1 counting in
gdal extentMin = 0.0 extentMax = 0.0 generateLookupTableFlag = False #
compute the min and max for the current extent extentMin, extentMax = \
layer.computeMinimumMaximumFromLastExtent( band ) # set the layer min
value for this band layer.setMinimumValue( band, extentMin,
generateLookupTableFlag ) # set the layer max value for this band
layer.setMaximumValue( band, extentMax, generateLookupTableFlag ) #
ensure that stddev is set to zero layer.setStandardDeviations( 0.0 ) #
let the layer know that the min max are user defined
layer.setUserDefinedGrayMinimumMaximum( True ) # ensure any cached
render data for this layer is cleared layer.setCacheImage( None ) #
make sure the layer is redrawn layer.triggerRepaint() return if
layer.drawingStyle() in allowedRgbStyles: #Everything looks fine so set
stretch and exit redBand = layer.bandNumber( layer.redBandName() )
greenBand = layer.bandNumber( layer.greenBandName() ) blueBand =
layer.bandNumber( layer.blueBandName() ) extentRedMin = 0.0
extentRedMax = 0.0 extentGreenMin = 0.0 extentGreenMax = 0.0
extentBlueMin = 0.0 extentBlueMax = 0.0 generateLookupTableFlag =
False # compute the min and max for the current extent extentRedMin,
extentRedMax = layer.computeMinimumMaximumFromLastExtent( redBand )
extentGreenMin, extentGreenMax =
layer.computeMinimumMaximumFromLastExtent( greenBand ) extentBlueMin,
extentBlueMax = layer.computeMinimumMaximumFromLastExtent( blueBand ) #
set the layer min max value for the red band layer.setMinimumValue(
redBand, extentRedMin, generateLookupTableFlag ) layer.setMaximumValue(
redBand, extentRedMax, generateLookupTableFlag ) # set the layer min
max value for the red band layer.setMinimumValue( greenBand,
extentGreenMin, generateLookupTableFlag ) layer.setMaximumValue(
greenBand, extentGreenMax, generateLookupTableFlag ) # set the layer
min max value for the red band layer.setMinimumValue( blueBand,
extentBlueMin, generateLookupTableFlag ) layer.setMaximumValue(
blueBand, extentBlueMax, generateLookupTableFlag ) # ensure that stddev
is set to zero layer.setStandardDeviations( 0.0 ) # let the layer know
that the min max are user defined
layer.setUserDefinedRGBMinimumMaximum( True ) # ensure any cached
render data for this layer is cleared layer.setCacheImage( None ) #
make sure the layer is redrawn layer.triggerRepaint() return # One of
our tests above failed - show and error message and exit
QMessageBox.information(None,"Raster Scale", \ "A single band raster
layer must be selected") return
Complete Plugin
The complete plugin is available here. I will add it to a plugin
repository once QGIS 1.6 comes out.

Updated to remove all those semi-colons from the end of lines…..

Things you can do from here:
- Subscribe to Linfiniti Geo Blog using Google Reader
- Get started using Google Reader to easily keep up with all your
favorite sites
-------------- next part --------------
An HTML attachment was scrubbed...
URL: http://lists.osgeo.org/pipermail/portugal/attachments/20100812/f8c8b218/attachment-0001.html


More information about the Portugal mailing list