PyQGIS · Material de apoyo para la clase de Programación en Ambientes de SIG (PASIG)

Ejercicio 6: NDMI y NBR con imágenes Landsat 8 o 9

Esta herramienta calcula dos índices distintos en una sola ejecución: el NDMI (humedad de la vegetación) y el NBR (severidad de áreas quemadas), generando dos capas de salida en lugar de una.

NDMI = (Banda 5 - Banda 6) / (Banda 5 + Banda 6)
NBR  = (Banda 5 - Banda 7) / (Banda 5 + Banda 7)

Conceptos clave de POO y PyQGIS usados en el ejercicio

  • Una herramienta, dos resultados: a diferencia de los ejercicios 4 y 5 (que devuelven una sola capa), aquí initAlgorithm declara dos parámetros de salida ('ndmi' y 'nbr'), y processAlgorithm ejecuta la calculadora ráster dos veces, una por cada índice.
  • Tres bandas de entrada: el NDMI usa las bandas 5 y 6, y el NBR usa las bandas 5 y 7; por eso se declaran 3 parámetros de entrada ('b5', 'b6', 'b7') en lugar de 2.
  • Reutilización del mismo patrón dos veces dentro de un método: dentro de processAlgorithm, el bloque que arma los parámetros y ejecuta processing.run(...) se repite dos veces con datos distintos, siguiendo la misma idea de "plantilla repetida" que ya se vio en el ejercicio 3 (Zonas oyamel).
  • Salto de línea dentro de un docstring: el texto de shortHelpString() incluye la secuencia \n\n\n, que inserta 3 saltos de línea en blanco para separar visualmente la explicación del NDMI de la del NBR dentro del cuadro de ayuda de QGIS.

Análisis y explicación de la estructura y lógica del código

06_toolscript_ndmi_nbr.py
"""
Herramienta de procesamiento para el calculo del índice SAVI

SAVI = ((Band 5 – Band 4) / (Band 5 + Band 4 + 0.5)) * (1.5)


"""
#Se importan las bibliotecas o módulos básicos
from qgis.core import QgsProcessingAlgorithm #Clase de la que se hereda en nuestra herramienta
from qgis.core import QgsProcessingParameterRasterLayer #Para cargar capas raster al diccionario de parámetros
from qgis.core import QgsProcessingParameterRasterDestination #Para guardar una salida ráster
from qgis.core import QgsProcessingMultiStepFeedback #Para mostrar el progreso de la operación
import processing #Para usar en "processing.run()"

