<div style="margin: 0px 2px; padding-top: 1px; background-color: #c3d9ff; font-size: 1px !important; line-height: 0px !important;"> </div>
<div style="margin: 0px 1px; padding-top: 1px; background-color: #c3d9ff; font-size: 1px !important; line-height: 0px !important;"> </div>
<div style="padding: 4px; background-color: #c3d9ff;"><h3 style="margin:0px 3px;font-family:sans-serif">Sent to you by Hugo Martins via Google Reader:</h3></div>
<div style="margin: 0px 1px; padding-top: 1px; background-color: #c3d9ff; font-size: 1px !important; line-height: 0px !important;"> </div>
<div style="margin: 0px 2px; padding-top: 1px; background-color: #c3d9ff; font-size: 1px !important; line-height: 0px !important;"> </div>
<div style="font-family:sans-serif;overflow:auto;width:100%;margin: 0px 10px"><h2 style="margin: 0.25em 0 0 0"><div class=""><a href="http://linfiniti.com/2010/08/a-simple-qgis-python-tutorial/">A simple QGIS python plugin tutorial</a></div></h2>
<div style="margin-bottom: 0.5em">via <a href="http://linfiniti.com" class="f">Linfiniti Geo Blog</a> by Tim Sutton on 8/11/10</div><br style="display:none">
<h2>Introduction to Python Plugins and QGIS</h2>
<p><strong>Note: </strong>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).</p>
<h3>Generating your plugin</h3>
<p>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<br>
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:</p>
<pre>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@linfiniti.com</pre>
<p>After clicking the ‘build it’ link you will see a screen like this:</p>
<pre>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)</pre>
<p>Click the download link and save it to your local disk.</p>
<h3>Testing your plugin</h3>
<p>Use your operating system to extract the plugin to your home directory .qgis folder:</p>
<pre>/home/[yourname]/.qgis/python/plugins/</pre>
<p>If this directory does not already exist, you should create it.</p>
<p>Now open QGIS.</p>
<p>Next do:</p>
<pre>Plugins -> Manage Plugins</pre>
<p>In the filter box enter</p>
<pre>Stretch</pre>
<p>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!</p>
<h3>Install the plugin reloader plugin in QGIS</h3>
<p>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:</p>
<pre>
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
</pre>
<p>Now we want to configure the plugin reloader to reload our raster scale plugin so do this:</p>
<pre>
Press Shift+F5
Choose rasterscale from the plugin list
Press Ok
</pre>
<p>Now whenever you press the F5 key, your plugin will be reloaded along with any changes you might have made to it.</p>
<h3>First tweaks to our plugin</h3>
<p>Lets make our first little tweaks to our plugin – just to test out the development process. Look at the name of your plugin:</p>
<pre>
click Plugins -> Raster
</pre>
<p>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:</p>
<pre>
self.action = QAction(QIcon(":/plugins/rasterscale/icon.png"), \
"Raster", self.iface.mainWindow())
</pre>
<p>Now change it so that it looks like this:</p>
<pre>
self.action = QAction(QIcon(":/plugins/rasterscale/icon.png"), \
"Raster Scale", self.iface.mainWindow())
</pre>
<p>You will see above that I have simply added the characters ‘Scale’ to the QAction’s name.</p>
<p>Now save that file and go back to QGIS. Hit the “F5“ key to reload your plugin. Now once again do</p>
<pre>
click Plugins -> Raster
</pre>
<p>You should see the submenu is now named ‘Raster Scale’ instead of just ‘Raster’.</p>
<p>This is the general process you should follow when writing a plugin -</p>
<pre>
Edit code
Save source file
Reload plugin in QGIS (F5)
Test
</pre>
<p>In the units that follow we will assume that you do this each time we ask you to modify your plugin sources.</p>
<h3>First steps into the QGIS api</h3>
<p>Writing python plugins in QGIS requires knowledge of three things:</p>
<p>1 Python<br>
2 <a href="http://www.riverbankcomputing.co.uk/static/Docs/PyQt4/html/classes.html">PyQt</a> – the Python language bindings for the Qt4 framework<br>
3 The <a href="http://doc.qgis.org/head/">QGIS api</a></p>
<p>A really good resource for learning PyQt is the <a href="http://www.commandprompt.com/community/pyqt/">command prompt reference</a></p>
<p>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.</p>
<p>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:</p>
<p><a href="http://www.flickr.com/photos/linfiniti/4882425660"><img src="http://farm5.static.flickr.com/4136/4882425660_de036a3d7f_z.jpg"></a></p>
<h3>Getting and checking the active layer</h3>
<p>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:</p>
<pre>
# 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
</pre>
<p>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.</p>
<p>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.</p>
<h3>Computing the min max values within the view extent for the layer</h3>
<p>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.:</p>
<pre>
# compute the min and max for the current extent
extentMin, extentMax = layer.computeMinimumMaximumFromLastExtent( band )
</pre>
<p>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:</p>
<p><a href="http://www.flickr.com/photos/linfiniti/4882415924/"><img src="http://farm5.static.flickr.com/4101/4882415924_67bfba72f0.jpg"></a></p>
<h3>Setting the band min and max values</h3>
<p>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.</p>
<p>Ok so to do this we can use the following api calls:</p>
<pre>
# 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 );
</pre>
<p>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.</p>
<h3>Extra house keeping</h3>
<p>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!</p>
<pre>
# 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();
</pre>
<h3>Putting it all together</h3>
<p>Lets look at our complete run method now:</p>
<pre>
# 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
</pre>
<h3>Testing</h3>
<p>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:</p>
<p><a href="http://www.flickr.com/photos/linfiniti/4882415928/"><img src="http://farm5.static.flickr.com/4081/4882415928_a3259ab196_z.jpg"></a><br>
Before scaling</p>
<p><a href="http://www.flickr.com/photos/linfiniti/4882415930"><img src="http://farm5.static.flickr.com/4114/4882415930_33430aabc9_z.jpg"></a><br>
After scaling</p>
<h3>Exercise</h3>
<p>See if you can update the plugin so that it works for RGB and palletted images too!</p>
<h3> Exercise Solution </h3>
<pre>
/***************************************************************************
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@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
</pre>
<h3>Complete Plugin</h3>
<p>The complete plugin is available <a href="http://linfiniti.com/downloads/rasterscale.zip">here</a>. I will add it to a plugin repository once QGIS 1.6 comes out.</p>
<p><b>Updated to remove all those semi-colons from the end of lines…..</b></p>
<img src="http://linfiniti.com/wp-content/plugins/pixelstats/trackingpixel.php?post_id=510&ts=1281556881" alt="pixelstats trackingpixel"></div>
<br>
<div style="margin: 0px 2px; padding-top: 1px; background-color: #c3d9ff; font-size: 1px !important; line-height: 0px !important;"> </div>
<div style="margin: 0px 1px; padding-top: 1px; background-color: #c3d9ff; font-size: 1px !important; line-height: 0px !important;"> </div>
<div style="padding: 4px; background-color: #c3d9ff;"><h3 style="margin:0px 3px;font-family:sans-serif">Things you can do from here:</h3>
<ul style="font-family:sans-serif"><li><a href="http://www.google.com/reader/view/feed%2Fhttp%3A%2F%2Flinfiniti.com%2Ffeed%2F?source=email">Subscribe to Linfiniti Geo Blog</a> using <b>Google Reader</b></li>
<li><a href="http://www.google.com/reader/?source=email">Get started using Google Reader</a> to easily keep up with <b>all your favorite sites</b></li></ul></div>
<div style="margin: 0px 1px; padding-top: 1px; background-color: #c3d9ff; font-size: 1px !important; line-height: 0px !important;"> </div>
<div style="margin: 0px 2px; padding-top: 1px; background-color: #c3d9ff; font-size: 1px !important; line-height: 0px !important;"> </div>