Historical Map  1.0
Plugin for automatic extraction of old forest from historical map
 All Classes Namespaces Files Functions Variables
historical_map.py
Go to the documentation of this file.
1 """!@brief Interface between qgisForm and function_historical_map.py
2 ./***************************************************************************
3  HistoricalMap
4  A QGIS plugin
5  Mapping old landcover (specially forest) from historical maps
6  -------------------
7  begin : 2016-01-26
8  git sha : $Format:%H$
9  copyright : (C) 2016 by Karasiak & Lomellini
10  email : karasiak.nicolas@gmail.com
11  ***************************************************************************/
12 
13 /***************************************************************************
14  * *
15  * This program is free software; you can redistribute it and/or modify *
16  * it under the terms of the GNU General Public License as published by *
17  * the Free Software Foundation; either version 2 of the License, or *
18  * (at your option) any later version. *
19  * *
20  ***************************************************************************/
21 """
22 # -*- coding: utf-8 -*-
23 from PyQt4 import QtGui
24 from PyQt4.QtGui import QAction, QIcon, QFileDialog, QDialog
25 from PyQt4.QtCore import QSettings, QTranslator, qVersion, QCoreApplication
26 import os.path
27 import function_historical_map as fhm
28 # Initialize Qt resources from file resources.py
29 #import resources
30 # Import the code for the dialog
31 from historical_map_dialog import HistoricalMapDialog
32 
33 
34 from qgis.core import QgsMessageLog
35 
36 
37 class HistoricalMap( QDialog ):
38  """!@brief QGIS Plugin Implementation."""
39 
40  def __init__(self, iface):
41  """!@brief Constructor.
42 
43  param iface: An interface instance that will be passed to this class
44  which provides the hook by which you can manipulate the QGIS
45  application at run time.
46  type iface: QgsInterface
47 
48  declare all fields to fill, such as output raster, columns to find from a shp...
49  """
50  QDialog.__init__(self)
51  sender = self.sender()
52  """
53  """# Save reference to the QGIS interface
54  self.iface = iface
55  legendInterface = self.iface.legendInterface()
56 
57  # initialize plugin directory
58  self.plugin_dir = os.path.dirname(__file__)
59  # initialize locale
60  locale = QSettings().value('locale/userLocale')[0:2]
61  locale_path = os.path.join(
62  self.plugin_dir,
63  'i18n',
64  'HistoricalMap_{}.qm'.format(locale))
65 
66  if os.path.exists(locale_path):
67  self.translator = QTranslator()
68  self.translator.load(locale_path)
69 
70  if qVersion() > '4.3.3':
71  QCoreApplication.installTranslator(self.translator)
72 
73  # Create the dialog (after translation) and keep reference
74  self.dlg = HistoricalMapDialog()
75  # Declare instance attributes
76  self.actions = []
77  self.menu = self.tr(u'&Historical Map')
78  # TODO: We are going to let the user set this up in a future iteration
79  self.toolbar = self.iface.addToolBar(u'HistoricalMap')
80  self.toolbar.setObjectName(u'HistoricalMap')
81 
82 
83  ## Init to choose file (to load or to save)
84  self.dlg.outRaster.clear()
85  self.dlg.selectRaster.clicked.connect(self.select_output_file)
86  self.dlg.outModel.clear()
87  self.dlg.selectModel.clicked.connect(self.select_output_file)
88  self.dlg.outMatrix.clear()
89  self.dlg.selectMatrix.clicked.connect(self.select_output_file)
90 
91  self.dlg.btnFilter.clicked.connect(self.runFilter)
92  self.dlg.btnTrain.clicked.connect(self.runTrain)
93  self.dlg.btnClassify.clicked.connect(self.runClassify)
94  self.dlg.inModel.clear()
95  self.dlg.selectModelStep3.clicked.connect(self.select_load_file)
96  self.dlg.outShp.clear()
97  self.dlg.selectOutShp.clicked.connect(self.select_output_file)
98 
99 
100  ## init fields
101 
102  self.dlg.inTraining.currentIndexChanged[int].connect(self.onChangedLayer)
103 
104  ## By default field list is empty, so we fill with current layer
105  ## if no currentLayer, no filling, or it will crash Qgis
106  self.dlg.inField.clear()
107  if self.dlg.inField.currentText() == '' and self.dlg.inTraining.currentLayer() and self.dlg.inTraining.currentLayer()!='NoneType':
108  activeLayer = self.dlg.inTraining.currentLayer()
109  provider = activeLayer.dataProvider()
110  fields = provider.fields()
111  listFieldNames = [field.name() for field in fields]
112  self.dlg.inField.addItems(listFieldNames)
113 
114 
115  def onChangedLayer(self,index):
116  """!@brief If active layer is changed, change column combobox"""
117  # We clear combobox
118  self.dlg.inField.clear()
119  # Then we fill it with new selected Layer
120  if self.dlg.inField.currentText() == '' and self.dlg.inTraining.currentLayer() and self.dlg.inTraining.currentLayer()!='NoneType':
121  activeLayer = self.dlg.inTraining.currentLayer()
122  provider = activeLayer.dataProvider()
123  fields = provider.fields()
124  listFieldNames = [field.name() for field in fields]
125  self.dlg.inField.addItems(listFieldNames)
126 
127 
128 
129  # noinspection PyMethodMayBeStatic
130  def tr(self, message):
131  """!@brief Get the translation for a string using Qt translation API.
132 
133  We implement this ourselves since we do not inherit QObject.
134 
135  :param message: String for translation.
136  :type message: str, QString
137 
138  :returns: Translated version of message.
139  :rtype: QString
140  """
141  # noinspection PyTypeChecker,PyArgumentList,PyCallByClass
142  return QCoreApplication.translate('HistoricalMap', message)
143 
144 
145  def add_action(
146  self,
147  icon_path,
148  text,
149  callback,
150  enabled_flag=True,
151  add_to_menu=True,
152  add_to_toolbar=True,
153  status_tip=None,
154  whats_this=None,
155  parent=None):
156  """Add a toolbar icon to the toolbar.
157 
158  :param icon_path: Path to the icon for this action. Can be a resource
159  path (e.g. ':/plugins/foo/bar.png') or a normal file system path.
160  :type icon_path: str
161 
162  :param text: Text that should be shown in menu items for this action.
163  :type text: str
164 
165  :param callback: Function to be called when the action is triggered.
166  :type callback: function
167 
168  :param enabled_flag: A flag indicating if the action should be enabled
169  by default. Defaults to True.
170  :type enabled_flag: bool
171 
172  :param add_to_menu: Flag indicating whether the action should also
173  be added to the menu. Defaults to True.
174  :type add_to_menu: bool
175 
176  :param add_to_toolbar: Flag indicating whether the action should also
177  be added to the toolbar. Defaults to True.
178  :type add_to_toolbar: bool
179 
180  :param status_tip: Optional text to show in a popup when mouse pointer
181  hovers over the action.
182  :type status_tip: str
183 
184  :param parent: Parent widget for the new action. Defaults None.
185  :type parent: QWidget
186 
187  :param whats_this: Optional text to show in the status bar when the
188  mouse pointer hovers over the action.
189 
190  :returns: The action that was created. Note that the action is also
191  added to self.actions list.
192  :rtype: QAction
193  """
194 
195  icon = QIcon(icon_path)
196  action = QAction(icon, text, parent)
197  action.triggered.connect(callback)
198  action.setEnabled(enabled_flag)
199 
200  if status_tip is not None:
201  afilenamection.setStatusTip(status_tip)
202 
203  if whats_this is not None:
204  action.setWhatsThis(whats_this)
205 
206  if add_to_toolbar:
207  self.toolbar.addAction(action)
208 
209  if add_to_menu:
210  self.iface.addPluginToRasterMenu(
211  self.menu,
212  action)
213 
214  self.actions.append(action)
215 
216  return action
217 
218  def initGui(self):
219  """!@brief Create the menu entries and toolbar icons inside the QGIS GUI."""
220 
221  icon_path = ':/plugins/HistoricalMap/icon.png'
222  self.add_action(
223  icon_path,
224  text=self.tr(u'Select historical map'),
225  callback=self.showDlg,
226  parent=self.iface.mainWindow())
227 
228 
229  def unload(self):
230  """!@brief Removes the plugin menu item and icon from QGIS GUI."""
231  for action in self.actions:
232  self.iface.removePluginRasterMenu(
233  self.tr(u'&Historical Map'),
234  action)
235  self.iface.removeToolBarIcon(action)
236  # remove the toolbar
237  del self.toolbar
238 
240  """!@brief Select file to save, and gives the right extension if the user don't put it"""
241  sender = self.sender()
242 
243  fileName = QFileDialog.getSaveFileName(self.dlg, "Select output file")
244 
245  if not fileName:
246  return
247 
248  # If user give right file extension, we don't add it
249 
250  fileName,fileExtension=os.path.splitext(fileName)
251  if sender == self.dlg.selectRaster:
252  if fileExtension!='.tif':
253  self.dlg.outRaster.setText(fileName+'.tif')
254  else:
255  self.dlg.outRaster.setText(fileName+fileExtension)
256  elif sender == self.dlg.selectModel:
257  self.dlg.outModel.setText(fileName+fileExtension)
258  elif sender == self.dlg.selectMatrix:
259  if fileExtension!='.csv':
260  self.dlg.outMatrix.setText(fileName+'.csv')
261  else:
262  self.dlg.outMatrix.setText(fileName+fileExtension)
263  elif sender == self.dlg.selectOutShp:
264  if fileExtension!='.shp':
265  self.dlg.outShp.setText(fileName+'.shp')
266  else:
267  self.dlg.outShp.setText(fileName+fileExtension)
268  elif sender == self.dlg.selectModelStep3:
269  self.dlg.inModel.setText(fileName)
270 
271  def select_load_file(self):
272  """!@brief Select file to load in the field"""
273  sender=self.sender()
274  fileName = QFileDialog.getOpenFileName(self.dlg, "Select your file","")
275  if not fileName:
276  return
277  if sender == self.dlg.selectModelStep3:
278  self.dlg.inModel.setText(fileName)
279  def showDlg(self):
280  self.dlg.show()
281 
282  def runFilter(self):
283  """!@brief Performs the filtering of the map by calling function_historical_map.py
284 
285  First step is validating the form, then if all is ok, proceed to the filtering.
286  """
287  message=''
288  try:
289  inRaster=self.dlg.inRaster.currentLayer()
290  inRaster=inRaster.dataProvider().dataSourceUri()
291  rasterName,rasterExt=os.path.splitext(inRaster)
292  if not rasterExt == '.tif' or rasterExt == '.tiff':
293  message = "You have to specify a tif in image to filter. You tried to had a "+rasterExt
294 
295  except:
296  message="Impossible to load raster"
297  if self.dlg.outRaster.text()=='':
298  message = "Sorry, you have to specify as output raster"
299 
300  if message!='':
301  QtGui.QMessageBox.warning(self, 'Information missing or invalid', message, QtGui.QMessageBox.Ok)
302 
303  else:
304  """
305  PROCESS IF ALL OK
306  """
307  # Get args
308  # inRaster=self.dlg.inRaster.currentLayer()
309  # inRaster=inRaster.dataProvider().dataSourceUri()
310  inShapeGrey=self.dlg.inShapeGrey.value()
311  inShapeMedian=self.dlg.inShapeMedian.value()
312  outRaster=self.dlg.outRaster.text()
313  iterMedian=self.dlg.inShapeMedianIter.value()
314 
315  # Do the job
316 
317  fhm.historicalFilter(inRaster,outRaster,inShapeGrey,inShapeMedian,iterMedian)
318 
319  # Show what's done
320  self.iface.messageBar().pushMessage("New image", "Filter with "+str(inShapeGrey)+' closing size and '+str(inShapeMedian)+ ' median size', 3, 10)
321  self.iface.addRasterLayer(outRaster)
322 
323 
324  def runTrain(self):
325  """!@brief Performs the training by calling function_historical_map.py
326 
327  First step is validating the form, then if all is ok, proceed to the training.
328  Tell the user who don't have sklearn they can't use classifier except GMM.
329 
330  Input :
331  Fields from the form
332  Output :
333  Open a popup to show where the matrix or the model is saved
334  """
335  # Validation
336  message=''
337  if self.dlg.outModel.text()=='':
338  message = "Sorry, you have to specify as model name"
339  if self.dlg.outMatrix.text()=='':
340  message = "Sorry, you have to specify as matrix name"
341  if not self.dlg.inClassifier.currentText()=='GMM':
342  try:
343  import sklearn
344  except:
345  message = "It seems you don't have Scitkit-Learn on your computer. You can only use GMM classifier. Please consult the documentation for more information"
346 
347  if message != '':
348  QtGui.QMessageBox.warning(self, 'Information missing or invalid', message, QtGui.QMessageBox.Ok)
349 
350  else:
351 
352  # Getting variables from UI
353  inFiltered=self.dlg.inFiltered.currentLayer()
354  inFiltered=inFiltered.dataProvider().dataSourceUri()
355  inTraining=self.dlg.inTraining.currentLayer()
356 
357  # Remove layerid=0 from SHP Path
358  inTraining=inTraining.dataProvider().dataSourceUri().split('|')[0]
359 
360 
361  inClassifier=self.dlg.inClassifier.currentText()
362  outModel=self.dlg.outModel.text()
363  outMatrix=self.dlg.outMatrix.text()
364  #> Optional inField
365  inField=self.dlg.inField.currentText()
366  inSeed=self.dlg.inSeed.value()
367  inSeed=int(inSeed)
368  inSplit=self.dlg.inSplit.value()
369 
370  # add model to step 3
371  self.dlg.inModel.setText(outModel)
372  # Do the job
373  fhm.learnModel(inFiltered,inTraining,inField,inSplit,inSeed,outModel,outMatrix,inClassifier)
374 
375  # show where it is saved
376  if self.dlg.outMatrix.text()!='':
377  QtGui.QMessageBox.information(self, "Information", "Training is done!<br>Confusion matrix saved at "+str(outMatrix)+".")
378  else:
379  QtGui.QMessageBox.information(self, "Information", "Model is done!<br>Model saved at "+str(outModel)+", and matrix at"+str(outMatrix)+".")
380 
381 
382  def runClassify(self):
383  """!@brief Performs the classification by calling function_historical_map.py
384  Method that performs the classification
385 
386  First step is validating the form, then if all is ok, proceed to the classification.
387  """
388  message=''
389  if self.dlg.inModel.text()=='':
390  message = "Sorry, you have to specify a model"
391  if self.dlg.outShp.text()=='':
392  message = "Sorry, you have to specify a vector field to save the results"
393  if not os.path.splitext(self.dlg.outShp.text())[1]=='.shp':
394  message = "Sorry, you have to specify a *.shp type in output"
395  if message != '':
396  QtGui.QMessageBox.warning(self, 'Information missing or invalid', message, QtGui.QMessageBox.Ok)
397 
398  else:
399 
400  # Get filtered image
401  inFilteredStep3=self.dlg.inFilteredStep3.currentLayer()
402  inFilteredStep3=str(inFilteredStep3.dataProvider().dataSourceUri())
403 
404  # Get model done at Step 2
405  inModel=str(self.dlg.inModel.text())
406 
407  # Get min size for polygons
408  # Multipied by 10 000 to have figure in hectare
409  # Input of 0,6 (0,6 hectare) will be converted to 6000 m2
410  inMinSize=int(self.dlg.inMinSize.value()*10000)
411 
412  outShp=str(self.dlg.outShp.text())
413  inClassForest=int(self.dlg.inClassForest.value())
414 
415  # do the job
416  try:
417  classify=fhm.classifyImage()
418  classifyProgress=fhm.progressBar('Classifying image...',3) # Add progressBar
419 
420  # Predicting image
421  try:
422  temp=classify.initPredict(inFilteredStep3,inModel)
423  except:
424  QgsMessageLog.logMessage("Problem while predicting image")
425 
426  classifyProgress.addStep()
427 
428  # Rastering and filtering image
429  try:
430  temp=classify.rasterMod(temp,int(inClassForest))
431  except:
432  QgsMessageLog.logMessage("Problem while rastering filtering")
433 
434  classifyProgress.addStep()
435 
436  # Vectorizing and filtering image
437  try:
438  temp=classify.vectorMod(temp,inMinSize,outShp)
439  except:
440  QgsMessageLog.logMessage("Problem while vectorizing filtering")
441 
442  classifyProgress.addStep()
443 
444  # Add layer
445  layerName=os.path.basename(os.path.splitext(temp)[0])
446  self.iface.addVectorLayer(temp,layerName,'ogr')
447  self.iface.messageBar().pushMessage("New vector : ",outShp, 3, duration=10)
448  classifyProgress.reset()
449 
450  except:
451  QgsMessageLog.logMessage("Problem while classifying "+inFilteredStep3+" with model "+inModel)
452  QtGui.QMessageBox.warning(self, 'Problem while classifying', 'Something went wrong, please show log. If your system is Windows, we\'re working on it', QtGui.QMessageBox.Ok)
453  classifyProgress.reset()
454 
455 
456 
457 
def onChangedLayer
If active layer is changed, change column combobox.
def runClassify
Performs the classification by calling function_historical_map.py Method that performs the classifica...
def runTrain
Performs the training by calling function_historical_map.py.
def unload
Removes the plugin menu item and icon from QGIS GUI.
def initGui
Create the menu entries and toolbar icons inside the QGIS GUI.
def select_load_file
Select file to load in the field.
def tr
Get the translation for a string using Qt translation API.
def runFilter
Performs the filtering of the map by calling function_historical_map.py.
def select_output_file
Select file to save, and gives the right extension if the user don't put it.