#Se crea la clase para la herramienta
class landsat_ndmi_nbr(QgsProcessingAlgorithm):
    """
    Métodos básicos de la herramienta:
    
    1. Nombre de la la herramienta (name).
    2. Nombre a mostrar en la GUI (displayName).
    3. Grupo de herramientas al que pertenece la herramienta actual (group)
    4. ID del grupo al que pertence la herramienta (groupId)
    5. Ayuda corta de la herramienta (shortHelpString)
    6. Cargar entradas como parámetros de algorítmo (initAlgorithm)
    7. Algoritmo de procesamiento (processAlgorithm)
    8. Creación de la instancia (createInstance)
    """
    #1. Nombre de la herramienta
    def name(self):
        return 'landsat_ndmi_nbr'
    
    #2. Nombre a mostrar en la GUI
    def displayName(self):
        return 'NDMI y NBR con imágenes Landsat 8 o 9'
    
    #3. Grupo de herramientas al que pertenece la herramienta actual
    def group(self):
        return 'Landsat'
    
    #4. ID del grupo al que pertenece la herramienta
    def groupId(self):
        return 'landsat'
        
    #5. Ayuda corta de la herramienta
    def shortHelpString(self):
        descripcion = """
            El Índice de Humedad de Diferencia Normalizada (NDMI) derivado de la reflectancia superficial de Landsat
            se genera a partir de escenas de las colecciones 1 y 2 de Landsat 4–5 Thematic Mapper (TM),
            Landsat 7 Enhanced Thematic Mapper Plus (ETM+) y Landsat 8-9* Operational Land Imager (OLI)/Thermal Infrared Sensor (TIRS),
            procesadas como productos de reflectancia superficial de Nivel 2 de Landsat. *Landsat 9 = solo Colección 2.

            El NDMI se utiliza para determinar el contenido de agua en la vegetación.
            Se calcula como una relación entre los valores de NIR y SWIR, siguiendo el método tradicional.
            
            NDMI = (Banda 5 – Banda 6) / (Banda 5 + Banda 6).
            \n\n\n
            Por otro lado, el índice NBR (Normalized Burn Ratio) derivado de la reflectancia de superficie de Landsat está disponible
            para las escenas de la Colección 1 y la Colección 2 de Landsat 4–5 Thematic Mapper (TM), Landsat 7 Enhanced
            Thematic Mapper Plus (ETM+) y Landsat 8-9* Operational Land Imager (OLI)/Thermal Infrared Sensor (TIRS) que
            han sido procesadas como productos de reflectancia de superficie de Nivel 2 (Level-2). *Landsat 9 = solo Colección 2.

            El NBR se utiliza para identificar áreas quemadas y proporcionar una medida de la severidad del incendio.
            Se calcula como una relación entre los valores NIR y SWIR, siguiendo el método tradicional.
            
            NBR = (Banda 5 – Banda 7) / (Banda 5 + Banda 7)
        """
        return descripcion
    
    #6. Cargar entradas como parámetros de algoritmo
    def initAlgorithm(self, config=None):
        #Capa de entrada con la banda 5
        self.addParameter(QgsProcessingParameterRasterLayer('b5', 'Banda 5 de Landsat 8 o 9', defaultValue=None))
        #Capa de entrada con la banda 6
        self.addParameter(QgsProcessingParameterRasterLayer('b6', 'Banda 6 de Landsat 8 o 9', defaultValue=None))
        #Capa de entrada con la banda 7
        self.addParameter(QgsProcessingParameterRasterLayer('b7', 'Banda 7 de Landsat 8 o 9', defaultValue=None))
        
        #Capa de salida con el NDMI
        self.addParameter(QgsProcessingParameterRasterDestination('ndmi', 'Capa ráster con el NDMI', createByDefault=True, defaultValue=None))
        #Capa de salida con el NBR
        self.addParameter(QgsProcessingParameterRasterDestination('nbr', 'Capa ráster con el NBR', createByDefault=True, defaultValue=None))

    #7. Algoritmo de procesamiento
    def processAlgorithm(self, parameters, context, model_feedback):
        outputs = {}
        results = {}
        feedback = QgsProcessingMultiStepFeedback(1, model_feedback)
        #Parametros del algoritmo para NDMI (calculadora ráster)
        alg_params = {'EXPRESSION':f'("{parameters["b5"]}@1" - "{parameters["b6"]}@1") / ("{parameters["b5"]}@1" + "{parameters["b6"]}@1")',
                      'LAYERS':[parameters["b5"], parameters["b6"]],
                      'CELLSIZE': 0,
                      'EXTENT':None,
                      'CRS':None,
                      'OUTPUT':parameters["ndmi"]}
        outputs['ndmi'] = processing.run('qgis:rastercalculator', alg_params, context=context, feedback=feedback)
        results['ndmi'] = outputs['ndmi']['OUTPUT']
        
        #Parametros del algoritmo para NBR (calculadora ráster)
        alg_params = {'EXPRESSION':f'("{parameters["b5"]}@1" - "{parameters["b7"]}@1") / ("{parameters["b5"]}@1" + "{parameters["b7"]}@1")',
                      'LAYERS':[parameters["b5"], parameters["b7"]],
                      'CELLSIZE': 0,
                      'EXTENT':None,
                      'CRS':None,
                      'OUTPUT':parameters["nbr"]}
        outputs['nbr'] = processing.run('qgis:rastercalculator', alg_params, context=context, feedback=feedback)
        results['nbr'] = outputs['nbr']['OUTPUT']
        
        return results
    
    #8. Creación de la instancia
    def createInstance(self):
        return self.__class__()

Sección 1 · Para qué sirve cada biblioteca importada

Se importan exactamente las mismas 5 bibliotecas que en los ejercicios 4 y 5, y cumplen el mismo propósito: QgsProcessingAlgorithm (clase base), QgsProcessingParameterRasterLayer (capas de entrada), QgsProcessingParameterRasterDestination (capas de salida), QgsProcessingMultiStepFeedback (progreso) y processing (para ejecutar la calculadora ráster). La diferencia de este ejercicio no está en las importaciones, sino en que se declaran 2 salidas en vez de 1.

Sección 2 · Para qué sirve cada método de la clase

  • name() / displayName(): identifican la herramienta como 'landsat_ndmi_nbr' / 'NDMI y NBR con imágenes Landsat 8 o 9'.
  • group() / groupId(): la ubican en el grupo 'Landsat', igual que el ejercicio 5.
  • shortHelpString(): en este caso devuelve un texto largo que explica, por separado, qué es el NDMI y qué es el NBR, sus fórmulas y para qué sirve cada uno.
  • initAlgorithm(): declara 3 capas de entrada (bandas 5, 6 y 7) y 2 capas de salida (NDMI y NBR).
  • processAlgorithm(): calcula ambos índices, uno tras otro, usando dos veces la calculadora ráster.
  • createInstance(): permite a QGIS crear nuevas copias de la herramienta.

