pythonqgispyqgis

Applying style to raster layer with pyqgis - style panel and toc issues


I'm trying to apply a custom colour ramp to a single band QgsRasterLayer using PyQGIS. The colour ramp comes from matplotlib. For the map-canvas this works fine. However, in the TOC the values reach from 0 to 255 (rather than the actual min/max values), though the colour ramp seems correct (for the tiny range). In the style panel it shows the last colour ramp I used (even after restarting QGIS). Is it possible to update these correctly?

In the following example I apply viridis.

screenshot of TOC and style panel in QGIS after code execution

Example Code:

from matplotlib import colormaps
import numpy as np

lyr = iface.activeLayer()
if isinstance(lyr, QgsRasterLayer):
    if lyr.bandCount() == 1:
        # define color map by name
        map = colormaps['viridis']

        # get min/max values
        stats = lyr.dataProvider().bandStatistics(1, QgsRasterBandStats.All)
        val_min = stats.minimumValue
        val_max = stats.maximumValue

        # limit number of color steps to 20
        if map.N > 20:
            colors = map(np.linspace(1, map.N, 20, dtype=np.int16))
        else:
            colors = map(range(1,map.N))
        colorsN = len(colors)

        # raster values for the color steps
        valList = np.linspace(val_min, val_max, colorsN)

        # making a colorRamp-list with
        colorsList = []
        for idx, col in enumerate(colors):
            qcol = QColor(int(col[0]*255), int(col[1]*255), int(col[2]*255))
            val = valList[idx]
            #print(val, qcol)
            colorsList.append( QgsColorRampShader.ColorRampItem(val, qcol) )

        # apply code (cf. https://docs.qgis.org/3.34/en/docs/pyqgis_developer_cookbook/raster.html#single-band-rasters)
        fcn = QgsColorRampShader()
        fcn.setColorRampType(QgsColorRampShader.Interpolated)
        fcn.setColorRampItemList(colorsList)
        shader = QgsRasterShader()
        shader.setRasterShaderFunction(fcn)
        renderer = QgsSingleBandPseudoColorRenderer(lyr.dataProvider(), 1, shader)                
        lyr.setRenderer(renderer)
        lyr.renderer().createLegendNodes(QgsLayerTreeLayer(lyr.id()))

lyr.triggerRepaint()
lyr.emitStyleChanged()

Solution

  • With the help of this answer I could solve it eventually:

    So the working code is:

    from matplotlib import colormaps
    import numpy as np
    
    lyr = iface.activeLayer()
    if isinstance(lyr, QgsRasterLayer):
        if lyr.bandCount() == 1:
            # define color map by name
            map = colormaps['viridis']
    
            # get min/max values
            stats = lyr.dataProvider().bandStatistics(1, QgsRasterBandStats.All)
            val_min = stats.minimumValue
            val_max = stats.maximumValue
    
            # limit number of color steps to 20
            if map.N > 20:
                colors = map(np.linspace(1, map.N, 20, dtype=np.int16))
            else:
                colors = map(range(1,map.N))
            colorsN = len(colors)
    
            # making a colorRamp-list with
            colorsList = []
            for idx, col in enumerate(colors):
                qcol = QColor(int(col[0]*255), int(col[1]*255), int(col[2]*255))
                if idx == 0:
                    startColor = qcol
                elif idx == colorsN - 1:
                    endColor = qcol
                else:
                    val = idx * 1/colorsN
                    colorsList.append( QgsGradientStop((idx + 1) * 1 / (colorsN - 1), qcol) )
            
            colorRamp = QgsGradientColorRamp(startColor, endColor, False, colorsList)
    
            renderer = QgsSingleBandPseudoColorRenderer(lyr.dataProvider(), 1)
            renderer.setClassificationMin(val_min)
            renderer.setClassificationMax(val_max)
            renderer.createShader(colorRamp)
            
            lyr.setRenderer(renderer)
    
    lyr.triggerRepaint()