Sección 3 · Declaración de parámetros: 3 entradas y 2 salidas

    def initAlgorithm(self, config=None):
        #Capa de entrada con la banda 5
        self.addParameter(QgsProcessingParameterRasterLayer('b5', 'Banda 5 de Landsat 8 o 9', defaultValue=None))
        #Capa de entrada con la banda 6
        self.addParameter(QgsProcessingParameterRasterLayer('b6', 'Banda 6 de Landsat 8 o 9', defaultValue=None))
        #Capa de entrada con la banda 7
        self.addParameter(QgsProcessingParameterRasterLayer('b7', 'Banda 7 de Landsat 8 o 9', defaultValue=None))
        
        #Capa de salida con el NDMI
        self.addParameter(QgsProcessingParameterRasterDestination('ndmi', 'Capa ráster con el NDMI', createByDefault=True, defaultValue=None))
        #Capa de salida con el NBR
        self.addParameter(QgsProcessingParameterRasterDestination('nbr', 'Capa ráster con el NBR', createByDefault=True, defaultValue=None))
  • 'b5', 'b6', 'b7': las 3 bandas necesarias, ya que el NDMI usa 5 y 6, mientras que el NBR usa 5 y 7 (la banda 5 se comparte entre ambos índices).
  • 'ndmi' y 'nbr': dos parámetros de salida independientes; el usuario deberá indicar (o dejar en automático) dónde guardar cada capa resultante.

Sección 4 · Cálculo del NDMI y del NBR (processAlgorithm)

    def processAlgorithm(self, parameters, context, model_feedback):
        outputs = {}
        results = {}
        feedback = QgsProcessingMultiStepFeedback(1, model_feedback)
        #Parametros del algoritmo para NDMI (calculadora ráster)
        alg_params = {'EXPRESSION':f'("{parameters["b5"]}@1" - "{parameters["b6"]}@1") / ("{parameters["b5"]}@1" + "{parameters["b6"]}@1")',
                      'LAYERS':[parameters["b5"], parameters["b6"]],
                      'CELLSIZE': 0,
                      'EXTENT':None,
                      'CRS':None,
                      'OUTPUT':parameters["ndmi"]}
        outputs['ndmi'] = processing.run('qgis:rastercalculator', alg_params, context=context, feedback=feedback)
        results['ndmi'] = outputs['ndmi']['OUTPUT']
        
        #Parametros del algoritmo para NBR (calculadora ráster)
        alg_params = {'EXPRESSION':f'("{parameters["b5"]}@1" - "{parameters["b7"]}@1") / ("{parameters["b5"]}@1" + "{parameters["b7"]}@1")',
                      'LAYERS':[parameters["b5"], parameters["b7"]],
                      'CELLSIZE': 0,
                      'EXTENT':None,
                      'CRS':None,
                      'OUTPUT':parameters["nbr"]}
        outputs['nbr'] = processing.run('qgis:rastercalculator', alg_params, context=context, feedback=feedback)
        results['nbr'] = outputs['nbr']['OUTPUT']
        
        return results
  • alg_params = {...} (primer bloque): arma la expresión del NDMI usando las bandas 5 y 6, y guarda el resultado directamente en parameters["ndmi"].
  • outputs['ndmi'] = processing.run(...) / results['ndmi'] = outputs['ndmi']['OUTPUT']: ejecuta la calculadora ráster para el NDMI y guarda su resultado en el diccionario de resultados finales.
  • alg_params = {...} (segundo bloque): reutiliza la misma variable alg_params, pero ahora con la expresión del NBR (bandas 5 y 7). Como el primer cálculo ya se ejecutó y se guardó en outputs['ndmi'], no hay problema en sobrescribir alg_params para el segundo cálculo.
  • outputs['nbr'] = processing.run(...) / results['nbr'] = outputs['nbr']['OUTPUT']: ejecuta la calculadora ráster para el NBR y agrega su resultado a results, junto al del NDMI.
  • return results: al final, results contiene ambas capas de salida ('ndmi' y 'nbr'), y QGIS las entrega juntas al terminar la ejecución.

Nota cómo, a diferencia de los ejercicios 1 a 3 (que usaban feedback.setCurrentStep(n) entre cada sub-proceso), aquí no se actualiza el paso actual entre el cálculo del NDMI y el del NBR; ambos se ejecutan "de corrido" usando el mismo objeto feedback de un solo paso.

Pon a prueba lo analizado
Volver a PASIG II